feat: 服务化改造 — ctx.betterSidebar 注册表(registerTab/registerFileViewer),内置 6 tab + 6 viewer 迁移到 registry,v0.4.0

- 新增 src/client/service.ts: BetterSidebarService 接口 + createBetterSidebarService 工厂
  - registerTab/registerFileViewer 返回 disposer(HMR-safe,挂到 cordis fiber)
  - matchFileViewer 三段匹配: priority desc → detect(内容嗅探) → exts
  - openTab 走 descriptor.dedupeKey 统一去重(替代 isSingle/editor-path/diff-id 三分支)
  - createTab 回调支持自定义 tab 创建(terminal 的 nextTerminal 递增)
- 新增 src/client/builtins.tsx: 6 内置 tab + 6 内置 viewer 自我注册(吃狗粮)
  - tab: editor(hidden) / explorer / git / subagent / terminal / diff(hidden)
  - viewer: image / pdf / docx / xlsx / pptx / binary-download
  - code/markdown 保持 EditorView 内联兜底(matchFileViewer 返回 undefined 时走 fsRead+CodeMirror)
- 新增 src/client/OrphanedTab.tsx: 未注册 type 的占位组件(插件未加载时降级)
- 改造 src/client/index.tsx: ctx.provide('betterSidebar') + registerBuiltins
- 改造 src/client/Sidebar.tsx: TabContent/buildNewTabOptions/onNewTab 查表替代 switch
- 改造 src/client/EditorView.tsx: 预览路由查 matchFileViewer 替代 if/else 链
- 改造 src/client/state.ts: TabType 联合字面量→string,sanitizeNode 接受所有 string type
- 改造 src/context-types.ts: declare module 'cordis' 加 betterSidebar 字段
- 改造 src/index.ts: re-export Context + 服务类型(consumer import type 触发 augmentation)
- package.json: 加 ./client/service exports 子路径,version 0.4.0
- tests/service.spec.ts: 12 个新测试覆盖 register/dispose/matchFileViewer/dedupe/createTab
- tests/unit.spec.ts: sanitizeState 测试更新(未知 type 不再被拒绝,渲染 OrphanedTab)
This commit is contained in:
HuanLinOTO
2026-08-11 00:31:23 +08:00
parent fbebcf5138
commit 291158a3ba
15 changed files with 878 additions and 156 deletions
+16 -8
View File
@@ -11,7 +11,18 @@
*/
import { SIDEBAR_PREFS_DEFAULTS, type SidebarPrefs } from '../prefs-shared.ts'
export type TabType = 'explorer' | 'git' | 'editor' | 'terminal' | 'subagent' | 'diff'
/**
* Tab type identifier. Builtins use the strings below; external plugins
* register their own ids (e.g. `'my-plugin:db'`) through the sidebar
* service. Kept as `string` so the registry is open; {@link SIDEBAR_BUILTIN_TAB_TYPES}
* lists the builtins for back-compat checks.
*/
export type TabType = string
/** The tab type ids the plugin itself registers (see {@link ./builtins.ts}). */
export const SIDEBAR_BUILTIN_TAB_TYPES = [
'explorer', 'git', 'editor', 'terminal', 'subagent', 'diff',
] as readonly string[]
/** What a diff tab shows: a worktree/index change of one path, or one commit's full patch. */
export type SidebarDiffRef =
@@ -622,13 +633,10 @@ function sanitizeNode(node: unknown, seen: Set<string>, reid: Map<string, string
droppedDiff = true
continue
}
if (
candidate.type !== 'explorer' && candidate.type !== 'git'
&& candidate.type !== 'editor' && candidate.type !== 'terminal'
&& candidate.type !== 'subagent'
) {
return undefined
}
// Tab types are an open set (external plugins register their own);
// accept any string type here — an unregistered type renders an
// <OrphanedTab/> at view time and recovers if its plugin loads later.
if (typeof candidate.type !== 'string') return undefined
tabs.push({
id: candidate.id,
type: candidate.type,