diff --git a/frontend/src/KnowledgeGraph.css b/frontend/src/KnowledgeGraph.css index 7f2ef7f76..7d404469e 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; } @@ -100,9 +100,14 @@ stroke-width: 2.5; } +/* "Evidence" nodes (source-grounded but not yet catalog-linked) previously + used --warning/--ink/--muted, custom properties nobody ever defined -- + an invalid color-mix() falls back to fill's own initial value (SVG: + black), and the label text used the same undefined --ink, so the text + was black-on-black and invisible even where the box wasn't. */ .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 +127,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.stories.tsx b/frontend/src/KnowledgeGraph.stories.tsx new file mode 100644 index 000000000..fd188fd95 --- /dev/null +++ b/frontend/src/KnowledgeGraph.stories.tsx @@ -0,0 +1,90 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { KnowledgeGraphView } from "./KnowledgeGraph"; +import type { KnowledgeGraph } from "./api"; + +// Regression fixture for the 2026-08-22 black-node bug: "evidence" nodes +// (source-grounded but not yet catalog-linked) must render with visible +// text on a visible background, not solid black. Covers all three visual +// states -- focus (the post itself), catalog (an already-cataloged +// entity), and evidence -- in one graph so a broken token shows immediately. +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: "org-observed", + node_type_code: "node_corporate_entity", + node_id: "org-observed", + label: "Case Main Contractor", + ontology_label: "organization", + is_focus: false, + is_evidence_text_node: true, + }, + { + id: "project-observed", + node_type_code: "node_project", + node_id: "project-observed", + 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: "org-observed", + target: "project-observed", + edge_type_code: "edge_responsible_for", + ontology_label: "Responsible for", + confidence: 0.82, + evidence_text: "Main Contract - Case Main Contractor", + 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, + }, +}; diff --git a/frontend/src/knowledgeGraphCss.test.ts b/frontend/src/knowledgeGraphCss.test.ts new file mode 100644 index 000000000..e4a6c8293 --- /dev/null +++ b/frontend/src/knowledgeGraphCss.test.ts @@ -0,0 +1,24 @@ +/// +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("never reintroduces the undefined --ink/--muted/--line/--line-strong/--warning custom properties", () => { + // 2026-08-22 bug: these names were used throughout but never defined + // anywhere in the app. An invalid var()/color-mix() falls back to + // fill's own initial value (SVG: black), so "evidence" nodes rendered + // as solid black boxes with black-on-black (invisible) text. + for (const undefinedToken of ["--ink", "--muted", "--line-strong", "--line)", "--warning"]) { + expect(css).not.toContain(`var(${undefinedToken}`); + } + }); + + it("evidence nodes use a real, globally-defined color token", () => { + expect(css).toContain("var(--color-accent-orange)"); + }); +});