diff options
| author | Hermes SysOp <sysop@webx11.duckdns.org> | 2026-11-12 17:40:27 +0100 |
|---|---|---|
| committer | Hermes SysOp <sysop@webx11.duckdns.org> | 2026-11-12 17:40:27 +0100 |
| commit | facd53ed5fc932c53aa7c3a5f4100da234906f9a (patch) | |
| tree | a200605e254d01cdb2b7de39db21eddbbdc59f41 | |
| parent | 5dac5f6ed7c88d544fb16d7a5a3bb5e9bfac9db3 (diff) | |
fix(newos4): ?ui=<mode> is an opt-in too — the gate had killed every shared mode link (v4.6.163)
v4.6.162 shipped the opt-in gate as `get('ui') === '1'`. But the UI shell has always treated
`?ui=<mode>` as a REQUEST — `module-uishell.js` reads `q = get('ui')` and honours it when the value
is one of `lazyIds()` (easy|chat|studio|workspace|commander|cockpit), with `_fromQuery` set so the
request never becomes a stored preference. So every shared link of the form `main.html?ui=chat`
stopped mounting the shell entirely and booted classic, silently, with no error: the gate required a
literal '1' that the shell's own convention never sends.
Found by an independent verification pass that tried `probe-ui-modes.py --cycle` against prod and got
`FAIL easy@390x844: wait_for_selector timeout` — a false red on a working build, which traced back to
this. The probe's own breakage was mine (I had just changed its default base to `?ui=1`, and it
appends `?ui=<mode>`, producing `?ui=1?ui=easy`); that is fixed in the next commit.
Gate now: opt in on ANY `ui` value except an explicit off ('0'/'false'), plus the localStorage key.
q = get('ui'); if (q !== null && q !== '' && q !== '0' && q !== 'false') return true;
return localStorage.getItem('webx11_ui_experimental') === '1';
Verified on the served page (v4.6.163):
* `?ui=chat` -> uiShell object, data-ui-mode=chat, body wx11-ui-chat, #chat-stream present,
_fromQuery=true, picker NOT shown, and localStorage UNCHANGED
(webx11_ui_experimental=null, webx11_ui_mode=null) — a link is not a preference.
* no flag -> uiShell undefined, 0 #ui-root, no picker (unchanged from v4.6.162).
* `?ui=1` -> shell + picker (guard's browser assertion still passes).
Served core-bundle.js byte-identical to disk; guard green (7/7 checks, rc=0); probe-ui-modes.py
--cycle ALL PASS 13 checks; maintain.py --fix PASS.
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 8 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/terminal.js | 8 |
4 files changed, 18 insertions, 6 deletions
@@ -1350,13 +1350,13 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=v4.6.162"></script> +<script src="modules/core/core-bundle.js?v=v4.6.163"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=v4.6.162"></script> +<script src="modules/core/module-loader.js?v=v4.6.163"></script> <!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work --> -<script src="modules/ext/critical-ext-bundle.js?v=v4.6.162"></script> +<script src="modules/ext/critical-ext-bundle.js?v=v4.6.163"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index d52764d..349c02b 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -4104,7 +4104,13 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm // (cron ui-gate-guard) fails if the gate is lost or the bare call returns. const _uiExp = (function () { try { - if (new URLSearchParams(location.search).get('ui') === '1') return true; + // ANY `?ui=<value>` opts in except an explicit off ('0'/'false'), because + // `?ui=<mode>` is the shell's own shared-link request (?ui=chat deep-links a + // mode, validated against lazyIds inside the shell). Requiring exactly '1' + // made every shared mode link boot classic with no error — measured + // 2026-11-12, the day this gate shipped. + const q = new URLSearchParams(location.search).get('ui'); + if (q !== null && q !== '' && q !== '0' && q !== 'false') return true; return localStorage.getItem('webx11_ui_experimental') === '1'; } catch (e) { return false; } })(); diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 06a809f..754b532 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = 'v4.6.162'; + const LATEST_VER = 'v4.6.163'; const CACHE = '?v=' + LATEST_VER; // Exposed so ext modules can register their own URL with the same cache-busting version // instead of an unversioned path the browser may already have cached (shell.js did). diff --git a/modules/core/terminal.js b/modules/core/terminal.js index cd7f5ae..9360f9e 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -1398,7 +1398,13 @@ // (cron ui-gate-guard) fails if the gate is lost or the bare call returns. const _uiExp = (function () { try { - if (new URLSearchParams(location.search).get('ui') === '1') return true; + // ANY `?ui=<value>` opts in except an explicit off ('0'/'false'), because + // `?ui=<mode>` is the shell's own shared-link request (?ui=chat deep-links a + // mode, validated against lazyIds inside the shell). Requiring exactly '1' + // made every shared mode link boot classic with no error — measured + // 2026-11-12, the day this gate shipped. + const q = new URLSearchParams(location.search).get('ui'); + if (q !== null && q !== '' && q !== '0' && q !== 'false') return true; return localStorage.getItem('webx11_ui_experimental') === '1'; } catch (e) { return false; } })(); |