Skip to content

fix(shared): 中文风险文本按显示列宽折行,分享卡不再溢出 - #248

Merged
helsome merged 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-share-card-cjk-wrap
Oct 18, 2026
Merged

helsome merged 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-share-card-cjk-wrap

Conversation

@wxrbyte

@wxrbyte wxrbyte commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Closes #247

问题

packages/shared/src/export/card.ts 的 wrapText() 按空白分词(text.split(/\s+/))。中文句子没有词间空格,整句被当成一个不可断的「单词」原样返回,于是:

  • 多长都不折行;
  • 也永远不触发 MAX_CHARS = 112 的截断分支,… 不会追加。

zh-CN 是受支持的 locale,kernelHost.ts 的 reportToShareCard(redactForShare(report)) 会走到这里。59 个全角字的常见风险句以单行 767px 溢出 640px 卡片的 576px 内容区(超出约 33%)。

改动

packages/shared/src/export/card.ts

  1. wrapText(text, maxColumns) 改为按显示列宽折行:
    • 新增 FULL_WIDTH_RE / isFullWidth():识别 CJK 表意文字与标点、假名、谚文、全角形式;
    • 新增 displayWidth():全角字计 2 列,与卡片字体栈中的实际推进宽度一致;
    • 新增 atomize():把文本切成「最小不可断原子 + 其前置空白」。断点出现在空白之后与 CJK 字之间,拉丁字符连续段保持为一个原子;
    • 主循环改为比较 displayWidth(joined),词间空白按原样拼接(不再统一替换成单个空格)。
  2. keyRiskLines() 的 112 列预算同步改为列宽口径,并用 clampToColumns() 为省略号预留 1 列——否则截断后再拼 … 会多出 1 列,把两行撑成三行。

保留的语义(均有既有用例守着):

  • 拉丁词内部不断开:wrapText('supercalifragilistic', 5) 仍返回 ['supercalifragilistic'];
  • 词间空白与折行点与原实现一致:wrapText('one two three four', 9) 仍为 ['one two', 'three', 'four'];
  • 空输入返回 [];确定性渲染(相同输入 → 相同字节)不变。

测试

packages/shared/src/export/card.test.ts 新增 3 条:

用例 断言
breaks CJK text between ideographs — it has no whitespace to split on wrapText('中文测试中文测试', 6) → ['中文测', '试中文', '测试']
keeps the whitespace that separates mixed CJK and Latin atoms wrapText('AAPL 上涨明显', 10) → ['AAPL 上涨', '明显']
wraps a whitespace-free CJK key risk into the two-line risk block 59 字中文风险 → 卡上恰好 2 行,每行 ≤ 28 个全角字,且含 …

TDD 证据:

# 修复前
$ bun test packages/shared/src/export/card.test.ts
15 pass / 3 fail     # 三条新用例全红

# 修复后
$ bun test packages/shared/src/export/card.test.ts
18 pass / 0 fail

修复后实测:

wrapText(risk, 56) -> 3 line(s)          # 未截断的 59 字原文,按 28 字/行
卡片 KEY RISK 区渲染行数 = 2               # 卡内已截断,2 行 × 56 列 = 364px < 576px
  y=310 "估值仍处高位而盈利增速与订单动能同步放缓一旦指引下修可能"
  y=334 "触发估值与业绩同步回落须警惕流动性收紧与汇率波动带来的…"

门禁:

  • cd packages/shared && bun run typecheck → 0 error
  • bun test packages/shared/src/export → 31 pass / 0 fail

说明

  • wrapText 的第二个参数由 maxChars 更名为 maxColumns(位置参数,调用方不受影响),因为度量单位从「码元数」变成了「显示列数」。
  • 列宽按 2 计是刻意保守的:ASCII 在 SF Mono 下约 0.6em,全角字为 1em,比值约 1.67;取 2 只会让折行提前,不会溢出。
  • 幅面范围只覆盖 BMP(U+1100–U+FFE6 的常用宽/全角区),足以覆盖实际中文文本;CJK 扩展 B 区及以后的罕见字仍按 1 列计。

wrapText() 按空白分词,中文句子没有词间空格,整句被当成一个不可断的
「单词」原样输出:既不折行,也不会触发 112 字截断分支。59 个全角字因此
以单行 767px 溢出 640px 卡片的 576px 内容区,keyRiskLines() 注释中承诺的
「至多两行」在中文下失效。

改为按显示列宽折行:

- 全角字(CJK 表意文字与标点、假名、谚文、全角形式)计 2 列;
- 在 CJK 字之间提供断点,与日常 CJK 断行规则一致;
- 保留既有语义:拉丁词内部仍不断开,词间空白原样保留,超出预算的单个
  不可断原子仍独占一行。

keyRiskLines() 的 112 列预算同步改为列宽口径,并为省略号预留 1 列,使
「至多两行」的承诺在中文下也成立。

Closes helsome#247

@helsome helsome left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

独立代码审查及精确 HEAD focused 31/0 通过。按当前顺序累计组合完整测试1924 pass / 7 skip / 0 fail、全workspace typecheck、i18n通过,secret扫描零命中。相关货币/排版确定性回归符合本PR范围,不将其声称为全桌面或真实模型E2E。

@helsome
helsome merged commit f5f1f29 into helsome:main Oct 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(shared): 分享卡的中文风险文本不折行,单行溢出卡片右边界

2 participants