Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); emrg: GUI workspace panel P2 framework — resizable panel + tab bar + per-session tab state (rant 2026-08-11T12:20:35) by argszero · Pull Request #664 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state); 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)
Expand DownExpand Up@@ -113,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (187: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 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` + 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 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(188 项:43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(193 项:43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,7 +147,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (188: 43 daemon_client + 19 conn-manager + 22 app-commands + 67 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (193: 43 daemon_client + 19 conn-manager + 22 app-commands + 72 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)

---

Expand Down
106 changes: 105 additions & 1 deletion emrg/gui/renderer/css/layout.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
display: flex;
height: 100vh;
width: 100vw;
position: relative; /* #result-resizer 绝对定位锚点(P2 框架) */
}

/* ── 侧边栏 ─────────────────────────────── */
Expand DownExpand Up@@ -317,15 +318,24 @@ body.sidebar-collapsed #app {
min-width: 40px;
border-left: 1px solid var(--border);
}
/* P2 框架:拖拽调整宽度期间抑制 width transition(R1-①:transition 与 mousemove 冲突) */
#result-panel.dragging {
transition: none;
}
#result-panel.collapsed .result-list {
display: none; /* 只藏内容区 */
}
#result-panel.collapsed .result-pane {
display: none; /* 折叠时所有 pane 隐藏(含激活中的文件 pane) */
}
#result-panel.collapsed .result-header {
display: flex;
justify-content: center;
padding: var(--sp-3) 0;
}
#result-panel.collapsed .result-title {
#result-panel.collapsed .result-title,
#result-panel.collapsed .result-tabs,
#result-panel.collapsed .result-tabbar {
display: none; /* 窄条只留 toggle 按钮 */
}
#result-panel.collapsed .result-toggle {
Expand DownExpand Up@@ -359,6 +369,100 @@ body.sidebar-collapsed #app {
background: var(--bg-soft);
}

/* ── P2 框架:Tab 栏(文件 / 产物 + 打开文件 Tab) ── */
.result-tabs {
display: flex;
align-items: center;
gap: var(--sp-1);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabs::-webkit-scrollbar { display: none; }
.result-tab {
background: none;
border: none;
padding: var(--sp-1) var(--sp-3);
font-size: var(--fs-secondary);
color: var(--text-3);
cursor: pointer;
border-radius: 6px;
white-space: nowrap;
}
.result-tab:hover {
color: var(--text-1);
background: var(--bg-soft);
}
.result-tab.active {
color: var(--text-1);
background: var(--bg-soft);
font-weight: 600;
}
/* 打开文件 Tab 条(P3 查看器接入;每文件一个可关闭 Tab) */
.result-tabbar {
display: none;
flex-direction: row;
gap: var(--sp-1);
padding: var(--sp-1) var(--sp-3);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.result-tabbar.has-tabs { display: flex; }
.result-tabbar::-webkit-scrollbar { display: none; }
.result-filetab {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 2px var(--sp-2);
font-size: var(--fs-small);
color: var(--text-2);
background: var(--bg-soft);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
max-width: 160px;
overflow: hidden;
}
.result-filetab.active {
color: var(--text-1);
border-color: var(--accent);
}
.filetab-close {
color: var(--text-3);
font-size: 12px;
line-height: 1;
padding: 0 2px;
border-radius: 4px;
}
.filetab-close:hover {
color: var(--text-1);
background: var(--bg-hover);
}

/* ── P2 框架:内容 pane 切换(激活 pane 显示) ── */
.result-pane { display: none; }
.result-pane.active { display: flex; flex-direction: column; }

/* ── 宽度拖拽手柄 ── */
#result-resizer {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
right: 277px; /* JS 随宽度更新(right = panelWidth - 3) */
cursor: col-resize;
z-index: 40;
}
#result-resizer:hover {
background: var(--accent);
opacity: 0.35;
}
#result-resizer.dragging {
background: var(--accent);
opacity: 0.5;
}

.result-list {
flex: 1;
overflow-y: auto;
Expand Down
18 changes: 15 additions & 3 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,26 @@
</div>
</main>

<!-- 结果面板(右栏 Artifacts,WorkBuddy P1) -->
<!-- 结果面板(右栏工作区,rant 2026-08-11T12:20:35;P2 框架:Tab 栏 + 可拖拽宽度) -->
<aside id="result-panel">
<div class="result-header">
<span class="result-title" data-i18n="result.title">产物</span>
<div class="result-tabs" id="result-tabs" role="tablist">
<button class="result-tab" id="result-tab-files" data-tab="files" data-i18n="result.tabFiles">文件</button>
<button class="result-tab active" id="result-tab-artifacts" data-tab="artifacts" data-i18n="result.tabArtifacts">产物</button>
</div>
<button id="result-toggle" class="result-toggle" title="折叠/展开 (⌘\)" data-i18n-title="result.collapse">»</button>
</div>
<div id="result-list" class="result-list"></div>
<!-- 打开文件 Tab 条(P3 查看器接入;框架已就绪) -->
<div id="result-tabbar" class="result-tabbar"></div>
<!-- 文件浏览 pane(P3.1 文件树接入;当前为空态占位) -->
<div id="result-files" class="result-pane result-files">
<div class="result-empty" data-i18n="result.filesEmpty">文件浏览器即将推出</div>
</div>
<!-- 产物 pane(WorkBuddy P1 工具卡片暂保留,P3.2 改为 write/edit 文件登记) -->
<div id="result-list" class="result-list result-pane active"></div>
</aside>
<!-- 宽度拖拽手柄:绝对定位(不进 #app flex 流,防 WebContentsView bounds x 偏移) -->
<div id="result-resizer" title="拖拽调整宽度" data-i18n-title="result.resize"></div>
</div>

<!-- 设置对话框 -->
Expand Down
4 changes: 3 additions & 1 deletion emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -631,6 +631,7 @@ const App = (() => {
// P3 slice 1:切换只移动激活指针——每会话条目保留自己的 busy/ownStreamRequestId
// P3 slice 2:激活该会话容器(状态保留,不 Chat.clear——切回继续看到原消息/流式现场)
activateSessionView(sid);
ResultPanel.switchSession(sid); // P2 框架:右栏 Tab/产物状态按 sid 隔离
updateEmptyState();
if (res.error === "session_not_found") {
Chat.addSystemMessage(_t("app.deletedSwitch"));
Expand DownExpand Up@@ -671,6 +672,7 @@ const App = (() => {
state.sessionId = res.session_id;
state.drafts.set(res.session_id, ""); // 新会话无草稿
activateSessionView(state.sessionId); // P3 slice 2:新会话独立容器(空)
ResultPanel.switchSession(state.sessionId); // P2 框架:新会话右栏状态复位
Chat.clear(state.sessionId); // 新会话从空开始(容器可能被复用)
updateEmptyState(); // 欢迎屏即反馈
await refreshSessions();
Expand DownExpand Up@@ -1172,7 +1174,7 @@ const App = (() => {
break;
case "tool_finished":
Chat.handleToolEnd(data, sid);
ResultPanel.addToolResult(data);
ResultPanel.addToolResult(data, sid); // P2 框架:产物登记按事件 sid 归类(P3.2 消费)
break;
case "cancelled":
Chat.clearTyping(sid); // rant 14:11:取消时移除在途节点 typing 光标(无 request_id,全清)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,10 @@ const I18N = (() => {
"result.title": "产物",
"result.collapse": "折叠/展开 (⌘\\)",
"result.empty": "还没有产物,对话中生成的文件会出现在这里",
"result.tabFiles": "文件",
"result.tabArtifacts": "产物",
"result.filesEmpty": "文件浏览器即将推出",
"result.resize": "拖拽调整宽度",

// 设置
"settings.title": "设置",
Expand DownExpand Up@@ -377,6 +381,10 @@ const I18N = (() => {
"result.title": "Artifacts",
"result.collapse": "Collapse/expand (⌘\\)",
"result.empty": "No artifacts yet — files generated in the conversation will appear here",
"result.tabFiles": "Files",
"result.tabArtifacts": "Artifacts",
"result.filesEmpty": "File browser coming soon",
"result.resize": "Drag to resize",

// Settings
"settings.title": "Settings",
Expand Down
Loading
Loading