developer-experience: fix tui pad() to use real Unicode display width - #21
Draft
ruvnet wants to merge 2 commits into
Draft
developer-experience: fix tui pad() to use real Unicode display width#21ruvnet wants to merge 2 commits into
ruvnet wants to merge 2 commits into
Conversation
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
#20, PR #21) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017w6WfMFNz1yaHrMk8ttdm2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/Verdicttext contains wide (CJK/fullwidth) or emoji Unicode characters, when the TUI dashboard'spad()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 exactlyWdisplay 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(newcodePointWidth/displayWidthfunctions,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 ownbenchentrypoint).8ce3857)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 --versionconfirmed available (0.9.2) — this is a scope decision, not an availability blocker.Evidence
Reproduced live tonight (grade A, first-hand): a synthetic CJK
Findingbroke 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 passesn<=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 theuflag), 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'.lengthregression 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
Verify:
sha256sum docs/dream-cycle/2026-08-19-developer-experience-report.md(pre-Witness-section content, same convention as prior nights) thenprintf '%s%s' "b3f64b5641107de39d60bd35bd2798f1492cef7876b8199a725a097fe46288a9" "8ce385786faa5e63cc0e7105cc6e96f663a51f07" | sha256summust equal the witness above.Merge Policy
Draft — human review required. This PR does not carry the
automerge-safelabel. It touches rendering behavior intui.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-coloroutput. Confirmed pre-existing (reproduces identically on the parent commit) — recommended as the nextdeveloper-experience/tuicandidate.Generated by Claude Code