diff options
| -rw-r--r-- | help.html | 20 | ||||
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 28 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-world-model.js | 5 | ||||
| -rw-r--r-- | modules/core/virtual-fs.js | 23 |
6 files changed, 68 insertions, 16 deletions
@@ -1689,6 +1689,26 @@ listmods</pre> <h2>📋 Changelog</h2> <div class="version-item"> + <div class="v-header"><span class="v-num">v4.6.88–90 — October 2026</span> + <span class="v-date">one phantom entry, three bugs</span></div> + <ul> + <li><strong>A directory listing invented a <code>sys</code> entry.</strong> <code>ls</code> + injects the virtual <code>/sys</code> — but it tested the <em>argument</em> instead of the + result, so after <code>cd _agents</code> the listing showed <code>sys</code> inside + <code>_agents</code>, and a follow-up <code>list_files('_agents/sys')</code> failed with + "Directory not found". The listing lied; the resolver was right. Injection now happens only + for the root directory.</li> + <li><strong><code>tree</code> crashed on the root listing.</strong> The same virtual entry was + pushed into the walk and then dereferenced, throwing + <code>Cannot read properties of undefined</code> mid-output — a partial tree, which is where + two different file counts came from.</li> + <li><strong>The file counts now agree.</strong> The world model's walk descended into the + synthetic <code>/sys</code> subtree, so it reported 146 files in 114 directories for a store + that holds 44 in 15. It skips virtual entries now, and matches an independent walk exactly.</li> + </ul> + </div> + + <div class="version-item"> <div class="v-header"><span class="v-num">v4.6.86–87 — October 2026</span> <span class="v-date">audit honesty, static assets</span></div> <ul> @@ -1349,13 +1349,13 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=v4.6.87"></script> +<script src="modules/core/core-bundle.js?v=v4.6.90"></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.87"></script> +<script src="modules/core/module-loader.js?v=v4.6.90"></script> <!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work --> -<script src="modules/ext/critical-ext-bundle.js?v=v4.6.87"></script> +<script src="modules/ext/critical-ext-bundle.js?v=v4.6.90"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index c8a4c2e..f72a20e 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -548,8 +548,11 @@ class VirtualFileSystem { size: dir.content[name]._factory ? 0 : (dir.content[name].content ? dir.content[name].content.length : 0), _virtual: !!dir.content[name]._virtual })); - // Inject virtual /sys/ at root level - if ((!path || path === '/' || path === '') && !entries.some(e => e.name === 'sys' && e._virtual)) { + // Inject virtual /sys/ — but ONLY when the directory being listed IS the root. + // The old test was on the argument (`!path`), not on the result: `cd _agents; ls` + // printed a phantom `sys` inside _agents, and a follow-up list_files('_agents/sys') + // failed with 'Directory not found'. The listing lied; the resolver was right. + if (dir === this.root && !entries.some(e => e.name === 'sys' && e._virtual)) { entries.push({ name: 'sys', type: 'dir', size: 0, _virtual: true }); } return entries; @@ -765,8 +768,11 @@ class VirtualFileSystem { size: dir.content[name]._factory ? 0 : (dir.content[name].content ? dir.content[name].content.length : 0), _virtual: !!dir.content[name]._virtual })); - // Inject virtual /sys/ at root level - if ((!path || path === '/' || path === '') && !entries.some(e => e.name === 'sys' && e._virtual)) { + // Inject virtual /sys/ — but ONLY when the directory being listed IS the root. + // The old test was on the argument (`!path`), not on the result: `cd _agents; ls` + // printed a phantom `sys` inside _agents, and a follow-up list_files('_agents/sys') + // failed with 'Directory not found'. The listing lied; the resolver was right. + if (dir === this.root && !entries.some(e => e.name === 'sys' && e._virtual)) { entries.push({ name: 'sys', type: 'dir', size: 0, _virtual: true }); } return entries; @@ -804,9 +810,14 @@ class VirtualFileSystem { const isLast = i === entries.length - 1; const connector = isLast ? '└── ' : '├── '; result += prefix + connector + name + '\n'; - if (dir.content[name].type === 'dir' || dir.content[name]._virtual) { + // The loop pushes the virtual 'sys' name into `entries`, which has no node behind it: + // dereferencing dir.content['sys'].type threw 'Cannot read properties of undefined' + // and `tree` died on the root listing — often after printing a partial tree, which is + // where the disagreeing file counts came from. + const child = dir.content[name]; + if (child && child.type === 'dir') { const ext = isLast ? ' ' : '│ '; - result += this.tree(path + '/' + name, prefix + ext); + result += this.tree((path || '') + '/' + name, prefix + ext); } } return result; @@ -18814,6 +18825,11 @@ class WorldModelModule { const entries = fs.ls(path); if (!entries) return; for (const e of entries) { + // /sys is synthesized by ls() on demand and has no node behind it. Walking into it + // counted a synthetic subtree (114 dirs / 146 files in a store that holds 43 files), + // which is the "the counts disagree" report: two different populations were both + // printed as "files". + if (e._virtual) continue; if (e.type === 'file') fileCount++; else { dirCount++; walk(path === '/' ? '/' + e.name : path + '/' + e.name); } } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 37212f3..95176d9 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.87'; + const LATEST_VER = 'v4.6.90'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-world-model.js b/modules/core/module-world-model.js index 54a17e4..6e8f3c3 100644 --- a/modules/core/module-world-model.js +++ b/modules/core/module-world-model.js @@ -203,6 +203,11 @@ class WorldModelModule { const entries = fs.ls(path); if (!entries) return; for (const e of entries) { + // /sys is synthesized by ls() on demand and has no node behind it. Walking into it + // counted a synthetic subtree (114 dirs / 146 files in a store that holds 43 files), + // which is the "the counts disagree" report: two different populations were both + // printed as "files". + if (e._virtual) continue; if (e.type === 'file') fileCount++; else { dirCount++; walk(path === '/' ? '/' + e.name : path + '/' + e.name); } } diff --git a/modules/core/virtual-fs.js b/modules/core/virtual-fs.js index a52a393..e639cba 100644 --- a/modules/core/virtual-fs.js +++ b/modules/core/virtual-fs.js @@ -546,8 +546,11 @@ class VirtualFileSystem { size: dir.content[name]._factory ? 0 : (dir.content[name].content ? dir.content[name].content.length : 0), _virtual: !!dir.content[name]._virtual })); - // Inject virtual /sys/ at root level - if ((!path || path === '/' || path === '') && !entries.some(e => e.name === 'sys' && e._virtual)) { + // Inject virtual /sys/ — but ONLY when the directory being listed IS the root. + // The old test was on the argument (`!path`), not on the result: `cd _agents; ls` + // printed a phantom `sys` inside _agents, and a follow-up list_files('_agents/sys') + // failed with 'Directory not found'. The listing lied; the resolver was right. + if (dir === this.root && !entries.some(e => e.name === 'sys' && e._virtual)) { entries.push({ name: 'sys', type: 'dir', size: 0, _virtual: true }); } return entries; @@ -763,8 +766,11 @@ class VirtualFileSystem { size: dir.content[name]._factory ? 0 : (dir.content[name].content ? dir.content[name].content.length : 0), _virtual: !!dir.content[name]._virtual })); - // Inject virtual /sys/ at root level - if ((!path || path === '/' || path === '') && !entries.some(e => e.name === 'sys' && e._virtual)) { + // Inject virtual /sys/ — but ONLY when the directory being listed IS the root. + // The old test was on the argument (`!path`), not on the result: `cd _agents; ls` + // printed a phantom `sys` inside _agents, and a follow-up list_files('_agents/sys') + // failed with 'Directory not found'. The listing lied; the resolver was right. + if (dir === this.root && !entries.some(e => e.name === 'sys' && e._virtual)) { entries.push({ name: 'sys', type: 'dir', size: 0, _virtual: true }); } return entries; @@ -802,9 +808,14 @@ class VirtualFileSystem { const isLast = i === entries.length - 1; const connector = isLast ? '└── ' : '├── '; result += prefix + connector + name + '\n'; - if (dir.content[name].type === 'dir' || dir.content[name]._virtual) { + // The loop pushes the virtual 'sys' name into `entries`, which has no node behind it: + // dereferencing dir.content['sys'].type threw 'Cannot read properties of undefined' + // and `tree` died on the root listing — often after printing a partial tree, which is + // where the disagreeing file counts came from. + const child = dir.content[name]; + if (child && child.type === 'dir') { const ext = isLast ? ' ' : '│ '; - result += this.tree(path + '/' + name, prefix + ext); + result += this.tree((path || '') + '/' + name, prefix + ext); } } return result; |