Files
resolutionflow/frontend/src/components/pilot/sections/WhatWeKnow.tsx
Michael Chihlas 8879f96fbf
All checks were successful
Mirror to GitHub / mirror (push) Successful in 4s
fix(pilot): drop sticky section headers in task lane
Each lane section (What we know, Questions, Diagnostic Checks, Suggested
fix) had its own `position: sticky; top: 0` header. As the engineer
scrolled past a section, that section's header would pin until the
section's bottom edge cleared the viewport, producing an "orphaned"
label floating over unrelated content below. Headers now scroll with
their content — in a 340px-wide lane the affordance was negative value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:01:14 -04:00

92 lines
3.2 KiB
TypeScript

/**
* What-we-know section — the load-bearing structural feature of the FlowPilot
* task lane (per FLOWPILOT-MIGRATION.md Section 0 architectural claim #2).
*
* Owns the list of confirmed facts for a session. Facts arrive via three paths:
* 1. AI [PROMOTE] markers parsed in unified_chat_service (most common)
* 2. Engineer "+ Add a note" (manual user_note)
* 3. Engineer-initiated promotion of a question/check (future affordance)
*
* This component is the parent's contract: it takes a fact list + handlers
* and renders the section. Loading/refresh logic lives in the parent
* (AssistantChatPage) so it can coordinate with the chat send cycle.
*/
import { Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { SessionFact } from '@/api/sessionFacts'
import { WhatWeKnowItem } from './WhatWeKnowItem'
import { AddNoteButton } from './AddNoteButton'
interface WhatWeKnowProps {
facts: SessionFact[]
onAddNote: (text: string, summary: string | null) => Promise<void> | void
onUpdateFact: (factId: string, text: string, summary: string | null) => Promise<void> | void
onDeleteFact: (factId: string) => Promise<void> | void
loading?: boolean
}
export function WhatWeKnow({
facts,
onAddNote,
onUpdateFact,
onDeleteFact,
loading,
}: WhatWeKnowProps) {
const count = facts.length
return (
<section
className={cn(
'rounded-lg p-3 -mx-1 mb-1',
// Subtle green-to-transparent gradient distinguishes this section
// from the rest of the lane (mockup 01-session-primary.png).
'bg-gradient-to-b from-success/[0.05] to-transparent',
)}
>
<div className="pb-2">
<div className="flex items-center gap-2 text-[10px] font-semibold uppercase tracking-[1.2px] text-muted-foreground pl-0.5">
<span className="w-1.5 h-1.5 rounded-full bg-success" />
What we know
<span className="text-muted-foreground">·</span>
<span className="tabular-nums">{count}</span>
{loading && (
<span
className="ml-auto flex items-center gap-1 text-[0.625rem] font-medium normal-case tracking-normal text-muted-foreground"
title="AI may be synthesizing new facts from this turn"
>
<Loader2 size={10} className="animate-spin" />
synthesizing
</span>
)}
</div>
</div>
{count === 0 && loading && (
<div className="space-y-2 px-1 py-2">
<div className="h-3 w-3/4 rounded bg-elevated/60 animate-pulse" />
<div className="h-3 w-1/2 rounded bg-elevated/60 animate-pulse" />
</div>
)}
{count === 0 && !loading && (
<div className="text-[0.75rem] text-muted-foreground italic px-1 py-2">
Nothing confirmed yet facts appear here as the engineer answers questions and runs checks.
</div>
)}
{facts.map((fact) => (
<WhatWeKnowItem
key={fact.id}
fact={fact}
onSave={(text, summary) => onUpdateFact(fact.id, text, summary)}
onDelete={() => onDeleteFact(fact.id)}
/>
))}
<AddNoteButton onAdd={onAddNote} />
</section>
)
}
export default WhatWeKnow