# search-engine-widget（任务清单）

> 目标：在 WIDGET 场景实现确定性 Beam Search（文档 9.2），支持 `widthMode=MEASURE/APPROX`，并输出最优 layout 或可解释失败原因。

## 0. 对齐与准备

- [x] 阅读并对齐口径
  - [x] 阅读 `spec_kit/Text Wrap/modules/search-engine-widget/spec.md`
  - [x] 阅读 `spec_kit/Text Wrap/modules/search-engine-widget/plan.md`
  - [x] 阅读 `设计说明文档/文档换行算法.md` 的 9.2/9.3/11 章节
- [x] 创建客户端模块目录
  - [x] 新建 `client/src/features/textWrap/searchWidget/`

## 1. 类型定义与导出

- [x] 新建 `client/src/features/textWrap/searchWidget/types.ts`
  - [x] 定义 `WidthMode = 'MEASURE' | 'APPROX'`
  - [x] 定义 `SearchWidgetConfig`：`beamK`、`expandM`、`tooLongThresholds`
  - [x] 定义 `SearchWidgetInput`：
    - [x] `tokens/lang/breakpoints/availableWidth/maxLines/context='WIDGET'`
    - [x] `measure: { widthMode; contextProfile?; fontSpec?; measureWidthImpl? }`
    - [x] `scoring: { config; lexicons; debug? }`
  - [x] 定义 `SearchWidgetResult`：
    - [x] 成功：`bestLayout { breaks; lines; wrappedText; meta }`
    - [x] 失败：`{ ok:false; reason:'TOO_LONG'|'NO_CANDIDATE'; meta? }`
- [x] 新建 `client/src/features/textWrap/searchWidget/index.ts`
  - [x] 统一导出 types 与入口 `searchBestLayoutWidget()`

## 2. TopK/BeamK 维护（确定性）

- [x] 新建 `client/src/features/textWrap/searchWidget/topK.ts`
  - [x] 实现 compare：score desc；tieKey asc；breaks 字典序
  - [x] 实现去重：同 breaks 只保留最优
  - [x] 实现 `insertTopK`（用于全局 beamK 保留）

## 3. 约束与降级 meta

- [x] 新建 `client/src/features/textWrap/searchWidget/constraints.ts`
  - [x] `isTooLong`（TC>60 / EN>30）
  - [x] `isLineStartPunctTC(tokens, nextPos, tcPunctuations)`（H6）
  - [x] `approxWidth(line, lang)`（EN=tokenCount / TC=charCount）

## 4. Beam Search 主流程

- [x] 新建 `client/src/features/textWrap/searchWidget/beam.ts`
  - [x] 初始化 beams：`[{pos:0, breaks:[], lines:[], score:0}]`
  - [x] 循环 lineIndex=1..maxLines
    - [x] 对每个 beam 枚举 nextPos（breakpoints>pos + N）
    - [x] 裁剪：仅取前 `expandM` 个 nextPos（pos 升序）
    - [x] 构造行文本：`joinTokens`
    - [x] 计算行宽：
      - [x] widthMode=MEASURE：尝试 `measureSliceWidthCached`；失败则降级 approx 并 meta.reason 标记
      - [x] widthMode=APPROX：直接 approx，并 meta.reason=WIDTH_UNKNOWN
    - [x] 硬约束过滤：
      - [x] 禁止空行、maxLines、TC H6
      - [x] 若 width 可用且 > availableWidth：过滤（仅 MEASURE 可信）
    - [x] 评分：`scoreLayout` + `buildTieKey`
    - [x] 全局 newBeams 维护 TopK（beamK）
  - [x] 结束：从 beams 中选 pos==N 最优，否则 NO_CANDIDATE

## 5. 单测（Vitest）

- [x] 新建 `client/src/features/textWrap/searchWidget/__tests__/searchWidget.test.ts`
  - [x] 确定性：同输入重复执行结果一致
  - [x] widthMode=APPROX：仍可输出，且 meta.reason=WIDTH_UNKNOWN
  - [x] 性能约束：expandM/beamK 生效（可统计测量/扩展次数）
  - [x] TC H6：行首标点被禁止

## 6. 收尾

- [x] 跑测试与类型检查
  - [x] `npm test`
  - [x] `npx tsc --noEmit`
- [x] 将本 `tasks.md` 全部勾选完成
- [x] 更新 `spec_kit/overview.md`
  - [x] 标记 `search-engine-widget` 已完成编码（阶段性）
  - [x] 写入变更文件清单

