C6 removed apes:tasks as a duplicate of the host session panel; in practice the two answer different questions (host panel = switch workspaces, plugin tab = who is running right now), so the tab is restored and the info layer goes back to five types. tasks: live session list (current / running / subagent markers) + showInactive toggle via pluginSettings['apes:tasks']; messages: placeholder that states the host has no message feed yet; both get badge counters, and the whole layer now honours the visible gate (auto-collapse) so hidden tabs stop waking on session events. zh/en/ja dictionaries in sync (AGENTS 5); wiki 02/03/07 reverted from three to five tabs; decision record in docs/plans/2026-10-11-apes-tab-revival.md.
9.3 KiB
9.3 KiB
07 · 开发与测试
面向改代码的人(与 agent)。先读 02-系统架构 §六「关键不变量」, 再动手改,能省掉大部分回退。
一、工程约定
| 项 | 约定 |
|---|---|
| 语言 | TypeScript strict(含 noUncheckedIndexedAccess)、ESM、导入写 .ts 扩展名 |
| 构建 | tsdown(rolldown),host/client 双产物;懒加载 chunk 走 src/client/chunks/ |
| 包管理 | pnpm(11.x);pnpm install 会触发 prepare 构建 |
| 测试 | vitest;默认 node 环境,需要 DOM 的 spec 用 // @vitest-environment jsdom |
| 宿主 peer | dsh-better-sidebar(本仓库已改名)永不打包,必须留在 peer/neverBundle 一侧 |
| 命名 | 信息层 tab id 前缀 apes:;插件名 dsh-apes-sidebar |
| 颜色 | 只用 --dsw-alias-* 设计令牌(var(--token, <回退>)),不写死色值 |
二、目录结构(main 实况)
src/
index.ts HOST 入口:name / inject / apply(ctx, config),路由与围栏装配
invariant.ts 零依赖不变量面(host/client 共用)
context-types.ts cordis 服务面契约(ctx.sessions / sidebarRight / …)
trust-fence.ts 请求信任围栏(Host 头 / 令牌)
html-route.ts /sidebar/html 路由(路径围栏 + 体积上限)
config.ts 挂载行配置 schema 与默认值(readLimit / mediaLimit / …)
fs-*.ts git.ts … 文件系统、Git、会话、任务、终端等 host 能力
client/
index.tsx CLIENT 入口(apply):装配 store/service/native/apes
service.ts ctx.betterSidebar 服务实现(对外 API + 能力清单)
state.ts 侧边栏状态存储(store)
native/ 原生右侧栏适配(tab 记录、tab 体、tab 芯片标题)
builtins/ 内置面板:editor / git / subagent / sidechat / terminal / browser + 预览器
sidebar/ 自绘底部工作台与开合控件
apes/ 信息层五 tab(本 fork 增量:tabs.tsx / icons.tsx / locales.ts)
changes/ diff/ 文件变动与 diff 视图
chunks/ 懒加载 chunk:editor / locale / mermaid / terminal
locales*.ts i18n(`locales.ts` + 19 个语言覆盖文件)
*.module.css 组件样式(含 sidebar.module.css:面板宿主层契约)
tests/ vitest specs(131 个文件,含 e2e/ 的挂载脚本型用例)
scripts/ install / switch / e2e 挂载 / 发布辅助
docs/ external-plugin-guide.md(对外契约)+ plans/(上游历史设计)+ wiki/(本百科源文件)
三、门禁(提交前必跑)
pnpm typecheck # tsc --noEmit(strict)
pnpm build # rm -rf lib && tsc(d.ts) && tsdown
pnpm test # vitest run
当前基线(本次会话实测):main = 131 文件 / 1367 通过 + 9 skip;
sync/v0.24.1 = 141 文件 / 1515 通过 + 9 skip。全量 test 约 3–4 分钟。
任何一条不过就不要提交;三条全绿才算完成。
四、测试体系(按层次)
| 层次 | 环境 | 手法 | 代表 spec |
|---|---|---|---|
| 宿主插件形状 / 对外契约 | node | 断言导出面与语义(含与 package.json 的版本一致性) |
smoke.spec.ts · plugin-shape.spec.ts · service.spec.ts · consumer-types.ts |
| 原生侧边栏适配 | node | 用「类型为真、运行时为假」的 mock 注册表 | native-surface.spec.ts |
| 面板宿主与层叠契约 | node + jsdom | CSS 断言 + DOM 挂载点解析 | panel-host-css.spec.ts · panel-host-parent.spec.ts |
| 预览交付 | node + jsdom | 纯逻辑(<base> 注入)+ 客户端挂载(stub fetch,createRoot + act) |
html-preview.spec.ts · html-preview-frame.spec.tsx |
| 沙箱契约 | node | 对渲染结果做字符串断言(服务端渲染把 srcDoc 输出为 camelCase srcDoc="",与客户端设置的真实属性 srcdoc 不同 —— 断言方式随之不同) |
sandbox-views.spec.tsx |
| 信息层 tab | node | mock ctx.sessions.list 与投影 |
apes-tabs.spec.ts |
| 桌面环境探测 | node | stub 全局(URL stamp / preload 标记) | desktop-env.spec.ts |
约定:
- 不引入 jsdom 到默认环境:需要 DOM 的 spec 在文件头写
// @vitest-environment jsdom。 - 不依赖真实 DSH 宿主:需要真宿主的用例归到
tests/e2e/(脚本型挂载),不进单元门禁。 - 新增「契约」必须同时新增 pin 它的 spec(例如本文档提到的 25 层级、沙箱令牌、
srcDoc交付方式, 都有对应断言)。改契约 = 改代码 + 改 spec,两者必须一起提交。
五、扩展点(对外服务)
其他插件通过 ctx.betterSidebar 接入:
// 注册一个 tab 类型
await ctx.betterSidebar.registerTab(descriptor)
// 注册一个文件预览器
await ctx.betterSidebar.registerFileViewer(viewer)
- 服务按能力清单做前向兼容门控(能力只增不减),消费方应据此判断可用性而非版本号比较。
- 完整契约、字段表与示例见仓库内 docs/external-plugin-guide.md。
- 改服务面时:只能新增;删除或改语义需同步更新能力清单、指南与本百科。
六、上游同步:测量式 re-base(本项目的方法论)
不 merge 上游。上游每个版本差距可达数百文件,直接合并会引入无关变更并放大冲突面; 本项目改用「先测量、再重建、后补增量」:
步骤
① 测出自己的增量(把改名归一化掉)
# 对每个变更文件:取出两边 blob,把改名归一化后比较
old = git('show', f'{base}:{path}'); new = git('show', f'HEAD:{path}')
rename_only = old.replace('dsh-better-sidebar', 'dsh-apes-sidebar') == new
→ 得到三张清单:仅改名(自动处理)/ 真改动 / 新增。数量级参考:v0.19.1→v0.24.1 为 78 / 11 / 10。
② 确定改名的精确范围(不要重新推断)
# 仍保留上游名字的文件 = 有意保留(本仓库为 README.md、package.json、docs/plans/*)
comm -12 <(git grep -l 'dsh-better-sidebar' <旧基线> | cut -d: -f2 | sort) \
<(git grep -l 'dsh-better-sidebar' HEAD | cut -d: -f2 | sort)
其余文件只做子串替换 dsh-better-sidebar → dsh-apes-sidebar;camelCase 服务名 betterSidebar 必须保留。
③ 造一个"改名归一化基座",让增量与改名解耦
git checkout -B tmp/norm-<旧基线> <旧基线 tag>
# → 套同一口径改名 → commit
git diff tmp/norm-<旧基线> HEAD -- . ':(exclude)pnpm-lock.yaml' > /tmp/apes-delta.patch
# 校验:这条 diff 的文件清单必须恰好等于 ① 的「真改动 + 新增」
④ 在新基线上重建 + 三点合并
git checkout -B sync/<新基线 tag> <新基线 tag>
# → 套同一口径改名 → commit("rename only")
git apply -3 /tmp/apes-delta.patch # 冲突会以 <<<<<<< ours / >>>>>>> theirs 留存(命令本身不失败)
⑤ 逐条判读冲突(只有五种形态)
| 形态 | 判定 |
|---|---|
| 版本/身份 JSON | 取新基线侧,再改成本 fork 的版本;两侧各自新增的元数据取并集(依赖、files 条目、forkedFrom 块) |
| 上游搬走了某个 import | 取上游的新路径,保留本 fork 的新 import |
| 上游新环境里的注册块 | 保留本 fork 的块,放进上游的新上下文 |
| 同一函数两边都定义(上游上移/下移了它) | 删除本 fork 那份;用 grep -c 'const <name>' 断言只剩 1 处 |
| 长文档(README) | 整体取上游原文,再机械套上本 fork 的两处增量:顶部 fork notice + 包名替换(跳过 github.com/<上游 org> 与徽章域名行) |
⑥ 迁移检查(API 漂移)+ 门禁
- 先解完所有冲突标记再
pnpm install(prepare: tsdown会因为语法错误直接失败), 并删掉 lockfile 重新生成(不要把旧 lockfile 带过来)。 - 逐条跑
pnpm typecheck修类型漂移。经验:漂移通常集中在本 fork 自己新增的模块里。 上游会在context-types.ts的注释里写清迁移路径(例:0.1.6 的subagentsByParent→ 0.1.7 的projectionsBySession[id].values.subagentCatalog,list.current被删除),照注释做即可。 - 最后跑门禁三连,并且用
grep核对关键标记(tab id、srcDoc、dsh-desktop-content-viewport等)。
⑦ 收尾(纪律)
- 安全锚点:
backup/pre-sync-<新基线>分支 +apes-v<版本>tag,推送远端。 - 不要把新线同步进正在使用的 profile:peer 范围可能已跨 minor,同步过去只会「静默失效」, 同时把本机环境弄脏。保持 profile 在已验证的线上。
七、提交与分支纪律
- 提交信息用
type(scope): 摘要(feat/fix/chore/docs/refactor),正文说明为什么与验证方式。 - 一个提交一件事;re-base 类改动拆成「rename only」+「增量移植」两个提交,便于回溯与回滚。
- 分支命名:
sync/<上游 tag>(跟随上游)、backup/pre-sync-<基线>(锚点)、其余按需。 - 提交前:门禁三连 + 关键标记 grep + (若涉及部署)提醒 profile 重装与完整重启。