summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/core/core-bundle.js33
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/module-uishell.js33
3 files changed, 61 insertions, 7 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index 349c02b..a9a8de5 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -23468,6 +23468,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';
}
@@ -23527,6 +23528,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') {
@@ -23536,7 +23554,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;
}
@@ -23568,6 +23592,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
@@ -23586,7 +23613,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;
}
@@ -23599,7 +23626,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).
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 754b532..d87b11f 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.163';
+ const LATEST_VER = 'v4.6.164';
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/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).