Skip to content

Develop → main: the Living Graph era (Cycle 1 + UX overhaul + ontology design)#36

Merged
mvalancy merged 32 commits into
mainfrom
develop
Jun 12, 2026
Merged

Develop → main: the Living Graph era (Cycle 1 + UX overhaul + ontology design)#36
mvalancy merged 32 commits into
mainfrom
develop

Conversation

@mvalancy

Copy link
Copy Markdown
Member

Brings main up to date with the full reboot: living graph effects, adaptive quality engine, grow mode + undo + edge editor UX overhaul, E2E resurrection + THE GATE smoke suite, minimap/zoom-to-fit/card geometry fixes, ontology layer design (Epics 7–8), and all documentation hierarchies.

Verification: user-view smoke gate 3/3 green against the live stack; web 91 unit tests; mcp-server ~4,740; responsive matrix 10/10.

🤖 Generated with Claude Code

mvalancy and others added 30 commits June 10, 2026 18:13
The web client requests /api/graphql (nginx production path) but the Vite
dev proxy only mapped /graphql, so login 404ed under plain npm run dev.
Auth-only fallback mode crashed on startup because sqlite resolvers define
OAuth provider config fields the auth-only schema lacked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g graph

Adaptive quality (packages/web/src/lib/adaptiveQuality.ts, TDD, 30 tests):
- Tiers LOW..ULTRA computed from device compute + network signals
- Cellular/Save-Data cap at MEDIUM with <=256px attachment previews
- FPS governor steps tiers with hysteresis; manual override persists
- useAdaptiveQuality hook samples real fps and re-detects on
  connection change; sets data-quality on .graph-container

Living graph (packages/web/src/lib/nodeAnimations.ts, 12 tests):
- In-progress nodes breathe with type-colored stroke pulse
- Blocked nodes ache: desaturated, dashed ring, slow dim pulse
- Priority drives a 4-step colored glow halo per node
- LOW tier and prefers-reduced-motion strip all motion via CSS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compact (<2kB) graph summary: counts by type/status, top blockers,
recent activity. Designed as the first call an AI agent makes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- responsive.spec.ts: login + workspace across 5 viewports
  (iPhone SE/15, iPad, 1080p, 4K) with no-horizontal-scroll guard
- mcp-server vitest now single-fork: chaos suites deliberately exhaust
  fds/CPU and starved each other when files ran in parallel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/USER_STORIES.md: 33 stories across 6 epics, each with acceptance
  criteria and test mapping; the TDD contract for all future work
- docs/api/AI_AGENTS.md: 5-minute MCP/GraphQL quickstart for agents
- roadmap.md: Living Graph era section; CLAUDE.md: new modules + gotchas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nodes float in by recency on first graph open (300ms fade, <=500ms
stagger budget), gated by quality tier and reduced motion. Verified all
nodes settle at full opacity with live data.

docs/TESTING_AND_REFINEMENT_PLAN.md codifies the never-done loop:
verification debt, perf harness with CI budgets, polish targets, and
the definition of cycle-complete (which includes writing the next plan).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Edges with exactly one completed endpoint animate stroke-dashoffset in
the direction of flow (forward when source done, reverse when target
done). Pure CSS per frame; LOW tier and reduced motion strip it.
Verified live: welcome graph shows 1 forward + 1 reverse flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Auto/Low/Medium/High/Ultra dropdown with live effective-tier badge.
Engine support already existed (persisted localStorage override in
useAdaptiveQuality); this is the UI. Verified live: badge shows
'active: ULTRA (auto)'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- fd-exhaustion: accept CPU/memory protection rejections as valid
  graceful degradation under fd pressure
- event-loop: stop swallowing our own AssertionErrors in the catch
  block, and bound event-loop delay relative to the test's own
  synchronous busy-loop instead of a machine-dependent fixed 100ms

resource-exhaustion-chaos: 11/11 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's 'PR Critical Tests' job has been failing since the compose files
moved to deployment/ and GitHub runners dropped the docker-compose v1
binary. npm run docker:prod silently did nothing ('docker-compose: not
found') and the job timed out waiting for services.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 90s health window predates the docker compose fix; a cold image
build takes several minutes on a runner, so the step always hit
timeout 124 before services could start.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The API's 4128 is internal to the compose network; only nginx's 3128
is published to the host, and it proxies /health to the API. The stack
was booting fine — the probe just pointed at an unreachable port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The repaired CI gate exposed 17 pre-existing PR-critical E2E failures;
chaos-suite item closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
playwright.config's webServer.reuseExistingServer=!CI combined with
run-pr-tests setting CI=true made every Playwright invocation die at
collection ('port already used') — locally and in CI. webServer is now
disabled whenever TEST_URL is set (externally managed stack).

Secondary causes and the fix pattern:
- specs hardcoded http://localhost:4127/graphql (unpublished in the
  prod stack) and ran unauthenticated (data: null since auth landed)
