feat: gather defense discussion topics
This commit is contained in:
@@ -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),
|
||||
}));
|
||||
Reference in New Issue
Block a user