From eeea2a8fb6b82a722f3d65185988df1eda5f0a22 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Mon, 10 Aug 2026 14:19:03 +0800 Subject: [PATCH] emrg: keep result-panel toggle visible when collapsed (rant 2026-08-10T14:11:18) Co-authored-by: EMRG Evolution --- Agent.md | 4 ++-- README.cn.md | 4 ++-- README.md | 2 +- emrg/gui/renderer/css/layout.css | 21 ++++++++++++++++----- emrg/gui/test/renderer.smoke.test.js | 24 ++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Agent.md b/Agent.md index ece380b0..70597a92 100644 --- a/Agent.md +++ b/Agent.md @@ -66,7 +66,7 @@ EMRG is a self-evolving AI agent architecture experiment. Python implementation, - 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` (110: 29 daemon_client + 22 app-commands + 31 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` + - Unit tests `npm test` (111: 29 daemon_client + 22 app-commands + 32 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` - **Auto project tracking** — Automatically detects and records working directories; project-scoped sessions - **Rant-driven evolution** — User feedback via `/rant` drives automatic self-improvement cycles - **Headless GitHub auth** — Non-interactive evolution auto-extracts `GH_TOKEN` from git credential store (osxkeychain / credential helper); PR comment/LGTM queries fall back to REST API (GraphQL needs `read:org` scope) @@ -94,7 +94,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` Python: `uv run pytest tests/ -v` (674) — import check: `uv run python -c "from emrg.client.app import run_client"` -GUI: `cd emrg/gui && npm test` (110: 29 daemon_client + 22 app-commands + 31 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` +GUI: `cd emrg/gui && npm test` (111: 29 daemon_client + 22 app-commands + 32 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` + 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/README.cn.md b/README.cn.md index 35da8955..58a9e261 100644 --- a/README.cn.md +++ b/README.cn.md @@ -282,8 +282,8 @@ uv run python -m emrg # 启动 TUI cd emrg/gui npm ci # 安装依赖(生产模式可 --omit=dev) npm start # 启动 GUI(自动拉起 daemon) -npm test # 运行 Node 测试(110 项:29 daemon_client + 22 app-commands + 31 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config;集成测试在 CI 跑,本地可 npm run test:integration) -npm test # 运行 Node 测试(110 项:29 daemon_client + 22 app-commands + 31 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config;集成测试在 CI 跑,本地可 npm run test:integration) +npm test # 运行 Node 测试(111 项:29 daemon_client + 22 app-commands + 32 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config;集成测试在 CI 跑,本地可 npm run test:integration) +npm test # 运行 Node 测试(111 项:29 daemon_client + 22 app-commands + 32 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config;集成测试在 CI 跑,本地可 npm run test:integration) ``` CI 通过 GitHub Actions 自动运行测试并检查冲突标记(`.github/workflows/test.yml`)。 diff --git a/README.md b/README.md index b7a5474a..c100c6b6 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ uv run python -m emrg # launch TUI cd emrg/gui npm ci # install deps (production: --omit=dev) npm start # launch GUI (auto-starts daemon) -npm test # run Node tests (110: 29 daemon_client + 22 app-commands + 31 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config; integration runs in CI, local: npm run test:integration) +npm test # run Node tests (111: 29 daemon_client + 22 app-commands + 32 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config; integration runs in CI, local: npm run test:integration) ``` CI runs tests and checks for conflict markers automatically via GitHub Actions (`.github/workflows/test.yml`). diff --git a/emrg/gui/renderer/css/layout.css b/emrg/gui/renderer/css/layout.css index 6db46a77..468f01f6 100644 --- a/emrg/gui/renderer/css/layout.css +++ b/emrg/gui/renderer/css/layout.css @@ -251,13 +251,24 @@ body.sidebar-collapsed #app { transition: width 0.2s cubic-bezier(.2,.8,.3,1), min-width 0.2s cubic-bezier(.2,.8,.3,1); } #result-panel.collapsed { - width: 0; - min-width: 0; - border-left: none; + /* 方案 A(rant 2026-08-10T14:11:18):折叠成窄条而非 0,header(含 toggle 按钮)保持可见可点 */ + width: 40px; + min-width: 40px; + border-left: 1px solid var(--border); } -#result-panel.collapsed .result-header, #result-panel.collapsed .result-list { - display: none; + display: none; /* 只藏内容区 */ +} +#result-panel.collapsed .result-header { + display: flex; + justify-content: center; + padding: var(--sp-3) 0; +} +#result-panel.collapsed .result-title { + display: none; /* 窄条只留 toggle 按钮 */ +} +#result-panel.collapsed .result-toggle { + transform: rotate(180deg); /* » → « 指示可展开 */ } .result-header { diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index a3cc6445..10113e25 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -14,6 +14,7 @@ const path = require("node:path"); const vm = require("node:vm"); const RENDERER_JS = path.join(__dirname, "..", "renderer", "js"); +const RENDERER_CSS = path.join(__dirname, "..", "renderer", "css"); // ── DOM mock(最小但真实:classList 操作同步到 className) ── function makeEl(id) { @@ -867,6 +868,29 @@ test("WorkBuddy P1:ResultPanel 折叠切换(⌘\ 与按钮)", async () => assert.ok(!els["result-panel"].classList.contains("collapsed"), "再次 toggle 应展开"); }); +test("WorkBuddy P1:折叠后 toggle 按钮仍可见可点(rant 2026-08-10T14:11:18 死锁回归)", async () => { + // CSS 规则断言(沙箱无 CSS 引擎,源级验证折叠态布局语义) + const css = fs.readFileSync(path.join(RENDERER_CSS, "layout.css"), "utf8"); + const start = css.indexOf("#result-panel.collapsed"); + const end = css.indexOf(".result-header {", start); // 折叠规则组结束(下一个独立规则) + const block = css.slice(start, end > start ? end : start + 400); + // 折叠 = 40px 窄条(非 0 宽度) + assert.ok(/width:\s*40px/.test(block), "折叠态应为 40px 窄条,而非 width: 0(否则按钮不可点)"); + assert.ok(!/width:\s*0/.test(block), "折叠态不得 width: 0"); + // header 保留可见(display: flex),只藏内容区 .result-list + assert.ok(/#result-panel\.collapsed \.result-header\s*{[^}]*display:\s*flex/.test(css.replace(/\n/g, " ")), "折叠态 .result-header 应 display: flex(含 toggle 按钮)"); + assert.ok(!/display:\s*none[^}]*result-header/.test(css), "折叠态不得隐藏 .result-header"); + assert.ok(/#result-panel\.collapsed \.result-list\s*{[^}]*display:\s*none/.test(css.replace(/\n/g, " ")), "折叠态 .result-list 应隐藏"); + // toggle 按钮折叠态旋转提示可展开 + assert.ok(/#result-panel\.collapsed \.result-toggle\s*{[^}]*rotate\(180deg\)/.test(css.replace(/\n/g, " ")), "折叠态 toggle 应 rotate(180deg) 指示可展开"); + // DOM 存活:折叠后 toggle 按钮仍在文档中 + const { ctx } = makeSandbox(); + await tick(); + await vm.runInContext("ResultPanel.toggle()", ctx); + const btnStillThere = vm.runInContext('document.getElementById("result-toggle") !== null', ctx); + assert.ok(btnStillThere, "折叠后 toggle 按钮不得从 DOM 移除"); +}); + test("工具调用上限中断 → 系统提示可继续(对齐 TUI,跨项目教训)", async () => { const { ctx, els } = makeSandbox({}); await tick();