The shell now imports through exactly two barrels, enforced by
import/no-restricted-paths (npm run lint):
components/** -> "@/src/core" (pure core API, browser-safe)
app/** -> "@/src/core" + "@/src/server" (server entries)
Zone violations are lint errors with architecture-pointer messages; the
pure-core modules (contracts, question-core, plan-tree, phases, pending,
math-delims, ui-message) may never import agent/server/store.
Supporting moves (direction: IO layers -> core):
- core/index.ts: the core public API barrel
- core/ui-message.ts: moved from src/agent (it was shell-facing all along)
- quiz/ask input zod schemas moved agent/tools -> core/question-core
(the question contract, alongside the grading logic)
- ChatRecord/ChatSummary moved store/chats -> core/contracts (single schema)
- GraphSnapshot/NodeStateClass moved core/graph -> core/contracts (the
/api/graph payload IS a contract; the pure renderer needs only these)
- src/server/{chats,graph,jobs}.ts + index.ts: thin server entries for the
transport layer; GET /api/graph's fold side-effect now lives behind
graphView() instead of inline in the route
Gates: tsc clean, 190/190 tests, eslint clean (law verified to fire on
store/agent/subpath violations), production build clean.
(Repo repair en route: a git object (app/chat/[id] tree) was missing from
the object store — reconstructed byte-exactly via mktree from its blob;
the broken node_modules that stalled the build was cleanly reinstalled.)
30 lines
1.3 KiB
TypeScript
30 lines
1.3 KiB
TypeScript
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>
|
|
);
|
|
}
|