refactor(ui): L-shell step 5e.0 — migrate coordinator.js to an ES module

Lift coordinator.js off the window.createCoordinatorPane bridge onto a real ESM
export, so the upcoming shared conversational module (5e) is import-consumed on
both sides rather than through a classic window global. The console shell and the
standalone page's bootstrap both import the factory now; zero behaviour change.

- coordinator.js: export the factory, drop the window bridge — no classic
  consumer remains (unlike interactive.js, whose ui/static app.js still uses its
  global).
- shell.js: import the coordinator factory by URL (mirrors the interactive
  import) and call it directly.
- console index.html: stop script-tagging coordinator.js; shell.js's import loads
  it (a classic tag chokes on the top-level export).
- coordinator/index.html: the standalone bootstrap becomes a module that imports
  the factory — a classic eager IIFE ran before the deferred module loaded it.
- tests: pin the new ESM seam (export, shell import, module bootstrap).
This commit is contained in:
Patrick Buckley
2026-06-06 14:56:00 -07:00
parent 1d399703b1
commit 6614b4a549
6 changed files with 56 additions and 35 deletions
+15 -2
View File
@@ -503,7 +503,14 @@ def test_coordinator_de_globalized_to_pane_factory():
index_html = (base / "index.html").read_text(encoding="utf-8")
assert "function createCoordinatorPane(root, wsId, opts) {" in coord_js
assert "window.createCoordinatorPane = createCoordinatorPane;" in coord_js
# Step 5e.0: coordinator.js is a real ES module the shell imports — the bare
# `export` is its only seam. No `window.*` bridge (unlike interactive.js,
# whose classic ui/static/app.js still needs the global): both the console
# shell and the standalone bootstrap import the factory.
assert "export { createCoordinatorPane };" in coord_js
assert "window.createCoordinatorPane" not in coord_js, (
"no dead window bridge — both consumers import the factory"
)
assert "function destroy() {" in coord_js, "a pane must have a teardown path"
# ws_id is a constructor arg now, not read off <html>; lookups are root-scoped.
assert "document.documentElement.dataset.wsId" not in coord_js
@@ -513,7 +520,13 @@ def test_coordinator_de_globalized_to_pane_factory():
# No page-global collision points (multi-instance safe).
for gone in ("window.coordSend", "window.coordCloseSession", "window.onLoginSuccess"):
assert gone not in coord_js, f"de-globalized: {gone} must be gone"
# Standalone page = one pane filling the body; the inline close onclick is gone.
# Standalone page = one pane filling the body, bootstrapped by a MODULE that
# imports the factory (a classic eager IIFE would run before the deferred
# coordinator module loaded it); the inline close onclick is gone.
assert '<script type="module">' in index_html
assert (
'import { createCoordinatorPane } from "/static/coordinator/coordinator.js"' in index_html
)
assert "createCoordinatorPane(document.body" in index_html
assert 'onclick="coordCloseSession()"' not in index_html
+9 -6
View File
@@ -245,7 +245,7 @@ def test_step4_coordinator_pane_registered_and_wired() -> None:
the console loads the coordinator controller + its migrated chrome CSS."""
shell = _SHELL_JS.read_text(encoding="utf-8")
assert 'registerType("coordinator"' in shell, "shell must register the coordinator pane type"
assert "window.createCoordinatorPane(this.bodyEl, id" in shell, (
assert "createCoordinatorPane(this.bodyEl, id" in shell, (
"onMount must build the controller into the pane body"
)
assert "this._ctl.connect()" in shell and "this._ctl.destroy()" in shell, (
@@ -259,23 +259,26 @@ def test_step4_coordinator_pane_registered_and_wired() -> None:
assert 'paneManager.openPane("coordinator", ws.id)' in rail, (
"rail coordinator clicks must open a pane, not full-page nav"
)
assert 'from "/static/coordinator/coordinator.js"' in shell, (
"step 5e.0: the shell IMPORTS the coordinator controller (ESM), not a <script> tag"
)
index = _CONSOLE_INDEX.read_text(encoding="utf-8")
assert "/static/coordinator/coordinator.js" in index, (
"console must load the coordinator controller"
assert "/static/coordinator/coordinator.js" not in index, (
"coordinator.js is imported by shell.js now, not <script>-tagged in the console"
)
assert "coord-chrome.css" in index, "console must load the coordinator chrome CSS"
def test_step5_interactive_pane_registered_and_wired() -> None:
"""Step 5b: the shell registers a ws_id-keyed interactive pane over the
NODE-PROXIED transport. Unlike the classic coordinator (read off window),
the interactive pane is a real ES module the shell IMPORTS; its node is
NODE-PROXIED transport. Both panes are ES modules the shell IMPORTS (step
5e.0 lifted the coordinator off window too); its node is
derived from the Tier-1 snapshot (nodeForWs) or an open-time hint; the rail
opens it as a pane passing the owning node; and the console loads the shared
interactive stylesheet."""
shell = _SHELL_JS.read_text(encoding="utf-8")
assert 'import { createInteractivePane } from "./interactive.js"' in shell, (
"interactive is ESM — the shell imports it (coordinator stays window.*)"
"interactive is ESM — the shell imports it (as it now does the coordinator)"
)
assert 'registerType("interactive"' in shell, "shell must register the interactive pane type"
assert "createInteractivePane(this.bodyEl, id, {" in shell, (
@@ -5150,4 +5150,9 @@ function createCoordinatorPane(root, wsId, opts) {
};
}
window.createCoordinatorPane = createCoordinatorPane;
// Imported by the console shell (shell.js) and the standalone coordinator page's
// bootstrap (coordinator/index.html), the same way shell.js imports the
// interactive pane. No `window.*` bridge here: unlike interactive.js — whose
// classic standalone ui/static/app.js still consumes the global — the
// coordinator has no classic consumer, so the bare ESM export is the only seam.
export { createCoordinatorPane };
+16 -16
View File
@@ -45,23 +45,23 @@
<script src="/shared/katex-0.17.0/katex.min.js"></script>
<script src="/shared/hljs-11.11.1/highlight.min.js"></script>
<script src="/shared/renderer.js"></script>
<script src="/static/coordinator/coordinator.js"></script>
<script>
<script type="module">
// Standalone coordinator page = one coordinator pane filling the body.
// (The console shell instead creates panes on demand via the same
// window.createCoordinatorPane factory — one per ws_id.)
(function () {
var wsId = document.documentElement.dataset.wsId || "";
var pane = window.createCoordinatorPane(document.body, wsId, {
standalone: true,
});
if (pane) {
pane.connect();
window.onLoginSuccess = function () {
pane.onLogin();
};
}
})();
// (The console shell instead creates panes on demand via the same imported
// createCoordinatorPane factory — one per ws_id.) A module so the import
// resolves before use: a classic eager IIFE would run before the deferred
// coordinator module loaded the factory.
import { createCoordinatorPane } from "/static/coordinator/coordinator.js";
const wsId = document.documentElement.dataset.wsId || "";
const pane = createCoordinatorPane(document.body, wsId, {
standalone: true,
});
if (pane) {
pane.connect();
window.onLoginSuccess = function () {
pane.onLogin();
};
}
</script>
</body>
</html>
+2 -2
View File
@@ -4296,9 +4296,9 @@
<script src="/static/admin.js"></script>
<script src="/static/governance.js"></script>
<script src="/static/app.js"></script>
<script src="/static/coordinator/coordinator.js"></script>
<!-- L-shell (ES module) — deferred, so it runs after the classic scripts
above and drives window.TS_APP.boot(). -->
above and drives window.TS_APP.boot(). It imports the coordinator +
interactive panes (both ES modules), so neither is <script>-tagged here. -->
<script type="module" src="/shared/shell.js"></script>
</body>
</html>
+8 -8
View File
@@ -21,10 +21,12 @@
import { PaneManager, ShellPane } from "./pane.js";
import { mountRail, mountManage } from "./rail.js";
// The interactive pane is a real ES module (step 5a) — import it directly. The
// coordinator pane stays a classic legacy script read off `window.*` below; this
// asymmetry is the incremental "pulled by the adopting pane" modernization.
// Both conversational panes are real ES modules the shell imports directly. The
// interactive pane lives beside us in /shared (step 5a); the coordinator pane is
// at an absolute /static path (step 5e.0 lifted it off `window.*`), so it imports
// by URL.
import { createInteractivePane } from "./interactive.js";
import { createCoordinatorPane } from "/static/coordinator/coordinator.js";
function make(tag, className, text) {
const node = document.createElement(tag);
@@ -232,11 +234,9 @@ function mountShell() {
glyph: "◆",
});
pane.onMount = function () {
if (typeof window.createCoordinatorPane === "function") {
this._ctl = window.createCoordinatorPane(this.bodyEl, id, {
onClose: () => pm.close(pane.id),
});
}
this._ctl = createCoordinatorPane(this.bodyEl, id, {
onClose: () => pm.close(pane.id),
});
};
pane.onActivate = function () {
if (this._ctl && !this._connected) {