从 0 重写的 v2.0.0 DSH 侧边栏插件第一阶段: - 工程骨架:tsdown 双构建(host ESM + client CJS closure)、vitest、strict TS - host 半:namespace 插件形状(name/inject/apply,无 default),minimal apply - client 半:closure factory 注册 5 个 apes:* tab 占位 + zh/en i18n - 不变量面:assert/semver/peer guard(dsh-better-sidebar >= 0.18.0) - 依赖策略:dsh-better-sidebar=peerDependency(不打包),cordis=devDependency(仅类型) - 门禁:pnpm typecheck + build + test 全绿(14 单测通过) - AGENTS.md 工程约定 + .gitignore(排除 lib/node_modules/maxkb-integration) 模板:dsh-better-sidebar。作用域锁定信息层(tasks/agents/apps/messages/profile), 不含 file/terminal/browser(归 better-sidebar)、不含 team/room(归 background-agents)。
131 lines
6.3 KiB
Markdown
131 lines
6.3 KiB
Markdown
# apes-sidebar — engineering conventions
|
|
|
|
A DSH (DeepSeek Harness) **sidebar plugin** that registers five information-layer
|
|
tabs into the `dsh-better-sidebar` host: `apes:tasks`, `apes:agents`,
|
|
`apes:apps`, `apes:messages`, `apes:profile`.
|
|
|
|
This repo is a **from-scratch v2.0.0 rewrite** of the legacy JS-bundle sidebar.
|
|
The engineering template is `dsh-better-sidebar` (TS + tsdown + host/client
|
|
split). If a convention here conflicts with `dsh-better-sidebar`, follow
|
|
better-sidebar and file a note in `docs/`.
|
|
|
|
## Stack
|
|
|
|
- **Language**: TypeScript (strict, `noUncheckedIndexedAccess`, ESM, `.ts` extension imports)
|
|
- **Build**: `tsdown` (rolldown). Dual output — `lib/index.js` + `lib/invariant.js`
|
|
(host, ESM) and `lib/client.js` (client, CJS closure factory)
|
|
- **Type check**: `tsc --noEmit -p tsconfig.json`
|
|
- **Unit tests**: `vitest` (node environment, no DOM)
|
|
- **Package manager**: `pnpm` (Node ≥ 22.13 required — `node:sqlite` + pnpm 11)
|
|
- **Peer**: `dsh-better-sidebar` ≥ 0.18.0 (host-provided, never bundled)
|
|
- **Dev-only types**: `@deepseek-ai/cordis` (types; host provides at runtime)
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
pnpm install # install deps (preinstall re-checks on build)
|
|
pnpm typecheck # tsc --noEmit (strict)
|
|
pnpm build # rm -rf lib && tsc (d.ts) && tsdown (bundles)
|
|
pnpm test # vitest run (unit only; no e2e in Phase 0)
|
|
pnpm build && pnpm test && pnpm typecheck # the full gate
|
|
```
|
|
|
|
Run the **full gate** before pushing. All three must be green.
|
|
|
|
## Layout
|
|
|
|
```
|
|
src/
|
|
index.ts # HOST entry: export name/inject/apply (namespace plugin shape)
|
|
invariant.ts # invariant surface (assert, semver, peer guard) — dep-free
|
|
context-types.ts # service face contracts (ApesContext = CordisContext & {...})
|
|
client/
|
|
index.tsx # CLIENT entry: closure factory registering apes:* tabs
|
|
locales.ts # i18n (zh default, en fallback) + useApesLocale hook
|
|
tests/
|
|
smoke.spec.ts # host plugin shape + apply + invariant surface
|
|
locales.spec.ts # i18n contract against a mock ApesLocaleService
|
|
tsdown.config.ts # dual build config (host ESM + client CJS)
|
|
vitest.config.ts
|
|
cordis.patch.yml # plugin registration into the DSH profile
|
|
```
|
|
|
|
## Hard rules
|
|
|
|
1. **Namespace plugin export shape.** `src/index.ts` MUST export
|
|
`{ name, inject, apply }` as named exports — **never** a `default` export.
|
|
A stray `default` makes the cordis `Loader.unwrapExports` discard
|
|
`name`/`inject`/`apply` and the plugin silently fails to mount.
|
|
`tests/smoke.spec.ts` pins this.
|
|
|
|
2. **Host / client split is load-bearing.**
|
|
- The **host** half (`lib/index.js`) runs in Node. It may use Node builtins.
|
|
- The **client** half (`lib/client.js`) runs in the DSH browser shell. It is
|
|
a **CJS closure factory** — `module.exports = (hostApi) => {...}`. React is
|
|
external (host-provided). Never `import` a Node builtin into `src/client/**`.
|
|
- `src/invariant.ts` is reachable from BOTH halves — it must stay
|
|
dependency-free (no Node builtins, no @deepseek-ai imports).
|
|
|
|
3. **`dsh-better-sidebar` is a peerDependency, not a bundled dep.** The tsdown
|
|
config lists it in `deps.neverBundle`. If a build inlines it, the client
|
|
bundle ships a second copy of the sidebar registry — tabs register into a
|
|
dead instance and never appear.
|
|
|
|
4. **`@deepseek-ai/cordis` is devDependency (types only).** The host provides
|
|
the real cordis context at runtime. Do NOT add cordis to `dependencies` or
|
|
to `deps.neverBundle` — that would force the client to resolve it.
|
|
|
|
5. **Tab IDs are `apes:*`.** Never `wb:*` (legacy prefix) or any better-sidebar
|
|
internal id. The five Phase 1 tabs: `apes:tasks`, `apes:agents`,
|
|
`apes:apps`, `apes:messages`, `apes:profile`.
|
|
|
|
6. **Scope lock.** This plugin is the **information layer only**:
|
|
tasks / agents / apps / messages / profile. Do NOT add file/terminal/browser
|
|
panels — those belong to `dsh-better-sidebar` itself. Do NOT add team/room
|
|
features — those belong to `dsh-background-agents`.
|
|
|
|
7. **Bilingual i18n.** Every user-facing string goes through `t(key)` in
|
|
`src/client/locales.ts` with BOTH a `zh` and an `en` entry. No raw strings in
|
|
JSX.
|
|
|
|
8. **pnpm build-script approval.** `node-pty` (a transitive runtime dep of
|
|
better-sidebar's terminal panel) needs its native build approved once:
|
|
`pnpm approve-builds` (pick `node-pty`, `y`). Without this, `pnpm build`'s
|
|
preinstall dep-check hard-fails. It is approved in the committed
|
|
`pnpm-lock.yaml` state — don't reset it.
|
|
|
|
## Testing conventions
|
|
|
|
- Unit tests run in a **node** vitest environment. No DOM, no jsdom (the
|
|
client React components are not rendered in Phase 0 — a full cordis scratch
|
|
mount smoke lands in Phase 1).
|
|
- The host `apply()` is tested against a **minimal fake context** (see
|
|
`tests/smoke.spec.ts`) — never against a live DSH shell in CI.
|
|
- The locale contract is tested against a **mock `ApesLocaleService`** (see
|
|
`tests/locales.spec.ts`).
|
|
- A test that needs the real DSH host is a **Phase 1+** concern (cordis
|
|
scratch profile); do not add such tests to Phase 0.
|
|
|
|
## Gotchas (learned, don't rediscover)
|
|
|
|
- `tsdown` 0.22 deprecated `external`/`noExternal` in favour of
|
|
`deps.neverBundle`/`deps.alwaysBundle`. **`external` and `deps.neverBundle`
|
|
cannot coexist** — using both throws at build. Use only the `deps` API.
|
|
- `allowImportingTsExtensions` (for `.ts`-extension imports) is set in
|
|
`tsconfig.json`. `tsconfig.build.json` inherits it but emits `d.ts`, which is
|
|
compatible (tsdown handles the strip). If `tsc` build complains, the
|
|
declaration emit needs `emitDeclarationOnly` + `noEmit: false` — that is the
|
|
current `tsconfig.build.json` setup.
|
|
- `node-pty` build approval is **per-machine state** in `pnpm-lock.yaml` + the
|
|
pnpm store. A fresh `pnpm install` on a clean checkout will re-prompt.
|
|
- The workspace path contains spaces and Chinese characters
|
|
(`文稿 - figmarLite的MacBook Pro/...`) — always quote it in shell commands.
|
|
|
|
## Phase status
|
|
|
|
- **Phase 0 (done)**: engineering skeleton, host/client split, i18n, invariant
|
|
surface, build gate green, unit smoke green.
|
|
- **Phase 1 (next)**: real tab registration for the five `apes:*` panels against
|
|
a mock host registry, plus a cordis scratch mount smoke.
|
|
- **Phase 2 (later)**: badge/reminder counts, auto-collapse, per-tab state.
|