feat(grid): split-grid v2 — 1×3/2×3, drag-to-quadrant, Ctrl+` cycle, maximize

Builds on the v1 watch board:
- Two more layouts: row-3 (1×3) and grid-6 (2×3). A single `grid` marker class on
  #term now carries the shared cell chrome so it no longer enumerates each lay-*.
- Ctrl+` cycles the focused quadrant (Ctrl+Shift+` reverses); matchFocusCycleKey
  is an exported pure helper so it's unit-tested. No-op / passthrough in single mode.
- Per-quadrant ⛶ maximize: the focused cell expands to fill the grid as an absolute
  overlay while siblings stay live behind it; follows focus, resets on layout
  change / tab close.
- Drag a tab from the tab bar onto a quadrant to assign it there (reuses the
  existing tab-drag dragIndex); grid-only.

Adversarial review (3 lenses → per-finding verify, incl. a headless-Chrome repro)
caught a HIGH: maximizing via `grid-column/row: 1/-1` shoved siblings into implicit
rows — a strip instead of fullscreen AND a spurious resize to backgrounded live
PTYs. Fixed with an absolute-overlay (`position:absolute; inset:0`), then verified
in real Chromium (Playwright): maximized cell fills #term, siblings 0px size delta.
Also: silence a covered pane's pending pulse under maximize; coarse-pointer target
for .cell-max. Verified: typecheck + build:web clean, 1579 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2026-07-11 19:49:39 +02:00
parent 06814ba276
commit 5475b661ae
7 changed files with 324 additions and 24 deletions

View File

@@ -32,7 +32,9 @@
- **核心设计**: `activeIndex` 语义**不变**=聚焦格(keybar/voice/approval 全部零改动);新增 `gridLayout` + 派生 `visibleIndices`;每个 pane 包进 `.term-cell`(header + 终端 + 可选内联审批 footer);`applyLayout()` 独占 pane 显隐/grid class/cell 排序/占位符;`#term` CSS grid。**服务端/WS 协议零改动**。
- **文件**: `public/grid-layout.ts`(纯逻辑 + matchMedia + 持久化 + toolbar 分段控件)、`tabs.ts`(applyLayout/renderCell/renderInlineApprove/setFocused/setGridLayout/refitVisible + `activate()` 改为**board-aware**)、`terminal-session.ts`(`show({focus})` 4 格抢焦点 + `onFocus` 回调)、`main.ts`( toggle + refitVisible)、`style.css`(cell/grid/焦点环/pending 脉冲/内联审批/占位符/toggle + `.term-pane` position:relative flex )。新测 `test/grid-layout.test.ts` + `tabs.test.ts` 追加 split-grid
- **交叉验证抓修 3 真缺陷**:**HIGH** `activate()` 原先非 board-aware:满格(4/4)时点 "+" 会让新 tab 成为 activeIndex `display:none`,用户对着看不见的 session 打字(空最常见路径触发)。修:把 off-boardmoveTab 上板逻辑折进 `activate()`,`setFocused`/`setGridLayout` 委托之。**LOW** 通知抑制未算 `homeForced`(⌂ 覆盖时 pane 实际不在屏);修:`onScreen = !homeForced && isVisible`。**LOW** toggle 触控尺寸(coarse-pointer 平板);修:`@media (pointer:coarse)` 加大全部补了回归测试
- **[~] 下一步**: v2(1×3/2×3 布局 tab 到格Ctrl+` 循环焦点、单格最大化)→ v3(可拖拽分隔条、只读 monitor 格走 `/live-sessions/:id/preview` 共享 PTY 缩放布局预设)。
- **[x] v2 DONE(2026-07-11)** orchestrator 独立复验全绿:typecheck config 干净build:web 干净、**全套 1579 测试**(+13),覆盖率 grid-layout 95%/tabs 93%。加了:**1×3(row-3)/2×3(grid-6)布局**、**Ctrl+`/Ctrl+Shift+` 循环焦点**(main.ts capture keydown cycleFocus,单格模式不吞键)、**每格最大化 ⛶**(`.maximized` 覆盖层)、** tab 到格**(wireCellDropTarget 复用 dragIndex)。用单个 `grid` 标记类承载共享 cell 样式(不再枚举每个 lay-*)。
- **交叉验证抓修 3 真缺陷**:**HIGH** 最大化原用 `grid-column/row: 1/-1` **span 网格**是错的:CSS Grid 把兄弟格挤进隐式行→"最大化"格变成细条(非全屏)+ 兄弟格 box 变化触发 ResizeObserver→**给后台 live PTY 发错误 resize(伪 SIGWINCH)**评审在** headless Chrome 里实测复现**。修:`.maximized` `position:absolute; inset:0; z-index:4` 脱离网格流做覆盖层。**独立 Playwright 复验**:最大化格填满 #term(1000×700)、兄弟格尺寸 0px 变化,对照组证实旧规则确会 891×572 细条+兄弟塌成 52px。**MED** 最大化时被盖住的 pending amber 脉冲外溢;修:`(!maximized || focused)` 。**LOW** `.cell-max` coarse-pointer 触控尺寸均补回归测试;并把 Ctrl+` 键匹配抽成 `matchFocusCycleKey` 纯函数单测
- **[~] 下一步**: v3(可拖拽分隔条只读 monitor 格走 `/live-sessions/:id/preview` 免共享 PTY 缩放布局预设)。
- **探索产物**: 交互式原型 artifact(single/1×2/2×2 切换点击移焦内联审批)已给用户看过并据此拍板方向
### 🔐 TUNNEL AUTOMATION — 零接触隧道注入(客户永不碰证书/密钥;2026-07-08)