更新换行算法和APP-PUSH
This commit is contained in:
109
spec_kit/Text Wrap/modules/overflow-fallback/plan.md
Normal file
109
spec_kit/Text Wrap/modules/overflow-fallback/plan.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# overflow-fallback(技术计划)
|
||||
|
||||
## 1. 计划目标
|
||||
|
||||
实现统一的“溢出与兜底”策略模块,用于在以下情况给出确定性且可解释的返回:
|
||||
|
||||
- 搜索器无法在 `<=maxLines` 覆盖到结束边界 `N`
|
||||
- `lineMode=FIXED` 需要刚好 `maxLines` 但无解
|
||||
- 测量不可用或失败导致无法执行宽度派约束(尤其 Widget)
|
||||
|
||||
支持三种模式:
|
||||
|
||||
- `ELLIPSIS`:最后一行加省略号并保证不超宽(必要时回退移除 token 再加)
|
||||
- `CLIP`:截断到 `maxLines`(不加省略号)
|
||||
- `SYSTEM_DEFAULT`:算法层不插入换行,交给系统排版(仅在 meta 标记)
|
||||
|
||||
## 2. 对外输入/输出(与 spec 对齐)
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- `tokens: Token[]`
|
||||
- `partialLayout?: { breaks: number[]; lines: Array<{ start; end; text? }> }`
|
||||
- 若提供:表示搜索器的 best-effort(可能未覆盖到 N)
|
||||
- `overflowMode: 'ELLIPSIS' | 'CLIP' | 'SYSTEM_DEFAULT'`
|
||||
- `availableWidth: number`
|
||||
- `maxLines: number`
|
||||
- `lang: 'TC' | 'EN'`
|
||||
- `context: 'APP' | 'WIDGET'`
|
||||
- `ellipsisToken: string`(推荐 `"…"`,配置化)
|
||||
- `measure?: { contextProfile; fontSpec; measureWidthImpl; widgetEnableMeasure? }`(用于“加省略号后再测量”)
|
||||
- `reason: 'NO_CANDIDATE' | 'WIDTH_UNKNOWN' | 'TOO_LONG' | 'WIDOW' | 'PARTICLE' | string`
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
统一返回:
|
||||
|
||||
- `result: { lines: string[]; wrappedText: string; meta: { fallback_type; overflow_type; reason } }`
|
||||
|
||||
其中:
|
||||
|
||||
- `fallback_type`: `NONE | RELAX_RULES | SYSTEM_DEFAULT`
|
||||
- `overflow_type`: `NONE | ELLIPSIS | CLIP`
|
||||
|
||||
## 3. 核心行为细则(对应文档 12.x)
|
||||
|
||||
### 3.1 overflow 判定
|
||||
|
||||
在本模块内不重新跑搜索,仅基于输入判断:
|
||||
|
||||
- 若 `partialLayout` 覆盖到 `N`(即最后一行 `end==N`),则 overflow_type=NONE
|
||||
- 否则为 overflow,按 overflowMode 执行
|
||||
|
||||
### 3.2 SYSTEM_DEFAULT(12.4A)
|
||||
|
||||
- 返回 `lines=[原文单行]`,`wrappedText=原文`
|
||||
- `meta.fallback_type='SYSTEM_DEFAULT'`
|
||||
- `meta.overflow_type='NONE'`(因为不再输出算法换行;由 UI 决定是否完全交给系统)
|
||||
- `meta.reason=输入 reason`
|
||||
|
||||
### 3.3 CLIP
|
||||
|
||||
- 若 `partialLayout` 有 lines:取前 `maxLines` 行,重组 `wrappedText=lines.join('\n')`
|
||||
- 若无:返回单行原文
|
||||
- `meta.overflow_type='CLIP'`,`fallback_type='NONE'`,`reason=输入 reason`
|
||||
|
||||
### 3.4 ELLIPSIS(12.3/12.5)
|
||||
|
||||
仅处理最后一行:
|
||||
|
||||
1. 选取 baseLines:
|
||||
- 优先使用 `partialLayout.lines`(若为空则把全文当单行)
|
||||
- 截断到 `maxLines`(只在最后一行做 ellipsis)
|
||||
2. 清理规则(固定,确定性):
|
||||
- 不输出 `" …"`:最后一行末尾空白先 trim
|
||||
- 不输出 `",…"/"。…"`:若最后一行末尾是常见 TC 标点(`,。!?;:、`),则移除该标点再加 ellipsis
|
||||
3. 宽度检查(12.3):
|
||||
- 若提供测量能力且启用:
|
||||
- 重新测量 `lastLine+ellipsisToken`
|
||||
- 若超宽:按语言回退单位移除 token 再加省略号并重测
|
||||
- EN:按整词(token)
|
||||
- TC:按 grapheme(token)
|
||||
- 若测量不可用:不做重测,直接输出(meta.reason 仍保留)
|
||||
|
||||
> 说明:首版不做“避免截断 emotionPhrase 的整段前移/整段省略”,该策略可在后续结合 scoring 决策升级,但必须保持确定性。
|
||||
|
||||
## 4. 代码落位(客户端)
|
||||
|
||||
- `client/src/features/textWrap/overflow/`
|
||||
- `types.ts`
|
||||
- `ellipsis.ts`
|
||||
- `fallback.ts`(入口:applyOverflowFallback)
|
||||
- `index.ts`
|
||||
- `__tests__/overflowFallback.test.ts`
|
||||
|
||||
## 5. 测试计划(Vitest)
|
||||
|
||||
- SYSTEM_DEFAULT:lines 单行,meta.fallback_type=SYSTEM_DEFAULT
|
||||
- CLIP:超过 maxLines 时截断行数
|
||||
- ELLIPSIS:
|
||||
- 末尾空白去除,不输出 `" …"`
|
||||
- 末尾 TC 标点去除,不输出 `",…"`
|
||||
- 测量超宽时按 token 回退直到不超宽(用 mock measureWidthImpl)
|
||||
|
||||
## 6. 完成定义(DoD)
|
||||
|
||||
- 三种 overflowMode 行为稳定且可解释
|
||||
- ELLIPSIS 的清理与回退测量实现完毕(可测)
|
||||
- 输出 meta 字段满足文档 12/14 的治理需求
|
||||
|
||||
52
spec_kit/Text Wrap/modules/overflow-fallback/spec.md
Normal file
52
spec_kit/Text Wrap/modules/overflow-fallback/spec.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# overflow-fallback(子模块规范)
|
||||
|
||||
## 子模块名称
|
||||
|
||||
overflow-fallback(溢出与兜底)
|
||||
|
||||
## 目标描述
|
||||
|
||||
统一定义“无解/溢出”时的行为与返回语义,确保:
|
||||
|
||||
- APP 与 WIDGET 在无法覆盖全文时表现一致且可解释
|
||||
- ELLIPSIS 的字符、清理规则与再次测量约束统一
|
||||
- SYSTEM_DEFAULT 的返回语义明确(算法层不擅自改变 UI 行为)
|
||||
|
||||
## 输入/输出定义
|
||||
|
||||
### 输入
|
||||
|
||||
- `tokens: Token[]`
|
||||
- `partialBest?: Layout`(搜索器找到的最佳 partial 或 best effort)
|
||||
- `overflowMode: 'ELLIPSIS' | 'CLIP' | 'SYSTEM_DEFAULT'`
|
||||
- `availableWidth: number`
|
||||
- `maxLines: number`
|
||||
- `lang: 'TC' | 'EN'`
|
||||
- `context: 'APP' | 'WIDGET'`
|
||||
- `ellipsisToken: string`(推荐 `"…"`,必须配置化)
|
||||
- `measureWidth?: fn`(用于“加省略号后再测量”)
|
||||
|
||||
### 输出
|
||||
|
||||
- `result: { lines: string[]; wrappedText: string; meta: { fallback_type: 'NONE' | 'RELAX_RULES' | 'SYSTEM_DEFAULT'; overflow_type: 'NONE' | 'ELLIPSIS' | 'CLIP'; reason: string } }`
|
||||
|
||||
## 验收标准(可验证)
|
||||
|
||||
- **overflow 判定一致**:
|
||||
- 搜索无法在 `<=maxLines` 覆盖到结束边界 N,即为 overflow
|
||||
- **ELLIPSIS 规则一致**:
|
||||
- 仅处理最后一行
|
||||
- 加省略号后必须再次检查不超宽(必要时回退移除 token 再加省略号)
|
||||
- EN 回退单位为整词;TC 回退单位为 grapheme
|
||||
- 清理规则:不输出 `" …"`;不输出 `",…"/"。…"`(按配置决定是否移除末尾标点再加省略号,但必须固定)
|
||||
- **SYSTEM_DEFAULT 语义一致**:
|
||||
- 仍返回 `lines/wrappedText`
|
||||
- 仅在 meta 标记 `fallback_type=SYSTEM_DEFAULT`
|
||||
- UI 可依据 meta 决定是否完全交给系统排版
|
||||
- **确定性**:同输入同输出(含 meta)
|
||||
|
||||
## 依赖与关联
|
||||
|
||||
- **依赖**:`core-contract`(重组)、`width-measurement`(测量/降级)、`scoring-tiebreak`(避免截断短语的策略可复用评分)
|
||||
- **被依赖**:`search-engine-app`、`search-engine-widget`、`integration`、`golden-tests`
|
||||
|
||||
65
spec_kit/Text Wrap/modules/overflow-fallback/tasks.md
Normal file
65
spec_kit/Text Wrap/modules/overflow-fallback/tasks.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# overflow-fallback(任务清单)
|
||||
|
||||
> 目标:统一实现 `ELLIPSIS/CLIP/SYSTEM_DEFAULT` 的溢出与兜底语义(文档 12.x),输出稳定可解释的 meta。
|
||||
|
||||
## 0. 对齐与准备
|
||||
|
||||
- [x] 阅读并对齐口径
|
||||
- [x] 阅读 `spec_kit/Text Wrap/modules/overflow-fallback/spec.md`
|
||||
- [x] 阅读 `spec_kit/Text Wrap/modules/overflow-fallback/plan.md`
|
||||
- [x] 阅读 `设计说明文档/文档换行算法.md` 的 12.x/14 章节
|
||||
- [x] 创建客户端模块目录
|
||||
- [x] 新建 `client/src/features/textWrap/overflow/`
|
||||
|
||||
## 1. 类型与对外接口
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/overflow/types.ts`
|
||||
- [x] 定义 `OverflowMode = 'ELLIPSIS' | 'CLIP' | 'SYSTEM_DEFAULT'`
|
||||
- [x] 定义 `FallbackType = 'NONE' | 'RELAX_RULES' | 'SYSTEM_DEFAULT'`
|
||||
- [x] 定义 `OverflowType = 'NONE' | 'ELLIPSIS' | 'CLIP'`
|
||||
- [x] 定义 `OverflowReason`(至少包含:NO_CANDIDATE/WIDTH_UNKNOWN/TOO_LONG/WIDOW/PARTICLE)
|
||||
- [x] 定义 `PartialLayoutInput`:`breaks` + `lines[{start,end,text?}]`
|
||||
- [x] 定义 `ApplyOverflowFallbackInput/Result`
|
||||
- [x] 新建 `client/src/features/textWrap/overflow/index.ts`
|
||||
- [x] 统一导出 types 与入口 `applyOverflowFallback()`
|
||||
|
||||
## 2. ELLIPSIS 核心逻辑
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/overflow/ellipsis.ts`
|
||||
- [x] 实现 `cleanLineBeforeEllipsis(line, lang)`:
|
||||
- [x] trim 末尾空白,避免 `" …"`
|
||||
- [x] TC:若末尾是 `,。!?;:、`,移除该标点,避免 `",…"`
|
||||
- [x] 实现 `applyEllipsisToLastLine(...)`:
|
||||
- [x] 仅处理最后一行
|
||||
- [x] EN 回退单位=整词;TC 回退单位=grapheme
|
||||
- [x] 若提供测量能力:加省略号后必须重新测量,超宽则循环回退再测量
|
||||
- [x] 若测量不可用:直接输出(确定性)
|
||||
|
||||
## 3. 入口:applyOverflowFallback
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/overflow/fallback.ts`
|
||||
- [x] 实现 `applyOverflowFallback(input)`:
|
||||
- [x] overflow 判定:partialLayout 是否覆盖到 N
|
||||
- [x] `SYSTEM_DEFAULT`(12.4A):返回单行原文 + meta.fallback_type=SYSTEM_DEFAULT
|
||||
- [x] `CLIP`:截断到 maxLines
|
||||
- [x] `ELLIPSIS`:调用 ellipsis 逻辑
|
||||
- [x] meta 输出:`fallback_type/overflow_type/reason`
|
||||
|
||||
## 4. 单测(Vitest)
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/overflow/__tests__/overflowFallback.test.ts`
|
||||
- [x] SYSTEM_DEFAULT 语义
|
||||
- [x] CLIP 截断行为
|
||||
- [x] ELLIPSIS 清理规则(不输出 `" …"`、不输出 `",…"`)
|
||||
- [x] ELLIPSIS 测量回退:mock measureWidthImpl,让超宽时按 token 回退直到不超宽
|
||||
|
||||
## 5. 收尾
|
||||
|
||||
- [x] 跑测试与类型检查
|
||||
- [x] `npm test`
|
||||
- [x] `npx tsc --noEmit`
|
||||
- [x] 将本 `tasks.md` 全部勾选完成
|
||||
- [x] 更新 `spec_kit/overview.md`
|
||||
- [x] 标记 `overflow-fallback` 已完成编码(阶段性)
|
||||
- [x] 写入变更文件清单
|
||||
|
||||
Reference in New Issue
Block a user