<feed xmlns='http://www.w3.org/2005/Atom'>
<title>newos4, branch v4.6.168</title>
<subtitle>newOS4 - the browser terminal that runs WebX11 user spaces</subtitle>
<id>https://newos.webx11.duckdns.org/newos4/atom?h=v4.6.168</id>
<link rel='self' href='https://newos.webx11.duckdns.org/newos4/atom?h=v4.6.168'/>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/'/>
<updated>2026-11-14T17:05:27+00:00</updated>
<entry>
<title>docs: boot-path audit (dav-auth unit), the two checker fixes, and the scheduled cold start</title>
<updated>2026-11-14T17:05:27+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-14T17:05:27+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=2e16127f5cbff1cdec9f38f3a721afcaeddd0b97'/>
<id>urn:sha1:2e16127f5cbff1cdec9f38f3a721afcaeddd0b97</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(tooling): two checkers stopped manufacturing false audit signals</title>
<updated>2026-11-14T17:04:08+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-14T17:04:08+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=1975106e9aa221af629da749ae95c572a8a84821'/>
<id>urn:sha1:1975106e9aa221af629da749ae95c572a8a84821</id>
<content type='text'>
Both defects are parser bugs in tooling, and both were producing a signal the operator sees on every
run. Plan 2026-11-13_062052, implemented as written.

1. `help coverage` WARN was a quote-pairing artefact (every run, always 367/368).
   `newos4-maintain.py:238-239` captured `this._baseWhitelist = [...]` with a regex and then pulled
   `'([^']+)'` out of it. A JS comment inside that array literal contains the apostrophe in "don't"
   (terminal.js:16), which desynchronised pairing: it matched forward to the opening quote of 'ui' and
   the pseudo-command `t work"\n // …` joined the audited set — so a genuine undocumented command was
   indistinguishable from parser debris. Fix: strip `/* */` and `//` before extracting, and keep only
   tokens matching `^[a-z][a-z0-9_-]{0,40}$`. The same shape bug existed for `const internals = [...]`
   and is fixed by the same helper. The WARN list is now single-line (newlines collapsed) so one bad
   token cannot reflow the digest.

   Proven both ways: injected `'zzz-probe-cmd'` into the whitelist -&gt; `WARN: help coverage 367/368 —
   undocumented: zzz-probe-cmd`; reverted (md5 verified) -&gt; `PASS: help coverage 367/367`.

2. Auto-review "still present in shipped code" fired on generic tokens.
   `STEER-20261112-195037-1eed [high] cmds.indexOf is not a function` was approved because
   `identifiers()` kept `indexOf` and `function` (hits: 36 and 74 files) and R4 approved on `any()`
   present token. `isCommand` — the only name that identifies the fault — was never considered, because
   stack frames were not parsed at all. It does not exist anywhere in the tree.

   Fix: JS keywords/builtins/DOM handler names join the skip set; `stack_functions()` extracts frame
   names (dropping `onclick`, `anonymous`, `eval`); R4 now requires a **distinctive** token (&gt;=6 chars,
   present in modules/, not generic); and a stack whose TOP frame is `&lt;anonymous&gt;` is classified as
   generated code (`js-run` / `new Function`), which can never justify "still present in shipped
   source". The bottom `HTMLButtonElement.onclick (…/main.html)` frame proves only that a click
   started it — deciding on "does any frame name a real file" got this wrong once already.

   Proven: `identifiers('cmds.indexOf is not a function')` -&gt; `[]`; `distinctive(['indexOf','function'],
   {...})` -&gt; `[]`; the real report replayed into a fixture reports `nothing provable (1 left for the
   operator)` with **no** decision row written; production dry-run `nothing provable (0 left)`.
   `decisions.jsonl` still carries exactly 1 row for that report (append-only history untouched).

