diff --git a/frontend/src/KnowledgeGraph.css b/frontend/src/KnowledgeGraph.css
index 7f2ef7f76..e22536fc8 100644
--- a/frontend/src/KnowledgeGraph.css
+++ b/frontend/src/KnowledgeGraph.css
@@ -20,9 +20,9 @@
.knowledge-graph-controls button {
align-items: center;
background: var(--surface);
- border: 1px solid var(--line-strong);
+ border: 1px solid var(--border);
border-radius: 0.45rem;
- color: var(--ink);
+ color: var(--text-h);
display: inline-flex;
font: inherit;
justify-content: center;
@@ -44,7 +44,7 @@
}
.knowledge-graph-controls span {
- color: var(--muted);
+ color: var(--text-muted);
font-variant-numeric: tabular-nums;
min-width: 3.25rem;
text-align: center;
@@ -55,14 +55,14 @@
}
.knowledge-graph-description {
- color: var(--muted);
+ color: var(--text-muted);
margin: 0.2rem 0 0;
}
.knowledge-graph-viewport {
cursor: grab;
overflow: hidden;
- border: 1px solid var(--line);
+ border: 1px solid var(--border);
border-radius: 0.75rem;
background: var(--surface);
touch-action: none;
@@ -78,19 +78,19 @@
}
.knowledge-graph-arrow {
- fill: var(--muted);
+ fill: var(--text-muted);
}
.knowledge-graph-edge path {
fill: none;
marker-end: inherit;
- stroke: var(--line-strong);
+ stroke: var(--border);
stroke-width: 1.3;
}
.knowledge-graph-node rect {
fill: var(--surface);
- stroke: var(--line-strong);
+ stroke: var(--border);
stroke-width: 1.5;
}
@@ -101,8 +101,8 @@
}
.knowledge-graph-node.evidence rect {
- fill: color-mix(in srgb, var(--warning) 10%, var(--surface));
- stroke: var(--warning);
+ fill: color-mix(in srgb, var(--color-accent-orange) 10%, var(--surface));
+ stroke: var(--color-accent-orange);
stroke-dasharray: 4 3;
}
@@ -122,18 +122,18 @@
}
.knowledge-graph-node-label {
- fill: var(--ink);
+ fill: var(--text-h);
font-size: 12px;
font-weight: 650;
}
.knowledge-graph-node-type {
- fill: var(--muted);
+ fill: var(--text-muted);
font-size: 9px;
}
.knowledge-graph-evidence {
- border-top: 1px solid var(--line);
+ border-top: 1px solid var(--border);
padding-top: 0.7rem;
}
diff --git a/frontend/src/KnowledgeGraph.css.test.ts b/frontend/src/KnowledgeGraph.css.test.ts
new file mode 100644
index 000000000..88185c59e
--- /dev/null
+++ b/frontend/src/KnowledgeGraph.css.test.ts
@@ -0,0 +1,15 @@
+///
+import { readFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { describe, expect, it } from "vitest";
+
+const here = dirname(fileURLToPath(import.meta.url));
+const css = readFileSync(join(here, "KnowledgeGraph.css"), "utf-8");
+
+describe("Knowledge Graph CSS contract", () => {
+ it("uses only defined shared tokens for graph colors", () => {
+ expect(css).not.toMatch(/var\(\s*--(?:ink|muted|line|line-strong|warning)\b/);
+ expect(css).toContain("var(--color-accent-orange)");
+ });
+});
diff --git a/frontend/src/KnowledgeGraph.stories.tsx b/frontend/src/KnowledgeGraph.stories.tsx
new file mode 100644
index 000000000..420fef7ee
--- /dev/null
+++ b/frontend/src/KnowledgeGraph.stories.tsx
@@ -0,0 +1,82 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { KnowledgeGraphView } from "./KnowledgeGraph";
+import type { KnowledgeGraph } from "./api";
+
+const mixedStateGraph: KnowledgeGraph = {
+ post_id: "post-1",
+ nodes: [
+ {
+ id: "post-1",
+ node_type_code: "node_post",
+ node_id: "post-1",
+ label: "Case introduction meeting",
+ is_focus: true,
+ },
+ {
+ id: "team-1",
+ node_type_code: "node_team",
+ node_id: "team-1",
+ label: "Case design team",
+ ontology_label: "Team",
+ is_focus: false,
+ is_evidence_text_node: false,
+ },
+ {
+ id: "organization-1",
+ node_type_code: "node_corporate_entity",
+ node_id: "organization-1",
+ label: "Case main contractor",
+ ontology_label: "Organization",
+ is_focus: false,
+ is_evidence_text_node: true,
+ },
+ {
+ id: "project-1",
+ node_type_code: "node_project",
+ node_id: "project-1",
+ label: "Case grid project",
+ ontology_label: "Project",
+ is_focus: false,
+ is_evidence_text_node: true,
+ },
+ ],
+ edges: [
+ {
+ source: "post-1",
+ target: "team-1",
+ edge_type_code: "edge_mention",
+ ontology_label: "Mentioned in post",
+ confidence: 0.9,
+ evidence_post_ids: ["post-1"],
+ },
+ {
+ source: "organization-1",
+ target: "project-1",
+ edge_type_code: "edge_responsible_for",
+ ontology_label: "Responsible for",
+ confidence: 0.82,
+ evidence_text: "Main contract evidence",
+ evidence_post_ids: ["post-1"],
+ },
+ ],
+};
+
+const meta = {
+ title: "Evidence/KnowledgeGraph",
+ component: KnowledgeGraphView,
+ parameters: { layout: "padded" },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const MixedNodeStates: Story = {
+ args: { graph: mixedStateGraph, onSelectPost: () => undefined },
+};
+
+export const EmptyState: Story = {
+ args: {
+ graph: { post_id: "post-1", nodes: [], edges: [] },
+ onSelectPost: () => undefined,
+ },
+};