feat(file-icon-theme): 可选彩色图标主题接线(懒加载 chunk + 设置开关 + i18n)

- 新 chunk `file-icons`(`lib/client-file-icons.js`,255 kB / gzip 90 kB):把数据表
  注册成 `registerFileIcon` 描述符——按 glyph+color 归并,563 条规则 → 约 250 条注册;
  目录规则只走 `folderNames`(只认领列出的目录,未列出的仍用内置 glyph)。
- `src/client/file-icon-theme.ts`:pref → loadChunk → 注册/注销,generation 守卫
  丢弃「切换中途落地」的加载;chunk 拉取失败只 log,保持内置 glyph。
- pref `fileIconTheme`(默认 `'builtin'`,未知值按 builtin 处理)+ 设置页
  「文件 → 文件图标」下拉(内置单色 / 彩色品牌图标)。
- chunk 名同步四处(chunk-loader / bundle-route / tsdown / manifest-consistency)+
  `bundle-route.spec`;tsdown 为该 chunk 加 react-icons ESM alias——否则整包打进
  6.4 MB,alias 后 255 kB。
- i18n:6 个新 key 镜像到全部 19 份词典(zh/en 之外),locale 键集守护通过。
- 皮肤契约:指南 §12 增豁免条款(品牌色是内容非 chrome,仅限该懒加载 chunk),
  AGENTS §5 同步;`tests/theme.spec.ts` 新增边界用例(核心图标模块零颜色字面量、
  核心不静态 import 该 chunk)。
- 测试:`tests/file-icon-theme.spec.tsx` 7 例(loader 六态 + 真实数据三表注册);
  prefs 新增默认值/解析用例;builtins / prefs / plugin-shape / smoke 夹具补字段。
- 全量 `pnpm test` 失败集与 main 基线逐条一致(5 文件 / 26 例预存环境失败),
  新增用例全绿;typecheck / lint / build / check:consumer-types 全通过。
This commit is contained in:
Menghuan1918
2026-09-08 21:43:28 +08:00
parent 8909455df2
commit 162c16ec32
41 changed files with 603 additions and 16 deletions
+38
View File
@@ -13,6 +13,8 @@
*/
// @vitest-environment jsdom
import { afterEach, describe, expect, it } from 'vitest'
import { readFileSync, readdirSync } from 'node:fs'
import { resolve } from 'node:path'
import { colorAlpha, effectiveTokenValue, tokenValue } from '../src/client/theme.ts'
afterEach(() => {
@@ -80,3 +82,39 @@ describe('effectiveTokenValue', () => {
expect(effectiveTokenValue('--probe')).toBe('')
})
})
/**
* Skin contract (guide §12): every visual value rides a `--dsw-alias-*` /
* `--dsw-font-*` / `--ds-*` token. The ONE documented exception is the
* optional colored icon theme: brand colors are identity, not chrome, so
* they are hardcoded — but only inside the lazy `file-icons` chunk, which
* the core bundle must never import. This guards that boundary.
*/
// jsdom has no file:// import.meta.url; vitest runs from the repo root.
const ROOT = process.cwd()
describe('skin contract boundary for hardcoded icon colors', () => {
const coreIconModules = ['src/client/file-icons.tsx', 'src/client/file-icon-theme.ts']
it('the core icon modules carry no color literals', () => {
for (const file of coreIconModules) {
const source = readFileSync(resolve(ROOT, file), 'utf8')
expect(source, file).not.toMatch(/#[0-9a-fA-F]{3,8}\b/)
expect(source, file).not.toMatch(/\brgba?\(/)
}
})
it('only the lazy chunk may carry the colored dataset, and the core never imports it statically', () => {
const chunk = readFileSync(resolve(ROOT, 'src/client/chunks/file-icons.tsx'), 'utf8')
expect(chunk).toMatch(/#[0-9a-fA-F]{6}/)
const clientDir = resolve(ROOT, 'src/client')
const offenders: string[] = []
for (const entry of readdirSync(clientDir, { recursive: true, withFileTypes: true })) {
if (!entry.isFile() || !/\.tsx?$/.test(entry.name)) continue
const path = resolve(entry.parentPath, entry.name)
if (path.includes('/chunks/')) continue
if (/from '[^']*chunks\/file-icons/.test(readFileSync(path, 'utf8'))) offenders.push(path)
}
expect(offenders).toEqual([])
})
})