Files
2026-02-10 11:39:33 +08:00

161 lines
6.2 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.
# width-measurement任务清单
> 对应计划:`spec_kit/Text Wrap/modules/width-measurement/plan.md`
>
> 状态含义:`[ ]` 未完成,`[x]` 已完成。
> 执行完本清单后,需要在 `spec_kit/overview.md` 的 `Text Wrap` 条目下补充“已完成编码/任务执行完毕”的标记(见最后一节)。
---
## 0. 任务标记规则
- 用勾选框标记执行状态:
- `[ ]` 未完成
- `[x]` 已完成
- 每个任务必须可独立验收(有明确产出与检查方式)。
- 所有代码注释必须为简体中文,并把“缓存 key / 降级语义 / 报错口径”写死,避免后续模块漂移。
---
## 1. 前置检查(依赖与约束确认)
- [x] 1.1 确认 `availableWidth` 由设备侧传入(本模块不内置 widgetProfiles
- **验收**:在本模块实现中不引入任何固定宽度常量表;仅消费上层传入的宽度与 profile。
- [x] 1.2 确认 APP 测量方案采用成熟库(默认 `react-native-text-size`
- **验收**`client/package.json` 中存在该依赖(或团队等价成熟方案),并且测量封装函数仅依赖该库/注入点。
---
## 2. 依赖与目录骨架(客户端侧实现)
- [x] 2.1 新建目录 `client/src/features/textWrap/measure/`
- **产出**(建议文件):
- `types.ts`FontSpec/ContextProfile/MeasureResult
- `errors.ts`(缺字段报错)
- `fontSpecKey.ts`
- `cache.ts`(两级缓存 + 上限)
- `measureWidthImpl.ts`(对接成熟测量库)
- `measureWidthCached.ts`
- `measureSliceWidthCached.ts`
- `__tests__/widthMeasurement.test.ts`
- `index.ts`(统一导出)
- **验收**目录存在TS 可正常 import不报路径错误
- [x] 2.2 新增依赖 `react-native-text-size`(如项目未安装)
- **命令**(示例):
- `cd client && npm install react-native-text-size`
- **验收**
- 安装成功
- `npm test` 不受影响(后续任务再补充本模块测试)
---
## 3. fontSpec 强校验(缺失直接报错)
- [x] 3.1 定义 `FontSpec` 类型(必须字段:`fontSize/fontFamily/fontWeight`
- **验收**:类型层面可表达“必填字段”,并在运行时也做校验。
- [x] 3.2 实现运行时校验与错误(简体中文)
- **要求**
- 缺失任一字段直接抛错
- 错误信息包含:缺失字段名 + 建议调用方补齐的位置(例如 `wrapText({ fontSpec: ... })`
- 禁止隐式默认值
- **验收**:单测断言会抛错且错误信息包含缺失字段名。
---
## 4. fontSpecKey 与 contextProfile确定性 key 体系)
- [x] 4.1 实现 `buildFontSpecKey(fontSpec)`
- **规则**`fontFamily|fontWeight|fontSize`(顺序固定)
- **验收**同输入同输出fontSize 非有限数时报错。
- [x] 4.2 定义 `contextProfile` 拼接规则并实现 helper
- **要求**
- APP`APP|<platform>|<scale?>`(字段顺序固定)
- WIDGET`WIDGET|<widgetSize?>`(无 widgetSize 时为 `WIDGET`
- **验收**:输出字符串稳定;不同 profile 必须产生不同缓存 key。
---
## 5. 两级缓存(有上限 + 确定性)
- [x] 5.1 实现缓存容器(模块级常驻)
- **要求**
- 字符串测量缓存textKey
- 切片测量缓存sliceKey
- 容量上限策略LRU 优先;若不引入依赖,先 Map + 超限清空(并预留打点钩子)
- **验收**:单测可验证缓存命中会减少底层测量调用次数。
- [x] 5.2 定义 key 生成规则并实现
- **规则**
- `textKey = <contextProfile>|<fontSpecKey>|<text>`
- `sliceKey = <contextProfile>|<fontSpecKey>|<start>|<end>`
- **验收**key 生成不依赖对象遍历顺序;同输入同 key。
---
## 6. 测量实现与降级approx mode
- [x] 6.1 实现 `measureWidthImpl(text, fontSpec)`APP
- **要求**
- 依赖成熟库测量宽度
- 返回值必须校验:有限且非负
- **验收**:在单测中用 mock 替代真实库,验证封装逻辑与校验逻辑即可(不做像素级断言)。
- [x] 6.2 实现 `measureWidthCached(...)`
- **行为**
- 正常测量:返回 `{ width:number, meta:{ isApprox:false } }`
- 进入 approx返回 `{ width:null, meta:{ isApprox:true, reason } }`
- **approx 触发条件**(任一满足):
- 未提供测量能力 / context=WIDGET 且上层不启用测量 -> `WIDTH_UNKNOWN`
- 抛错/NaN/Infinity/负数 -> `MEASURE_FAILED`
- **验收**:单测覆盖两类 reason。
- [x] 6.3 实现 `measureSliceWidthCached(...)`(切片测量)
- **要求**
- 通过 `joinTokens(start,end)` 生成切片文本
- 使用切片缓存避免 DP 反复测量
- **验收**:单测验证同 sliceKey 不重复调用底层测量。
---
## 7. 单元测试Vitest
- [x] 7.1 新建 `__tests__/widthMeasurement.test.ts` 并覆盖以下用例
- **fontSpec 报错**:缺字段必抛错(错误信息含字段名)
- **缓存命中**:同 key 不重复调用底层测量 mock
- **缓存隔离**:不同 `contextProfile/fontSpecKey` 不互相污染
- **降级**
- 缺测量能力 -> `width=null` + `WIDTH_UNKNOWN`
- 测量抛错/NaN -> `width=null` + `MEASURE_FAILED`
- **验收**`npm test` 稳定通过。
---
## 8. 最终自检清单(合入前)
- [x] 8.1 `npm test` 通过(包含本模块新增用例)
- **验收**:不影响现有测试文件。
- [x] 8.2 `npx tsc --noEmit` 通过(或项目既有 TS 检查命令通过)
- **验收**:无类型错误。
- [x] 8.3 注释与口径自检(简体中文)
- **检查点**
- `fontSpec` 缺字段“必须报错”
- key 生成规则与 contextProfile 口径
- approx mode 的 reason 语义WIDTH_UNKNOWN / MEASURE_FAILED
- **验收**:后续模块开发者只看代码也不会产生歧义。
---
## 9. 文档回写(任务清单执行完毕后必须做)
- [x] 9.1 在 `spec_kit/overview.md``Text Wrap` 条目下补充执行状态
- **建议写法**
- 增加一行:`- **已完成编码(阶段性)**width-measurement宽度测量与降级`
- **验收**overview 能反映该子模块已完成,便于全局追踪。