从 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)。
6.3 KiB
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,.tsextension imports) - Build:
tsdown(rolldown). Dual output —lib/index.js+lib/invariant.js(host, ESM) andlib/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
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
-
Namespace plugin export shape.
src/index.tsMUST export{ name, inject, apply }as named exports — never adefaultexport. A straydefaultmakes the cordisLoader.unwrapExportsdiscardname/inject/applyand the plugin silently fails to mount.tests/smoke.spec.tspins this. -
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). Neverimporta Node builtin intosrc/client/**. src/invariant.tsis reachable from BOTH halves — it must stay dependency-free (no Node builtins, no @deepseek-ai imports).
- The host half (
-
dsh-better-sidebaris a peerDependency, not a bundled dep. The tsdown config lists it indeps.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. -
@deepseek-ai/cordisis devDependency (types only). The host provides the real cordis context at runtime. Do NOT add cordis todependenciesor todeps.neverBundle— that would force the client to resolve it. -
Tab IDs are
apes:*. Neverwb:*(legacy prefix) or any better-sidebar internal id. The five Phase 1 tabs:apes:tasks,apes:agents,apes:apps,apes:messages,apes:profile. -
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-sidebaritself. Do NOT add team/room features — those belong todsh-background-agents. -
Bilingual i18n. Every user-facing string goes through
t(key)insrc/client/locales.tswith BOTH azhand anenentry. No raw strings in JSX. -
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(picknode-pty,y). Without this,pnpm build's preinstall dep-check hard-fails. It is approved in the committedpnpm-lock.yamlstate — 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 (seetests/smoke.spec.ts) — never against a live DSH shell in CI. - The locale contract is tested against a mock
ApesLocaleService(seetests/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)
tsdown0.22 deprecatedexternal/noExternalin favour ofdeps.neverBundle/deps.alwaysBundle.externalanddeps.neverBundlecannot coexist — using both throws at build. Use only thedepsAPI.allowImportingTsExtensions(for.ts-extension imports) is set intsconfig.json.tsconfig.build.jsoninherits it but emitsd.ts, which is compatible (tsdown handles the strip). Iftscbuild complains, the declaration emit needsemitDeclarationOnly+noEmit: false— that is the currenttsconfig.build.jsonsetup.node-ptybuild approval is per-machine state inpnpm-lock.yaml+ the pnpm store. A freshpnpm installon 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.