diff --git a/cmd/gui/chat-panel-dom.test.mjs b/cmd/gui/chat-panel-dom.test.mjs
new file mode 100644
index 00000000..cf56a594
--- /dev/null
+++ b/cmd/gui/chat-panel-dom.test.mjs
@@ -0,0 +1,149 @@
+// chat 子面板 DOM 结构判据(2026-10-05)。
+//
+// ## 要判的是什么
+//
+// 实测故障:对话页里「终端 / 运行中命令 / 记忆 / 上下文 / 知识」五个页签
+// 点进去全是空白,只有「对话」和「星图」正常。
+//
+// 根因是 **HTML 少一个 **:构造星图面板的那段 HTML 里有 4 个
+//
开、只有 3 个
闭,于是它没闭合 —— 后面拼接的所有
+// chat-panel 都被浏览器塞进 chat-panel-starmap **内部**。
+//
+// 实测证据(CDP 查真实 DOM):
+//
+// .chat-layout 的直接子节点只有 3 个:
+// DIV.chat-tabs / DIV#chat-panel-chat / DIV#chat-panel-starmap
+//
+// 而 terminal/cmd/memory/context/knowledge 的父节点是
+// chat-panel-starmap —— 不是同级的 chat-layout。
+//
+// 后果:这些面板 display 是 flex(switchChatPanel 把 active 加对了),
+// 但父级 display:none ⇒ getBoundingClientRect() 恒为 0x0 ⇒ 看起来空白。
+//
+// ★ 为什么需要判据:标签不配对在源码里完全看不出来(字符串拼接 +
+// 跨行的 HTML 片段),浏览器也不报错 —— 它只是「尽力修复」成畸形结构。
+// 只有断言「开闭标签数相等」才能挡住。
+//
+// 运行:node chat-panel-dom.test.mjs
+
+import { readFileSync, existsSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname, join } from "node:path";
+
+const here = dirname(fileURLToPath(import.meta.url));
+const APP = join(here, "renderer", "app.js");
+
+let failures = 0;
+const check = (name, ok, detail) => {
+ if (ok) {
+ console.log(` ✓ ${name}`);
+ } else {
+ failures++;
+ console.log(` ✗ ${name}${detail ? " — " + detail : ""}`);
+ }
+};
+
+if (!existsSync(APP)) {
+ console.error(`找不到 ${APP}`);
+ process.exit(1);
+}
+const src = readFileSync(APP, "utf8");
+
+// ── 1) 抽出构造 chat 子面板的整段 HTML ─────────────────────────────
+//
+// 从第一个 chat-panel 面板开始,到最后一个面板结束。
+
+const startIdx = src.indexOf(
+ `'`,
+);
+if (startIdx < 0) {
+ console.error("定位不到 chat 面板段落 —— app.js 结构可能已变");
+ process.exit(1);
+}
+const lastIdx = src.indexOf("chat-panel-knowledge");
+const seg = src.slice(startIdx, lastIdx + 4000);
+
+// 把跨行拼接的字符串还原成一段连续 HTML:
+// '...' + \n '...' → '...'
+const html = seg
+ .replace(/'\s*\+\s*\n\s*'/g, "")
+ .replace(/'\s*\+\s*'/g, "")
+ .replace(/"/g, "'");
+
+const opens = (html.match(/
/g) || []).length;
+
+check(
+ "chat 面板段落 div 标签配平",
+ opens === closes,
+ `开 ${opens} / 闭 ${closes},差 ${opens - closes} —— ` +
+ "少一个
会让后面所有面板被塞进前一个面板内部(表现为空白)",
+);
+
+// ── 2) 每个 chat-panel 自身也必须配平 ─────────────────────────────
+//
+// 总数配平还不够:某处多一个
、另一处少一个,总数仍为 0。
+// 逐面板断言能定位到具体是哪个。
+
+const PANELS = ["chat", "starmap", "terminal", "cmd", "memory", "context", "knowledge"];
+const unbalanced = [];
+for (const name of PANELS) {
+ const marker = `id='chat-panel-${name}'`;
+ const at = html.indexOf(marker);
+ if (at < 0) {
+ unbalanced.push(`${name}(未找到)`);
+ continue;
+ }
+ // 从该面板的 /g) || []).length;
+ if (o !== c) unbalanced.push(`${name}(开${o}/闭${c})`);
+}
+check(
+ "各子面板独立配平",
+ unbalanced.length === 0,
+ unbalanced.length ? "不配平:" + unbalanced.join("、") : "",
+);
+
+// ── 3) 所有 chat-panel 必须同属一个父容器 ─────────────────────────
+//
+// 形态判据:源码里每个面板片段的起始处都应是独立的
'(一个字符串字面量的结束)或 +(拼接)。
+
+const nested = [];
+for (const name of PANELS) {
+ const marker = `id='chat-panel-${name}'`;
+ const at = html.indexOf(marker);
+ if (at < 0) continue;
+ const from = html.lastIndexOf("
${opens} /
${closes}`);
+console.log(` 面板数: ${PANELS.length}`);
+console.log("");
+if (failures > 0) {
+ console.log(`失败 ${failures} 条`);
+ process.exit(1);
+}
+console.log("全部通过");
\ No newline at end of file
diff --git a/cmd/gui/renderer/app.js b/cmd/gui/renderer/app.js
index db15e5bd..6b0cc5ee 100644
--- a/cmd/gui/renderer/app.js
+++ b/cmd/gui/renderer/app.js
@@ -1873,7 +1873,7 @@ function buildChatLayout() {
'
' +
__("星图", "Star Map") +
"
" +
- '
';
+ '
';
html +=
'
' +
__("终端", "Terminal") +