Merge feat/ui-polish: three-phase session model, plan graph, layout restructure

chat.tsx woven by hand: UI's session-surface layout (transcript column +
right sidebar as full-height siblings, h-10 header with PhaseChip) wrapped
in sub-agents' JobsContext.Provider; the message map carries both the
steer ternary (steers bypass the Message wrapper — the model-facing glue
text never renders) and UI's PhaseDivider emission (steers are verified
phase-boundary no-ops). Right sidebar = sub-agents' fleet panel column
(SubAgentsPanel above TreePanel, xl-only) carried onto UI's skeleton.
contracts.ts auto-merged (ChatSummary.phase/pendingQuestion + ChatListItem,
additive above the async-protocol section).

Smoke-verified on the merged tree: steer renders bare as its artifact
badge with no learner bubble, no rendered glue text, and no spurious
phase divider at the steer boundary.
This commit is contained in:
amosblomqvist
2026-09-02 16:40:47 +03:00
17 changed files with 1576 additions and 183 deletions
+3 -3
View File
@@ -2,7 +2,6 @@ import type { ComponentType } from "react";
import type { ArtifactPartProps } from "@/src/core";
import { DemoCard } from "@/components/artifacts/demo-card";
import { JobCard } from "@/components/artifacts/job-card";
import { PlanDivider } from "@/components/artifacts/plan-divider";
import { VisualCard } from "@/components/artifacts/visual-card";
/**
@@ -19,8 +18,9 @@ import { VisualCard } from "@/components/artifacts/visual-card";
*/
export const artifactRenderers: Record<string, ComponentType<ArtifactPartProps>> = {
research: JobCard,
// Phase divider where the plan lands (W5 fills in the component):
plan_init: PlanDivider,
// plan_* parts render nothing inline by design: the phase seam is a
// message-level boundary, drawn by PhaseDivider in chat.tsx (the old
// part-level plan_init divider retired with it).
// Artifacts (W6 fills in the components):
demo: DemoCard,
visual: VisualCard,
-29
View File
@@ -1,29 +0,0 @@
import type { ArtifactPartProps, PlanToolResult } from "@/src/core";
/**
* The probe→teach divider (vision §2 "Two phases, clearly separated").
*
* Rendered by the artifact registry wherever the session's plan_init part
* sits in the transcript — the plan call IS the transition marker, so the
* divider marks the seam: probing above, teaching below. Deliberately quiet:
* a hairline rule with a small chip, never a banner.
*
* Renders null until the tool's output is available AND the init succeeded —
* a failed first plan_init must not mark the seam (the retry that succeeds
* draws the divider at its own, correct spot).
*/
export function PlanDivider({ part }: ArtifactPartProps) {
if (part.state !== "output-available") return null;
const result = part.output as PlanToolResult | undefined;
if (!result || result.ok !== true) return null;
const nodeCount = result.tree?.nodes?.length ?? 0;
return (
<div className="my-4 flex w-full items-center gap-3" data-testid="plan-divider">
<span className="h-px flex-1 bg-border/80" />
<span className="rounded-full border border-border/80 px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
Teaching{nodeCount > 0 ? ` · ${nodeCount} node${nodeCount === 1 ? "" : "s"}` : ""}
</span>
<span className="h-px flex-1 bg-border/80" />
</div>
);
}