# 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 能反映该子模块已完成，便于全局追踪。

