MessageFlow constellation hardening¶
Motivation¶
A self-critique pass over the new D3 "Bounded Lanes (A1)" constellation (MessageFlowConstellation.tsx) surfaced a set of correctness, accessibility, and performance defects that the mechanical review (build + focused tests) could not see. None changed the data contract; all are presentation-layer hardening.
User-facing change¶
Twenty fixes, grouped:
Accessibility
1. svg role="img" → role="group" so the focusable job buttons inside are no
longer hidden from screen readers (an img collapses its subtree).
2. Decorative layers (boundary, labels, links, session hulls) marked
aria-hidden so assistive tech only walks the interactive job nodes.
3. Dynamic svg aria-label summary ("N producers, M active jobs, K clusters").
4. Producer nodes get role="img" + aria-label (kind, alias, job count).
5. Consumer/cluster nodes get role="img" + aria-label (name, running, queued).
6. Producer <title> with the full untruncated alias + active count.
7. Cluster <title> with the full untruncated cluster name.
Hover / interaction correctness
8. Cluster-hover no longer dims the whole graph (clusters share a sentinel
alias; hovering one must not set it as the active submitter).
9. Hover state is re-applied after a 20 s refetch rebuild so an in-progress
hover survives the rebuild.
10. A hovered alias that left the snapshot is dropped before re-apply, so a
departed submitter can never pin the graph into an all-dimmed state.
11. Drag gains a clickDistance(5) so a tiny jitter while clicking a job no
longer suppresses opening its JSON detail (and a real drag no longer
accidentally opens it).
Performance
12. requestAnimationFrame-debounced ResizeObserver so a drag-resize burst
coalesces into a single rebuild instead of restarting the simulation per
pixel.
13. Per-tick session bounding-box pass uses a precomputed alias → members
map (O(jobs) instead of O(sessions × jobs)).
14. Live prefers-reduced-motion listener: flipping the OS setting rebuilds
and immediately honours it (no remount needed).
Visual bounds / robustness
15. Job radius capped at 18 px so a pathologically large query cannot grow a
node to fill the broker region.
16. Producer alias labels truncated (22 chars) so long UPNs do not run off the
SVG edge.
17. Cluster name labels truncated (18 chars).
18. Session labels truncated (18 chars).
19. Removed dead anchorX / anchorY node fields.
20. Model unit test extended with the radius-cap case; the existing sqrt-scaling
assertion moved to a sub-cap input.
API / IaC diff summary¶
None. No backend, route, schema, or IaC change — the component still consumes
the same read-only MessageFlowSnapshot.
Validation evidence¶
cd web && npm run build— clean (tsc + vite).cd web && npx eslint src/components/cards/MessageFlow/— clean.cd web && npx vitest run— 852 passed (96 files), including the extendedconstellationModel.test.ts(radius cap + sqrt scaling).