Regression cover: `python3 scripts/newos4-feedback-autoreview.py --self-test` -&gt; 6/6.
maintain: node tests 42/42, help coverage 367/367, result OK.
</content>
</entry>
<entry>
<title>chore: cron artefacts — feedback snapshot, health/anomaly reports, regenerated admin pages</title>
<updated>2026-11-14T15:12:20+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-14T15:12:20+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=46b2d9862fa22ab18c2746aa2b28878972098fd8'/>
<id>urn:sha1:46b2d9862fa22ab18c2746aa2b28878972098fd8</id>
<content type='text'>
</content>
</entry>
<entry>
<title>docs: v4.6.168 change-history — why `ui` was answered by the AI</title>
<updated>2026-11-12T17:29:59+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T17:29:59+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=7a601af2d396d22f37fe808dd6b9bde47db52bfc'/>
<id>urn:sha1:7a601af2d396d22f37fe808dd6b9bde47db52bfc</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(newos4): `ui` now works in classic — it was being answered by the AI (v4.6.168)</title>
<updated>2026-11-12T17:29:53+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T17:29:53+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=0acf9b61c3d3c750e4af39900c43fc1cdd221578'/>
<id>urn:sha1:0acf9b61c3d3c750e4af39900c43fc1cdd221578</id>
<content type='text'>
Operator report: "Ui command don't work". It was worse than a broken command: typing `ui` in the
classic terminal printed

    Security: Unknown command "ui" — routing to AI (did you mean "ai"?)...
    → Routing to AI assistant...

and an AI turn answered it. Three layers each looked healthy on its own:

1. `ui` is registered by UIShellModule, which since the v4.6.162 gate only loads when the visitor has
   already opted in — so the command that switches the interface did not exist in the state you need
   it from.
2. `_classify()` decides "known" from the command registry (module commands + a short builtin list),
   not from the safety whitelist, so `ui` was `unknown: true`. At the default security level 1 an
   unknown command that is not whitelisted is offered as a typo and then routed to the AI. `ui` was
   not whitelisted.
3. Nothing printed an error anyone would recognise as failure — it looked like the terminal was
   answering you.

Fix:
  * `'ui'` joins `_baseWhitelist` (114 -&gt; 115). It is a navigation command: safe, non-destructive, and
    it must never depend on the interface it navigates to.
  * `dispatchCommand` gets an `if (cmd === 'ui')` fallback that mounts the shell on demand via a new
    `_mountUIShell()` (register + init, the same two steps the boot loop performs) and then opens the
    picker. It also writes `webx11_ui_experimental=1`, so the opt-in survives the reload.
  * The mount is deliberately NOT `_safeRegister('UIShellModule', …)`: that bare call is the boot gate
    and `verify-newos4-ui-gate.py` fails on any occurrence of it. A guard that grants one exception
    has to grant whatever is written next to it, so the on-demand path is separate by name.

Guard: new check `ui_command_in_classic` — executes `ui` on the served page with no flag and waits for
`webx11.uiShell` + `#wx11-ui-picker`. Deliberately behavioural: `_classify('ui').known` reads FALSE even
when the command works (registry vs whitelist), so a text or classifier assertion would have been a lie
in both directions. The RED case is on record: the AI-routing line above, captured in the live
scrollback before the fix.

Verified on v4.6.168 (served): `ui` -&gt; picker with 7 options; `ui easy` -&gt; easy; `ui cockpit` -&gt;
cockpit; `ui classic` -&gt; classic; `ui nonsense` -&gt; "Unknown mode 'nonsense'. Available: classic, easy,
cockpit" and stays put. Guard 8/8 rc=0. maintain OK.
</content>
</entry>
<entry>
<title>fix(newos4): the three glyph-only switch buttons now say what they do (v4.6.165)</title>
<updated>2026-11-12T17:21:09+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T17:21:09+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=a14d8ab15130779e4d612f690cd3c806800875c6'/>
<id>urn:sha1:a14d8ab15130779e4d612f690cd3c806800875c6</id>
<content type='text'>
Asked "how do I switch?", I checked all six modes and found the controls were already uniform in
function — every mode calls `api.exec('ui picker')` — but not in labelling:

  easy       ⇄ Change view
  chat       ⇄ Style
  workspace  ⇄ Style
  studio     ⇄          &lt;- glyph only
  commander  ⇄          &lt;- glyph only
  cockpit    ⇄          &lt;- glyph only

An unlabelled ⇄ on a phone is a guess. The three bare ones now carry
title="Change view — pick another interface" and aria-label="Change view", which api.el() forwards
via setAttribute. Verified on the served build: the snapshot reads `button "Change view"` in studio,
and clicking it opens the picker with all seven options (six modes + "Keep the classic terminal").

