Skip to content

Qt-removal R7.2: relocate 33 Qt-free survivor modules out of graphlink_app/#140

Merged
dovvnloading merged 1 commit into
mainfrom
qt-removal/r7-2-relocate-survivors
Jul 26, 2026
Merged

Qt-removal R7.2: relocate 33 Qt-free survivor modules out of graphlink_app/#140
dovvnloading merged 1 commit into
mainfrom
qt-removal/r7-2-relocate-survivors

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Relocates everything backend/ has a live runtime dependency on — 17 flat modules (api_provider, graphlink_task_config, graphlink_licensing, the chart/chat/artifact agents, graphlink_grid_view_settings, graphlink_navigation_pins, graphlink_prompts, graphlink_version, graphlink_audio, graphlink_model_catalog, graphlink_secrets, graphlink_token_estimator, graphlink_memory) plus the graphlink_plugins/ package — to sit as ordinary siblings of backend/ at the repo root. Their existing bare-name cross-imports (import api_provider, etc.) are unchanged; backend/__init__.py's sys.path.insert hack that used to reach into graphlink_app/ for this is removed entirely.

content_codec.py stays put, gets ported instead

graphlink_session/deserializers.py/serializers.py (Qt-tainted, still live) import it via a normal package-relative import that a physical move would break. Its 6 functions are ported into backend/canvas.py instead — same "reimplement, don't import" precedent chat_library.py/crash_recovery.py already follow — removing the raw importlib.util.spec_from_file_location workaround this replaces.

graphlink_plugins/ moved wholesale, not selectively

An earlier plan tried to leave 3 still-Qt-tainted files behind (graphlink_plugin_context_menu.py, graphlink_plugin_portal.py, web_research/worker.py) to keep the new location Qt-pure. That broke immediatelyModuleNotFoundError on collection — because Python resolves a dotted import like graphlink_plugins.graphlink_plugin_portal against one specific package, whichever directory of that name comes first on sys.path. Two same-named packages can't coexist. So graphlink_plugins/ moved wholesale (all 19 files) and is not 100% Qt-free at its new home — that closes at the R7.6 cutover.

Two required fixes a design/verification pass missed entirely

It only checked backend/'s own consumption of these modules, not graphlink_app/'s own internal consumption of the files being pulled out from under it:

  • graphlink_app/tests/conftest.py's sys.path.insert only reached graphlink_app/ itself — now inserts repo root too, or ~30 test files fail collection.
  • graphlink_app/graphlink_app.py (the legacy entry point) needed its own sys.path insert before its first graphlink_* import — verified by actually importing the module and watching it fail at the wrong line when placed one line too late.

pyproject.toml

The 17 relocated modules are dropped from py-modules (setuptools' build_py only ever consults package_dir[""] for flat modules — confirmed empirically that per-module overrides are silently ignored — so a real wheel build won't bundle them; a known, accepted, currently-inert gap since nothing in this project's pipeline runs pip install -e . today). packages.find now searches both roots with a package-dir override for graphlink_plugins — verified via a real python -m build --wheel, which now succeeds. The dynamic version resolution does hard-fail a real build now (verified, not guessed) since it can only ever resolve against package_dir[""], which must stay pointed at graphlink_app/ for the ~89 modules still there — documented plainly as a known gap of the same tier.

Verification

2375 pytest — identical count to before (no test lost or duplicated), compileall clean from repo root, qt_burndown.json's pin re-verified unchanged at 152/84/68, 1053 vitest/tsc/eslint/build unaffected (no frontend touched). Both real entry points smoke-tested directly: importing graphlink_app.py as a module resolves every graphlink_* import cleanly, and backend.app.create_app() resolves everything from its new repo-root location with zero sys.path setup.

R7.3 (port the 3 confirmed test-coverage gaps for now-relocated survivor logic into backend/tests/) is next.

…hlink_app/

Moves everything backend/ has a live runtime dependency on - 17 flat modules
(api_provider, graphlink_task_config, graphlink_licensing, graphlink_chart_agent,
graphlink_chart_data, graphlink_chart_rendering, graphlink_chat_agent,
graphlink_artifact_agent, graphlink_grid_view_settings, graphlink_navigation_pins,
graphlink_prompts, graphlink_version, graphlink_audio, graphlink_model_catalog,
graphlink_secrets, graphlink_token_estimator, graphlink_memory) plus the
graphlink_plugins/ package - to sit as ordinary siblings of backend/ at the
repo root, preserving their existing bare-name cross-imports unchanged.
backend/__init__.py's sys.path.insert hack (which reached into graphlink_app/
for exactly this reason) is removed entirely - repo root is already on
sys.path whenever backend/ itself is importable, so no path manipulation is
needed for its new siblings either.

