# 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||`(字段顺序固定) - WIDGET:`WIDGET|`(无 widgetSize 时为 `WIDGET`) - **验收**:输出字符串稳定;不同 profile 必须产生不同缓存 key。 --- ## 5. 两级缓存(有上限 + 确定性) - [x] 5.1 实现缓存容器(模块级常驻) - **要求**: - 字符串测量缓存(textKey) - 切片测量缓存(sliceKey) - 容量上限策略:LRU 优先;若不引入依赖,先 Map + 超限清空(并预留打点钩子) - **验收**:单测可验证缓存命中会减少底层测量调用次数。 - [x] 5.2 定义 key 生成规则并实现 - **规则**: - `textKey = ||` - `sliceKey = |||` - **验收**: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 能反映该子模块已完成,便于全局追踪。