aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-11-07 19:29:06 +0100
committerAgando <roduccomm@gmail.com>2026-11-07 19:29:06 +0100
commita46ebee30cfbbac86abb45068411bc28e1c65503 (patch)
tree35d6b5d1d68a58b11095cf06d7db241c3f7ecf0f
parent05d72563637e88825ff852d803621c567de47e34 (diff)
fix(newos4): workspace re-opened the modules panel every tick; picker lost two cards; commander was blackHEADv4.6.156main
Three defects reported against v4.6.155, each reproduced before touching code: 1. 'the modules tab keeps opening' (workspace). api.capture() runs dispatchCommand() for real, and the Modules card called bare 'modules' — whose only effect is terminal.openModuleManager(). Every 15 s refresh re-opened the panel (measured: #module-manager display:flex at t+9s and t+18s). The card now captures 'listmods' (same inventory as text), and api.capture() refuses UI-only commands outright so a future card cannot reintroduce it. 2. 'the picker got reduced to 4 items'. showPicker() used this.list() once four modes were registered — but modes are lazy, so that is 'modes you have already visited'. It now always renders the six in level order, taking registered specs for label/tagline. 3. 'commander is mostly black' on a phone. The base sheet's #ui-root rule used body[data-ui-mode]:not(...) #ui-root (1,2,1) and therefore beat every mode's own body[data-ui-mode='x'] #ui-root (1,1,1): commander/studio/cockpit asked for display:grid and got flex column. Commander collapsed to a 253 px centre column in an 844 px page with both rails hidden. Base specificity is now (1,0,0) with an explicit !important hide for classic/boot, and the commander phone query pins grid-template-rows: minmax(0,1fr) so the single row fills. Measured after: picker 6 cards after visiting four modes; #module-manager stays display:none across a refresh cycle; commander display:grid, phone centre 390x810 with a 673 px terminal, desktop 232/788/260 columns. probe-ui-modes.py --cycle -> ALL PASS, 13 checks; shell test 25/25. v4.6.156
-rw-r--r--CHANGELOG.md15
-rw-r--r--main.html6
-rw-r--r--modules/core/core-bundle.js28
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/module-uishell.js28
-rw-r--r--modules/ext/ui-commander.js4
-rw-r--r--modules/ext/ui-workspace.js5
7 files changed, 66 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f0a9d6..090694e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,21 @@ Generated from git history by `scripts/build-changelog.py` — do not edit by ha
<!-- generated:start -->
+## v4.6.155 — 2026-11-07
+
+_Touches modules (10), CHANGELOG.md (1), main.html (1) · fix 1_
+
+- **Second review round — identity for every stock node, load dedup, sweep race, a11y** `0acee7d`
+ - Four models (kimi-k2.7-code, deepseek-v4-pro, minimax-m3, glm-5.3) reviewed the shipped change set. Review log with every accepted and rejected finding: .hermes/plans/2026-11-07_163420-newos4-six-ui-modes.review-log.md
+ - identity guard now covers every snapshot node, not just #terminal-container: terminal.js also
+ - binds #output, #command-input, #input-area and #status-bar, so a cloned one leaves the terminal writing into a detached node while the page looks perfect
+ - _restoreStock anchors on the next stock node already home, then on the recorded trailing
+ - sibling; a raw child index was measured wrong (terminal,input,status-bar,quick-actions)
+ - _loading map: a double-clicked card or a repeated 'ui easy' loads the mode once
+ - _sweepPending returns while that id is in flight instead of declaring failure at 6 s and
+ - overwriting the stored preference
+ - … run `git show <sha>` for the rest
+
## v4.6.153 — 2026-11-07
_Touches CHANGELOG.md (1), main.html (1), modules (1) · project 1_
diff --git a/main.html b/main.html
index eb0fe9a..a6e17e9 100644
--- a/main.html
+++ b/main.html
@@ -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.155"></script>
+<script src="modules/core/core-bundle.js?v=v4.6.156"></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.155"></script>
+<script src="modules/core/module-loader.js?v=v4.6.156"></script>
<!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work -->
-<script src="modules/ext/critical-ext-bundle.js?v=v4.6.155"></script>
+<script src="modules/ext/critical-ext-bundle.js?v=v4.6.156"></script>
<script>
// ═══ ONE TRUE VERSION SOURCE ═══
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index 623bce5..c2b52d0 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -23326,10 +23326,13 @@ class UIShellModule {
// scrollIntoView() on new output scrolled #ui-root by 464px — the title, the action cards
// and the composer all left the screen (measured 2026-11-07). `clip` cannot be scrolled.
'#ui-root{position:fixed;inset:0;display:none;overflow:hidden;overflow:clip;z-index:40;background:var(--bg-color);color:var(--text-color)}',
- // flex column, not block: every mode is a vertical stack (chrome / content / composer /
- // footer) and a block container makes each child's `flex:1 1 auto` inert — the terminal
- // collapsed to 0px and the composer sat at the very bottom of a 4 600px flow.
- 'body[data-ui-mode]:not([data-ui-mode="classic"]) #ui-root{display:flex;flex-direction:column;height:100%}',
+ // flex column, not block: a block container makes each child's `flex:1 1 auto` inert and the
+ // terminal collapsed to 0px. Specificity is deliberately (1,0,0) so a mode that wants a GRID
+ // wins — with the old `body[data-ui-mode]:not(...) #ui-root` (1,2,1) every mode's
+ // `display:grid` was beaten and commander/studio/cockpit laid out as a plain stack
+ // (commander on a phone: two rails hidden, 253px of content in an 844px black page).
+ '#ui-root{display:flex;flex-direction:column;height:100%}',
+ 'body:not([data-ui-mode]) #ui-root,body[data-ui-mode="classic"] #ui-root{display:none!important}',
'#ui-root > *{min-width:0;min-height:0}',
// A non-classic mode owns the viewport: the page itself must not scroll, or terminal
// output drags the action cards off-screen and the composer never comes back.
@@ -23412,6 +23415,14 @@ class UIShellModule {
.map((a) => a.replace(/^["']/, '').replace(/["']$/, ''));
const cmd = parts[0];
if (!cmd) return Promise.resolve('');
+ // Commands with UI side effects are not capturable: dispatchCommand() runs them for
+ // real, so capturing `modules` re-opened the module-manager panel on every refresh
+ // tick ("the modules tab keeps opening", reported 2026-11-07).
+ const UI_ONLY = ['modules', 'config', 'edit', 'course', 'dashboard', 'ui', 'clear',
+ 'expand', 'sysmsg', 'font-size', 'setfont', 'startup-modules', 'load-local'];
+ if (UI_ONLY.indexOf(cmd) !== -1) {
+ return Promise.resolve('(' + cmd + ' opens a panel — not capturable)');
+ }
return term.capturePipeOutput(cmd, parts.slice(1), raw);
},
fs: term.fs,
@@ -23482,16 +23493,17 @@ class UIShellModule {
const firstCard = wrap.querySelector('#wx11-ui-picker-list button');
if (firstCard && firstCard.focus) firstCard.focus();
const list = wrap.querySelector('#wx11-ui-picker-list');
- // Fewer than 4 registered means the ext modules are still in flight — use the static list so
- // the user never sees a one-card chooser (the card handler re-checks and warns if missing).
- const modes = this.list().length >= 4 ? this.list() : [
+ // ALWAYS the six, in level order. `this.list()` only holds modes that have already been
+ // fetched, so switching on it showed 4 cards once the user had visited 4 modes and silently
+ // dropped commander/cockpit (reported 2026-11-07). Registered specs win for label/tagline.
+ const modes = [
{ id: 'easy', label: 'Easy', level: 1, icon: '🌱', tagline: 'Just ask. No commands.' },
{ id: 'chat', label: 'Chat', level: 2, icon: '💬', tagline: 'Conversation first.' },
{ id: 'studio', label: 'Studio', level: 3, icon: '🎨', tagline: 'Chat plus a place for your files.' },
{ id: 'workspace', label: 'Workspace', level: 4, icon: '🗂', tagline: 'Everything at a glance.' },
{ id: 'commander', label: 'Commander', level: 5, icon: '⌨️', tagline: 'Terminal with rails.' },
{ id: 'cockpit', label: 'Cockpit', level: 6, icon: '🛰', tagline: 'Live system telemetry.' }
- ];
+ ].map((m) => this.modes[m.id] || m);
modes.forEach(m => {
const b = document.createElement('button');
b.className = 'wx11-btn';
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 7f9ffe8..67be01b 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.155';
+ const LATEST_VER = 'v4.6.156';
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 11bad38..94e9bd1 100644
--- a/modules/core/module-uishell.js
+++ b/modules/core/module-uishell.js
@@ -365,10 +365,13 @@ class UIShellModule {
// scrollIntoView() on new output scrolled #ui-root by 464px — the title, the action cards
// and the composer all left the screen (measured 2026-11-07). `clip` cannot be scrolled.
'#ui-root{position:fixed;inset:0;display:none;overflow:hidden;overflow:clip;z-index:40;background:var(--bg-color);color:var(--text-color)}',
- // flex column, not block: every mode is a vertical stack (chrome / content / composer /
- // footer) and a block container makes each child's `flex:1 1 auto` inert — the terminal
- // collapsed to 0px and the composer sat at the very bottom of a 4 600px flow.
- 'body[data-ui-mode]:not([data-ui-mode="classic"]) #ui-root{display:flex;flex-direction:column;height:100%}',
+ // flex column, not block: a block container makes each child's `flex:1 1 auto` inert and the
+ // terminal collapsed to 0px. Specificity is deliberately (1,0,0) so a mode that wants a GRID
+ // wins — with the old `body[data-ui-mode]:not(...) #ui-root` (1,2,1) every mode's
+ // `display:grid` was beaten and commander/studio/cockpit laid out as a plain stack
+ // (commander on a phone: two rails hidden, 253px of content in an 844px black page).
+ '#ui-root{display:flex;flex-direction:column;height:100%}',
+ 'body:not([data-ui-mode]) #ui-root,body[data-ui-mode="classic"] #ui-root{display:none!important}',
'#ui-root > *{min-width:0;min-height:0}',
// A non-classic mode owns the viewport: the page itself must not scroll, or terminal
// output drags the action cards off-screen and the composer never comes back.
@@ -451,6 +454,14 @@ class UIShellModule {
.map((a) => a.replace(/^["']/, '').replace(/["']$/, ''));
const cmd = parts[0];
if (!cmd) return Promise.resolve('');
+ // Commands with UI side effects are not capturable: dispatchCommand() runs them for
+ // real, so capturing `modules` re-opened the module-manager panel on every refresh
+ // tick ("the modules tab keeps opening", reported 2026-11-07).
+ const UI_ONLY = ['modules', 'config', 'edit', 'course', 'dashboard', 'ui', 'clear',
+ 'expand', 'sysmsg', 'font-size', 'setfont', 'startup-modules', 'load-local'];
+ if (UI_ONLY.indexOf(cmd) !== -1) {
+ return Promise.resolve('(' + cmd + ' opens a panel — not capturable)');
+ }
return term.capturePipeOutput(cmd, parts.slice(1), raw);
},
fs: term.fs,
@@ -521,16 +532,17 @@ class UIShellModule {
const firstCard = wrap.querySelector('#wx11-ui-picker-list button');
if (firstCard && firstCard.focus) firstCard.focus();
const list = wrap.querySelector('#wx11-ui-picker-list');
- // Fewer than 4 registered means the ext modules are still in flight — use the static list so
- // the user never sees a one-card chooser (the card handler re-checks and warns if missing).
- const modes = this.list().length >= 4 ? this.list() : [
+ // ALWAYS the six, in level order. `this.list()` only holds modes that have already been
+ // fetched, so switching on it showed 4 cards once the user had visited 4 modes and silently
+ // dropped commander/cockpit (reported 2026-11-07). Registered specs win for label/tagline.
+ const modes = [
{ id: 'easy', label: 'Easy', level: 1, icon: '🌱', tagline: 'Just ask. No commands.' },
{ id: 'chat', label: 'Chat', level: 2, icon: '💬', tagline: 'Conversation first.' },
{ id: 'studio', label: 'Studio', level: 3, icon: '🎨', tagline: 'Chat plus a place for your files.' },
{ id: 'workspace', label: 'Workspace', level: 4, icon: '🗂', tagline: 'Everything at a glance.' },
{ id: 'commander', label: 'Commander', level: 5, icon: '⌨️', tagline: 'Terminal with rails.' },
{ id: 'cockpit', label: 'Cockpit', level: 6, icon: '🛰', tagline: 'Live system telemetry.' }
- ];
+ ].map((m) => this.modes[m.id] || m);
modes.forEach(m => {
const b = document.createElement('button');
b.className = 'wx11-btn';
diff --git a/modules/ext/ui-commander.js b/modules/ext/ui-commander.js
index 03af5be..873d7cc 100644
--- a/modules/ext/ui-commander.js
+++ b/modules/ext/ui-commander.js
@@ -61,7 +61,9 @@ body[data-ui-mode="commander"] #ui-root { display: grid; grid-template-columns:
#cm-palette li.sel { background: var(--bg-tertiary); border-left: 3px solid var(--accent-color); }
#cm-palette li .d { color: var(--text-muted); font-size: .82em; display: block; margin-top: 2px; }
@media (max-width: 900px) {
- body[data-ui-mode="commander"] #ui-root { grid-template-columns: 1fr; }
+ /* one row that FILLS the viewport: without an explicit 1fr row the single grid row is auto, so
+ the centre column stopped at its content height and the rest of the screen was black. */
+ body[data-ui-mode="commander"] #ui-root { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr); }
#cm-left, #cm-right { display: none; }
}
`);
diff --git a/modules/ext/ui-workspace.js b/modules/ext/ui-workspace.js
index ee20aac..d660283 100644
--- a/modules/ext/ui-workspace.js
+++ b/modules/ext/ui-workspace.js
@@ -71,7 +71,10 @@ body[data-ui-mode="workspace"] #ui-root { display: flex; flex-direction: column;
const cards = [
{ id: 'files', ico: '📁', title: 'Files', cmd: 'ls -la' },
{ id: 'sys', ico: '🖥', title: 'Heap', cmd: 'cat /sys/heap/stats' },
- { id: 'modules', ico: '🧩', title: 'Modules', cmd: 'modules' },
+ // `modules` OPENS the module-manager panel — capturing it re-opened the panel on every
+ // 15 s refresh ("the modules tab keeps opening", reported 2026-11-07). `listmods` prints
+ // the same inventory as text.
+ { id: 'modules', ico: '🧩', title: 'Modules', cmd: 'listmods' },
{ id: 'agents', ico: '🤖', title: 'Agents', cmd: 'agent-list' },
{ id: 'cron', ico: '⏱', title: 'Scheduled', cmd: 'cron-list' },
{ id: 'errors', ico: '🐞', title: 'Recent errors', cmd: 'cat /sys/logs/errors' }