Plan blocks in chat: full DAG borderless in the flow, fragment + glow, live popup (wave-4 item 3)
- components/plan-block.tsx: hand-built SVG (user decision) — deterministic layered layout (Kahn topo + longest-path depth, rows centered, authoring tie-break), bezier edges, the retired panel's status color language. - PlanBlock renders data-plan-block parts AS CARRIED (frozen snapshots, never re-derived): kind full = borderless, viewBox = layout extent so nodes hug the column edges, natural height; kind neighborhood = own small labeled 'Plan fragment' container with the current node glowing (pulsing primary halo). - PlanPopup: full-screen overlay with the LIVE plan — latest tree from the transcript, statuses derived at click time via derivePlanStatuses (contracts/plan.ts). Esc / backdrop / X close; drag pan, wheel zoom-to-cursor, zoom buttons; fit-floor semantics harvested from the retired tree panel (zoom floor = the fit, pan locks at it). - chat.tsx: data-plan-block case in the part switch + popup mount. - src/core/ui-message.ts: TeacherUIDataParts gains "plan-block": PlanBlockData (additive, type-only — lane touch flagged to master). - tests/render-plan-block.test.tsx: pure layout (layering, centering, dangling-edge tolerance), wrap, frozen-snapshot render, live derivation.
This commit is contained in:
@@ -21,6 +21,8 @@ import { SubagentSteerCard } from "@/components/artifacts/steer-card";
|
||||
import { Composer } from "@/components/composer";
|
||||
import { JobsContext } from "@/components/jobs-context";
|
||||
import { StatsBar } from "@/components/stats-bar";
|
||||
import { PlanBlock, PlanPopup } from "@/components/plan-block";
|
||||
import type { PlanBlockData } from "@/src/core";
|
||||
import { QuestionDock } from "@/components/question-dock";
|
||||
import { SidebarToggle } from "@/components/sidebar";
|
||||
import { SubAgentsPanel } from "@/components/sub-agents-panel";
|
||||
@@ -177,6 +179,9 @@ export function Chat({
|
||||
}
|
||||
}, [chatId, setMessages, sendMessage, status]);
|
||||
|
||||
// The live plan popup (wave 4): opened by any transcript plan block.
|
||||
const [planOpen, setPlanOpen] = useState(false);
|
||||
|
||||
// ── Session usage (wave-4 stats bar) ──
|
||||
// The money channel: the record's turns log, folded client-side by
|
||||
// core/prices.ts. Seeded once on mount (the page doesn't carry turns),
|
||||
@@ -389,6 +394,16 @@ export function Chat({
|
||||
);
|
||||
case "step-start":
|
||||
return pi > 0 ? <Separator key={pi} className="my-3" /> : null;
|
||||
case "data-plan-block":
|
||||
// The plan in the chat (wave 4): a frozen snapshot
|
||||
// block — rendered as carried, never re-derived.
|
||||
return (
|
||||
<PlanBlock
|
||||
key={pi}
|
||||
data={part.data as PlanBlockData}
|
||||
onOpen={() => setPlanOpen(true)}
|
||||
/>
|
||||
);
|
||||
case "tool-quiz":
|
||||
case "tool-ask_user_question": {
|
||||
// Pending/streaming questions live in the dock, not the
|
||||
@@ -464,6 +479,7 @@ export function Chat({
|
||||
<TreePanel messages={messages} />
|
||||
</div>
|
||||
</div>
|
||||
{planOpen && <PlanPopup messages={messages} onClose={() => setPlanOpen(false)} />}
|
||||
</JobsContext.Provider>
|
||||
</ChatActionsContext.Provider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user