Adds the load-bearing structural feature of the FlowPilot migration: a
"What we know" panel that holds confirmed facts for a session, fed by AI
[PROMOTE] markers and engineer-added notes. Facts feed the resolution
note preview (Phase 3) and survive across turns via stable UUIDs assigned
to pending_task_lane items.
Backend:
- FactSynthesisService: create/update/soft-delete facts with atomic
state_version bumps; LLM-backed synthesize_from_question/check on the
fact_synthesis (Haiku) action tier per Section 6.6.
- /api/v1/ai-sessions/{id}/facts CRUD + /facts/promote (proposed_text or
via synthesis). PATCH returns 403 for question/diagnostic_check facts
(edit the source item instead, Section 7.3).
- unified_chat_service: [PROMOTE] marker parser (JSON-block per Section
8.1 spec drift note), stable-UUID assignment for pending_task_lane
questions/actions preserved by exact text/label match across turns.
- ASSISTANT_SYSTEM_PROMPT: documents [PROMOTE] format, when to/not to
emit, hallucination guardrails, source_ref handling.
- 17 tests covering parser, stable IDs, service validation, CRUD,
editability rule, both promote modes, 422 null-synthesis path,
state_version invariant.
Frontend:
- src/components/pilot/sections/{WhatWeKnow,WhatWeKnowItem,AddNoteButton}
— green-gradient section above Questions, dashed-circle check, inline
edit/delete gated by the server's editable flag.
- TaskLane gains a whatWeKnowSlot prop (existing assistant/ folder kept
per the doc's "rename is opportunistic" guidance).
- AssistantChatPage fetches facts on selectChat and refetches after each
chat send (so [PROMOTE]-synthesized facts appear immediately); auto-
opens the lane when facts exist.
Verification: end-to-end smoke against the local docker stack confirms
all five endpoints (list/create/patch/delete/promote) plus the 403
editability rule. pytest suite verifies the same with mocked LLM. Live
[PROMOTE] flow remains untested until used in the UI — the marker shape
is covered by parser tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
/**
|
|
* "+ Add a note" affordance for the What-we-know section.
|
|
*
|
|
* Inline composer that posts a `user_note` fact when the engineer wants to
|
|
* record something the AI didn't surface (a hunch, an observation, a piece
|
|
* of customer context). Per FLOWPILOT-MIGRATION.md Section 3.1.
|
|
*/
|
|
import { useState } from 'react'
|
|
import { Plus, Check } from 'lucide-react'
|
|
|
|
interface AddNoteButtonProps {
|
|
onAdd: (text: string, summary: string | null) => Promise<void> | void
|
|
}
|
|
|
|
export function AddNoteButton({ onAdd }: AddNoteButtonProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const [text, setText] = useState('')
|
|
const [summary, setSummary] = useState('')
|
|
const [busy, setBusy] = useState(false)
|
|
|
|
const reset = () => {
|
|
setText('')
|
|
setSummary('')
|
|
setOpen(false)
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
if (!text.trim()) return
|
|
setBusy(true)
|
|
try {
|
|
await onAdd(text.trim(), summary.trim() || null)
|
|
reset()
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
if (!open) {
|
|
return (
|
|
<button
|
|
onClick={() => setOpen(true)}
|
|
className="flex items-center gap-1.5 text-[0.75rem] text-muted-foreground hover:text-accent-text transition-colors pl-1 mt-1"
|
|
>
|
|
<Plus size={12} />
|
|
Add a note
|
|
</button>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-lg border border-default bg-card p-3 mb-2">
|
|
<textarea
|
|
autoFocus
|
|
value={text}
|
|
onChange={(e) => setText(e.target.value)}
|
|
placeholder="What did you observe or confirm?"
|
|
className="w-full rounded-md border border-default bg-input px-2.5 py-1.5 text-[0.8125rem] text-heading placeholder:text-muted-foreground resize-y min-h-[44px] max-h-[140px] focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30"
|
|
rows={2}
|
|
/>
|
|
<input
|
|
type="text"
|
|
value={summary}
|
|
onChange={(e) => setSummary(e.target.value)}
|
|
placeholder="Short label (optional)"
|
|
className="mt-1.5 w-full rounded-md border border-default bg-input px-2.5 py-1 text-[0.75rem] text-heading placeholder:text-muted-foreground focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30"
|
|
/>
|
|
<div className="mt-1.5 flex items-center gap-2">
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={busy || !text.trim()}
|
|
className="flex items-center gap-1 rounded-md bg-accent px-2.5 py-1 text-[0.75rem] font-medium text-white disabled:opacity-40 hover:bg-accent-hover transition-colors"
|
|
>
|
|
<Check size={11} /> Add
|
|
</button>
|
|
<button
|
|
onClick={reset}
|
|
disabled={busy}
|
|
className="text-[0.75rem] text-muted-foreground hover:text-heading"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default AddNoteButton
|