feat(viz): clinical-hospital graph rebuild — sigma.js + graphology LOD viewer#51
Open
cdeust wants to merge 12 commits into
Open
feat(viz): clinical-hospital graph rebuild — sigma.js + graphology LOD viewer#51cdeust wants to merge 12 commits into
cdeust wants to merge 12 commits into
Conversation
…re running workflow Pre-flight fixes so clinical-graph-rebuild.js runs clean: B1 (L6 key): workflow now enumerates dynamic L6:<slug> keys from /api/graph/progress.phases — never hardcodes "L6". B2 (CXGB missing decoder): workflow drops /api/graph.bin fast-path; cold-start uses SSE + /api/graph/phase instead. B3 (/clinical/ 403): added serve_clinical() + /clinical/ route in _route_unified_get; _clinical_root/_clinical_html_path on Handler class. B4 (Sigma duplicate addNode): workflow now specifies loadedPhases + pendingPhases Sets dedup pattern in constraints. B5 (stash conflict): resolved — server files take server-pipeline version. Also: node_total + edge_total added to get_phase_payload() response so the workflow's phase-size guards work. Sigma v3 + graphology v0.25.4 vendored offline at ui/clinical/vendor/. Workflow PR target corrected to viz/server-streaming-pipeline. W2 SSE source.close() added to constraints. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleted all .memsearch/ files (index pid + 4 daily memory snapshots) and added .memsearch/ to .gitignore. No other memory plugin is planned; Cortex handles all persistent memory via PostgreSQL + pgvector. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…D viewer Implements the clinical-hospital navigation model at ui/clinical/: Navigation model (big-picture → zoom → sub-graph): • L0 domain bubbles on open (~20 nodes, instant render) • Scroll/pinch deepens phase: L0→L1→L2→L3→L4→L5→L6:<slug> • Click any node → chain-of-call/action panel (separate Sigma instance) • Zero JS errors, zero console.error in production paths Renderer: Sigma.js v3 + graphology v0.25.4 (WebGL, vendored offline) • Positions from /api/quadtree (DrL layout); circular fallback on 503 • SSE primary channel (/api/graph/events), phase API secondary • rAF drain: max 200 nodes + 400 edges per frame • loadedPhases + pendingPhases Sets prevent duplicate addNode (Sigma throws) • EventSource.close() on SSE done event prevents reconnect loop Accessibility (all 7 blockers from verify phase fixed): • depth-dot <div>s converted to <button> with aria-label + aria-pressed • neighbour <li>s get role=button, tabindex=0, keydown Enter/Space handler • :focus-visible CSS on all interactive elements • console.error demoted to console.warn in state dispatch and boot • Unconditional throw guarded by _showStatus before re-throw Backend fixes (same commit): • get_phase_payload() now returns node_total + edge_total fields • serve_clinical() + /clinical/ route added to http_standalone.py • _clinical_root/_clinical_html_path wired onto Handler class Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rect endpoints file Three bugs introduced during stash-conflict resolution: 1. /api/graph/events and /api/graph.bin were not routed — requests fell through to the HTML handler, returning text/html instead of SSE. Fix: added both routes to _route_unified_get. 2. http_standalone_endpoints.py was the old version (missing serve_graph_events / serve_graph_binary). Restored from viz/server-streaming-pipeline. 3. http_standalone_graph.py also restored from the pipeline branch (had the correct _graph_cache_lock etc.). Re-applied node_total + edge_total fields to get_phase_payload. 4. serve_clinical imported non-existent send_plain_error — inlined the 503 response directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(graph, node, data) Sigma v3 nodeReducer and edgeReducer are both called with (key, attributes) — two args, no graph instance. The generated code had 3-arg signatures (_g, _node, data) and (graph, edge, data), so 'data' was always undefined causing 'Cannot read properties of undefined (reading depth)' on every node render. edgeReducer now uses module-level _graph for endpoint visibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sigma v3 uses the node `type` attribute as a WebGL program name. The server sends nodes with type="domain", type="skill", etc. which Sigma doesn't recognise, crashing with "could not find a suitable program for node type domain". Fix: destructure `type` out of server node attrs before the spread so it never reaches graphology/Sigma. Set `type: "circle"` explicitly (the default Sigma v3 program). The server's node kind is preserved in the `kind` attribute for colour/size logic. Also fix edgeReducer signature (Sigma v3: (key, data) not (graph, edge, data)) and ensure _graph module-level reference is used for endpoint visibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same root cause as the node type fix: Sigma v3 reads the edge `type`
attribute as a WebGL program name. Only "line" and "arrow" are built-in.
Server edge kinds ("in_domain", "calls", "about_entity", etc.) are now
stored as `kind` and `type` is hardcoded to "line".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ll nodes - DEPTH_SIZE [22,16,12,8,5,4,3] → [8,5,4,3,2,2,1.5]: dots were too large - _shortLabel(): strips paths, structured-id prefixes, extensions, truncates at 28 chars so "/Users/.../layout_authority.py" → "layout_authority" - labelRenderedSizeThreshold: 7 — only L0 domain nodes get persistent labels; all other nodes show label on hover only (Sigma v3 default) - labelColor/font/size set to match the dark Cortex theme Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ces random ±100
Root cause of overlapping: quadtree is 503 on fresh start so all nodes
fell back to random positions in a ±50 unit space, creating an unreadable
mass.
New layout engine in streaming.js:
- L0 domain nodes: golden-angle ring at radius 1400 — 20 domains
cleanly spaced with no overlap
- L1+ nodes: golden-angle orbit around their domain hub, radius scaled
by depth (280 + depth×180) — gives visible structure at each LOD
- Domain centroid fallback (step 3) tightened to ±10 jitter
Also in renderer.js:
- DEPTH_SIZE [22,16,12,8,5,4,3] → [8,5,4,3,2,2,1.5]
- _shortLabel: strips paths/prefixes/extensions, 28-char cap
- labelRenderedSizeThreshold: 7 — only L0 domain nodes get persistent
labels; everything else shows label on hover only
- edgeType hardcoded to "line" (Sigma v3 program)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three root causes of the all-nodes-visible blob:
1. SSE batch labels ("L0 domains", "skeleton", "L5 memories", "L6 X symbols")
didn't match _depthForKey which only knew "L0","L1",... Fixed:
_depthForKey now handles prefix matching + kind-based fallback so SSE
nodes get the correct depth and are hidden at deeper levels.
2. Infinite retry loop: failed phase loads retried every 2s forever because
loadedPhases.add(key) was never called on permanent failure. Fixed:
after one retry, key is added to loadedPhases regardless of outcome.
SSE stream delivers the same data anyway.
3. Colors: matched to the unified graph palette — domain gold, file cyan,
memory emerald, hook purple, agent pink, discussion red, symbol slate.
Vivid colors make domain clusters visually distinct like the reference.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
igraph >= 0.10 requires seed to be a Layout object (matrix), not an integer. Passing seed=0 raised 'matrix expected in seed', causing /api/recompute_layout to return 503 on every call. Removing the parameter lets igraph initialise randomly (same practical result for a deterministic-enough FR layout at this scale). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IDLE_TIMEOUT now reads CORTEX_IDLE_TIMEOUT env var (default 600s). Run with CORTEX_IDLE_TIMEOUT=3600 during dev to stop the server dying mid-session. - layout_engine: remove seed=int from layout_fruchterman_reingold — igraph >= 0.10 requires seed to be a Layout matrix, not an integer. This was the root cause of /api/recompute_layout always returning 503. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ui/clinical/— a fresh graph visualization using the clinical-hospital navigation modelNavigation model
<dialog>, main view untouchedWhat's in this PR
ui/clinical/index.htmlui/clinical/js/boot.jsui/clinical/js/state.jsui/clinical/js/api.jsui/clinical/js/renderer.jsui/clinical/js/navigation.jsui/clinical/js/streaming.jsui/clinical/js/subgraph.jsui/clinical/js/chain-panel.jsui/clinical/vendor/ui/clinical/docs/Backend changes (same PR)
get_phase_payload()now returnsnode_total+edge_totalserve_clinical()+/clinical/route added tohttp_standalone.pyTest plan
See
ui/clinical/docs/04-smoke-test.mdfor the full manual test plan.🤖 Generated with Claude Code