diff options
Diffstat (limited to 'modules/core/module-uishell.js')
| -rw-r--r-- | modules/core/module-uishell.js | 33 |
1 files changed, 30 insertions, 3 deletions
diff --git a/modules/core/module-uishell.js b/modules/core/module-uishell.js index 94e9bd1..2817790 100644 --- a/modules/core/module-uishell.js +++ b/modules/core/module-uishell.js @@ -25,6 +25,7 @@ class UIShellModule { this._pending = null; // mode requested before its ext module loaded this._picker = null; this._fromQuery = false; + this._pendingPersist = null; // was the pending apply a USER choice or a link? this.key = 'webx11_ui_mode'; this.seenKey = 'webx11_ui_seen'; } @@ -84,6 +85,23 @@ class UIShellModule { localStorage.setItem(this.key, this.current); } + /** Store a user's mode choice AND the opt-in it depends on. + * + * The gate lives in terminal.js and reads `webx11_ui_experimental` before UIShellModule is + * registered. Storing only `webx11_ui_mode` therefore saves a preference the gate cannot see: + * pick "easy" in the picker, reload, and you are back in classic with no error anywhere + * (measured on the operator's phone via /ui-selftest.html, 2026-11-12). Only a real user action + * reaches this method — links and pending restores pass persist:false. + */ + _persistMode(want) { + try { + localStorage.setItem(this.key, want); + if (want && want !== 'classic' && this.lazyIds().indexOf(want) !== -1) { + localStorage.setItem('webx11_ui_experimental', '1'); + } + } catch (e) { /* private mode / quota: a preference that cannot be stored is not fatal */ } + } + /* ---------- registry ---------- */ registerMode(spec) { if (!spec || !spec.id || typeof spec.mount !== 'function') { @@ -93,7 +111,13 @@ class UIShellModule { this.modes[spec.id] = spec; if (this._pending === spec.id) { this._pending = null; - this.apply(spec.id, { silent: true, persist: false }); + // Honour the ORIGINAL intent: a picker click persists, a shared link does not. This path + // used to hardcode persist:false, so a mode chosen from the picker lived for the session + // and vanished on the next reload — the opt-in flag stayed off and nothing reported it + // (measured 2026-11-12 on the operator's phone). + const persist = this._pendingPersist === true; + this._pendingPersist = null; + this.apply(spec.id, { silent: true, persist: persist }); } return true; } @@ -125,6 +149,9 @@ class UIShellModule { return false; } this._pending = want; + // Carry the intent into registerMode(): without this, a picker click that has to wait for + // the mode file is applied with persist:false and the choice dies at the next reload. + this._pendingPersist = o.persist !== false; if (this.terminal) this.toast('Loading ' + want + '…', 1500); this._loadMode(want).then((ok) => { // registerMode() applies the mode the moment it registers — which already bumped this @@ -143,7 +170,7 @@ class UIShellModule { // _syncChrome all key off data-ui-mode, so it must be set even when nothing changes. document.body.dataset.uiMode = want; document.body.classList.add('wx11-ui-' + want); - if (o.persist !== false) localStorage.setItem(this.key, want); + if (o.persist !== false) this._persistMode(want); return true; } @@ -156,7 +183,7 @@ class UIShellModule { .filter((c) => c.indexOf('wx11-ui-') === 0 && c !== 'wx11-ui-' + want) .forEach((c) => document.body.classList.remove(c)); document.body.classList.add('wx11-ui-' + want); - if (o.persist !== false) localStorage.setItem(this.key, want); + if (o.persist !== false) this._persistMode(want); // _syncChrome on EVERY switch: it hides #quick-actions, which otherwise stays in the tab order // and the accessibility tree behind the opaque mode root (found by review). |