Files
apes-sidebar/docs/plans/2026-09-11-file-icons-and-tab-glyphs.md
T

10 KiB
Raw Blame History

文件图标与彩色 Tab 图标(v0.19.1,#531 + #594 合并后的重做)

日期:2026-09-11 分支:feat/dsh-0.1.5-rc.2(并入 PR #626)

背景与决策

#611 希望文件树按类型显示图标(「仿照官方做法」),#429(@fenter)为此做了 563 条彩色图标数据,落成两个叠着的 PR:

  • #531(feat/file-icons-api,base=main):文件树/文件 tab 按扩展名分图标 + 对外 registerFileIcon API;
  • #594(feat/file-icon-theme,base=#531 分支):在其上叠「内置单色 / 彩色品牌图标」可选主题,彩色数据做成懒加载 chunk(255 kB)+ 设置页开关。

两者在 2026-09-08 都绿,但都基于 v0.18.1、落后 main 55 个 commit,且与 DSH 0.1.5-rc.2 的一个新事实冲突:rc.2 的 ui-primitives 已经导出官方文件类型图形 FileTypeIcon / CodeFileIcon(48 类全彩代码/配置图形 + 12 类类目色板图形 + classifyFileType 分类器)。也就是 #611 想要的东西上游自己做了。

用户决策(2026-09-11):两个 PR 都合、并进 v0.19.1 一个 PR;文件图标改用宿主官方 FileTypeIcon;内置 tab 图标彩色 + 原生右侧栏芯片也加图标;删掉主题开关,彩色即唯一行为。

上游事实(实测,非推断)

事实 证据
宿主导出官方图形 @deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.2 的 lib/types/index.d.ts 导出 FileTypeIcon、classifyFileType、fileExtension、CodeFileIcon、isCodeFileType 与 CodeFileType 类型;FileTypeIcon 声明 size 默认 28 并透传给内部 FileGlyph/CodeFileIcon(14px 树行可用)
全彩图形保留自己的调色板 CodeFileIcon.d.ts 的文档原话「its identifying palette intact」;kind:'folder' 落宿主的 IconFolderClose16(单色,随 currentColor)
分类器不返回文件夹 classifyFileType 只走 classifyCodeFileType ?? NAME_TYPES ?? EXTENSION_TYPES ?? 'other','folder' 只能由调用方以 kind 显式指定
官方图形在 rc.1 就有 npm pack @deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.1 的 tarball 内含 lib/FileTypeIcon.module.css 与 lib/types/{FileTypeIcon,CodeFileIcon,code-file-types}.d.ts(rc.2 只是把 SVG 数据拆进 code-file-icon-artwork.ts)
原生 tab 定义没有 icon 字段 dsh-client-ui-sidebar-right 的 SidebarRightTabDefinition 只有 id/kind/patterns/priority/canOpen/title/guide;icon 只在 SidebarRightGuideEntry 上
但 title 槽就是芯片内容 宿主 contract/slots.d.ts:'sidebar.right.pane.tab.title' —— 「A tab's title as its chip (and a floating panel's header) shows it」

合并(3 处冲突,均为注释/列表并集)

文件 冲突 解法
src/client/service.ts ① import 区(main 保留 extOf,PR 侧已改成 baseName, extOf + 自己的 isNarrowWidth)② SIDEBAR_FEATURES 注释与列表(main 写「v0.19.0 删除了 floatWindows」,PR 侧在旧清单上加 'fileIcons') ① 取 baseName, extOf:isNarrowWidth 属于 PR 自己的「新建会话按视口折叠」逻辑,main 已把那套 seeding 重写掉,合并后该 import 是死代码(实测 grep 确认后删除)。② 并集:保留「已移除 floatWindows」措辞 + 追加 'fileIcons'
src/client/Sidebar.tsx tabIconOf 上方注释(两侧各改一句) 并集:保留 main 的「The tab icon from the tab-type registry.」并补 PR 侧「带 path 的编辑器 tab 显示文件图标」的说明
docs/external-plugin-guide.md features 清单注释 并集:main 的新措辞(只增不删,唯一例外 v0.19.0 删除 floatWindows)+ 'fileIcons'

#594 的其余冲突(tests/plugin-shape.spec.ts、tests/prefs.spec.ts)取本线一侧:main 已删除这两个文件里的 store-seeding 用例并重写了 schema 断言(SIDEBAR_PREFS_DEFAULTS 解构),PR 侧重新加回的块描述的是 main 不再存在的行为——把旧块带回来只会测到死代码。

重做(把 #594 的彩色实现换成宿主官方图形)

删除

  • src/client/chunks/file-icons.tsx(563 条数据)、src/client/file-icon-theme.ts(chunk loader)、tests/file-icon-theme.spec.tsx;
  • fileIconTheme pref 三处(prefs-shared.ts / src/config.ts / src/client/prefs.ts)、设置页「文件图标」select 行、20 份词典里的 6 个 settingsFileIconTheme* key(共 126 行);
  • chunk 注册三处(src/bundle-route.ts 的 CHUNK_NAMES、src/client/chunk-loader.ts 的 ChunkName、tsdown.config.ts 的 CHUNKS 与 react-icons alias)与对应测试期望(bundle-route.spec.ts / manifest-consistency.spec.ts);
  • src/client/index.tsx 里驱动主题 loader 的 effect。

新增/改写

位置 内容
src/client/file-icons.tsx builtinFileIcon(path, size) → <FileTypeIcon path size />;builtinFolderIcon → <FileTypeIcon kind="folder" size />(宿主只有一个文件夹图形,开/合两态不另画);fallbackFileIcon(size) → <FileTypeIcon kind="other" size />
src/client/service.ts 的 fileIcon 链 具体注册 → catch-all 全局默认 → 宿主图形(原先 built-in map 排在 catch-all 之前)
src/client/builtins/tab-icons.tsx + .module.css 六个内置类型 + diff 视图的彩色 glyph;颜色全部 var(--dsw-alias-*)(files=brand、changes=success、tasks=warn、sidechat/browser=business、terminal=label-primary,各带 fallback 链)
src/client/native/tab-adapter.tsx 的 NativeTabTitle 渲染 [glyph][title]:编辑器 tab 带 path 时用 service.fileIcon(path, 14),其余用 descriptor.icon(14);glyph aria-hidden;NativeTitleInjected 增加 service 与 descriptorId,registerSlots 的 title 注入同步
src/client/sidebar.module.css 新增 .chipIcon(芯片 glyph 的排版,颜色继承)
tests/theme.spec.ts 豁免段整体删除,改为三条守护:图标模块零颜色字面量、tab-icons.module.css 里每条 color 都解析到 var(--dsw-、src/client/chunks/* 不含 6 位颜色字面量(图标数据不得再做回 chunk)
tests/file-icons.spec.tsx 内置断言从 codicon 身份改为宿主 FileTypeIcon;新增「path/size 透传」「文件夹用 kind:'folder'」;catch-all 用例改写为「注册了 catch-all 就接管全部未具体命中的行」
tests/native-surface.spec.ts 新增 3 例芯片 glyph:descriptor glyph 在标题前、编辑器 tab 用文件图标而非类型图标、类型注销后只剩标题

语义后果(必须写进文档)

宿主分类器覆盖任意路径,因此「插件自己已经能画这个扩展名」不再是拦住 catch-all 的理由:注册 exts: [] 的插件现在会接管所有未具体命中的文件行(原先会被内置 map 拦下)。旧版彩色主题那种「整表注册、靠内置 map 兜底」的形态不再成立——只想补几个类型的插件用 exts/names。已写进 指南 §7 与 AGENTS.md §5。

验证

已执行(macOS arm64,DSH 0.1.5-rc.2 钉版)

  • pnpm typecheck / pnpm lint / pnpm check:consumer-types 全绿;
  • pnpm test = 127 files / 1343 passed / 9 skipped(v0.19.1 基线 126/1312 + 合并与新增的 31 例,失败数 0;返工后 +1 例 glyph 身份断言);
  • pnpm build:lib/client.js 0.85 → 0.86 MB(+10 kB),没有 lib/client-file-icons.js;核心 bundle 内不含图标数据(只有 6 个被引用的 glyph 组件);
  • 真机挂载冒烟(DSH_CMD 指向 @deepseek-ai/dsh@0.1.5-rc.2):pnpm test:mount 7 passed(含 guide 深扫、tab 体填充断言、mermaid/README 预览、sidechat 路由),pnpm test:mount:aggregate 通过。

真机反馈的两处返工(用户人工验证后)

反馈 结论与改动
「任务管理」图标不对——该页是 subagent + 后台 jobs,不是 todo VscTasklist(清单)→ VscLayers(多层堆叠 = 后台在跑的工作)。tests/builtins.spec.ts 用 glyph 身份把这条读法钉住
终端图标「非常怪」 根因是观感权重:VscTerminal 是整套里最宽的实心矩形,在 14px 下比邻座重。只缩小一档(0.85 × size,下限 10px)——形状与绘制方式保持图标集原样
追加反馈:上一版给全部 tab glyph 加的 currentColor 发丝描边(strokeWidth: 1 + non-scaling-stroke)「太粗」 整体回退:不描边,glyph 只用包裹类的令牌着色。“描边”是我对「怪」的过度解读,反馈的本意只是终端尺寸。tests/builtins.spec.ts 现在同时钉住「终端尺寸更小」与「glyph 不带 style 覆盖」,防止描边再溜回来

真实 CI(已收敛)

ci / plugin-mount 每轮都绿;ci-windows 的 worker 回收竞态在 --maxWorkers=1(单 fork、零回收)下连续三次全绿:run 34575721734、34576146970、34576493202,均 127 files / 0 errors。两轮迭代(--maxWorkers=2 被真实结果证否)的取证见 rc.2 计划 §B。

待验证

  • 3080 人工验证(返工后第二轮):树行与编辑器 tab 的彩色图标、指南六行彩色、原生右侧栏芯片图标、14px 下官方 48 类图形的可读性、以及这次返工后的任务/终端两个图标(用户此前明确选择用官方图形;若细节折损不可接受,回退方案是「树行用官方图形、编辑器 tab 保留插件 glyph 着色」,改动只在 file-icons.tsx 一处)。

不做

  • 不改 DSH 源码;不把 #429 的 563 条数据搬回仓库(要走按需加载也应按懒加载 chunk 走,而不是常驻数据);
  • 不为彩色图标新增皮肤契约豁免(插件侧零硬编码颜色);
  • 不恢复 floatWindows;不动已合入的 rc.2 基线结论与 CI 修复。