Skip to content

developer-experience: fix tui pad() to use real Unicode display width - #21

Draft
ruvnet wants to merge 2 commits into
mainfrom
dream/2026-08-19-developer-experience
Draft

developer-experience: fix tui pad() to use real Unicode display width#21
ruvnet wants to merge 2 commits into
mainfrom
dream/2026-08-19-developer-experience

Conversation

@ruvnet

@ruvnet ruvnet commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Nightly Dream Cycle, 2026-08-19. DEEP=developer-experience, SCAN=cli,tui. Full report: docs/dream-cycle/2026-08-19-developer-experience-report.md. Issue: #20.

Hypothesis

Given a ledger row whose Finding/Deep/Verdict text contains wide (CJK/fullwidth) or emoji Unicode characters, when the TUI dashboard's pad() measures and truncates content using a real Unicode-aware display-width function instead of raw UTF-16 .length, then every rendered dashboard line should stay exactly W display columns wide (no broken right border, no premature wrap) — relative to the current .length-based implementation, which under-pads wide-character rows — subject to: ASCII-only ledgers render byte-identical output to today, all existing tests stay green, and truncation still ends in a single landing at exactly the target width. Frozen before implementation.

Candidate

+~140/-5 across packages/cli/src/tui.ts (new codePointWidth/displayWidth functions, pad() rewritten to use them, both exported for direct testing) + packages/cli/src/index.test.ts (8 new tests). One conceptual change: real Unicode display-width instead of raw string length, for both padding and truncation. Truncation now walks by code point (can't land mid surrogate-pair) and preserves any ANSI codes that follow the cut point instead of raw-index-slicing a string that may contain embedded escape sequences.

Evaluation Receipt

Real evaluator: npm test (vitest, this repo's own bench entrypoint).

Baseline (8ce3857) Candidate
Tests 96 103 (+7, 0 removed/modified)
Result 96 passed 103 passed

Baseline

Parent commit 8ce385786faa5e63cc0e7105cc6e96f663a51f07, 96/96 tests passing, clean build.

Darwin Lineage

Not run — DARWIN=not-applicable. No meaningful mutable population for a single pure width-classification function (same judgment as the 08-13/08-14/08-15 nights). npx @metaharness/darwin --version confirmed available (0.9.2) — this is a scope decision, not an availability blocker.

Evidence

Reproduced live tonight (grade A, first-hand): a synthetic CJK Finding broke the dashboard's right-border alignment pre-fix. displayWidth('性能改善') (4 CJK ideographs) = 8 vs. the old .length = 4 — the two diverge exactly where the bug lives. Post-fix, every rendered line (including the CJK row, truncated or not) has identical real display width — verified by test, not by inspection.

Reward-Hack Check

Independent critic (separate subagent, no shared context, given only the literal diff and full repo read access): CLEAR of reward-hacking. Scope confirmed clean (only tui.ts + its test file touched), no new exec/network/fs/dependency surface, no test weakened or self-referentially vacuous — two assertions are ground-truthed against known terminal widths, not just internal round-tripping.

Two issues surfaced by the critic: (1) pad(s, n<=0) on non-empty input still emits a 1-column instead of 0-width — pre-existing (identical in the old code), not a regression, no real call site ever passes n<=0, not fixed. (2) The truncation tokenizer's . (no dotAll) silently dropped literal newlines — a genuine new divergence from the old raw-slice behavior — fixed (.[^], still one full code point under the u flag), regression test added, real evaluator re-run afterward: 96→103, 0 regressions.

Security Review

Pure string/regex logic. No new exec, network, filesystem, or credential surface. No new dependency (file is dependency-free by design). No LLM calls. No evaluator/gate/threshold file touched.

Regression Analysis

0 pre-existing tests modified or removed. All 96 baseline tests pass unchanged; 7 new tests added. ASCII-only ledgers (the real ledger today) render byte-identical output to before — verified by a displayWidth('ascii') === 'ascii'.length regression test.

ADR

None. This is a bug fix to existing rendering behavior, not a new architectural decision.

Gist

No gh gist create / gist tool available in this environment (consistent with every prior night). Report committed instead. GIST=LOCAL.

Issue

#20

Witness

report_sha256 : b3f64b5641107de39d60bd35bd2798f1492cef7876b8199a725a097fe46288a9
session_commit: 8ce385786faa5e63cc0e7105cc6e96f663a51f07
witness       : 25a4b37f6cc3ddfafb2479be3106e289bdfc27557c9f9376e9ed8bd452bb5488

Verify: sha256sum docs/dream-cycle/2026-08-19-developer-experience-report.md (pre-Witness-section content, same convention as prior nights) then printf '%s%s' "b3f64b5641107de39d60bd35bd2798f1492cef7876b8199a725a097fe46288a9" "8ce385786faa5e63cc0e7105cc6e96f663a51f07" | sha256sum must equal the witness above.

Merge Policy

Draft — human review required. This PR does not carry the automerge-safe label. It touches rendering behavior in tui.ts, the same file flagged for a second, still-open finding tonight (verdictColor()'s no-color leak) — the session never applies that label itself and never merges.

Also discovered tonight, not fixed (disclosed as a follow-up, see report's Scan Findings): verdictColor() in the same file bypasses the no-color proxy, leaking a raw ANSI escape into --no-color output. Confirmed pre-existing (reproduces identically on the parent commit) — recommended as the next developer-experience/tui candidate.


Generated by Claude Code

claude added 2 commits August 19, 2026 09:21
pad() measured content width via .length on the ANSI-stripped string
(raw UTF-16 code units), not terminal display columns. CJK/fullwidth/
emoji Finding text under-pads and breaks the dashboard's box-drawing
alignment (reproduced live 2026-08-14 in issue #8, left unfixed then).

Replace with a small Unicode-aware displayWidth()/pad(): classify each
code point as width 0 (combining marks/ZWJ/variation selectors/skin-
tone modifiers), width 2 (CJK/Hangul/fullwidth/most emoji), or width 1
otherwise. Truncation now walks by code point (can't land mid
surrogate-pair) and keeps ANSI codes that follow the cut point instead
of raw-index-slicing a string that may contain embedded escapes.

Independent adversarial critic found one real issue: the tokenizer's
`.` (no dotAll) silently dropped literal newlines when truncation
triggered, a new divergence from the old raw-slice behavior. Fixed by
switching to `[^]` so line terminators survive as ordinary tokens.

npm test: 96 -> 103 (+7, 0 regressions). No new exec/network/fs/dep
surface. Issue #20.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w6WfMFNz1yaHrMk8ttdm2
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.

2 participants