| name | zterm-mac-dev |
| description | zterm Mac 客户端开发工作流 - Electron 壳、terminal renderer、local/remote tmux、自闭环验证 |
zterm-mac Dev Skill
适用场景
mac/ 下的 Electron / renderer / preload / local tmux / remote bridge 开发
- Mac terminal 渲染、分屏、tab、local tmux、remote 连接、资源占用审计
- 用户要求先自测闭环、拿证据,再让 Jason 手测
一、必读顺序
~/.codex/AGENTS.md
~/.codex/USER.md
coding-principals/SKILL.md
android/docs/decisions/0001-cross-platform-layout-profile.md
android/docs/architecture.md
android/docs/dev-workflow.md
mac/MEMORY.md
- 本
SKILL.md
二、Mac 开发硬规则
2.0 Skill 使用边界
- 本仓库 Mac 线默认只使用这一个项目 dev skill:
zterm-mac-dev。
- 不要再为同一条 Mac 开发链路额外切换/串联新的本地 dev skill,避免规则分叉。
- 若确实需要别的 skill,必须是 Jason 显式点名。
2.1 Terminal / renderer
- renderer 只消费 canonical buffer / render projection;禁止在 view 层继续造第二份 terminal 真相。
- terminal 优先 terminal-first:少 chrome、少常驻面板、主空间给 terminal pane/tab/split。
- 分屏默认是一行多列、垂直分屏;不要把上下堆叠当主方案。
- Mac 分屏视觉压缩只改 shared
PaneStage / resolvePaneProfile token 与 Mac shell chrome CSS;禁止在 runtime / renderer / buffer 层补外观。验证至少跑 pane targeted tests、type-check/build,并用渲染 smoke 证明 split DOM 与 spacing token(如 stage gap / divider width)已进入最新 bundle。
- reading/backfill 里
missingRanges 必须从 view -> runtime -> transport 原样透传;任一层清空它,scroll prefetch 都会静默失效。
- 若桌面端要接快捷按键组合语义,优先复用 shared composer 叶子模块;不要在 Mac renderer 再复制一套
Ctrl + 字母 编码/默认 label 规则。
- 若桌面端接入 terminal 主题选择 UI,只要界面显示“Active/正在使用”,点击动作就必须立即持久化到 shared
BridgeSettings.terminalThemeId;不能只改本页 draft,避免出现“看起来切了主题,切页后又回默认”的假激活。
2.2 连接 / tmux
- remote 连接与 local tmux 都必须走真实 runtime,不允许静态占位冒充 live terminal。
- “能列 session” 不等于 “已 attach”;需要真实 connect / attach / resize / input 路径验证。
- 修改 local tmux / remote bridge / renderer 任一层后,必须至少做一次实际 smoke,不只看编译通过。
- Electron local tmux head/sync 的 canonical capture 必须包含 scrollback + visible pane bottom;
capture-pane -E -1 会停在历史尾部,不能用于 app buffer truth,否则 packaged DOM 会落后真实 tmux capture-pane 尾部。
2.3 资源与生命周期
- 不允许只凭代码阅读宣称“没有内存泄漏/没有孤儿进程”;必须有运行态证据。
- 旧 app 必须先退出,再打开新包;不要叠多个实例污染结论。
- 禁止 broad kill;退出旧 app 用应用级 quit 或明确 PID 级关闭。
- tmux / daemon / CDP smoke 必须先盘点现有资源,再复用本轮已有专用 session / port / app 实例;禁止每次验证都新建 timestamp session。
- 只有两类 session 允许写入或重置:本轮明确创建的专用 session,或带项目 gate marker 且 owner/case 匹配的固定 gate session。已有用户 session 只允许只读观测。
- 每个 live / blackbox smoke 结束前必须复核生命周期:列出本轮新增 session / pipe-pane / app PID / debug port,关闭临时资源;若固定 gate session 需要保留供复用,必须说明 marker 与名称。
2.4 Desktop workspace owner gate
- Mac desktop workspace / multi-window / pane-tab-runtime / file browser 重构必须先查并同步:
mac/docs/function-map.md
mac/docs/mainline-call-map.json
mac/docs/testing/mac-desktop-workspace-test-design.md
- 未落地 owner 只标
binding pending,禁止伪造 symbol / caller / callee。
- 初始架构 gate 只锁当前已成立事实与 map parseability;不要把后续切片才会修的 transitional debt 提前做成误报红测。
- 当实现进入对应切片后,必须把 pending 规则升级为 hard gate,例如 runtime 创建只能在
MacRuntimeRegistry,pane UI 不得直接 connectRemote/connectLocalTmux。
三、强制闭环流程(Jason 新冻结)
3.1 默认执行顺序
任何 Mac 改动,默认按下面顺序闭环;没走完,不要向 Jason 报“可以手测”。
改代码
-> type-check
-> build
-> package(若影响 packaged 行为)
-> 退出旧 app
-> 启动新 app / 新包
-> 自己完成 smoke
-> 采集证据(命令输出 / 截图 / 资源快照)
-> 只有证据闭环后,才能汇报 Jason
3.2 触发 packaged smoke 的场景
出现以下任一项,必须跑 packaged app smoke,而不只 dev server:
- Electron main / preload / IPC 改动
- 本地 tmux 接入改动
- 窗口恢复 / 启动 / 单实例 / app 生命周期改动
- renderer 资源占用 / 退出态 / orphan process / memory leak 排查
- 用户明确说“我要实际使用”“我要重新编译安装”
3.3 最低验证门槛
A. 静态门槛
pnpm --filter @zterm/mac type-check
pnpm --filter @zterm/mac build
B. 核心连接门槛
Mac terminal / transport / runtime 改动必须先跑本地客户端核心连接 gate,不能用 daemon-only probe 替代。
pnpm --dir mac test -- --reporter dot
pnpm --dir mac run type-check
最低覆盖面:
bridge-transport:remote daemon WebSocket 两阶段握手、connected state、head/body/input 发到 live socket、stale socket 不污染当前连接。
local-tmux-transport:Electron local tmux API connect、connected event、head/body request、input/resize/activity/disconnect 同一 clientId。
terminal-runtime:head 变化触发 body sync,same-end revision 变化不能被去重吞掉。
- workbench active target:tab/pane 切换不重复 reconnect,不把 local/remote target 混成第二状态机。
证明范围:
- 证明 Mac client transport/runtime 核心连接逻辑可用。
- 不证明 packaged
.app、真实窗口、DOM 输入、资源/退出态已闭环。
C. packaged 门槛(按需)
pnpm --filter @zterm/mac package
D. 运行态 smoke 门槛
至少覆盖本轮改动直接影响的主路径:
- terminal 能打开
- input / resize / scroll / split / tab 中与本轮相关的关键路径
- local tmux 或 remote bridge 至少一条真实链路
- terminal buffer/render 正确性必须比较 session truth 和 app render output:
- session truth:
tmux capture-pane
- input oracle:专用 session 的
tmux pipe-pane
- app output:packaged app DOM rendered rows / 截图
- 必跑 gate:
pnpm --dir mac run blackbox:terminal-buffer -- --case=all
- 必须包含持续刷新底部 TUI case;只看到
connected、底部几何对齐或静态截图不算 terminal 数据闭环
- blackbox gate 必须复用固定专用 tmux session:
zterm_mac_gate_sequence / zterm_mac_gate_tui / zterm_mac_gate_large,并用 tmux option marker 验证 owner/case 后才允许 respawn / clear-history / cleanup;禁止 timestamp 新建一串 session,禁止碰无 marker 的用户 session
- blackbox gate 默认保留这三个固定 session 作为复用池;只有显式
--cleanup-sessions 才能在 marker 验证通过后精确关闭它们。运行结束必须复核 tmux list-sessions,确认没有遗留新的 zterm_mac_* 临时 session
- TUI fixture 每次 run 前必须重置内容和清 history;持续刷新只比较当前可见 screen 与 app rendered rows,历史/overscan 只能作为 raw evidence,不能进入 lag 判定
- large-reading fixture 必须证明真实 scroll 容器进入 reading:
scroll.atBottom=false、append 后 reading rows 不变、scroll-to-bottom 后 app tail 与 tmux tail 一致;若 clientHeight === scrollHeight,先修父容器高度约束,不准把 DOM 全量内容当作 reading 通过
- 若改的是资源/生命周期:补
ps/top 资源采样 + 退出态进程检查
E. 证据门槛
证据至少二选二:
- 命令输出
- app 截图
- 进程 / RSS / CPU 快照
- 必要时日志 / sample
证据落点:
mac/evidence/<date>-<topic>/
四、资源/泄漏专项闭环
4.1 资源审计最低动作
ps -axo pid,ppid,pgid,%cpu,rss,vsz,etime,comm | egrep 'PID|ZTerm|Electron Helper'
top -pid <renderer_pid> -stats pid,cpu,mem,threads,state,time -l 2
4.2 退出态检查
- 先退出旧实例
- 确认旧 PID 消失
- 再启动新实例
- 不允许跳过这一步就汇报“没有孤儿进程”
4.3 报告规则
- 先给证据,再给结论
- 若只完成编译、未完成运行态 smoke,只能报告“代码已编译,通过静态验证,未完成运行态闭环”
- 若只完成 dev server、未完成 packaged smoke,不能向 Jason 说“可安装使用”
五、反模式
- 编译过了就让 Jason 手测
- daemon/tmux probe 绿了就宣称 Mac client 连接正常
- Mac client core tests 绿了就宣称 packaged
.app 正常
- 只在浏览器里验证,却汇报 packaged app 可用
- 没退出旧 app 就直接打开新 app
- 没有运行态证据就下“无泄漏 / 无 orphan / 性能已优化”结论
- 让 Jason 帮忙补你本该先完成的基础 smoke
六、完成态汇报模板
只在闭环完成后使用:
Jason,已完成本轮自闭环:
1. 改动:
2. 静态验证:type-check/build/package 结果
3. 运行态 smoke:做了哪些真实操作
4. 证据:截图/命令输出/资源采样位置
5. 结论:哪些已验证通过,哪些仍未覆盖
6. 现在才轮到你手测的部分:
3.4 本地 package 签名授权规则
- 本地
pnpm --filter @zterm/mac package 必须默认跳过 macOS code signing:CSC_IDENTITY_AUTO_DISCOVERY=false + build.mac.identity=null。
- 禁止让
electron-builder 自动发现 distribution identity;否则每次 package 都可能触发 Keychain 授权弹窗。
- 只有正式发布/分发签名任务才允许显式启用签名 identity,并必须单独说明签名和 notarization 验证。
3.5 状态 / Alpha 汇报对账门禁
- 触发:Jason 问“今天完成了什么”“Mac 版本状态”“离 alpha 多远”“能不能手测/alpha 测试”,或上下文压缩/恢复后需要汇报 Mac 进度。
- 汇报前必须对账:
git log --oneline -- mac packages/shared .agents/skills/zterm-mac-dev
git status --short
mac/MEMORY.md
mac/task.md
mac/docs/function-map.md
mac/docs/testing/mac-desktop-workspace-test-design.md
mac/docs/alpha-readiness.md(若不存在或过期,先补齐)
mac/evidence/<date>-* 证据目录索引
- 汇报必须按证据层级拆开:已提交代码、白盒测试、packaged smoke、真实 daemon/local tmux/live UI、未验证缺口。
- 不得只根据最近 handoff、loop run、Android note 或当前聊天上下文汇报 Mac 状态;Mac 进度以 Mac docs/MEMORY/task/evidence/git 提交共同对账为准。
- 如果发现 evidence 仍是 untracked,只能报告 retention/cleanup 待决策;未经授权不得删除、移动、stage 或提交。
六、Mac dev runtime 单实例验证规则(2026-06-02)
- 调试 Electron dev app 时,同一轮只能保留一个
--remote-debugging-port 实例;新开前先用明确 PID/app-level quit 收掉旧实例,禁止多端口并发导致证据串线。
- 截图/DOM probe/输入验证必须指向同一个 CDP target、同一个 tmux session、同一个 evidence JSON;不得在多个 Electron 窗口之间交叉取证。
- 临时 CDP probe 不要往页面注入会持久影响事件链的监听器/异常代码;若注入失败导致 renderer error,必须 reload 或重启唯一实例后再验证。
- local tmux 颜色真源是
tmux capture-pane -e 的 SGR 输出;纯 capture-pane -p 只保留文本,会把 fg/bg 全部退成默认色。
- local tmux 数据真源还必须覆盖 visible pane bottom:
LocalTmuxManager 的 head/sync capture 保留 -e -p,但禁止加 -E -1;若黑盒出现 tmux/pipe 有完整尾部而 app DOM 缺尾部,先查该 capture 参数,不要在 renderer 补偿。
- local tmux TUI/alternate-screen 类刷新不得走 full-history live payload;
readSessionCapture 检测 alternate_on 后只用 bounded visible capture(-S -<paneRows>)作为当前 screen truth,避免历史帧累计成刷新延迟或旧行上移。
- Packaged multi-window smoke 不依赖
System Events 注入快捷键作为真源;优先通过正式 preload IPC / menu owner 触发 MacWindowManager.createWindow(),再用 CDP 验证 page target、renderer windowId、workspace key、quit/reopen restore。若 System Events 卡住,只中断该明确 osascript 会话,不能用它证明失败或成功。
- Packaged app 实际使用
preload.cts -> preload.cjs。凡修改 window.ztermMac bridge 或 IPC surface,必须同步更新 preload.ts 与 preload.cts,并用 packaged smoke 证明真实 preload bridge 可用;不能只看 renderer type 或 preload.ts。
- Packaged React 表单/controlled input smoke 不把直接
input.value = ... 当真源;自动化应先 focus/select 目标 input,再用 CDP Input.insertText 或等价真实输入路径触发 React state,最后点击正式 UI command。直接 setter 只可作诊断,不能作为 browse/connect/save 成功证据。
- Packaged QuickConnect/session discovery smoke 必须走真实 UI input path 和真实 daemon
list-sessions 路径:focus/select input 后用 Input.insertText 输入 host/port/token,点击正式 Discover / Save & connect;证明 discovery 不创建 runtime,Save & connect 才创建 remote runtime;只允许 dedicated marked tmux session;写入 evidence 前必须 redacted authToken / targetAuthToken / storage token 字段。
- Packaged CDP smoke helper 必须在 websocket
close/error 时 reject pending command,尤其是 Browser.close;否则数据对比已绿也会因为未 settle 的 top-level await 退出 13,并且缺少 process-after-close evidence。
- Packaged runtime A/B input isolation smoke 优先用本轮专用 tmux session +
tmux pipe-pane -o <log> 作为输入 oracle;capture-pane 对 detached cat fixture 可能不稳定,不能单独证明 app input 到达或串线。完成后用 tmux pipe-pane -t <session> 关闭观测管道,避免后台持续写日志。
- Runtime split/tab smoke 中,resize 必须同时看 DOM pane width 和 workspace record pane size;只看拖拽动作或 divider 存在不算 resize 闭环。关闭 active pane 后必须证明 renderer root 仍 mounted、workspace
activePaneId 指向现存 pane、剩余 runtime 还能输入。
- Server rail remote refresh smoke 是 read-only daemon observation:只能发
list-sessions/Refresh,允许用现有用户 sessions 做列表观测,但禁止写 input、create、kill、rename。证据必须同时证明 refresh 后 live sessions 进入 rail、workspace pane/tab 数不变、terminal stage 未自动打开 session、错误时显示 error 且 saved/open sessions 保留。
- Server rail remote open packaged smoke 必须分两阶段证明:Refresh 后只更新 live projection 且
runtimeEnsureCalls=0,explicit rail session click 后才创建 remote runtime 并渲染 dedicated marked session 输出。只能使用本轮 dedicated marked session,evidence/storage 里的 authToken / targetAuthToken 必须 redacted,结束时复核 debug port、ZTerm/Electron helper、tmux session 已精确清理。
- Disconnect/reconnect packaged smoke 必须诱发 transport owner close/error,不能用 UI Disconnect 冒充断线。local smoke-only forced close 只能挂在
--zterm-alpha-smoke 下,证据必须同时证明 active runtime error -> Reconnect -> connected、hidden runtime connect count 为 0、windowId 稳定、process-after-close* 为空或有明确解释。
- Legacy workspace cleanup closeout 不能只扫入口 import。必须同时证明旧 all-in-one source 文件物理不存在、生产源码无
ShellWorkspace 引用、architecture truth gate 锁 MAC-16-LegacyRemoval、packaged DOM 无 .shell-workspace-root / forbidden root。历史 zterm:mac:shell-workspace:v1 localStorage 残留只说明用户数据未清理,不可当作 fallback 存在或已读取的证据。
七、单 session 操作铁律(2026-06-02 新增)
7.1 禁止向任意 session 写入 input
- 绝对禁止:
tmux send-keys、send-text 到任何不在本次测试范围内的 tmux session
- 绝对禁止:用 IPC / CDP / AppleScript 向非目标 window/pane 注入按键
- 原因:会污染用户真实工作 session(如
fin、rcc、server 等已有 session)
- 触发场景:任何 input echo 验证、按键注入测试、session 列表遍历
- 正确做法:只操作本次 smoke 专用 session(如
zterm_mac_color),不碰其他 session
- 验证方式:
tmux capture-pane -p -t <session> 确认只含测试内容,无污染