From 6e5fde2b017773a58f3be376a3268b0232c54a5c Mon Sep 17 00:00:00 2001 From: byrongamatos Date: Sat, 11 Jul 2026 18:09:33 +0200 Subject: [PATCH] refactor(app): carve the plugin loader out of app.js into static/js/ (R3a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first carve, and deliberately the riskiest: app.js IS the plugin loader (the R0 host rails), so it goes first while the module graph is still one edge deep. static/js/plugin-loader.js (829 lines) — bodies VERBATIM. app.js 12,217 → 11,439. Core's first `static/js/` module, exactly as constitution II anticipates. CLOSURE (measured with acorn, not regex — brace-matching stripped source drifted): the block at app.js:11246-12031 is contiguous and self-contained. It needs only TWO things from the rest of app.js, and exports only TWO: exports: loadPlugins (the window contract), bootstrapPluginsAndUi (boot) inbound: window.showScreen — already the public host contract (constitution II), so it is called through `window`, not re-coupled as an import _populateVizPicker — injected via configurePluginLoader() WHY A SEAM, NOT AN IMPORT. plugin-loader must not import app.js: app.js imports it, so that would close a cycle. I checked whether _populateVizPicker could just move into the module instead (which would delete the seam entirely) — it drags 9 further symbols (_canRun3D, _autoMatchViz, _showPromotionNag, …), i.e. a whole viz cluster. That is its own carve, so the seam stays. THE SEAM'S DEFAULT IS LOUD, ON PURPOSE. A no-op stub is the classic silent failure for this pattern (see the editor's setHostHooks trap, hit twice): drop the wiring call and the loader keeps working while the viz picker quietly stops refreshing — no test, no boot check says a word. The default now console.errors, so the smoke harness catches it. VERIFIED BY BITE TEST: removing configurePluginLoader() from app.js surfaces "[plugin-loader] host seam not configured" at boot. The seam IS exercised on the plugin-startup path, so an unwired hook cannot pass silently. no-cycle is now LIVE on core's own graph for the first time. eslint.config.js gains `static/app.js` + `static/js/**` to the module block — app.js now `import`s, so parsing it as a script would be a syntax error. VERIFIED BY BITE TEST: making plugin-loader import app.js back fails with "Dependency cycle detected". HARNESSES (the R3a note said budget one conversion per carve — it was five): retargeted capability_inspector_nav, plugin_hydration_wipe, plugin_loader_script_type, plugin_style_injection, legacy_shim_hits (SPLIT — one test needs the loader, one still needs app.js) + test_plugin_runtime_idempotence. legacy_shim_hits was missed by a symbol-name grep because it greps for a code STRING; only the failing run found it. test_capability_events' NEGATIVE asserts now span app.js + the loader — carving code out of app.js would otherwise make them vacuous instead of failing. VERIFIED: A/B against origin/main in two browsers — mounted plugin screens, 14 loaded plugin scripts, the 3 module plugins injected as