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.
165 lines
9.3 KiB
Markdown
165 lines
9.3 KiB
Markdown
# 07 · 开发与测试
|
||
|
||
> 面向改代码的人(与 agent)。**先读 [02-系统架构](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/(本百科源文件)
|
||
```
|
||
|
||
## 三、门禁(提交前必跑)
|
||
|
||
```bash
|
||
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` 接入:
|
||
|
||
```ts
|
||
// 注册一个 tab 类型
|
||
await ctx.betterSidebar.registerTab(descriptor)
|
||
// 注册一个文件预览器
|
||
await ctx.betterSidebar.registerFileViewer(viewer)
|
||
```
|
||
|
||
- 服务按**能力清单**做前向兼容门控(能力只增不减),消费方应据此判断可用性而非版本号比较。
|
||
- 完整契约、字段表与示例见仓库内 [docs/external-plugin-guide.md](../blob/main/docs/external-plugin-guide.md)。
|
||
- 改服务面时:**只能新增**;删除或改语义需同步更新能力清单、指南与本百科。
|
||
|
||
## 六、上游同步:测量式 re-base(本项目的方法论)
|
||
|
||
**不 merge 上游**。上游每个版本差距可达数百文件,直接合并会引入无关变更并放大冲突面;
|
||
本项目改用「先测量、再重建、后补增量」:
|
||
|
||
### 步骤
|
||
|
||
**① 测出自己的增量(把改名归一化掉)**
|
||
|
||
```python
|
||
# 对每个变更文件:取出两边 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。
|
||
|
||
**② 确定改名的精确范围(不要重新推断)**
|
||
|
||
```bash
|
||
# 仍保留上游名字的文件 = 有意保留(本仓库为 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` 必须保留**。
|
||
|
||
**③ 造一个"改名归一化基座",让增量与改名解耦**
|
||
|
||
```bash
|
||
git checkout -B tmp/norm-<旧基线> <旧基线 tag>
|
||
# → 套同一口径改名 → commit
|
||
git diff tmp/norm-<旧基线> HEAD -- . ':(exclude)pnpm-lock.yaml' > /tmp/apes-delta.patch
|
||
# 校验:这条 diff 的文件清单必须恰好等于 ① 的「真改动 + 新增」
|
||
```
|
||
|
||
**④ 在新基线上重建 + 三点合并**
|
||
|
||
```bash
|
||
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 重装与完整重启。
|