mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
fix(gui): 补星图面板缺失的 </div>,修对话页 5 个页签空白
## 实测故障
对话页里「终端 / 运行中命令 / 记忆 / 上下文 / 知识」五个页签点进去全是
空白,只有「对话」和「星图」正常。
## 根因:少一个 </div>
构造星图面板的那段 HTML 里有 **4 个 `<div>` 开、只有 3 个 `</div>` 闭**,
它没闭合 —— 后面拼接的所有 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 ⇒ 显示为空白。
实测尺寸对照(修复前 → 修复后):
terminal 0x0 → 1156x621
cmd 0x0 → 1156x621
memory 0x0 → 1156x621
context 0x0 → 1156x621
knowledge 0x0 → 1156x621
## 为什么一直没被发现
标签不配对在源码里看不出来(跨行字符串拼接的 HTML 片段),浏览器也
**不报错** —— 它只是「尽力修复」成畸形结构。现象是「点了没反应/一片空白」,
很容易被归因为数据没到或后端挂了(我最初就查了半天气网络与接口 401)。
而且它只在部分面板上发作:chat 与 starmap 恰好是前两个,位置靠前不受影响,
于是「大部分能用」掩盖了「后面全废」。
## 判据(cmd/gui/chat-panel-dom.test.mjs)
3 条:整段 div 标签配平、各面板独立配平、各面板顶层不被嵌套进前一面板。
逐面板断言能定位到**具体是哪个**面板不配平(变异测试下报
`starmap(开4/闭3)`),而不只是「总数差了 1」。
变异测试:去掉一个 `</div>` ⇒ 前两条转红并指出 starmap。
判据自身也错过一次:还原 HTML 时把 `"` 全替换成 `'`,而 marker 用的是双引号,
7 个面板全报「未找到」。已修正并记在注释里。
This commit is contained in:
149
cmd/gui/chat-panel-dom.test.mjs
Normal file
149
cmd/gui/chat-panel-dom.test.mjs
Normal file
@ -0,0 +1,149 @@
|
||||
// chat 子面板 DOM 结构判据(2026-10-05)。
|
||||
//
|
||||
// ## 要判的是什么
|
||||
//
|
||||
// 实测故障:对话页里「终端 / 运行中命令 / 记忆 / 上下文 / 知识」五个页签
|
||||
// 点进去全是空白,只有「对话」和「星图」正常。
|
||||
//
|
||||
// 根因是 **HTML 少一个 </div>**:构造星图面板的那段 HTML 里有 4 个
|
||||
// <div> 开、只有 3 个 </div> 闭,于是它没闭合 —— 后面拼接的所有
|
||||
// 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(
|
||||
`'<div class="chat-panel active" id="chat-panel-chat">`,
|
||||
);
|
||||
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(/<div\b/g) || []).length;
|
||||
const closes = (html.match(/<\/div>/g) || []).length;
|
||||
|
||||
check(
|
||||
"chat 面板段落 div 标签配平",
|
||||
opens === closes,
|
||||
`开 ${opens} / 闭 ${closes},差 ${opens - closes} —— ` +
|
||||
"少一个 </div> 会让后面所有面板被塞进前一个面板内部(表现为空白)",
|
||||
);
|
||||
|
||||
// ── 2) 每个 chat-panel 自身也必须配平 ─────────────────────────────
|
||||
//
|
||||
// 总数配平还不够:某处多一个 </div>、另一处少一个,总数仍为 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;
|
||||
}
|
||||
// 从该面板的 <div class="chat-panel 开始算(marker 之前最近的那个)
|
||||
const from = html.lastIndexOf("<div", at);
|
||||
// 到下一个 chat-panel 之前为止
|
||||
const nextAt = html.indexOf("id='chat-panel-", at + marker.length);
|
||||
const to = nextAt < 0 ? html.length : html.lastIndexOf("<div", nextAt);
|
||||
const piece = html.slice(from, to);
|
||||
const o = (piece.match(/<div\b/g) || []).length;
|
||||
const c = (piece.match(/<\/div>/g) || []).length;
|
||||
if (o !== c) unbalanced.push(`${name}(开${o}/闭${c})`);
|
||||
}
|
||||
check(
|
||||
"各子面板独立配平",
|
||||
unbalanced.length === 0,
|
||||
unbalanced.length ? "不配平:" + unbalanced.join("、") : "",
|
||||
);
|
||||
|
||||
// ── 3) 所有 chat-panel 必须同属一个父容器 ─────────────────────────
|
||||
//
|
||||
// 形态判据:源码里每个面板片段的起始处都应是独立的 <div class="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("<div", at);
|
||||
const before = html.slice(Math.max(0, from - 24), from);
|
||||
// 合法前缀:以 ' 结尾(字符串结束)或 '+ 结尾(仍在拼接)
|
||||
if (!/'[+]?$/.test(before.trimEnd()) && !/\+\s*$/.test(before)) {
|
||||
nested.push(`${name}(前缀=${JSON.stringify(before.slice(-12))})`);
|
||||
}
|
||||
}
|
||||
check(
|
||||
"各面板顶层不被嵌套进前一面板",
|
||||
nested.length === 0,
|
||||
nested.length
|
||||
? "这些面板疑似接在未闭合结构里:" + nested.join("、")
|
||||
: "",
|
||||
);
|
||||
|
||||
// ── 汇总 ────────────────────────────────────────────────────────────
|
||||
|
||||
console.log("");
|
||||
console.log(` chat 面板段落: <div> ${opens} / </div> ${closes}`);
|
||||
console.log(` 面板数: ${PANELS.length}`);
|
||||
console.log("");
|
||||
if (failures > 0) {
|
||||
console.log(`失败 ${failures} 条`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("全部通过");
|
||||
@ -1873,7 +1873,7 @@ function buildChatLayout() {
|
||||
'<div class="chat-panel" id="chat-panel-starmap"><div class="card"><h2>' +
|
||||
__("星图", "Star Map") +
|
||||
"</h2>" +
|
||||
'<div id="sm-container-chat" style="display:flex;align-items:center;justify-content:center;min-height:480px"><div class="ha-dots-panel"><span class="ha-dots ha-dots-lg"><i></i><i></i><i></i></span></div></div></div>';
|
||||
'<div id="sm-container-chat" style="display:flex;align-items:center;justify-content:center;min-height:480px"><div class="ha-dots-panel"><span class="ha-dots ha-dots-lg"><i></i><i></i><i></i></span></div></div></div></div>';
|
||||
html +=
|
||||
'<div class="chat-panel" id="chat-panel-terminal"><div class="card"><h2>' +
|
||||
__("终端", "Terminal") +
|
||||
|
||||
Reference in New Issue
Block a user