content_codec.py stays exactly where it is (graphlink_app/graphlink_session/),
NOT moved - graphlink_session/deserializers.py and serializers.py (Qt-tainted,
still live) import it via a normal package-relative import that a physical
move would break. Its logic is ported into backend/canvas.py instead (the
same "reimplement, don't import" precedent backend/chat_library.py and
backend/crash_recovery.py already follow), removing the raw
importlib.util.spec_from_file_location workaround this replaces.

graphlink_plugins/ moved WHOLESALE - all 19 files, including 3 still-Qt-tainted
ones (graphlink_plugin_context_menu.py, graphlink_plugin_portal.py,
web_research/worker.py). A design pass had proposed leaving those 3 behind to
keep the new location Qt-pure; that broke immediately at collection time
(ModuleNotFoundError) because Python resolves a dotted import like
graphlink_plugins.graphlink_plugin_portal against ONE specific package -
whichever directory of that name comes first on sys.path - and two
same-named packages can never coexist. graphlink_plugins/ is therefore NOT
100% Qt-free at its new home; that closes at the R7.6 cutover.

Two required fixes an earlier design/verification pass had missed entirely
(it only checked backend/'s own consumption of these files, not
graphlink_app/'s own internal consumption of the files being pulled out from
under it):
- graphlink_app/tests/conftest.py's sys.path.insert only reached
  graphlink_app/ itself; now inserts repo root too, or ~30 test files under
  graphlink_app/tests/ fail collection.
- graphlink_app/graphlink_app.py (the legacy entry point) needed its own
  sys.path insert added BEFORE its first graphlink_* import (line 5,
  `from graphlink_window import ChatWindow` - not line 7 as first proposed;
  verified by actually importing the module and watching it fail at the
  wrong place), since graphlink_window.py itself transitively needs
  api_provider/graphlink_audio/graphlink_prompts/graphlink_navigation_pins/
  graphlink_token_estimator.

pyproject.toml: the 17 relocated modules are dropped from py-modules
(setuptools' build_py only ever consults package_dir[""] for flat modules,
and per-module package_dir overrides are silently ignored for modules -
confirmed empirically - so there's no way to keep listing them while they
live outside graphlink_app/; a real wheel/sdist build won't bundle them,
a known, accepted, currently-inert gap since nothing in this project's
pipeline runs `pip install -e .` today). packages.find now searches both
graphlink_app/ and repo root, with a package-dir override for
graphlink_plugins (package-dir[""] applies to every discovered package
regardless of which `where` root found it, so without this a real build
fails outright rather than silently mis-packaging - verified via a real
`python -m build --wheel`, which now succeeds and includes all 19
graphlink_plugins/ files at their new path). The dynamic version resolution
(`attr = "graphlink_version.APP_VERSION"`) does hard-fail a real build now -
verified empirically, not guessed - since setuptools' attr: resolution for a
bare module name only ever consults package_dir[""], which must stay
"graphlink_app" for the ~89 modules still there; documented plainly as a
known gap of the same tier, closing at R7.6.

Also fixed: 5 backend/tests/*.py files had comments describing the
now-removed sys.path mechanism (code was already correct, only the
explanation was stale); graphlink_app/tests/test_ui_token_acceptance.py's
hardcoded-hex/font allowlist had two entries for files that no longer live
in the scanned tree (graphlink_grid_view_settings.py, graphlink_chart_rendering.py) -
removed rather than left to fail the "still exists" assertion; CONTRIBUTING.md
and the PR template's compileall/pytest instructions and graphlink_plugins/
path reference updated to match.

Verification: 2375 pytest (identical count to before - no test lost or
duplicated), compileall clean from repo root, qt_burndown.json's pin
re-verified unchanged at 152/84/68 (relocating already-Qt-free files changes
nothing the gate counts), 1053 vitest/tsc/eslint/build unaffected (no
frontend touched). Both real entry points smoke-tested directly: importing
graphlink_app/graphlink_app.py as a module (up to but not including main())
resolves every graphlink_* import cleanly, and backend.app.create_app()
resolves api_provider/graphlink_task_config/graphlink_plugins.*/etc from
their new repo-root location with zero sys.path setup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit a1d9da9 into main Jul 26, 2026
2 checks passed
@dovvnloading
dovvnloading deleted the qt-removal/r7-2-relocate-survivors branch July 26, 2026 11:41
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