Files
mindfulness/spec_kit/Text Wrap/modules/search-engine-app/plan.md
2026-02-10 11:39:33 +08:00

105 lines
4.5 KiB
Markdown
Raw 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.
# search-engine-app技术计划
## 1. 计划目标
**APP** 场景实现确定性的“断点组合搜索器”,使用 **DP + TopK** 生成候选 layout 并选出最优解:
- 输入:`tokens[]``breakpoints[]``availableWidth``maxLines``lineMode`
- 过程DP 状态转移 + TopK 维护(去重、排序、确定性 tie-break
- 评分:调用 `scoring-tiebreak``scoreLayout` + `buildTieKey`
- 输出:`bestLayout`breaks/lines/wrappedText/meta或返回 “无解/超长/宽度不可用” 的可解释原因overflow-fallback 模块将统一处理最终语义)
## 2. 约束与确定性口径
### 2.1 确定性(必须)
同输入(含 configVersion/权重/词表/断点列表)必须同输出:
- DP 遍历顺序固定pos 升序、linesUsed 升序)
- nextPos 枚举顺序固定(按 `pos` 升序;最后补一个 `N` 结束边界)
- TopK 的排序固定:`score` 降序;再按 `tieKey` 逐项比较11 节);再按 `breaks` 字典序11.0A
- 去重固定:同 `breaks` 序列只保留最优score 更大;若相同用 tieKey
### 2.2 复杂度(必须可控)
遵循文档 9.3 建议:
- 默认 `TopK=10`
- 默认 `maxLines<=3`(或 4
- 超长输入触发 `TOO_LONG`TC>60 grapheme 或 EN>30 words首版写死后续 configVersion 管理)
## 3. DP + TopK 设计(对应文档 9.1
### 3.1 状态定义
`N=tokens.length`token 边界为 `pos in [0..N]`
- `dp[pos][linesUsed] = TopK partial layouts ending at token boundary pos`
- partial layout 至少包含:
- `pos`:当前结束位置
- `breaks: number[]`:已选择的断点序列(升序)
- `lines: LineInfo[]`已生成的行信息start/end/text/width/tokenCount/charCount
- `score: number` + `tieKey: number[]`(由 scoring-tiebreak 构造)
### 3.2 转移(生成下一行)
从状态 `(pos, linesUsed)` 选择 `nextPos` 生成新行区间 `[pos..nextPos)`
- nextPos 取值来自 `breakpoints``b.pos``b.pos > pos`,并按 `pos` 升序枚举
- 必须额外允许 `nextPos = N`(结束边界)
- 每次转移需:
- 构造 lineText使用 `core-contract/joinTokens`
- 测量 lineWidth优先使用 `width-measurement/measureSliceWidthCached`(切片缓存),若不可用返回 `WIDTH_UNKNOWN`
- 应用硬约束过滤(见 3.3
- 计算新 layout 的评分与 tieKey调用 scoring-tiebreak
- 插入 `dp[nextPos][linesUsed+1]` 并维护 TopK去重、排序
### 3.3 硬约束(本模块先落地最小集)
为了让 DP 行为稳定且可解释,首版在搜索层执行以下硬约束(其余放到评分):
- **H1 不超宽**:若 lineWidth 可用且 `lineWidth > availableWidth`,该转移无效
- **H5 最大行数**`linesUsed+1 <= maxLines`
- **禁止空行**`nextPos > pos`(区间非空)
- **TC 禁止行首标点H6**
-`lang=TC``pos>0``tokens[pos]` 属于 `tcPunctuations`,则该断点不可用(跳过)
> 说明protectedPhrases 的“span 内断点剔除”属于候选断点优化(可选),首版不在搜索层做剔除,依赖 scoring 的强惩罚淘汰(文档 6.2A / 10.2A)。
### 3.4 结束条件与 lineMode
- 正常结束:到达 `pos=N`
- `lineMode=AUTO`
-`dp[N][1..maxLines]` 里选最优
- `lineMode=FIXED`
- 优先从 `dp[N][==maxLines]` 里选最优
- 若无解:返回 “无解” 的原因与必要 meta由 overflow-fallback 统一降级链路与打点)
## 4. 代码落位(客户端)
建议目录:
- `client/src/features/textWrap/searchApp/`
- `types.ts`
- `dpTopK.ts`DP 主流程 + TopK 维护)
- `topK.ts`(去重、插入、排序、截断)
- `constraints.ts`H1/H6/TOO_LONG 判定)
- `index.ts`
- `__tests__/searchApp.test.ts`
## 5. 测试计划Vitest
- **确定性**:同输入运行 3 次输出完全一致breaks/lines/wrappedText
- **TopK 去重**:构造两条路径得到相同 breaks断言只保留一个且为最优
- **lineMode=FIXED**:有解时必须返回刚好 `maxLines`;无解时返回 reason例如 `NO_CANDIDATE`
- **TC 行首标点**:构造 tokens 在断点后行首为 ``,断言该转移被禁止
- **TOO_LONG**TC>60 或 EN>30 时直接返回 `TOO_LONG`
## 6. 完成定义DoD
- DP+TopK 可运行,且确定性通过单测
- 使用 `scoring-tiebreak` 进行评分与 tieKey 裁决
- 搜索层硬约束最小集落地H1/H6/空行/maxLines
- 无解/超长/宽度不可用时返回明确 reason不直接在本模块做最终 overflow