Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.d/ontology-node-readability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Changed

- Ontology neighborhood nodes now reuse light/dark design tokens for distinct
Post, Person, Organization, and Team surfaces while retaining shape, text,
keyboard controls, and the exact-value table as the authoritative cues.
36 changes: 16 additions & 20 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Product & Technical Gap Baseline

> Dashboard delivery snapshot: 2026-08-26 05:01 KST. Protected `main` was
> Dashboard delivery snapshot: 2026-08-26 06:20 KST. Protected `main` was
> `04e6b610655d0db91d5f7ba9486bdda1440e0b19`. This local branch is not
> protected-main release evidence.

Expand Down Expand Up @@ -60,14 +60,14 @@ only aggregate, non-identifying evidence to this repository.

### Exact open-PR boundary

At this snapshot there were 16 open PRs and 10 open issues. The exact-head
At this snapshot there were 11 open PRs and 10 open issues. The exact-head
inventory in section 1 is authoritative for this snapshot. Every open head
remained blocked on hosted gates and/or independent review. These observations
are not merge readiness. Re-fetch exact heads,
unresolved threads, checks, approvals, rulesets, and merge SHA before any
lifecycle claim.

> Audit snapshot: 2026-08-26 05:01 KST (refreshed by the autonomous merge
> Audit snapshot: 2026-08-26 06:20 KST (refreshed by the autonomous merge
> loop). This repository records synthetic fixtures and aggregate,
> non-identifying runtime evidence only. Open PRs and local checks are not
> protected-default-branch release evidence. Identifying post identifiers,
Expand All @@ -77,29 +77,25 @@ lifecycle claim.
## 1. Exact-head and governance evidence

The protected default branch was `04e6b610655d0db91d5f7ba9486bdda1440e0b19`
when this baseline was refreshed. The live queue contained 16 open PRs and 10
when this baseline was refreshed. The live queue contained 11 open PRs and 10
open issues. The exact-head inventory below supersedes older per-PR snapshots
elsewhere in this document; those older rows remain useful historical delivery
context only.

| PR | Exact observed head | Merge/check state at this snapshot |
| ---: | --- | --- |
| #663 | `be361f10` | project ontology traversal plus cutoff/snapshot-frozen project focus and labels |
| #662 | `92534118` | fail-closed injectable TEPP status/read boundary; the owning executable HTTP route remains unavailable |
| #661 | `11206c58` | Rust-estimated reconstruction fixture weights replace hand-authored reconstruction-test dictionaries |
| #660 | `c10cae01` (observed parent) | backend runtime repair plus the #664 semantic-unit stack; this documentation commit advances the head after capture |
| #659 | `e948bd27` | ontology node readability and tokenized UI fills |
| #658 | `fe830b0a` | evidence-honest Global Ask cutoff with revision-interval live-after semantics |
| #657 | `64f48679` | Dashboard case-metric contract work |
| #644 | `d9ff9980` | native-surface code splitting with modal-focus regression coverage |
| #643 | `0a1f8ec1` | accessible status-notice surfaces |
| #640 | `2d50fa01` | operations-dashboard contract alignment |
| #639 | `aee02dca` | terminal checks observed; exact-head independent approval still required |
| #636 | `f7b9a65f` | terminal checks observed; exact-head independent approval still required |
| #632 | `32c7d359` | active semantic provenance repair head; hosted checks and independent review required |
| #631 | `c0022c97` | terminal checks observed; exact-head independent approval still required |
| #629 | `4b4d6707` | terminal checks observed; exact-head independent approval still required |
| #579 | `689a21b6` | leftover interaction-map coordinate persistence; exact-head independent approval still required |
| #663 | `ab330a0f` | project ontology traversal plus cutoff/snapshot-frozen project focus and labels; exact-head checks/review required |
| #660 | `24fda085` | backend runtime repair plus semantic-unit stack; exact-head checks/review required |
| #659 | `0739b9d7` | ontology node readability and tokenized UI fills; branch is DIRTY against current main and requires conflict repair |
| #658 | `fe830b0a` | evidence-honest Global Ask cutoff with revision-interval live-after semantics; exact-head checks/review required |
| #657 | `64f48679` | Dashboard case-metric contract work; exact-head checks/review required |
| #644 | `d9ff9980` | native-surface code splitting with modal-focus regression coverage; exact-head checks/review required |
| #643 | `0a1f8ec1` | accessible status-notice surfaces; exact-head checks/review required |
| #640 | `2d50fa01` | operations-dashboard contract alignment; exact-head checks/review required |
| #639 | `aee02dca` | exact-head checks/review required |
| #632 | `a4059113` | active semantic provenance repair head; exact-head checks/review required |
| #631 | `1d9ac825` | documentation decomposition; branch is DIRTY against current main and requires conflict repair |
| #629 | `4b4d6707` | exact-head checks/review required |

No row above is merge evidence. Immediately before any lifecycle action,
re-fetch the head, unresolved threads, formal reviews, rulesets, and same-head
Expand Down
1 change: 1 addition & 0 deletions docs/storybook-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ operator-facing control you can click before changing product CSS.
| `Lineage/LineageDag` | Open a reconstructed connection to read its inferred channel scores and Allen interval relation, or open the current branch node; compare empty, single-branch, grouped/forked, mobile-scroll, ungrouped, and long-title states before changing graph CSS. On narrow viewports, swipe the named viewport or focus it and use arrow keys to inspect the full lineage. | `--color-accent-background`, `--radius-control`, `--surface`, `--border`, `--color-focus-border`, `--size-control-min`, `LineageDag` |
| `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` |
| `Workspace/WorkspaceCalendar` | Read observed Naruon events, or open a commitment to land on that post. Fail-closed copy stays `이 범위의 일정을 아직 받을 수 없습니다`. | `--color-chip-border`, `WorkspaceCalendar`, `EvidenceStatusMark` |
| `Evidence/OntologyExplorer` | Distinguish Post, Person, Organization, and Team by shape and text, use the token-backed surface as a secondary cue, then open the exact-value table or cited evidence. Compare desktop, narrow, drawer, empty, truncated, denied, stale, and rejected states. | `--ontology-node-*-fill`, `OntologyExplorer` |

Repeated web objects must use `frontend/src/styles/tokens.css` and a module
under `frontend/src/components/`. Do not add a second Node package manager;
Expand Down
33 changes: 31 additions & 2 deletions frontend/src/App.css
Comment thread
seonghobae marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -1148,11 +1148,30 @@

.ontology-node {
cursor: pointer;
fill: var(--color-background);
stroke: var(--color-text-heading);
stroke-width: 1.5;
}

.ontology-node-post {
fill: var(--ontology-node-post-fill);
}

.ontology-node-person {
fill: var(--ontology-node-person-fill);
}

.ontology-node-organization {
fill: var(--ontology-node-organization-fill);
}

.ontology-node-team {
fill: var(--ontology-node-team-fill);
}

.ontology-node-generic {
fill: var(--ontology-node-generic-fill);
}

.ontology-node text {
fill: var(--color-text-heading);
stroke: var(--color-background);
Expand All @@ -1161,7 +1180,7 @@
font-size: 0.8rem;
}

.ontology-node-type {
.ontology-node text.ontology-node-type {
font-size: 0.7rem;
fill: var(--color-text);
}
Expand Down Expand Up @@ -1207,7 +1226,17 @@

.ontology-exact-values {
margin-top: 1rem;
max-width: 100%;
overflow-x: auto;
overscroll-behavior-inline: contain;
scrollbar-gutter: stable;
-webkit-overflow-scrolling: touch;
}

.ontology-exact-values:focus-visible {
outline: 2px solid var(--color-focus-border);
outline-offset: 2px;
border-radius: var(--radius-control);
}

.ontology-exact-values table {
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/components/OntologyExplorer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ describe("OntologyExplorer", () => {
expect(
screen.getByText(/This is an ontology neighborhood, not Event Lineage/),
).toBeInTheDocument();
expect(screen.getByRole("region", { name: "Exact values" })).toHaveAttribute("tabindex", "0");
await userEvent.click(screen.getByRole("button", { name: "Select node: Post Demo public post" }));
expect(screen.getByRole("heading", { name: "Demo public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Open evidence post" }));
Expand All @@ -248,6 +249,23 @@ describe("OntologyExplorer", () => {
expect(screen.queryByRole("img")).not.toBeInTheDocument();
});

it("maps known ontology node types to token-backed visual classes", () => {
render(
<OntologyExplorer
focusNodeType="node_post"
focusNodeId={POST_ID}
neighborhood={neighborhood()}
/>,
);

expect(screen.getByRole("button", { name: "Select node: Post Demo public post" }))
.toHaveClass("ontology-node-post");
expect(screen.getByRole("button", { name: "Select node: Person Test Person" }))
.toHaveClass("ontology-node-person");
expect(screen.getByRole("button", { name: "Select node: Organization Demo Corp" }))
.toHaveClass("ontology-node-organization");
});

it("names empty, truncated, denied, and rejected next actions", () => {
const { rerender } = render(
<OntologyExplorer
Expand Down
20 changes: 18 additions & 2 deletions frontend/src/components/OntologyExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ const NODE_TYPE_LABEL: Record<string, string> = {
node_team: "Team",
};

const NODE_TYPE_CLASS: Record<string, string> = {
node_post: "ontology-node-post",
node_person: "ontology-node-person",
node_corporate_entity: "ontology-node-organization",
node_team: "ontology-node-team",
};

const TRUTH_LABEL: Record<string, string> = {
truth_authoritative: "Authoritative",
truth_observed: "Observed",
Expand Down Expand Up @@ -418,7 +425,11 @@ function OntologyGraph({
{layout.nodes.map((node) => (
<g
key={nodeKey(node)}
className={nodeKey(node) === selectedNodeKey ? "ontology-node ontology-node-selected" : "ontology-node"}
className={[
"ontology-node",
NODE_TYPE_CLASS[node.node_type_code] ?? "ontology-node-generic",
nodeKey(node) === selectedNodeKey ? "ontology-node-selected" : "",
Comment thread
seonghobae marked this conversation as resolved.
].filter(Boolean).join(" ")}
transform={`translate(${node.x}, ${node.y})`}
role="button"
tabIndex={0}
Expand Down Expand Up @@ -468,7 +479,12 @@ function OntologyExactValueTable({
onSelectEdge: (edgeId: string) => void;
}) {
return (
<div className="ontology-exact-values">
<div
className="ontology-exact-values"
role="region"
tabIndex={0}
aria-label={t("Exact values")}
>
<h4>{t("Exact values")}</h4>
{payload.exact_value_rows.length === 0 ? (
<p>{t("No visible ontology relations for this focus. Open a Keyman or affiliated organization next.")}</p>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/ontologyExplorerCss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ describe("Ontology Explorer CSS contracts", () => {
it("defines the ontology node focus-visible rule exactly once", () => {
expect(appCss.match(/\.ontology-node:focus-visible\s*\{/g)).toHaveLength(1);
});

it("lets the node-type label override the shared node text color", () => {
expect(appCss).toContain(".ontology-node text.ontology-node-type {");
});
});
13 changes: 13 additions & 0 deletions frontend/src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@
--color-dashboard-positive: #426b45;
--color-dashboard-surface: #f4f6fa;

/* Ontology node surfaces (ADR 0184: shape and text remain primary) */
--ontology-node-post-fill: #e3f2fd;
--ontology-node-person-fill: #ede7f6;
--ontology-node-organization-fill: #fff3e0;
--ontology-node-team-fill: #e0f2f1;
--ontology-node-generic-fill: var(--color-background);

/* Layout & Breakpoint Tokens (§2.1 – 화면 해상도 / 반응형) */
--breakpoint-phone: 768px;
--breakpoint-tablet: 1024px;
Expand Down Expand Up @@ -209,6 +216,12 @@
--badge-status-prediction-bg: rgba(251, 146, 60, 0.2);
--badge-status-prediction-text: #fdba74;

--ontology-node-post-fill: #173653;
--ontology-node-person-fill: #30234d;
--ontology-node-organization-fill: #4b2e1d;
--ontology-node-team-fill: #123f3b;
--ontology-node-generic-fill: var(--color-background);

--color-drawer-bg: #16171d;
--color-drawer-border: #2e303a;
--color-drawer-overlay: rgba(0, 0, 0, 0.65);
Expand Down
35 changes: 33 additions & 2 deletions frontend/src/styles/tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ const BADGE_AND_ACCENT_TOKENS = [
"--badge-status-prediction-text",
];

const ONTOLOGY_NODE_TOKENS = [
"--ontology-node-post-fill",
"--ontology-node-person-fill",
"--ontology-node-organization-fill",
"--ontology-node-team-fill",
"--ontology-node-generic-fill",
];

// Colors this file's dark-mode block replaced -- a regression here would
// silently make dark mode as bright as light mode again (ADR 0099).
const RETIRED_LIGHT_ONLY_HEX = [
Expand All @@ -55,7 +63,10 @@ const RETIRED_LIGHT_ONLY_HEX = [

// WCAG 2.x relative luminance / contrast ratio (SC 1.4.3).
function relativeLuminance(hex: string): number {
const rgb = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
const normalized = hex.length === 4
? `#${[...hex.slice(1)].map((digit) => `${digit}${digit}`).join("")}`
: hex;
const rgb = [1, 3, 5].map((i) => parseInt(normalized.slice(i, i + 2), 16) / 255);
const [r, g, b] = rgb.map((c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
Expand All @@ -76,7 +87,7 @@ function readToken(block: string, token: string): string {
// literal passed by the call sites below (e.g. "--color-footer-bg"), and
// escapeRegExp() neutralizes any regex metacharacters before
// interpolation, so no caller-controlled input reaches RegExp unescaped.
const match = block.match(new RegExp(`${escapeRegExp(token)}:\\s*(#[0-9a-fA-F]{6})`)); // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
const match = block.match(new RegExp(`${escapeRegExp(token)}:\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})(?![0-9a-fA-F])`)); // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
Comment thread
seonghobae marked this conversation as resolved.
if (!match) throw new Error(`${token} not found as a hex value`);
return match[1];
}
Expand Down Expand Up @@ -125,6 +136,26 @@ describe("design tokens", () => {
}
});

it("defines and consumes ontology node fills in both color schemes", () => {
for (const token of ONTOLOGY_NODE_TOKENS) {
expect(lightBlock, `${token} missing from the light :root block`).toContain(`${token}:`);
expect(darkBlock, `${token} missing from the dark prefers-color-scheme block`).toContain(
`${token}:`,
);
expect(appCss, `App.css never references var(${token})`).toContain(`var(${token})`);
}
});

it("keeps ontology labels and their halo at WCAG AA contrast in both color schemes", () => {
for (const block of [lightBlock, darkBlock]) {
const background = readToken(block, "--color-background");
for (const token of ["--color-text-heading", "--color-text"]) {
expect(contrastRatio(readToken(block, token), background), `${token} label contrast`)
.toBeGreaterThanOrEqual(4.5);
}
}
});

it("does not dim .post-meta with opacity -- it fails WCAG AA in both themes and carries role=\"status\" next-action text", () => {
const match = appCss.match(/\.post-meta\s*\{([^}]*)\}/);
expect(match, ".post-meta rule not found in App.css").not.toBeNull();
Expand Down
Loading