更新换行算法和APP-PUSH
This commit is contained in:
162
spec_kit/Text Wrap/modules/breakpoint-candidates/plan.md
Normal file
162
spec_kit/Text Wrap/modules/breakpoint-candidates/plan.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# breakpoint-candidates(技术计划)
|
||||
|
||||
## 1. 计划目标
|
||||
|
||||
基于 `spec.md` 与 `设计说明文档/文档换行算法.md v1.2.1`,实现“候选断点生成与裁剪”模块,输出**可控规模、确定性排序**的 breakpoints 集合,保证:
|
||||
|
||||
- EN/TC 断点生成口径一致(断点 `pos` 均是 token 边界索引)
|
||||
- 同输入必定同输出(去重、排序、裁剪与过滤全流程确定性)
|
||||
- 候选规模上限严格生效(尤其 TC)
|
||||
- 支持约束过滤:`forbiddenBreakRanges`(必须)与 `protectedPhrases`(可选优化)
|
||||
|
||||
本模块只产出 breakpoints,不做组合搜索与评分。
|
||||
|
||||
## 2. 默认技术决策(本计划采用)
|
||||
|
||||
- **输出结构**:`Array<{ pos, kind, priority }>`,最终按 `pos` 升序
|
||||
- **去重策略**:同一 `pos` 若出现多个来源候选,保留 `priority` 更高者(priority 相同按 `kind` 固定序优先)
|
||||
- **裁剪策略(TC)**:先按“候选重要性排序”截断到 `tcMaxCandidateBreaks`,再按 `pos` 升序输出
|
||||
- **过滤策略**:
|
||||
- 必做:`forbiddenBreakRanges` 命中直接剔除
|
||||
- 可选优化:若已计算 `protectedPhrases` 的 span,可在生成阶段剔除 span 内断点(否则交给评分阶段强惩罚淘汰)
|
||||
|
||||
## 3. 输入/输出与关键口径
|
||||
|
||||
### 3.1 输入(来自上游)
|
||||
|
||||
- `tokens: Token[]`
|
||||
- EN:WORD tokens(不包含 SPACE token)
|
||||
- TC:grapheme cluster tokens(允许包含空格 cluster `" "`,用于 SPACE 断点)
|
||||
- `lang: 'TC' | 'EN'`
|
||||
- `maxLines: number`
|
||||
- `constraints?: { protectedPhrases?: string[]; forbiddenBreakRanges?: Array<{ start: number; end: number }> }`
|
||||
- `config: { tcMaxCandidateBreaks: number; tcPunctuations: string[]; balanceRange: number }`
|
||||
|
||||
### 3.2 输出(确定性)
|
||||
|
||||
- `breakpoints: Array<{ pos: number; kind: 'PUNCT' | 'SPACE' | 'BALANCE' | 'OTHER'; priority: number }>`
|
||||
- `pos`:token 边界索引,范围 `0..N`
|
||||
- **最终输出必须按 `pos` 升序**
|
||||
- `meta?: { pruned: boolean; originalCount: number; finalCount: number }`
|
||||
|
||||
### 3.3 断点边界定义(统一口径)
|
||||
|
||||
- 候选断点只生成在“行内断点”位置:`pos ∈ [1, N-1]`
|
||||
- `pos=0` 与 `pos=N` 由搜索器作为“起止边界”处理(不作为候选断点输出)
|
||||
|
||||
## 4. 生成规则(按语言)
|
||||
|
||||
### 4.1 EN:词边界(kind=SPACE)
|
||||
|
||||
#### 规则
|
||||
|
||||
- tokens 仅为 WORD,不生成 SPACE token
|
||||
- 对每个词边界产生候选断点:
|
||||
- 对 `i in 1..N-1` 生成 `pos=i, kind='SPACE'`
|
||||
- `priority` 固定为基础值(建议 `priority=10`)
|
||||
|
||||
#### 验收要点
|
||||
|
||||
- `"I am so tired"`(N=4)→ breakpoints.pos 必为 `[1,2,3]`(升序)
|
||||
|
||||
### 4.2 TC:标点/空格/BALANCE
|
||||
|
||||
#### 4.2.1 标点后断点(kind=PUNCT,最高优先级)
|
||||
|
||||
- 若 `tokens[i].text` 属于 `tcPunctuations`:
|
||||
- 生成 `pos=i+1, kind='PUNCT'`
|
||||
- `priority` 建议最高(例如 `priority=30`)
|
||||
|
||||
#### 4.2.2 空格后断点(kind=SPACE,中优先级)
|
||||
|
||||
- 若 `tokens[i].text === ' '`:
|
||||
- 生成 `pos=i+1, kind='SPACE'`
|
||||
- `priority` 建议中等(例如 `priority=20`)
|
||||
|
||||
> 注:若上游对 TC 也做了空白 NORMALIZE,则空格通常不会连写,断点仍保持确定性。
|
||||
|
||||
#### 4.2.3 BALANCE 断点(kind=BALANCE,低优先级)
|
||||
|
||||
目的:在无标点时,仍在“接近理想位置”附近提供少量断点,提升可解性与观感。
|
||||
|
||||
**理想位置计算(确定性简化版)**:
|
||||
|
||||
- `N = tokens.length`
|
||||
- `targetLines = min(maxLines, N)`(至少 1,且不超过 N)
|
||||
- 对 `lineIndex in 1..targetLines-1`:
|
||||
- `idealPos = round((N * lineIndex) / targetLines)`
|
||||
- 在区间 `[idealPos - balanceRange, idealPos + balanceRange]` 生成少量候选 `pos`
|
||||
|
||||
**生成细则**:
|
||||
|
||||
- 候选 `pos` 必须落在 `[1, N-1]`
|
||||
- 去重前可以允许重复(后续统一去重)
|
||||
- `priority` 建议最低(例如 `priority=5`)
|
||||
|
||||
> 裁决补充口径:BALANCE 断点允许落在 emotionPhrase/protectedPhrases 的 span 内;是否可用由评分阶段强惩罚决定(本模块不做语义裁决)。
|
||||
|
||||
## 5. 过滤、去重、裁剪与排序(必须确定性)
|
||||
|
||||
### 5.1 forbiddenBreakRanges 过滤(必须)
|
||||
|
||||
- 若 `pos` 落在任一 `forbiddenBreakRanges` 的区间内(按项目约定:`start <= pos <= end` 或半开区间,必须写死一种),则剔除该 breakpoint
|
||||
- 过滤必须发生在最终输出前,保证确定性
|
||||
|
||||
### 5.2 去重(必须)
|
||||
|
||||
同一 `pos` 可能来自多个来源(例如 PUNCT 与 BALANCE):
|
||||
|
||||
- 取 `priority` 更高者
|
||||
- `priority` 相同则按固定 kind 序:`PUNCT > SPACE > BALANCE > OTHER`
|
||||
|
||||
### 5.3 TC 规模上限裁剪(必须)
|
||||
|
||||
当 `lang=TC` 且候选数超过 `tcMaxCandidateBreaks`:
|
||||
|
||||
1. 计算每个 pos 到最近 `idealPos` 的距离 `distToIdeal`(若无 idealPos 列表则设为大值)
|
||||
2. 按以下 key 排序后截断(排序必须固定):
|
||||
- `priority` 降序
|
||||
- `distToIdeal` 升序
|
||||
- `pos` 升序
|
||||
3. 取前 `tcMaxCandidateBreaks`
|
||||
|
||||
最后再按 `pos` 升序输出(输出顺序固定)。
|
||||
|
||||
### 5.4 meta 输出
|
||||
|
||||
- `originalCount`:过滤/去重/裁剪前的候选数量
|
||||
- `finalCount`:最终输出数量
|
||||
- `pruned`:是否发生过裁剪(finalCount < originalCount)
|
||||
|
||||
## 6. 测试计划(Vitest)
|
||||
|
||||
### 6.1 EN 断点生成
|
||||
|
||||
- 输入 tokens=[I, am, so, tired] → pos=[1,2,3](确定性)
|
||||
|
||||
### 6.2 TC 标点/空格断点
|
||||
|
||||
- `tokens=['我','好','累',',','😮💨']` 且 `tcPunctuations` 包含 `,`
|
||||
- 必须包含 `pos=4(kind=PUNCT)`
|
||||
|
||||
### 6.3 BALANCE 与裁剪
|
||||
|
||||
- 构造无标点长文本,balanceRange>0 且 `tcMaxCandidateBreaks` 很小
|
||||
- 验证裁剪后数量上限生效
|
||||
- 验证输出仍按 pos 升序
|
||||
|
||||
### 6.4 forbiddenBreakRanges
|
||||
|
||||
- 给定 ranges,断言命中区间内的 pos 一律被剔除
|
||||
|
||||
### 6.5 确定性(关键)
|
||||
|
||||
- 同输入多次调用 breakpoints 输出完全一致(包括 meta)
|
||||
|
||||
## 7. 完成定义(DoD)
|
||||
|
||||
- EN/TC 候选断点生成口径与裁剪规则写死并实现
|
||||
- 去重/排序/裁剪/过滤流程完全确定性
|
||||
- TC 上限 `tcMaxCandidateBreaks` 生效
|
||||
- 单测覆盖:EN/TC 基础、BALANCE、裁剪、forbiddenBreakRanges、确定性
|
||||
|
||||
51
spec_kit/Text Wrap/modules/breakpoint-candidates/spec.md
Normal file
51
spec_kit/Text Wrap/modules/breakpoint-candidates/spec.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# breakpoint-candidates(子模块规范)
|
||||
|
||||
## 子模块名称
|
||||
|
||||
breakpoint-candidates(候选断点生成与裁剪)
|
||||
|
||||
## 目标描述
|
||||
|
||||
基于 token 序列生成“可控规模、确定性排序”的候选断点集合(breakpoints),并应用去重、排序与约束过滤,确保后续搜索器复杂度可控且跨端一致。
|
||||
|
||||
本模块输出的是“断点候选集合”,不负责“组合搜索选最优”。
|
||||
|
||||
## 输入/输出定义
|
||||
|
||||
### 输入
|
||||
|
||||
- `tokens: Token[]`
|
||||
- `lang: 'TC' | 'EN'`
|
||||
- `maxLines: number`
|
||||
- `constraints?: { protectedPhrases?: string[]; forbiddenBreakRanges?: Array<{ start: number; end: number }> }`
|
||||
- `config: { tcMaxCandidateBreaks: number; tcPunctuations: string[]; balanceRange: number }`
|
||||
|
||||
### 输出
|
||||
|
||||
- `breakpoints: Array<{ pos: number; kind: 'PUNCT' | 'SPACE' | 'BALANCE' | 'OTHER'; priority: number }>`
|
||||
- **pos**:token 边界索引(0..N)
|
||||
- **排序**:按 `pos` 升序(最终输出必须确定性)
|
||||
- `meta?: { pruned: boolean; originalCount: number; finalCount: number }`
|
||||
|
||||
## 验收标准(可验证)
|
||||
|
||||
- **EN 口径**:
|
||||
- tokens 仅为 WORD(不生成 SPACE token),断点仅存在于词间
|
||||
- 每个词边界产生 `kind=SPACE` 的候选断点(按配置可做裁剪,但必须确定性)
|
||||
- **TC 口径**:
|
||||
- 标点后断点 `kind=PUNCT` 优先级最高
|
||||
- 空格后断点 `kind=SPACE` 次之
|
||||
- BALANCE 断点:围绕理想切分点附近生成少量断点(允许落在短语 span 内,是否可用交给评分惩罚)
|
||||
- **去重/排序/过滤确定性**:
|
||||
- 同一 `pos` 多来源断点:保留 priority 更高者
|
||||
- 输出按 `pos` 升序
|
||||
- `forbiddenBreakRanges` 命中者必定被剔除
|
||||
- **规模上限生效**:
|
||||
- TC 输出候选断点数不超过 `tcMaxCandidateBreaks`
|
||||
- 截断策略确定性(按 priority + 距离理想位置等固定规则)
|
||||
|
||||
## 依赖与关联
|
||||
|
||||
- **依赖**:`core-contract`(token 与索引语义)、`grapheme-segmentation`(TC tokens)
|
||||
- **被依赖**:`search-engine-app`、`search-engine-widget`
|
||||
|
||||
164
spec_kit/Text Wrap/modules/breakpoint-candidates/tasks.md
Normal file
164
spec_kit/Text Wrap/modules/breakpoint-candidates/tasks.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# breakpoint-candidates(任务清单)
|
||||
|
||||
> 对应计划:`spec_kit/Text Wrap/modules/breakpoint-candidates/plan.md`
|
||||
>
|
||||
> 状态含义:`[ ]` 未完成,`[x]` 已完成。
|
||||
> 执行完本清单后,需要在 `spec_kit/overview.md` 的 `Text Wrap` 条目下补充“已完成编码/任务执行完毕”的标记(见最后一节)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 任务标记规则
|
||||
|
||||
- 用勾选框标记执行状态:
|
||||
- `[ ]` 未完成
|
||||
- `[x]` 已完成
|
||||
- 每个任务必须可独立验收(有明确产出与检查方式)。
|
||||
- 所有代码注释必须为简体中文,并把“去重/裁剪/排序/过滤”的**确定性口径**写死,避免后续模块漂移。
|
||||
|
||||
---
|
||||
|
||||
## 1. 前置对齐(口径必须写死)
|
||||
|
||||
- [x] 1.1 明确 `pos` 的边界范围:仅输出 `pos ∈ [1, N-1]`
|
||||
- **原因**:`pos=0/N` 属于搜索器的起止边界,不应作为候选断点
|
||||
- **验收**:单测覆盖 `N=0/1/2` 等边界输入,不会输出非法 pos。
|
||||
|
||||
- [x] 1.2 明确 `forbiddenBreakRanges` 的区间口径(写死一种)
|
||||
- **本任务采用**:闭区间 `start <= pos && pos <= end`
|
||||
- **验收**:单测能验证闭区间边界命中(start/end 两端都被剔除)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 目录与代码骨架(客户端侧实现)
|
||||
|
||||
- [x] 2.1 新建目录 `client/src/features/textWrap/breakpoints/`
|
||||
- **产出**(建议文件):
|
||||
- `types.ts`(Breakpoint/Config/Constraints)
|
||||
- `generateBreakpoints.ts`(主入口,纯函数)
|
||||
- `tcCandidates.ts`(TC:PUNCT/SPACE/BALANCE 生成)
|
||||
- `enCandidates.ts`(EN:SPACE 断点生成)
|
||||
- `filterAndDedup.ts`(过滤/去重/排序/裁剪)
|
||||
- `__tests__/generateBreakpoints.test.ts`
|
||||
- `index.ts`(统一导出)
|
||||
- **验收**:目录存在,TS 可正常 import(不报路径错误)。
|
||||
|
||||
- [x] 2.2 定义最小类型集合(只覆盖本模块)
|
||||
- **必须包含**:
|
||||
- `Breakpoint = { pos: number; kind: 'PUNCT'|'SPACE'|'BALANCE'|'OTHER'; priority: number }`
|
||||
- `BreakpointMeta = { pruned: boolean; originalCount: number; finalCount: number }`
|
||||
- `Constraints = { forbiddenBreakRanges?: Array<{ start: number; end: number }>; protectedPhrases?: string[] }`
|
||||
- `Config = { tcMaxCandidateBreaks: number; tcPunctuations: string[]; balanceRange: number }`
|
||||
- **验收**:后续实现文件引用类型清晰,且不会引入无关依赖。
|
||||
|
||||
---
|
||||
|
||||
## 3. 断点生成(按语言)
|
||||
|
||||
- [x] 3.1 EN 候选断点生成(kind=SPACE,priority 固定)
|
||||
- **规则**:
|
||||
- 对 `i in 1..N-1` 生成 `pos=i, kind='SPACE'`
|
||||
- `priority=10`(写死)
|
||||
- **验收**:
|
||||
- tokens=[I, am, so, tired] → pos=[1,2,3]
|
||||
|
||||
- [x] 3.2 TC:标点后断点(kind=PUNCT)
|
||||
- **规则**:
|
||||
- 若 `tokens[i].text ∈ tcPunctuations`,生成 `pos=i+1, kind='PUNCT', priority=30`
|
||||
- **验收**:
|
||||
- tokens=['我','好','累',',','😮💨'] → 包含 `pos=4(kind=PUNCT)`
|
||||
|
||||
- [x] 3.3 TC:空格后断点(kind=SPACE)
|
||||
- **规则**:
|
||||
- 若 `tokens[i].text === ' '`,生成 `pos=i+1, kind='SPACE', priority=20`
|
||||
- **验收**:构造含空格 tokens,断点生成稳定且不越界。
|
||||
|
||||
- [x] 3.4 TC:BALANCE 断点生成(kind=BALANCE)
|
||||
- **规则**:
|
||||
- `targetLines = min(maxLines, N)`
|
||||
- 对 `lineIndex in 1..targetLines-1`:
|
||||
- `idealPos = round((N * lineIndex) / targetLines)`
|
||||
- 在 `[idealPos-balanceRange, idealPos+balanceRange]` 内生成 pos(裁剪到 `[1,N-1]`)
|
||||
- `priority=5`
|
||||
- **验收**:
|
||||
- 无标点长文本:可生成接近理想位置的候选断点(数量受控、确定性)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 过滤、去重、裁剪与最终排序(必须确定性)
|
||||
|
||||
- [x] 4.1 forbiddenBreakRanges 过滤(闭区间)
|
||||
- **规则**:命中任一 range 则剔除该 `pos`
|
||||
- **验收**:range 边界 start/end 都会剔除。
|
||||
|
||||
- [x] 4.2 去重:同 pos 只保留一个 breakpoint
|
||||
- **规则**:
|
||||
- priority 更高者优先
|
||||
- priority 相同按 kind 固定序:`PUNCT > SPACE > BALANCE > OTHER`
|
||||
- **验收**:构造同 pos 多来源候选,结果唯一且确定性。
|
||||
|
||||
- [x] 4.3 TC 裁剪:超过 `tcMaxCandidateBreaks` 时截断(确定性排序后截断)
|
||||
- **排序 key(写死)**:
|
||||
- priority 降序
|
||||
- distToIdeal 升序(到最近 idealPos 的距离;无 idealPos 时为大值)
|
||||
- pos 升序
|
||||
- **验收**:
|
||||
- 当候选数 > 上限时,finalCount==tcMaxCandidateBreaks
|
||||
- 截断结果稳定(同输入同输出)
|
||||
|
||||
- [x] 4.4 最终输出排序:按 `pos` 升序
|
||||
- **验收**:无论内部裁剪排序如何,最终输出始终 `pos` 升序。
|
||||
|
||||
- [x] 4.5 meta 输出
|
||||
- **规则**:
|
||||
- `originalCount`:过滤/去重/裁剪前的候选数量
|
||||
- `finalCount`:最终输出数量
|
||||
- `pruned = finalCount < originalCount`
|
||||
- **验收**:单测断言 meta 与候选数量一致。
|
||||
|
||||
---
|
||||
|
||||
## 5. 单元测试(Vitest)
|
||||
|
||||
- [x] 5.1 新建 `generateBreakpoints.test.ts`,覆盖 EN 基础用例
|
||||
- **验收**:pos=[1,2,3] 且升序。
|
||||
|
||||
- [x] 5.2 覆盖 TC:PUNCT/SPACE/BALANCE 生成
|
||||
- **验收**:关键样例存在,且 BALANCE 不越界。
|
||||
|
||||
- [x] 5.3 覆盖 forbiddenBreakRanges(闭区间)
|
||||
- **验收**:start/end 命中都剔除。
|
||||
|
||||
- [x] 5.4 覆盖去重与 kind 优先级
|
||||
- **验收**:同 pos 多候选时输出唯一且正确 kind。
|
||||
|
||||
- [x] 5.5 覆盖 TC 裁剪上限与确定性
|
||||
- **验收**:
|
||||
- 数量上限严格生效
|
||||
- 同输入多次调用输出完全一致(包括 meta)
|
||||
|
||||
---
|
||||
|
||||
## 6. 最终自检清单(合入前)
|
||||
|
||||
- [x] 6.1 `npm test` 通过(包含本模块新增用例)
|
||||
- **验收**:不影响现有测试文件。
|
||||
|
||||
- [x] 6.2 `npx tsc --noEmit` 通过(或项目既有 TS 检查命令通过)
|
||||
- **验收**:无类型错误。
|
||||
|
||||
- [x] 6.3 注释与口径自检(简体中文)
|
||||
- **检查点**:
|
||||
- `pos` 边界范围 `[1,N-1]`
|
||||
- forbiddenBreakRanges 闭区间口径
|
||||
- 去重优先级与裁剪排序 key 的固定顺序
|
||||
- **验收**:后续模块开发者只看代码也不会产生歧义。
|
||||
|
||||
---
|
||||
|
||||
## 7. 文档回写(任务清单执行完毕后必须做)
|
||||
|
||||
- [x] 7.1 在 `spec_kit/overview.md` 的 `Text Wrap` 条目下补充执行状态
|
||||
- **建议写法**:
|
||||
- 增加一行:`- **已完成编码(阶段性)**:breakpoint-candidates(候选断点生成与裁剪)`
|
||||
- **验收**:overview 能反映该子模块已完成,便于全局追踪。
|
||||
|
||||
Reference in New Issue
Block a user