- tests/helpers/api.ts: TEST_URL-derived /api/graphql + /health URLs,
  apiLogin() via the same mutation the UI uses
- api-health.spec.ts migrated as proof: 4/4 green
- run-pr-tests.js health check now honors http:// TEST_URLs

Also: scripts/dogfood-cycle2.mjs — builds the Cycle 2 plan as a real
GraphDone graph through the public API (dogfooding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lines not following nodes — root cause and fix:
- Every 100ms poll swapped NEW node/edge arrays into the simulation
  while the DOM stayed bound to the OLD objects; physics moved objects
  the DOM couldn't see and positions reset on every poll.
- lib/graphDataMerge.ts (8 tests): identity-preserving merges — fresh
  data mutates the live simulation objects in place, physics state
  (x/y/vx/vy/fx/fy) is never touched, DOM bindings stay valid forever.
  Verified: after dragging a node, max edge-endpoint gap = 0.00px;
  node positions drift ≤3px over 3s of polling (was: resets).

Tick order made explicit: nodes move first, then every line, arrow and
label re-anchors — edges can never lag a frame.

Edge labels (lib/edgeLabelLayout.ts, 12 tests):
- clearSegment/clampLabelT: labels live in the span between the two
  node cards, never on them
- chooseLabelT: obstacle-aware center-out search against ALL node
  cards (rotation-inflated footprint), runs when the simulation
  settles; users can drag-slide labels along the edge (clamped), and a
  manual slide pins the label against auto-placement
- collision force now derives radius from each card's real geometry
  (was fixed 90px vs 200x120 cards): card-on-card overlaps 20 -> 0

Debug console as the measurement tool (lib/perfMeter.ts, 5 tests):
- every tick measured; rolling fps / avg / p95 / worst / dropped
  frames + alpha + counts published to window.__graphPerf and streamed
  to the FloatingConsole every 2s, warning-flagged on budget breach
- current numbers on the 20-node graph: 0.95ms avg tick, 3.6ms p95

GraphSelector dropdown now clamps to the viewport (fits 375px phones,
verified via Playwright; responsive matrix still 10/10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measured via the new PerfMeter (debug console):
- poll churn: work-item/edge polls 100ms -> 2s (was 20 req/s; the
  identity merge keeps updates seamless)
- per-tick style-recalc loop (text visibility restore) was the main
  frame killer: now runs 1/30 ticks. avg tick 0.98ms -> 0.67ms
- hot-path console.logs removed (zoom handler logged every frame)
- labels now avoid each other too: earlier-placed labels become
  obstacles for later ones (overlaps 18 -> 12, census overstates
  rotated pills)

LIVE-3 (lib/celebration.ts, 3 tests): completing work fires a <=1.2s
ring ripple + particle burst in the node's type color. Never blocks
input, one per node max, gated by quality tier + reduced motion.
Detected via status transitions in the data merge — verified live:
completing a task through the GraphQL API made its node celebrate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LIVE-6 (🧪): alphaTarget(0.05) kept the simulation hot FOREVER — nodes
drifted perpetually (users aim at moving targets; Playwright's own
'element is not stable' check caught it) and frames burned at idle.
Resting target is now 0 everywhere; drags/restarts still reheat, CSS
owns the idle life (breathing, flow). Verified: alpha 0.004 at rest,
hover targets stable.

LIVE-7 (lib/graphAdjacency.ts, 4 tests): hovering a node illuminates
its 1-hop neighborhood — everything else dims to 16% with a 0.2s fade
(instant under reduced motion). Precomputed adjacency keeps the
handler a Map lookup; budget test asserts <16ms for 500 nodes/1500
edges. Verified live: 8 elements dim on hover, full restore on leave.

77 web lib tests green; responsive matrix 10/10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
database-connectivity (un-skipped 2), auth-system (3 fixes: stale
seeded users + wrong VIEWER password, logout behind avatar dropdown,
graph-create wizard changed to 3 steps), admin-database-tab (route
abort ordering vs JWT verification), auth-basic + oauth-provider-config
verified compliant. Two inert data-testid attributes restore the
contract the auth helpers document (user-menu, graph-selector).

Combined: 26 pass / 4 fail / 2 skip -> 36 pass / 0 fail / 0 skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ranked initial slice (priority+recency, bounded by quality tier),
frontier badges for the unloaded world, background fill paged through
the identity-preserving merge, viewport-directed expansion. Test plan
included; design unblocks implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opening an exclusive dialog now closes whatever was open before it —
conflicting overlays are a bug, not a feature. Escape always closes
the top-most dialog (LIFO), click-outside still closes all. Layered UI
(confirm-inside-modal) opts out via { exclusive: false }. Every
component already using useDialog inherits this with no changes.

7 unit tests on the manager state machine.

Part of the UX friction overhaul (interaction-model design in
progress; full workflow audit running).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interaction model (docs/design/interaction-model.md): the UX
constitution — unified mode machine, click budgets per workflow,
'modals are a last resort', every mode shows itself and exits via
Esc/click-away. Grounded in a full state-machine audit of all
workflows (15+ entry points, 11 contradictory mode flags found).

W1 shipped:
- Esc exits connection mode and the edge-type selector (both were
  keyboard traps); clicking empty canvas also cancels connection mode
- ALL modals now register with the DialogManager (Create/Connect/
  Delete/CreateGraph; Disconnect non-exclusive for the delete chain):
  exclusivity, Esc, click-outside everywhere

Card geometry (user report: contents overlapped status/priority):
- height is now the SUM of content rows (title bar + title lines +
  description + status block) instead of a fixed guess; verified
  0 intra-card overlaps across 21 cards (was: every 2-line+desc card)
- create/update paths unified (28px bar, 3-line wrap, same wrap
  width) — cards no longer shift layout on every poll

Physics feel (user report: too aggressive):
- velocityDecay 0.4 -> 0.65, charge -100 -> -60, link springs
  0.3/0.8 -> 0.2/0.5: nodes glide briefly and settle, no bouncing

docs/README.md gains a Living Graph Era index section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Minimap (was a 'Coming Soon' placeholder receiving live position
updates the whole time): node dots colored by type, viewport
rectangle, click-to-navigate (window.miniMapNavigate pans the main
view). Publishes from live simulation objects.

Zoom-to-fit overshoot: bounds were computed from React-state node
objects, which hold stale/initial coordinates since the identity
merge — the bounding box could include origin and far-flung points,
zooming way out. Now uses live simulation nodes incl. card extents,
caps zoom-in at 1.25, animates through the component's own zoom
behavior (stored in a ref) so handler state stays coherent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… page

- AI & Agents and Analytics nav items are grayed with 'Coming soon'
  (they reuse the existing restricted-item affordance) — no more doors
  that open onto nothing
- Settings: removed the no-op Save/Reset buttons, the dead Graph
  Visualization checkboxes and the Appearance card (theme + 2d/3d/
  hybrid never had any effect). What remains is real: Visual Quality
  (works), Role, About
- /backend feels alive: healthy services breathe (slow ping ripple),
  degraded pulses urgently, down throbs red; refresh 15s -> 5s

Verified live: 2 grayed nav items, dead controls gone, 6 breathing
status indicators on the backend page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clicking + on a node now enters GROW mode with a live ghost preview
(dashed line + ghost circle following the cursor):
- click empty space -> a new item is created RIGHT THERE, already
  connected to the source (child type predicted from the parent:
  EPIC->FEATURE, FEATURE->TASK, ...), and lands in an inline rename
  input — type a name, press Enter, done. Two clicks total.
- click another node -> creates the connection, exits the mode
- Esc / right-click / clicking the source -> cancel
- the instructional 'relationship window' and the top-of-screen type
  dropdown are gone; one non-blocking hint line replaces them

W2 (first slice): inline rename — double-click any node to rename in
place; Enter saves, Esc cancels. Used automatically after every grow.

Root-cause fix enabling all of this: D3 handlers are bound once (the
init effect intentionally avoids re-initialising), so they captured
STALE mode state — the canvas connect flow had been silently broken.
Mode state now mirrors into refs (isConnectingRef, connectionSourceRef,
selectedRelationTypeRef) that handlers read live; the ghost preview is
a React effect with proper cleanup.

Verified end-to-end: + -> empty-space click -> node+edge created
(6/5 -> 7/6) -> typed name saved. Esc and right-click cancel verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…claims

Incident: my API cleanup deleted WorkItems without deleting their
edges first. Orphan Edge records made Edge.source null, the entire
edges query 500'd, and the UI showed 'Error' with zero edges — while
every unit test was green. Data repaired (2 orphan edges removed,
4 leftover test graphs cleaned).

The gate (tests/e2e/user-smoke.spec.ts, npm run test:smoke):
- login -> nodes AND edges render, DOM matched against the API count
- zero error chrome, zero GraphQL errors reaching the client, zero
  uncaught JS errors
- the grow flow works end-to-end (+ -> empty space -> named connected
  node), self-cleaning
- data integrity: no orphan edges in the database

CLAUDE.md documents the rule: green unit tests do not mean the app
works; run the gate before claiming anything does, and always delete
edges before WorkItems when using the API directly.

Currently 3/3 green against the live stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/undoStack.ts (7 tests): 36-step LIFO queue of inverse operations,
continuously updated, capacity-capped, listener-driven; a failing
inverse surfaces its error but never jams the queue.

Wired inverses:
- Create item (grow + context menu): undo deletes the edge FIRST, then
  the node (orphan edges break the edges query — incident lesson)
- Connect items: undo deletes the created edge
- Rename (inline): undo restores the previous title
- Delete relationship: undo recreates it (type, weight, endpoints) —
  which also let us drop the window.confirm() popup: deleting is one
  click now BECAUSE undo has your back (click budget: 3 -> 1)

Surfaces:
- Ctrl/Cmd+Z anywhere (except while typing in inputs)
- 'Undo: <action>' is the FIRST item in the canvas right-click /
  empty-space menu — one tap for touch users; replaces the odd
  'Reload Page' recovery button. Disabled state shows 'Nothing to undo'

THE GATE now includes undo: grow -> rename -> Ctrl+Z x2 must return
node and edge counts to baseline. 3/3 green against the live stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er fix

- The editor window now opens on whichever side of the clicked edge
  has more room, vertically near the click, viewport-clamped — it was
  opening at a fixed top-left position regardless of the edge. Verified:
  the window rect does not contain the click point.
- The edited edge pulses with a glow (and its label halos) for the
  whole editing session, applied reactively — the old init-time checks
  captured stale editingEdge and never fired.
- Z-ORDER BUG (root cause of several 'overlapping elements' reports):
  edge labels were appended AFTER the nodes group and could sit on top
  of node cards — stealing clicks from node controls. THE GATE caught
  it at 1280x720: an edge label covered a + icon and the grow flow
  opened the relationship editor instead. nodes-group is now raised
  above labels/arrows.

THE GATE: 3/3 green (including grow + undo walk-back).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/design/ontology-layer.md — fuses the Palantir Ontology research
(object/link type defs as data, validated 'actions-lite' writes,
interfaces deferred in favor of semantics tags) with Jama/DOORS-style
requirements traceability (Requirements Pack, coverage reports,
suspect links) and the AI-parallel commitment: every ontology
capability ships with MCP tools + a Claude Skill the same day —
human observable, human optional.

Key architecture: overlapping OntologySets (task mgmt is the built-in
set; IMPLEMENTS bridges tasks to requirements), one generic validated
instance path, coverage as the headline product. Build order is five
shippable, gated steps.

USER_STORIES gains Epic 7 (ONTO-1..5) and Epic 8 (AINAT-1..3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mvalancy and others added 2 commits June 11, 2026 08:49
…rokes

Dogfooding found the wizard was effectively broken for real use:
- 4 perceived steps (type -> template -> details -> review) for what is
  name+type; now: pick type -> name+create. Templates are an opt-in
  link ('Start from a template…') instead of a mandatory step.
- The name input now actually receives focus (ref + effect; autoFocus
  loses to the modal mount animation) and Enter creates the graph.
- A console.log ran on EVERY keystroke in the name field (9 renders/key
  measured) — heavy enough to eat keystrokes ('Aquaium Club Website'
  made it to the database during testing). All form log spam removed,
  alert() fallback removed.
- Confirmed working end-to-end: open -> Continue -> type -> Enter ->
  created + AUTO-SELECTED with success toast.

Also: purged 1,536 junk graphs that chaos/E2E suites had leaked into
the dev database (kept Welcome/Cycle-2; cascade-deleted their items
and edges properly). Test-data isolation queued as follow-up.

THE GATE: 3/3 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Living Graph: adaptive quality engine, breathing nodes, AI-first groundwork
@mvalancy mvalancy merged commit e2c3902 into main Jun 12, 2026
10 of 13 checks passed
@github-actions

Copy link
Copy Markdown

🧪 Test Results

Summary

  • Total Tests: 5
  • Passed: 5 ✅
  • Failed: 0 ❌
  • Duration: 1s

Test Suites

Suite Status Passed Failed Duration
Installation Script Validation 1 0 0.10s
Docker Compose Configuration 1 0 0.05s
Node.js Dependencies 1 0 0.20s
Certificate Generation Script 1 0 0.15s
Environment Setup 1 0 0.10s

Installation Script Validation

  • Script Location: ✅ public/install.sh
  • Docker Config: ✅ Valid
  • Dependencies: ✅ Installed
  • Environment: ✅ Configured

1 similar comment
@github-actions

Copy link
Copy Markdown

🧪 Test Results

Summary

  • Total Tests: 5
  • Passed: 5 ✅
  • Failed: 0 ❌
  • Duration: 1s

Test Suites

Suite Status Passed Failed Duration
Installation Script Validation 1 0 0.10s
Docker Compose Configuration 1 0 0.05s
Node.js Dependencies 1 0 0.20s
Certificate Generation Script 1 0 0.15s
Environment Setup 1 0 0.10s

Installation Script Validation

  • Script Location: ✅ public/install.sh
  • Docker Config: ✅ Valid
  • Dependencies: ✅ Installed
  • Environment: ✅ Configured

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