From a8e06ed2cdecc1edf4bcd8292ec3244759d1ec78 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Thu, 13 Aug 2026 12:52:25 +0800 Subject: [PATCH] emrg: fix GUI file-browser scroll + root collapse (rants 12:46/12:47) --- Agent.md | 4 ++-- emrg/gui/renderer/css/layout.css | 4 ++++ emrg/gui/renderer/js/file-tree.js | 9 ++++++++- emrg/gui/test/renderer.smoke.test.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Agent.md b/Agent.md index f31019a..9523fab 100644 --- a/Agent.md +++ b/Agent.md @@ -85,7 +85,7 @@ Usage: say "tool loop" for the whole process, "round N" for a single LLM request - Streaming chat with delta rendering (16ms batching), markdown on done (marked + DOMPurify + local highlight.js subset), tool call status cards (2000-char truncation + expand) - Session list/switch/new/delete + right-click rename (context menu, #423) synced with daemon; own-stream busy lock (G65); broadcast streams from other clients tagged "来自其他客户端" - Disconnect/reconnect: red status dot, auto daemon respawn (stale-port detection), session resume, input bar restored on disconnect (no 30s fake-timeout) - - Unit tests `npm test` (229: 44 daemon_client + 19 conn-manager + 22 app-commands + 107 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` + - Unit tests `npm test` (231: 44 daemon_client + 19 conn-manager + 22 app-commands + 109 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` - **Scheduled tasks** — Task generalization + CRUD (rant 2026-08-12T18:23:15, #709/#710/#711) - Task handler generalized: `TaskHandler` (renamed from `EvolutionHandler`), repo-configured self-heal for any project, template lookup builtin → `~/.emrg/task-templates/.md` → fallback - Daemon commands: `task_create/update/delete` + `task_template_create/list/update/delete` (tasks stored in `~/.emrg/tasks.yml`, custom type templates in `~/.emrg/task-templates/`) @@ -119,7 +119,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` Python: `uv run pytest tests/ -v` (761) — import check: `uv run python -c "from emrg.client.app import run_client"` -GUI: `cd emrg/gui && npm test` (229: 44 daemon_client + 19 conn-manager + 22 app-commands + 107 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` +GUI: `cd emrg/gui && npm test` (231: 44 daemon_client + 19 conn-manager + 22 app-commands + 109 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/emrg/gui/renderer/css/layout.css b/emrg/gui/renderer/css/layout.css index 566fb44..a29fed4 100644 --- a/emrg/gui/renderer/css/layout.css +++ b/emrg/gui/renderer/css/layout.css @@ -465,6 +465,10 @@ body.sidebar-collapsed #app { /* ── P3.1:文件浏览器(懒加载目录树,VS Code 风格 rant 2026-08-12T17:28:19) ── */ .result-files { + /* flex:1 + min-height:0(rant 2026-08-13T12:46:12):flex 子项默认 flex:0 1 auto 高度随内容 + 增长永不压缩进容器 → overflow-y:auto 永不触发(有滚动条槽但滚不动);与 .result-list 对齐 */ + flex: 1; + min-height: 0; overflow-y: auto; /* 滚动条 hover 显示(VS Code 行为:平时隐藏,移入文件树区域显示) */ scrollbar-width: thin; diff --git a/emrg/gui/renderer/js/file-tree.js b/emrg/gui/renderer/js/file-tree.js index e07aee2..22af51e 100644 --- a/emrg/gui/renderer/js/file-tree.js +++ b/emrg/gui/renderer/js/file-tree.js @@ -203,9 +203,16 @@ const FileTree = (() => { rootRow.appendChild(rootHead); const kids = el("div", { class: "ft-kids" }); rootRow.appendChild(kids); + // 根目录行与普通目录行一致的可展开/收起(rant 2026-08-13T12:47:18:render() 手写根行漏绑 + // click → 点击根目录无反应永远展开;toggleDir 已负责 expanded Map 持久 + 图标切换) + const st = ensure(root); + rootRow.addEventListener("click", (e) => { + if (e && e.stopPropagation) e.stopPropagation(); + toggleDir(root, kids, st, 1); + }); c.appendChild(rootRow); // 根自动展开(fire-and-forget;根子项 depth=1) - expandDir(root, kids, ensure(root), 1); + expandDir(root, kids, st, 1); } return { setRoot, setSession }; diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index bb14f82..358f1cd 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -1369,6 +1369,34 @@ test("P3.5(rant 17:28):展开态持久 + 滚动条 hover 样式(CSS 源 assert.ok(tabsIdx < tabbarIdx && tabbarIdx < toggleIdx, "tabbar 应嵌套在 result-tabs 内(同排),旧布局 toggle 在 tabbar 之前"); }); +test("P3.5(rant 2026-08-13T12:46:12):.result-files 滚动 CSS flex:1 + min-height:0", async () => { + // #result-panel 是 flex column;.result-list 有 flex:1 所以滚动正常,.result-files 缺 flex:1/min-height:0 + // → 高度随内容增长永不压缩,overflow-y:auto 永不触发(有滚动条槽但滚不动) + const css = fs.readFileSync(path.join(RENDERER_CSS, "layout.css"), "utf8").replace(/\n/g, " "); + const m = css.match(/\.result-files\s*\{([^}]*)\}/); + assert.ok(m, ".result-files 规则存在"); + assert.ok(/flex:\s*1/.test(m[1]), ".result-files 应 flex:1(对齐 .result-list)"); + assert.ok(/min-height:\s*0/.test(m[1]), ".result-files 应 min-height:0(允许压缩触发滚动)"); + assert.ok(/overflow-y:\s*auto/.test(m[1]), ".result-files 保留 overflow-y:auto"); +}); + +test("P3.5(rant 2026-08-13T12:47:18):根目录行可收起/展开", async () => { + const { ctx, els } = makeSandbox(); + await tick(); + await vm.runInContext('FileTree.setSession("s1", "/proj")', ctx); + await tick(); + const rootRow = els["result-files"].querySelectorAll(".ft-root")[0]; + assert.ok(rootRow, "根目录行存在"); + const kids = rootRow.querySelector(".ft-kids"); + assert.ok(kids, "根目录 kids 容器存在"); + assert.ok(!kids.classList.contains("hidden"), "默认展开(ft-kids 无 hidden)"); + rootRow.dispatch("click", { stopPropagation() {} }); + assert.ok(kids.classList.contains("hidden"), "点击根目录应收起"); + rootRow.dispatch("click", { stopPropagation() {} }); + await tick(); + assert.ok(!kids.classList.contains("hidden"), "再次点击应展开"); +}); + test("工具调用上限中断 → 系统提示可继续(对齐 TUI,跨项目教训)", async () => { const { ctx, els } = makeSandbox({}); await tick();