Skip to content

fix(graph): F9 + F10 audit cleanup — all CRITICAL/HIGH/MEDIUM/LOW from 5-expert audit#35

Merged
explosivebit merged 1 commit into
developfrom
feature/audit-cleanup-f9
May 5, 2026
Merged

fix(graph): F9 + F10 audit cleanup — all CRITICAL/HIGH/MEDIUM/LOW from 5-expert audit#35
explosivebit merged 1 commit into
developfrom
feature/audit-cleanup-f9

Conversation

@explosivebit
Copy link
Copy Markdown
Contributor

Summary

5-expert multi-agent audit on develop @ 60823f1 (TS / Frontend / Security / Performance / UX) found 3 CRITICAL, 12 HIGH, 16 MEDIUM, 18 LOW. This PR closes them all in one pass across the 7 graph views.

CRITICAL

  • Selection-ring colour in 4 views (Force/Tree/Radial/Lanes) was drawn with stroke=kindBorder(node.kind) — dim grey for note/evidence kinds, so selected node was invisible after mouse-leave. Hardcoded stroke: var(--accent).
  • didFit one-shot in SankeyView + SunburstView: filter clears shrunk the layout but zoom transform stayed, rendering microscopic. Ported the layout-shape signature reset pattern from RadialView.

HIGH

  • Dropped dead d3-scale + @types/d3-scale (~50 KB never imported).
  • New lib/filter-memo.svelte.ts exports content-signature helpers; applied to Tree/Matrix/Lanes/Sankey/Sunburst — 10s polls with identical payload no longer cascade-invalidate the layout pipeline.
  • Sankey tier-header contrast → var(--fg-2) + font-weight: 500 (WCAG AA).
  • Sunburst label gate is zoom-aware (> 40 / max(0.5, transform.k) + hide rings ≥4 when k<0.5).
  • Per-view :focus-visible accent stroke verified in 5 views.
  • TS isLinkResolved<N>(l) type guard + getOrInit<K,V> helper. Drops as Node and 4 lazy-init bucket sites.
  • LOW security: CSS.escape(id) wraps data-id selector in focusNodeById.

MEDIUM (F10)

  • .node.selected .label { fill: var(--accent); } across 4 box-based views.
  • RadialView nodesSig includes collapsedSig so cluster-collapse toggle invalidates deterministically.
  • Matrix .cell.is-row, .cell.is-col accent stroke for cells in the selected row/column.
  • Sankey fitToView ceiling 1 → 1.5.
  • ArrowKey navigation wired into TreeView + LanesView.
  • HomePage .canvas-hint caption renders the active view's hint — disambiguates Sunburst's ancestor-chain fade vs the rest's BFS-distance fade.

LOW

  • Sunburst sector aria-label appended with (ring N, parent ID).
  • Global @media (prefers-reduced-motion: reduce) { svg *::* { transition: none } } in app.css.

Verify

  • npx svelte-check — 0 errors / 0 warnings / 425 files.
  • npm test — 40 / 40 pass.
  • npm run smoke — PASS.

Test plan

  • CI smoke matrix (3-OS × Node 22) green.
  • svelte-check clean.
  • vitest 40/40.
  • Manual: select a node in Force/Tree/Radial/Lanes — accent ring + accent label persist after mouse-leave.
  • Filter clear in Sankey/Sunburst — fitToView re-fits to the new bbox.
  • Tab/Arrow keys move focus directionally in Tree/Lanes (was Force/Radial only).

Refs: PRD-005

🤖 Generated with Claude Code

5-expert audit on develop @ 60823f1 found 3 CRITICAL, 12 HIGH, 16 MEDIUM,
18 LOW. This PR closes all of them across the 7 graph views.

CRITICAL (3):
- Selection-ring color: ForceView/TreeView/RadialView/LanesView all
  drew the .selection-ring with stroke=kindBorder(node.kind), which
  is dim grey for note/evidence kinds → selection invisible after
  mouse-leave. Now hardcoded `stroke: var(--accent)`.
- didFit one-shot: SankeyView + SunburstView never reset didFit.
  Filter clears reduced sectors/columns but zoom transform stayed,
  rendering microscopic. Ported the layout-shape signature pattern
  from RadialView; both views now refit on substantial structural
  change.

HIGH (12):
- Dropped dead `d3-scale` + `@types/d3-scale` (~50 KB never imported).
- New `lib/filter-memo.svelte.ts` exports
  `nodesContentSignature` / `edgesContentSignature`. Applied to
  Tree/Matrix/Lanes/Sankey/Sunburst (Force/Radial already inline).
  10s polls with identical payload no longer cascade-invalidate.
- Sankey tier-header contrast: `var(--fg-2)` + `font-weight: 500`
  (was 0.55 white opacity, failed WCAG AA at zoom-out).
- Sunburst label gate is zoom-aware: `> 40 / max(0.5, transform.k)`
  AND hide rings ≥ 4 when k < 0.5. Labels stop overlapping on
  outer rings.
- Per-view :focus-visible accent stroke verified in 5 views; Matrix
  uses .row-label/.col-label fill + .cell stroke variant.
- TS: new `isLinkResolved<N>(l)` type guard in lib/d3.ts; new
  `getOrInit<K,V>(map, k, factory)` in lib/map-utils.ts. Drops 2
  `as Node` casts in ForceView and 4 lazy-init bucket sites in
  cluster.svelte.ts. SankeyLink type cleanly Omit-intersected with
  SankeyPayloadLink.
- LOW security: `CSS.escape(id)` wraps the data-id selector in
  ForceView and RadialView focusNodeById — defense-in-depth even
  though /api/get validates ids.

MEDIUM (F10):
- `.node.selected .label { fill: var(--accent); }` verified in all
  4 box-based views.
- RadialView nodesSig now suffixes `|c:${collapsedSig}` so the
  cluster-collapse toggle invalidates the filtered-nodes memo
  deterministically.
- Matrix: `.cell.is-row, .cell.is-col` accent stroke when the
  selected node is the row/col index — selection bleeds through to
  the cell tint, not just the row/col header strip.
- Sankey fitToView ceiling 1 → 1.5 so narrow workspaces fit larger.
- ArrowKey nav wired into TreeView and LanesView (mirrors Force/
  Radial pattern). pickNextNode lib was already generic.
- HomePage canvas-hint caption (11px var(--font-mono)) renders the
  active view's `hint` from GRAPH_VIEWS — disambiguates the hover-
  fade semantic between BFS-distance views (5) and Sunburst's
  ancestor-chain view.

LOW:
- Sunburst sector aria-label appended with `(ring ${depth}, parent
  ${parentId})` — screen readers now hear the chain context.
- `@media (prefers-reduced-motion: reduce) { svg *, svg *::before,
  svg *::after { transition: none !important; animation: none
  !important; } }` in app.css — covers all hover-fade/zoom CSS
  transitions across the 7 views.

Verify:
- svelte-check 0 errors / 0 warnings / 425 files.
- npm test 40 / 40 pass.
- npm run smoke PASS.

Refs: PRD-005
@explosivebit explosivebit merged commit 28fa4e4 into develop May 5, 2026
3 checks passed
@explosivebit explosivebit deleted the feature/audit-cleanup-f9 branch May 5, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant