# 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）、组合字符（如 `é`）
- 同输入同输出（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́`（至少覆盖 `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 触发条件、返回结构）

