Skip to content

feat(ax): item 19 screen-reader 纯文本渲染 (无 ANSI 装饰) + 装饰 char 替换 (CC 2.1.208, #109) - #110

Merged
dahai80 merged 1 commit into
mainfrom
feat/screen-reader-plaintext-item19
Aug 21, 2026
Merged

feat(ax): item 19 screen-reader 纯文本渲染 (无 ANSI 装饰) + 装饰 char 替换 (CC 2.1.208, #109)#110
dahai80 merged 1 commit into
mainfrom
feat/screen-reader-plaintext-item19

Conversation

@dahai80

@dahai80 dahai80 commented Aug 21, 2026

Copy link
Copy Markdown
Owner

item 19 — screen-reader 纯文本渲染 (无 ANSI 装饰) + 装饰 char 替换

Closes #109。P4.1 item 19 剩余 gap: Ink 纯文本渲染 (无 ANSI 装饰) + Unicode 装饰 char 降 ASCII, screen-reader (prefersReducedMotion) 激活时生效。默认 off = byte-identical

设计: 2 choke point (in-function gate, 非 global chalk.level=0)

ANSI gate (src/ink/colorize.ts):

  • colorize() / applyTextStyles()screenReaderAnsiGateActive gate, 激活时返 raw string (零 ANSI escape)
  • applyColor 路由 colorize 自动继承
  • in-function gate 只影响 Ink 渲染路径, 不动非 Ink chalk (telemetry main.tsx / gracefulShutdown / markdown.ts 独立 import chalk) — 比 global chalk.level=0 (波及 11+ 文件, 违 surgical) 精准
  • applyScreenReaderAnsiGate(active) API 供运行时切换

装饰 char gate (src/constants/figures.ts):

  • getFigures() lazy factory: 首调用读 getInitialSettings().prefersReducedMotion, 返 frozen UNICODE_SET (默认, byte-identical 现常量) 或 ASCII_SET (降级: ●→*, ↑→^, ○→o, ×→x, ...)
  • 缓存 keyed on reducedMotion boolean, 变化时重算
  • createRequire 懒加载 settings — 打破 figures → settings → settings/types → PermissionMode → figures (PAUSE_ICON) 循环 (top-level import 触发 TDZ Cannot access 'PAUSE_ICON' before initialization)
  • 现有 export const (BLACK_CIRCLE 等) 保持不变, 未迁移消费方仍 Unicode

3 消费方迁移 + 2 inline box-drawing 站

文件 改动
EffortIndicator.ts EFFORT_*fig.EFFORT_*
ContextVisualization.tsx 12 tree-branch literal → TREE_BRANCH module-scope const (reducedMotion → - )
MarkdownTable.tsx //box border → VBAR/HBAR/BORDERS module-scope const (reducedMotion → +/-/| ASCII)

取舍 (Rule 2/3 surgical)

  • Spinner literal 未迁移: React Compiler memo block ($[0] === react.memo_cache_sentinel = compute-once 常量), settings-dependent 值风险 stale cache on mid-session toggle。defer
  • figures npm pkg (figures.pointer/tick) 未 gate — 保 Unicode, defer
  • indent 保持原风格: colorize.ts/figures.ts/EffortIndicator.ts/MarkdownTable.tsx 2-space/no-semi/single-quote (现文件既定风格, biome 配置 tab/double/semi 未曾对这些文件强制 — 避 biome --write 误重格式化扩 diff)

测试 (2 新文件, 14 case, 全 pass)

  • colorizeScreenReader.test.ts (9 case): gate OFF (chalk.level=3 force → ANSI emitted, length > 1) vs gate ON (raw, zero ANSI === "x"), early-return 不破, env entry-point chain 锁定
  • figuresScreenReader.test.ts (4 case): default byte-identical 现常量, env-on ASCII 降级, 缓存失效, ASCII/UNICODE 同键集

验证

  • bun run typecheck — 0 errors
  • bun test src/__tests__/ — 228 pass, 0 fail (无回归)
  • bun run build + bun run build:dev:full — 全 pass
  • 手工 smoke: default BLACK_CIRCLE=⏺ + colorize 输出 ANSI; FUSION_AX_SCREEN_READER=1 BLACK_CIRCLE=* + colorize 返 raw (零 ANSI) ✓
  • biome issue count 3 = 3 baseline (无新增)

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

…1.208, #109)

P4.1 item 19 剩余 gap: Ink 纯文本渲染 (无 ANSI 装饰) + Unicode 装饰 char
降 ASCII, 在 screen-reader (prefersReducedMotion) 激活时生效, 默认 off
保持 byte-identical。

两 choke point (in-function gate, 非 global chalk.level=0):
- src/ink/colorize.ts: colorize()/applyTextStyles() 加 screen-reader ANSI
  gate, 激活时返 raw string (零 ANSI escape)。applyColor 路由 colorize
  自动继承。in-function gate 只影响 Ink 渲染路径, 不动非 Ink chalk
  (telemetry/gracefulShutdown/markdown.ts 独立 import)。
- src/constants/figures.ts: getFigures() lazy factory, 首调用读
  prefersReducedMotion, 返 frozen UNICODE_SET (默认, byte-identical 现常量)
  或 ASCII_SET (降级)。createRequire 懒加载 settings 打破
  figures↔settings↔PermissionMode 循环 (PAUSE_ICON TDZ)。

3 消费方迁移 + 2 inline box-drawing 站:
- EffortIndicator.ts: EFFORT_* → fig.EFFORT_*
- ContextVisualization.tsx: 12 └ tree-branch literal → TREE_BRANCH
  module-scope const (reducedMotion → '- ')
- MarkdownTable.tsx: │/─/box border → VBAR/HBAR/BORDERS module-scope
  const (reducedMotion → +/-/| ASCII)

测试 (2 新文件, 14 case):
- colorizeScreenReader.test.ts: gate OFF (chalk.level=3 force, ANSI emitted)
  vs gate ON (raw, zero ANSI), early-return 不破, env chain 锁定
- figuresScreenReader.test.ts: default byte-identical 现常量, env-on ASCII
  降级, 缓存失效, ASCII/UNICODE 同键集

默认 off = byte-identical; screen-reader 激活 (FUSION_AX_SCREEN_READER=1
或 settings.axScreenReader) 剥 ANSI + 降 ASCII。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dahai80
dahai80 merged commit 4c18010 into main Aug 21, 2026
1 check passed
@dahai80
dahai80 deleted the feat/screen-reader-plaintext-item19 branch August 21, 2026 01:06
dahai80 added a commit that referenced this pull request Aug 21, 2026
…112)

二次规范化缓存: messages 严格 append-only, 已存对象引用稳定 → 按前缀引用
增量复用 normalized 输出, 仅尾部续算, 避免 O(n) 每 render/append 重 flatMap
27k 消息。compact/rewind 整体替换 → 前缀引用不匹配 → 全量重算 (自动失效,
无 stale)。

实现:
- messages.ts: 抽 normalizeMessagesCore(messages, seedIsNewChain) 返回
  {normalized, isNewChain}, 行为与原内联 loop 完全一致 (isNewChain 单调
  false→true + deriveUUID 确定性)。normalizeMessages 变薄 wrapper, 9 其它
  调用方零变化。
- normalizeMessagesCache.ts (新): 前缀引用增量缓存 NormalizedCacheState
  {sourceRef, segmentCounts, normalized, isNewChain}。逐 source message 记
  段长 (1 msg flatMap → N normalized), 按 segmentCounts 对齐复用前缀,
  seed isNewChain 续算尾部, UUID 与全量重算 byte-identical。
- Messages.tsx:464: useRef 持缓存, useMemo [messages] 内增量调用后写回,
  输出再 filter(isNotEmptyMessage)。
- 7 case 单测: baseline 等价 / 增量前缀复用 / isNewChain 跨边界续传 /
  compact 替换失效 / 部分前缀 / 空数组 / 段长正确。26 expect。

验证: typecheck 0 err; 235 test 全过无回归; build:dev OK; biome 无 err
(仅 test noNonNullAssertion warning, 与现有 test 风格一致)。

:598 层增量 + item 17 全屏完整历史推迟 (issue #111): reorderMessagesInUI
签名含 synthetic (尾部 append) + applyGrouping 全数组 resultsByToolUseId
+ buildMessageLookups orphan-marker messages.at(-1) 三处跨边界使前缀不可
定稿, 非 append-only 安全缓存。本 PR 仅 normalize 层。

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

item 19 — screen-reader 纯文本渲染 (无 ANSI 装饰) + 装饰 char 替换 (CC 2.1.208, §664)

1 participant