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

93 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文件图标与彩色 Tab 图标(v0.19.1,#531 + #594 合并后的重做)
日期:2026-09-11 分支:`feat/dsh-0.1.5-rc.2`(并入 PR [#626](https://github.com/omdsh-dev/DSH-better-sidebar/pull/626))
## 背景与决策
[#611](https://github.com/omdsh-dev/DSH-better-sidebar/issues/611) 希望文件树按类型显示图标(「仿照官方做法」),[#429](https://github.com/omdsh-dev/DSH-better-sidebar/pull/429)(@fenter)为此做了 563 条彩色图标数据,落成两个叠着的 PR:
- [#531](https://github.com/omdsh-dev/DSH-better-sidebar/pull/531)(`feat/file-icons-api`,base=main):文件树/文件 tab 按扩展名分图标 + 对外 `registerFileIcon` API;
- [#594](https://github.com/omdsh-dev/DSH-better-sidebar/pull/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](../external-plugin-guide.md) 与 [AGENTS.md](../../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](https://github.com/omdsh-dev/DSH-better-sidebar/actions/runs/34575721734)、[34576146970](https://github.com/omdsh-dev/DSH-better-sidebar/actions/runs/34576146970)、[34576493202](https://github.com/omdsh-dev/DSH-better-sidebar/actions/runs/34576493202),均 **127 files / 0 errors**。两轮迭代(`--maxWorkers=2` 被真实结果证否)的取证见 [rc.2 计划 §B](./2026-09-10-dsh-0.1.5-rc.2-adaptation.md)。
**待验证**
- 3080 人工验证(返工后第二轮):树行与编辑器 tab 的彩色图标、指南六行彩色、原生右侧栏芯片图标、14px 下官方 48 类图形的可读性、以及这次返工后的任务/终端两个图标(用户此前明确选择用官方图形;若细节折损不可接受,回退方案是「树行用官方图形、编辑器 tab 保留插件 glyph 着色」,改动只在 `file-icons.tsx` 一处)。
## 不做
- 不改 DSH 源码;不把 #429 的 563 条数据搬回仓库(要走按需加载也应按懒加载 chunk 走,而不是常驻数据);
- 不为彩色图标新增皮肤契约豁免(插件侧零硬编码颜色);
- 不恢复 `floatWindows`;不动已合入的 rc.2 基线结论与 CI 修复。