feat: gather defense discussion topics

This commit is contained in:
lda
2026-07-10 18:57:33 +07:00 Verified
parent 6b78ce819b
commit 0a02c23be2
9 changed files with 309 additions and 6 deletions
@@ -0,0 +1,32 @@
import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { discussionBranches } from "../storyboard.js";
import { defenseDiscussionGroups } from "./defense-discussion-index.js";
import { DefenseDiscussionIndex } from "./DefenseDiscussionIndex.js";
describe("DefenseDiscussionIndex", () => {
afterEach(cleanup);
it("renders seven labelled topic sections and every canonical branch title", () => {
render(<DefenseDiscussionIndex openDiscussion={vi.fn()} />);
const nav = screen.getByRole("navigation", { name: "defense discussion index" });
expect(within(nav).getAllByRole("heading", { level: 2 })).toHaveLength(7);
for (const group of defenseDiscussionGroups) {
const heading = within(nav).getByRole("heading", { name: group.label, level: 2 });
expect(heading.querySelector("svg")).not.toBeNull();
}
for (const branch of discussionBranches) {
expect(within(nav).getByRole("button", { name: branch.title })).toBeInTheDocument();
}
});
it("opens the canonical branch selected from the index", () => {
const openDiscussion = vi.fn();
render(<DefenseDiscussionIndex openDiscussion={openDiscussion} />);
fireEvent.click(screen.getByRole("button", { name: "Live demo reliability" }));
expect(openDiscussion).toHaveBeenCalledWith("demo-reliability");
});
});
@@ -0,0 +1,40 @@
import type { FC } from "react";
import { BadgeHelp, Boxes, ChartNoAxesCombined, FileCode2, Map, PlaySquare, Rocket } from "lucide-react";
import { defenseDiscussionGroups, type DefenseDiscussionTopicId } from "./defense-discussion-index.js";
const topicIcons = {
contribution: BadgeHelp,
positioning: Map,
runtime: Boxes,
authoring: FileCode2,
demo: PlaySquare,
evaluation: ChartNoAxesCombined,
production: Rocket,
} as const satisfies Record<DefenseDiscussionTopicId, typeof BadgeHelp>;
export const DefenseDiscussionIndex: FC<{ readonly openDiscussion: (branchId: string) => void }> = ({
openDiscussion,
}) => (
<nav className="defense-discussion-index" aria-label="defense discussion index">
{defenseDiscussionGroups.map((group) => {
const Icon = topicIcons[group.id];
return (
<section className="defense-discussion-index__group" key={group.id}>
<h2 className="defense-discussion-index__heading">
<Icon aria-hidden="true" focusable="false" />
<span>{group.label}</span>
</h2>
<ul className="defense-discussion-index__list">
{group.branches.map((branch) => (
<li key={branch.id}>
<button type="button" onClick={() => openDiscussion(branch.id)}>
{branch.title}
</button>
</li>
))}
</ul>
</section>
);
})}
</nav>
);
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { discussionBranches } from "../storyboard.js";
import { defenseDiscussionGroups, discussionTopicByBranchId } from "./defense-discussion-index.js";
describe("defense discussion index", () => {
it("exhaustively projects every canonical discussion branch exactly once", () => {
const indexedIds = defenseDiscussionGroups.flatMap((group) => group.branches.map((branch) => branch.id));
expect(indexedIds).toHaveLength(discussionBranches.length);
expect(new Set(indexedIds)).toEqual(new Set(discussionBranches.map((branch) => branch.id)));
expect(Object.keys(discussionTopicByBranchId).sort()).toEqual(
discussionBranches.map((branch) => branch.id).sort(),
);
});
it("derives indexed branch objects from the canonical definitions", () => {
for (const branch of discussionBranches) {
const indexed = defenseDiscussionGroups.flatMap((group) => group.branches).find(({ id }) => id === branch.id);
expect(indexed).toBe(branch);
}
});
});
@@ -0,0 +1,58 @@
import { discussionBranches, type DiscussionBranchDefinition, type DiscussionBranchId } from "../storyboard.js";
export type DefenseDiscussionTopicId =
| "contribution"
| "positioning"
| "runtime"
| "authoring"
| "demo"
| "evaluation"
| "production";
export type DefenseDiscussionGroup = {
readonly id: DefenseDiscussionTopicId;
readonly label: string;
readonly branches: readonly DiscussionBranchDefinition[];
};
// This explicit record is intentionally exhaustive: adding a Q&A branch must
// also place it in the end-of-defense index instead of silently hiding it.
export const discussionTopicByBranchId: Record<DiscussionBranchId, DefenseDiscussionTopicId> = {
"where-is-ai-agent": "contribution",
"title-ai-agent-wording": "contribution",
"direct-orchestration": "positioning",
"generated-scripts": "positioning",
"hosted-automation": "positioning",
"durable-agent-graphs": "positioning",
"mcp-agent-scale": "positioning",
"not-just-scripts": "positioning",
"not-just-cli": "runtime",
"lifecycle-states": "runtime",
"run-persistence": "runtime",
"raw-plan-import": "authoring",
"validation-diagnostics": "authoring",
"why-schemas": "authoring",
"typed-interrupts": "authoring",
"replay-provenance": "demo",
"demo-reliability": "demo",
"prepared-replay-boundary": "demo",
"evaluation-validity": "evaluation",
"provider-security": "production",
"security-production-boundary": "production",
"production-readiness": "production",
};
const discussionTopicGroups = [
{ id: "contribution", label: "Contribution" },
{ id: "positioning", label: "Positioning" },
{ id: "runtime", label: "Runtime" },
{ id: "authoring", label: "Authoring" },
{ id: "demo", label: "Demo" },
{ id: "evaluation", label: "Evaluation" },
{ id: "production", label: "Production" },
] as const satisfies readonly { id: DefenseDiscussionTopicId; label: string }[];
export const defenseDiscussionGroups: readonly DefenseDiscussionGroup[] = discussionTopicGroups.map((topic) => ({
...topic,
branches: discussionBranches.filter((branch) => discussionTopicByBranchId[branch.id] === topic.id),
}));