aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--help.html20
-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-world-model.js5
-rw-r--r--modules/core/virtual-fs.js23
6 files changed, 68 insertions, 16 deletions
diff --git a/help.html b/help.html
index 2ab9bfa..a464d5f 100644
--- a/help.html
+++ b/help.html
@@ -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>
diff --git a/main.html b/main.html
index 66077b5..16b4d63 100644
--- a/main.html
+++ b/main.html
@@ -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;