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

137 lines
5.0 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.
# grapheme-segmentation技术计划
## 1. 计划目标
基于 `spec.md``设计说明文档/文档换行算法.md v1.2.1`在客户端侧JS/TS落地**可复用且确定性**的 TC grapheme cluster字符簇分割能力用于 Text Wrap 的 TC tokens 生成,确保:
- 不拆 surrogate pair、ZWJ、VS16、肤色修饰符、国旗regional indicator flags、组合字符`é`
- 同输入同输出clusters 内容与顺序完全一致)
- 输出边界可作为 TC 断点边界(后续模块仅在 cluster 边界断行)
- 提供可解释 meta采用何种策略、是否降级
## 2. 默认技术决策(本计划采用)
> 说明:本模块要解决的是“分割口径”,不引入排版/搜索逻辑。
- **优先策略**:若运行环境支持 `Intl.Segmenter`,优先使用:
- `new Intl.Segmenter('zh-Hant', { granularity: 'grapheme' })`
- 取其 `segment(text)``segment` 字段作为 clusters
- **兜底策略(两种实现路径,默认选 A**
- **方案 A推荐**:引入轻量依赖 `grapheme-splitter` 作为 fallback避免手写不完整的 Unicode 规则导致漏拆/误拆
- **方案 B无依赖兜底**:实现“最低可用”分割(满足文档列出的组合不拆),但不承诺覆盖所有 Unicode 边界规则(风险较高)
> 本计划默认采用 **方案 A**。若后续明确“禁止新增依赖”,再切换到方案 B 并补齐更多回归。
## 3. 目录与产物
本子模块目录:
- `spec_kit/Text Wrap/modules/grapheme-segmentation/spec.md`
- `spec_kit/Text Wrap/modules/grapheme-segmentation/plan.md`(本文)
建议未来代码落位(实现阶段落地):
- `client/src/features/textWrap/grapheme/`
- `segmentGraphemes.ts`
- `strategies/intlSegmenter.ts`
- `strategies/fallback.ts`
- `__tests__/segmentGraphemes.test.ts`
## 4. API 设计(实现阶段的稳定契约)
实现一个最小可用纯函数:
- `segmentGraphemes(text: string, mode: 'PREFERRED' | 'FALLBACK') => { clusters: string[]; meta: { strategy: 'INTL_SEGMENTER' | 'FALLBACK'; hadFallback: boolean } }`
设计约束:
- `clusters.join('') === text`(不允许丢字符/改字符顺序)
- `clusters` 为空数组时必须是 `text==''`(不允许把空白当成 cluster 误输出)
## 5. 实现步骤(按落地顺序)
### 5.1 优先策略Intl.Segmenter
- 检测 `globalThis.Intl?.Segmenter` 是否可用
- 若可用且 `mode='PREFERRED'`
- 使用 `granularity='grapheme'` 分割
- 输出 `meta.strategy='INTL_SEGMENTER'``hadFallback=false`
注意:
- 需要确认 Expo/RN 的运行时是否始终具备 `Intl.Segmenter`(不同 JS 引擎/版本可能差异)
- 即使可用,也必须通过回归样例验证“不拆”要求
### 5.2 兜底策略Fallback
当出现以下任一情况时进入 fallback
- `mode='FALLBACK'`
- `Intl.Segmenter` 不存在
- `Intl.Segmenter` 运行抛错/返回异常结果(如空、丢字符)
#### 方案 A`grapheme-splitter`
- 新增依赖:`grapheme-splitter`
- 使用其分割能力输出 clusters
- 输出 `meta.strategy='FALLBACK'``hadFallback=true`
#### 方案 B最低可用手写规则仅在禁止依赖时启用
实现最低要求:
- 合并 surrogate pair
- 合并 ZWJ sequence`U+200D` 连接)
- 合并 variation selector`U+FE0F`
- 合并 skin tone modifier`U+1F3FB..U+1F3FF`
- 合并 regional indicator flags两两成对
- 合并组合字符combining marks与预组合等价形式至少覆盖 `e\u0301`
风险提示:
- 该实现容易漏掉其他扩展 grapheme cluster 规则;需要更高的测试覆盖与持续维护
## 6. 回归用例与测试计划Vitest
### 6.1 必须覆盖的样例(文档要求)
以下输入必须“不拆”为单个 cluster
- `👨‍👩‍👧‍👦`
- `🇸🇬`
- `👍🏽`
- `😮‍💨`
- `é`(至少覆盖 `e\u0301` 组合形式)
断言:
- `clusters.length === 1`
- `clusters[0] === input`
### 6.2 基础性质测试(建议)
- **可逆性**`clusters.join('') === input`
- **确定性**:同输入多次调用输出完全一致
- **空字符串**`'' -> []`
### 6.3 跨策略一致性(建议)
在支持 `Intl.Segmenter` 的环境中:
- 同一输入在 `PREFERRED``FALLBACK` 两种模式下输出 clusters 应一致
- 若出现差异,必须新增回归样例并明确差异原因(并在上层通过 configVersion 治理)
## 7. 性能与安全
- 单次分割复杂度应接近 \(O(n)\)
- 对超长文本(例如 > 2000 code units建议在上层模块触发裁剪或打点本模块仅保证不崩溃
- 任何异常必须被捕获并降级到 fallback保证“可用性优先”
## 8. 完成定义DoD
- `segmentGraphemes()` 在本地可运行,并通过必测回归样例
- 输出 meta 能区分 `INTL_SEGMENTER``FALLBACK`
- 单测覆盖:必测样例 + 可逆性 + 确定性
- 文档口径与实现一致不拆要求、fallback 触发条件、返回结构)