Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions frontend/src/KnowledgeGraph.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down
90 changes: 90 additions & 0 deletions frontend/src/KnowledgeGraph.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof KnowledgeGraphView>;

export default meta;
type Story = StoryObj<typeof meta>;

export const MixedNodeStates: Story = {
args: {
graph: mixedStateGraph,
onSelectPost: () => undefined,
},
};

export const EmptyState: Story = {
args: {
graph: { post_id: "post-1", nodes: [], edges: [] },
onSelectPost: () => undefined,
},
};
24 changes: 24 additions & 0 deletions frontend/src/knowledgeGraphCss.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// <reference types="node" />
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}`);
}
Comment on lines +16 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: CSS regression guard only matches exact var() form

The guard in knowledgeGraphCss.test.ts uses a substring check on var(--ink, var(--muted, etc. It catches the plain var(--x) form but not a reintroduction with a fallback such as var(--warning, red). Effective for current usage; not airtight.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

});

it("evidence nodes use a real, globally-defined color token", () => {
expect(css).toContain("var(--color-accent-orange)");
});
});