Qt-removal R1: canvas core - React Flow scene on the WS backend#100
Merged
Conversation
…down gate Per doc/QT_REMOVAL_PLAN.md phase R0 (terminal goal: ZERO PySide6/Qt): - backend/: FastAPI app + session-scoped WebSocket event bus carrying the same versioned full-state-snapshot + named-intent wire semantics as the IslandBridge layer it succeeds; static serving of the SPA build; /api/health; 14 pytest cases (bus + WS endpoint). - web_ui/src/app: the single-SPA target consolidating the islands (GRAPHLINK_ISLAND=app builds to dist/app). WS transport module with auto-reconnect/re-subscribe (9 tests), token-driven shell layout, live system panel. - graphlink_island_codegen.py: emits web_ui/src/lib/api-contract/topics.ts, the SPA's topic->type/validator registry derived from the same schema registry (--check enforced). - graphlink_desktop.py: NATIVE pywebview/WebView2 window (not a browser, zero Qt) that boots the backend and opens the SPA. - tests/test_no_qt_anywhere.py + qt_burndown.json: THE permanent gate. Pin = 168 files importing Qt (90 source + 78 tests); any increase fails, any decrease must lower the pin in-commit; flips to zero-mode assertions (package uninstalled, undeclared) when the pin reaches 0. backend/, web_ui/, and graphlink_desktop.py are held to zero Qt from day one. Acceptance drive PASSED: native window, SPA served by the Python backend, live WS ping round-trip 4.1 ms. 17 pytest + 544 vitest green. Burn-down: 168 of 168 remaining (R0 adds only Qt-free code by design). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… SPA target - App.tsx: drop the vestigial transportRef (ref write during render - react-hooks/refs error); the memoized transport was already the only consumer. - transport.test.ts: comma-expression assertion rewritten as a real assertion (no-unused-expressions error). - test_gl_vars_dev_css.py: src/app is a documented carve-out from the dev-only token rule - the SPA has no Qt host injecting :root tokens, so the shipped file IS its token source until R2's backend-served theme. Islands remain fully guarded. Local CI parity: npm run check exit 0; full offscreen pytest 1783 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per doc/QT_REMOVAL_PLAN.md phase R1: - backend/canvas.py: SceneDocument (nodes/edges/pins/settings) sourced from the surviving Qt-free models - GridViewSettings republished 1:1 on the grid-control topic (intent names match GridControlBridge's @Slots for the R2 island port), NavigationPinStore reused verbatim for pins. Full intent surface (add/move/remove/connect nodes+edges, pins CRUD, snap, drag factor), edges die with either endpoint, duplicate-connect idempotent. 11 new pytest cases. - graphlink_scene_payload.py + codegen entry: scene topic wire contract -> generated TS type + validator; topics.ts regenerated (20 artifact sets). - web_ui/src/app/canvas: SceneStore (validated snapshots, framework-free, 9 vitest cases incl. the scaleDragPosition drag-speed contract) + SceneCanvas (React Flow): model-driven grid background, snap grid, drag-speed scaling from drag start, connect via node Handles, selection + delete keys, minimap, LOD collapse threshold, pins panel (add view pin / jump / remove), double-click create (RF dblclick-zoom disabled - it swallowed the gesture; found in the live drive). Live acceptance PASSED in the native pywebview window: created 2 nodes, moved one (backend persisted 523,215), connected n0->n1 by handle drag, pinned the view + jumped + removed, minimap tracked throughout; a FRESH WS client then received the complete backend-held scene - the round-trip criterion, proven with a second client rather than a reload. CI parity verified locally: npm run check exit 0 (52 files / 553 vitest), full offscreen pytest 1794 passed. Burn-down: 168 of 168 remaining (R1 adds only Qt-free code; deletion starts as R2+ cuts surfaces over). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e R1 superset Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dovvnloading
added a commit
that referenced
this pull request
Jul 22, 2026
…ng the R2 superset # Conflicts: # backend/app.py # backend/canvas.py # backend/tests/test_app_ws.py # backend/tests/test_canvas.py # graphlink_app/graphlink_island_codegen.py # graphlink_app/graphlink_scene_payload.py # web_ui/src/app/App.tsx # web_ui/src/app/canvas/SceneCanvas.tsx # web_ui/src/app/canvas/sceneStore.test.ts # web_ui/src/app/canvas/sceneStore.ts # web_ui/src/app/styles.css # web_ui/src/lib/bridge-core/generated/scene-state.schema.json # web_ui/src/lib/bridge-core/generated/scene-state.ts
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.
Phase R1 of the Qt/PySide6 full-removal plan
Stacked on #99 (R0 foundations) — merge #99 first and this diff auto-shrinks to the R1 changes. (GitHub rejected the stacked-base PR with its intermittent 'no commits between' error, same as PR #98; base is main as the workaround.)
The QGraphicsScene successor: a React Flow canvas in the SPA, driven by a backend-held scene document over the WebSocket bus.
Backend
backend/canvas.py— SceneDocument (nodes/edges/pins/settings) sourced from the surviving Qt-free models:GridViewSettingsrepublished on thegrid-controltopic with intent names 1:1 with GridControlBridge's @Slots (making the R2 control-island port a transport swap), andNavigationPinStorereused verbatim for pins. Full intent surface; edges die with either endpoint; duplicate connects are idempotent. 11 new tests.graphlink_scene_payload.py+ codegen entry — scene wire contract → generated TS type/validator,topics.tsregenerated.Frontend (
web_ui/src/app/canvas)scaleDragPositiondrag-speed contract).Live acceptance (native pywebview window) — all per plan R1
Verification (exact CI commands, locally)
npm run checkexit 0 — 52 files / 553 vitest ✅Burn-down: 168 of 168 remaining (R1 adds only Qt-free code; Qt deletion begins as R2+ cuts surfaces over).