No behaviour change — same handler, same picker; the control is now discoverable and announced by
screen readers.
</content>
</entry>
<entry>
<title>docs: v4.6.164 change-history — phone PASS, the two defects it exposed, corrected heap reading</title>
<updated>2026-11-12T16:56:25+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T16:56:25+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=5d48e3bb52d333f828b50945003864d99488599e'/>
<id>urn:sha1:5d48e3bb52d333f828b50945003864d99488599e</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(newos4): a mode the user picks now survives a reload (v4.6.164)</title>
<updated>2026-11-12T16:56:17+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T16:56:17+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=924779e5fd49daabc2056f17df91fcafbe6e956d'/>
<id>urn:sha1:924779e5fd49daabc2056f17df91fcafbe6e956d</id>
<content type='text'>
Found by the operator actually running /ui-selftest.html on his phone. The report said 6/6 PASS,
and the interesting part was what happened next: pick "easy" from the picker, reload, and you are
back in classic. Two defects stacked on top of each other, neither of which reported anything:

1. `apply()` on an unregistered mode starts a lazy load and returns false. When the mode registers,
   `registerMode()` re-applied it with a hardcoded `persist:false` — so the picker's choice was
   never written to `webx11_ui_mode` at all. The intent ("the user asked for this") was dropped the
   moment the mode file had to be fetched, which is the normal case for all six modes.
2. Even when the mode did persist, `webx11_ui_experimental` was never set — and that key is what the
   in-bundle gate reads before UIShellModule is registered. A stored mode behind a closed gate is a
   preference the product silently ignores.

Fix:
  * `_pendingPersist` carries the caller's intent through the lazy path; `registerMode()` honours it
    instead of hardcoding false, and the load-completion apply already forwarded `o.persist`;
  * `_persistMode(want)` writes the mode AND, for a real (non-classic) mode, sets
    `webx11_ui_experimental='1'`, so the gate opens on the next boot.

Both directions verified live on v4.6.164:
  * user pick (lazy path)  -&gt; webx11_ui_mode=easy + webx11_ui_experimental=1, then
    main.html with NO query boots easy (#easy-actions present, body wx11-ui-easy, picker suppressed);
  * shared link ?ui=chat    -&gt; mode applied for the session, _fromQuery=true, localStorage STILL null
    (a link must not become the recipient's preference — that is the shell's own rule and it survives).

Also: /ui-selftest.html no longer prints a meaningless `heapMB: 9.5`. Chromium pins
performance.memory.usedJSHeapSize at 10,000,000 without --enable-precise-memory-info, which a page
cannot request, so the report now says the heap is not measurable in-page instead of inventing a
number — the same fake-green that was fixed in probe-ui-crash.py.

guard 7/7 rc=0, maintain OK, served core-bundle byte-identical to disk.
</content>
</entry>
<entry>
<title>docs(newos4): change-history + changelog for v4.6.163</title>
<updated>2026-11-12T16:40:40+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T16:40:40+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=8013a6146533e2f043d386ad774b8c12f57add53'/>
<id>urn:sha1:8013a6146533e2f043d386ad774b8c12f57add53</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(newos4): ?ui=&lt;mode&gt; is an opt-in too — the gate had killed every shared mode link (v4.6.163)</title>
<updated>2026-11-12T16:40:27+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T16:40:27+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=facd53ed5fc932c53aa7c3a5f4100da234906f9a'/>
<id>urn:sha1:facd53ed5fc932c53aa7c3a5f4100da234906f9a</id>
<content type='text'>
v4.6.162 shipped the opt-in gate as `get('ui') === '1'`. But the UI shell has always treated
`?ui=&lt;mode&gt;` 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=&lt;mode&gt;`, 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 &amp;&amp; q !== '' &amp;&amp; q !== '0' &amp;&amp; q !== 'false') return true;
  return localStorage.getItem('webx11_ui_experimental') === '1';

Verified on the served page (v4.6.163):
  * `?ui=chat`  -&gt; 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     -&gt; uiShell undefined, 0 #ui-root, no picker (unchanged from v4.6.162).
  * `?ui=1`     -&gt; 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.
</content>
</entry>
</feed>
