feat: visualize prepared authoring phases

This commit is contained in:
lda
2026-07-11 06:47:57 +07:00 Verified
parent 2353ab114e
commit b5f8ae0c5e
5 changed files with 450 additions and 4 deletions
@@ -0,0 +1,46 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { projectPreparedAuthoringPhase } from "./authoring-projection.js";
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
afterEach(cleanup);
describe("AuthoringPhaseVisual", () => {
it("shows source inventory and the inspected contract", () => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("discover")} />);
expect(screen.getByRole("region", { name: /discovery evidence/i })).toBeInTheDocument();
expect(screen.getByText("local.lda_docs")).toBeInTheDocument();
expect(screen.getByText("local.lda_report")).toBeInTheDocument();
expect(screen.getByText(/documents.*analysis/i)).toBeInTheDocument();
});
it("shows the declared draft graph and outcome route", () => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("draft")} />);
expect(screen.getByRole("region", { name: /draft graph evidence/i })).toBeInTheDocument();
expect(screen.getByText("read_documents")).toBeInTheDocument();
expect(screen.getByText("analyze")).toBeInTheDocument();
expect(screen.getByText("ok → end")).toBeInTheDocument();
});
it("shows the validation diagnostic and repaired projection", () => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("validate")} />);
expect(screen.getByText(/no state projection/i)).toBeInTheDocument();
expect(screen.getByText("analysis → state.analysis")).toBeInTheDocument();
expect(screen.getByText(/valid draft/i)).toBeInTheDocument();
});
it("shows immutable artifact identity and version", () => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("artifact")} />);
expect(screen.getByText("lda_report_case_study")).toBeInTheDocument();
expect(screen.getByText("Version 1")).toBeInTheDocument();
expect(screen.getByText(/immutable/i)).toBeInTheDocument();
});
it("shows all concrete deployment bindings and validation", () => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("deployment")} />);
expect(screen.getAllByText("local.lda_docs")).toHaveLength(2);
expect(screen.getAllByText("local.lda_report")).toHaveLength(2);
expect(screen.getAllByText("local.issue_board")).toHaveLength(2);
expect(screen.getByText(/deployment valid/i)).toBeInTheDocument();
});
});
@@ -0,0 +1,80 @@
import {
AlertTriangle,
ArrowRight,
CheckCircle2,
Database,
FileJson,
Link2,
LockKeyhole,
Workflow,
} from "lucide-react";
import type { AuthoringPhaseProjection } from "./authoring-projection.js";
type AuthoringPhaseVisualProps = {
readonly projection: AuthoringPhaseProjection;
};
const InventoryVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "inventory" }> }) => (
<section className="authoring-visual authoring-visual--inventory" role="region" aria-label="discovery evidence">
<div className="authoring-inventory__sources">
{visual.sources.map((source) => (
<div key={source}><Database aria-hidden="true" /><code>{source}</code></div>
))}
</div>
<ArrowRight className="authoring-visual__arrow" aria-hidden="true" />
<div className="authoring-inventory__contract">
<strong><Workflow aria-hidden="true" /> Inspected capability</strong>
<code>{visual.capability}</code>
<span><FileJson aria-hidden="true" />{visual.contract}</span>
</div>
</section>
);
const GraphVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "graph" }> }) => (
<section className="authoring-visual authoring-visual--graph" role="region" aria-label="draft graph evidence">
<div className="authoring-graph__node"><Database aria-hidden="true" /><strong>{visual.nodes[0]}</strong></div>
<div className="authoring-graph__edge"><span>{visual.inputBinding}</span><ArrowRight aria-hidden="true" /></div>
<div className="authoring-graph__node"><Workflow aria-hidden="true" /><strong>{visual.nodes[1]}</strong></div>
<div className="authoring-graph__route"><span>{visual.route}</span><ArrowRight aria-hidden="true" /></div>
<div className="authoring-graph__end">END</div>
</section>
);
const RepairVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "repair" }> }) => (
<section className="authoring-visual authoring-visual--repair" role="region" aria-label="validation repair evidence">
<div className="authoring-repair__diagnostic"><AlertTriangle aria-hidden="true" /><span>Diagnostic</span><strong>{visual.diagnostic}</strong></div>
<ArrowRight aria-hidden="true" />
<div className="authoring-repair__correction"><Link2 aria-hidden="true" /><span>Repair</span><code>{visual.correction}</code></div>
<div className="authoring-repair__status"><CheckCircle2 aria-hidden="true" /><strong>{visual.status}</strong></div>
</section>
);
const ArtifactVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "artifact" }> }) => (
<section className="authoring-visual authoring-visual--artifact" role="region" aria-label="artifact evidence">
<LockKeyhole aria-hidden="true" />
<div><span>Immutable workflow artifact</span><strong>{visual.artifactId}</strong></div>
<dl><div><dt>Version</dt><dd>Version {visual.version}</dd></div><div><dt>Requirements</dt><dd>{visual.requiredSources} local sources</dd></div></dl>
</section>
);
const BindingsVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "bindings" }> }) => (
<section className="authoring-visual authoring-visual--bindings" role="region" aria-label="deployment binding evidence">
<header><Link2 aria-hidden="true" /><div><span>Deployment</span><strong>{visual.deploymentId}</strong></div><b><CheckCircle2 aria-hidden="true" />{visual.status}</b></header>
<div className="authoring-bindings__rows">
{visual.bindings.map((binding) => (
<div key={binding.requirement}><code>{binding.requirement}</code><ArrowRight aria-hidden="true" /><code>{binding.source}</code></div>
))}
</div>
</section>
);
/** Renders the factual product artifact appropriate to one authoring phase. */
export const AuthoringPhaseVisual = ({ projection }: AuthoringPhaseVisualProps) => {
switch (projection.visual.kind) {
case "inventory": return <InventoryVisual visual={projection.visual} />;
case "graph": return <GraphVisual visual={projection.visual} />;
case "repair": return <RepairVisual visual={projection.visual} />;
case "artifact": return <ArtifactVisual visual={projection.visual} />;
case "bindings": return <BindingsVisual visual={projection.visual} />;
}
};
@@ -8,6 +8,7 @@ describe("projectPreparedAuthoringPhase", () => {
expect(phase.label).toBe("Discover");
expect(phase.commands.length).toBeGreaterThanOrEqual(2);
expect(phase.summary).toMatch(/sources|capabilities|schema/i);
expect(phase.visual).toMatchObject({ kind: "inventory" });
});
it("projects the draft phase with graph and routes", () => {
@@ -15,6 +16,7 @@ describe("projectPreparedAuthoringPhase", () => {
expect(phase.label).toBe("Draft");
expect(phase.commands.length).toBeGreaterThanOrEqual(2);
expect(phase.summary).toMatch(/graph|routes/i);
expect(phase.visual).toMatchObject({ kind: "graph" });
});
it("projects the validate phase with diagnosis and repair", () => {
@@ -22,13 +24,16 @@ describe("projectPreparedAuthoringPhase", () => {
expect(phase.label).toBe("Validate");
expect(phase.commands.length).toBeGreaterThanOrEqual(2);
expect(phase.summary).toMatch(/diagnos|repair/i);
expect(phase.visual).toMatchObject({ kind: "repair" });
});
it("projects the artifact phase with immutable ID and version", () => {
const phase = projectPreparedAuthoringPhase("artifact");
expect(phase.label).toBe("Artifact");
expect(phase.commands.length).toBeGreaterThanOrEqual(2);
expect(phase.summary).toMatch(/id|version/i);
expect(phase.summary).toMatch(/id/i);
expect(phase.summary).toMatch(/version/i);
expect(phase.visual).toMatchObject({ kind: "artifact" });
});
it("projects the deployment phase with bindings and validation", () => {
@@ -36,15 +41,16 @@ describe("projectPreparedAuthoringPhase", () => {
expect(phase.label).toBe("Deployment");
expect(phase.commands.length).toBeGreaterThanOrEqual(2);
expect(phase.summary).toMatch(/bind|validat/i);
expect(phase.visual).toMatchObject({ kind: "bindings" });
});
it("validates phase diagnostic status is 'repaired'", () => {
it("keeps the validation diagnostic and repair command distinct", () => {
const phase = projectPreparedAuthoringPhase("validate");
const diagnosticCmd = phase.commands.find(
(cmd) => cmd.result === "diagnostic" && cmd.detail?.includes("repaired"),
(cmd) => cmd.result === "diagnostic" && cmd.detail?.includes("no state projection"),
);
expect(diagnosticCmd).toBeDefined();
expect(diagnosticCmd!.detail).toContain("repaired");
expect(phase.commands.some((cmd) => cmd.command.includes("draft set-output"))).toBe(true);
});
it("uses real public command syntax from the recording", () => {
@@ -5,7 +5,85 @@ export type AuthoringPhaseProjection = {
readonly beatId: string;
readonly label: string;
readonly summary: string;
readonly proof: readonly string[];
readonly commands: readonly PreparedAuthoringCommand[];
readonly visual: AuthoringPhaseVisualModel;
};
export type AuthoringPhaseVisualModel =
| {
readonly kind: "inventory";
readonly sources: readonly string[];
readonly capability: string;
readonly contract: string;
}
| {
readonly kind: "graph";
readonly nodes: readonly string[];
readonly route: string;
readonly inputBinding: string;
}
| {
readonly kind: "repair";
readonly diagnostic: string;
readonly correction: string;
readonly status: string;
}
| {
readonly kind: "artifact";
readonly artifactId: string;
readonly version: number;
readonly requiredSources: number;
}
| {
readonly kind: "bindings";
readonly deploymentId: string;
readonly bindings: readonly { readonly requirement: string; readonly source: string }[];
readonly status: string;
};
const visualForPhase = (phase: AuthoringPhaseId): AuthoringPhaseVisualModel => {
switch (phase) {
case "discover":
return {
kind: "inventory",
sources: ["local.lda_docs", "local.lda_report", "local.issue_board"],
capability: "local.lda_report.analyze_documents",
contract: "documents → analysis",
};
case "draft":
return {
kind: "graph",
nodes: ["read_documents", "analyze"],
route: "ok → end",
inputBinding: "state.documents → documents",
};
case "validate":
return {
kind: "repair",
diagnostic: "analysis has no state projection",
correction: "analysis → state.analysis",
status: "Valid draft",
};
case "artifact":
return {
kind: "artifact",
artifactId: "lda_report_case_study",
version: 1,
requiredSources: 3,
};
case "deployment":
return {
kind: "bindings",
deploymentId: "lda_report_case_study.default",
bindings: [
{ requirement: "local.lda_docs", source: "local.lda_docs" },
{ requirement: "local.lda_report", source: "local.lda_report" },
{ requirement: "local.issue_board", source: "local.issue_board" },
],
status: "Deployment valid",
};
}
};
/**
@@ -28,6 +106,8 @@ export const projectPreparedAuthoringPhase = (
.filter((t) => t.role === "assistant")
.map((t) => t.text)
.join(" ") || found.label,
proof: found.proof,
commands: found.commands,
visual: visualForPhase(found.phase),
};
};