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

4.5 KiB
Raw Blame History

search-engine-app技术计划

1. 计划目标

APP 场景实现确定性的“断点组合搜索器”,使用 DP + TopK 生成候选 layout 并选出最优解:

  • 输入:tokens[]breakpoints[]availableWidthmaxLineslineMode
  • 过程DP 状态转移 + TopK 维护(去重、排序、确定性 tie-break
  • 评分:调用 scoring-tiebreakscoreLayout + buildTieKey
  • 输出:bestLayoutbreaks/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_LONGTC>60 grapheme 或 EN>30 words首版写死后续 configVersion 管理)

3. DP + TopK 设计(对应文档 9.1

3.1 状态定义

N=tokens.lengthtoken 边界为 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 取值来自 breakpointsb.posb.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=TCpos>0tokens[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.tsDP 主流程 + TopK 维护)
    • topK.ts(去重、插入、排序、截断)
    • constraints.tsH1/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_LONGTC>60 或 EN>30 时直接返回 TOO_LONG

6. 完成定义DoD

  • DP+TopK 可运行,且确定性通过单测
  • 使用 scoring-tiebreak 进行评分与 tieKey 裁决
  • 搜索层硬约束最小集落地H1/H6/空行/maxLines
  • 无解/超长/宽度不可用时返回明确 reason不直接在本模块做最终 overflow