jeo-code 0.8.31 → 0.8.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +1 -1
- package/src/tui/components/width.ts +85 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
|
|
8
8
|
|
|
9
|
+
## [0.8.32] - 2026-07-20
|
|
10
|
+
_gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Display-width no longer over/under-counts emoji sequences, so box borders and hard-wraps never misalign when one appears in a message** (`src/tui/components/width.ts`) — gjc parity (`#1996` "preserve … Unicode grapheme semantics for … VS16 emoji presentation, ZWJ, keycaps, and emoji modifiers"). The old `visibleWidth`/`truncateToWidth` summed `charWidth()` per CODE POINT, which is wrong for any sequence a terminal renders as ONE glyph: `❤️` (heart + VS16) summed to 1 column where terminals render 2; `👍🏽` (thumbs-up + Fitzpatrick skin-tone modifier) summed to 4 where terminals render 2; a 4-person ZWJ family emoji (`👨👩👧👦`) summed to 8 where terminals render 2 — quantified live against the actual old per-code-point path before fixing (not asserted from inference). New `nextGraphemeCluster()` absorbs VS15/VS16, Fitzpatrick modifiers (`U+1F3FB`-`U+1F3FF`), keycap combiners (`U+20E3`), combining marks, and ZWJ-joined chains into one atomic width-2 (or width-1 for VS15 text-presentation) unit; `visibleWidth`/`truncateToWidth` (and therefore `wrapTextWithAnsi`, built on both) now consume it instead of raw per-code-point `charWidth()`, and `truncateToWidth` can no longer split one of these sequences in half at a wrap/truncation boundary (a second corruption vector the old code point-by-point cut was exposed to). Korean/CJK/Hangul-only text (no emoji sequences) was already correct and is byte-identical before/after.
|
|
14
|
+
|
|
15
|
+
### Verified
|
|
16
|
+
- `test/width.test.ts` — 11 pass (was 9): new `nextGraphemeCluster` unit coverage for the VS15/VS16/Fitzpatrick/keycap/ZWJ cases above, plus a `truncateToWidth` atomicity test proving a 2-wide cluster is dropped whole (never half) at a 1-column budget.
|
|
17
|
+
- Live (unmocked) reproduction: quantified the OLD per-code-point sum against the NEW `visibleWidth` for all 4 sequence classes — heart 1→2, thumbs-up+modifier 4→2, ZWJ family 8→2, keycap 1→2 — confirming this was a real, sometimes-severe (family emoji off by 6 columns) miscount, not a hypothetical.
|
|
18
|
+
- Live (unmocked) box-render smoke test: wrapped a real mixed Korean/emoji message (family emoji, VS16 heart, skin-tone thumbs-up) into a 20-column box via `wrapTextWithAnsi` — every wrapped line's `visibleWidth` fit the declared budget with correct padding, confirming no overflow/misalignment end-to-end (not just at the unit level).
|
|
19
|
+
- Full `bun test` — 3092 pass / 0 fail across 304 files (2 transient parallel-load flakes on the first run — unrelated `/model` pin routing + a Node `--inspect-brk` debug-session test, neither touching `width.ts` — both isolated-pass and a full clean re-run confirmed no regression).
|
|
20
|
+
- `bun run typecheck` — no errors.
|
|
21
|
+
|
|
9
22
|
## [0.8.31] - 2026-07-20
|
|
10
23
|
_`web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else._
|
|
11
24
|
|
package/README.ja.md
CHANGED
|
@@ -283,11 +283,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
283
283
|
## 変更履歴 (Changelog)
|
|
284
284
|
|
|
285
285
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
286
|
+
- **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
|
|
286
287
|
- **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
|
|
287
288
|
- **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
|
|
288
289
|
- **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
|
|
289
290
|
- **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
|
|
290
|
-
- **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
|
|
291
291
|
|
|
292
292
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
293
293
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -284,11 +284,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
284
284
|
## 변경 이력 (Changelog)
|
|
285
285
|
|
|
286
286
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
287
|
+
- **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
|
|
287
288
|
- **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
|
|
288
289
|
- **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
|
|
289
290
|
- **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
|
|
290
291
|
- **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
|
|
291
|
-
- **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
|
|
292
292
|
|
|
293
293
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
294
294
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -291,11 +291,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
291
291
|
## Changelog
|
|
292
292
|
|
|
293
293
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
294
|
+
- **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
|
|
294
295
|
- **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
|
|
295
296
|
- **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
|
|
296
297
|
- **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
|
|
297
298
|
- **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
|
|
298
|
-
- **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
|
|
299
299
|
|
|
300
300
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
301
301
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -283,11 +283,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
283
283
|
## 更新日志 (Changelog)
|
|
284
284
|
|
|
285
285
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
286
|
+
- **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
|
|
286
287
|
- **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
|
|
287
288
|
- **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
|
|
288
289
|
- **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
|
|
289
290
|
- **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
|
|
290
|
-
- **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
|
|
291
291
|
|
|
292
292
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
293
293
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -58,6 +58,85 @@ export function charWidth(cp: number): number {
|
|
|
58
58
|
}
|
|
59
59
|
return 1;
|
|
60
60
|
}
|
|
61
|
+
const ZWJ = 0x200d;
|
|
62
|
+
const VS15 = 0xfe0e; // variation selector: force TEXT (narrow) presentation
|
|
63
|
+
const VS16 = 0xfe0f; // variation selector: force EMOJI (wide) presentation
|
|
64
|
+
const FITZPATRICK_MIN = 0x1f3fb; // Emoji Modifier Fitzpatrick Type-1-2 .. Type-6
|
|
65
|
+
const FITZPATRICK_MAX = 0x1f3ff;
|
|
66
|
+
const KEYCAP_COMBINING = 0x20e3; // combining enclosing keycap (1️⃣, #️⃣, *️⃣)
|
|
67
|
+
|
|
68
|
+
function isFitzpatrick(cp: number): boolean {
|
|
69
|
+
return cp >= FITZPATRICK_MIN && cp <= FITZPATRICK_MAX;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** True for combining/zero-width marks that attach to a preceding base rather than
|
|
73
|
+
* starting a new cluster on their own (diacritics etc.) — the zero-width branch of
|
|
74
|
+
* `charWidth` MINUS variation selectors (VS15/VS16 are handled explicitly above,
|
|
75
|
+
* since they change the CLUSTER's width rather than merely contributing 0 to it). */
|
|
76
|
+
function isAttachableCombining(cp: number): boolean {
|
|
77
|
+
return (cp >= 0x0300 && cp <= 0x036f)
|
|
78
|
+
|| (cp >= 0x1ab0 && cp <= 0x1aff)
|
|
79
|
+
|| (cp >= 0x1dc0 && cp <= 0x1dff)
|
|
80
|
+
|| (cp >= 0x20d0 && cp <= 0x20ff)
|
|
81
|
+
|| cp === 0xfeff;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function codePointAndLength(s: string, i: number): { cp: number; length: number } {
|
|
85
|
+
const cp = s.codePointAt(i)!;
|
|
86
|
+
return { cp, length: cp > 0xffff ? 2 : 1 };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the visual CLUSTER starting at `s[i]` (not an SGR escape or tab — callers
|
|
91
|
+
* handle those separately) into its total consumed UTF-16 length and DISPLAY width,
|
|
92
|
+
* absorbing variation selectors, skin-tone modifiers, keycap combiners, combining
|
|
93
|
+
* marks, and ZWJ-joined sequences into ONE atomic unit (gjc parity: "preserve …
|
|
94
|
+
* Unicode grapheme semantics for … Korean tone marks, VS16 emoji presentation, ZWJ,
|
|
95
|
+
* keycaps, and emoji modifiers").
|
|
96
|
+
*
|
|
97
|
+
* Per-code-point summing (the old behavior) over- or under-counts every one of these:
|
|
98
|
+
* 👍🏽 (thumbs-up + Fitzpatrick modifier) summed to 4 columns where a terminal renders
|
|
99
|
+
* ONE 2-wide glyph; ❤️ (heart + VS16) summed to 1 where a terminal renders 2; a 4-person
|
|
100
|
+
* ZWJ family emoji summed to 8 where a terminal renders 2. Every one of those is a real,
|
|
101
|
+
* visible box-border/wrap misalignment ("깨짐") the instant such a sequence appears in a
|
|
102
|
+
* message — this collapses each sequence to the width a terminal ACTUALLY renders it at,
|
|
103
|
+
* atomically, so `truncateToWidth`/`wrapTextWithAnsi` also never split one in half at a
|
|
104
|
+
* boundary (this is a pragmatic subset of UAX #29 grapheme-cluster breaking scoped to
|
|
105
|
+
* the sequences a chat/coding TUI actually sees — not a general script-shaping engine).
|
|
106
|
+
*/
|
|
107
|
+
export function nextGraphemeCluster(s: string, i: number): { length: number; width: number } {
|
|
108
|
+
const base = codePointAndLength(s, i);
|
|
109
|
+
let width = charWidth(base.cp);
|
|
110
|
+
let len = base.length;
|
|
111
|
+
let j = i + base.length;
|
|
112
|
+
const peek = (): number => s.codePointAt(j) ?? -1;
|
|
113
|
+
|
|
114
|
+
if (peek() === VS15) { width = 1; len += 1; j += 1; }
|
|
115
|
+
else if (peek() === VS16) { width = 2; len += 1; j += 1; }
|
|
116
|
+
|
|
117
|
+
while (isFitzpatrick(peek())) {
|
|
118
|
+
const mod = codePointAndLength(s, j);
|
|
119
|
+
len += mod.length;
|
|
120
|
+
j += mod.length;
|
|
121
|
+
width = 2;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (peek() === KEYCAP_COMBINING) { len += 1; j += 1; width = 2; }
|
|
125
|
+
|
|
126
|
+
while (isAttachableCombining(peek())) {
|
|
127
|
+
const mark = codePointAndLength(s, j);
|
|
128
|
+
len += mark.length;
|
|
129
|
+
j += mark.length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (peek() === ZWJ && j + 1 < s.length) {
|
|
133
|
+
const joined = nextGraphemeCluster(s, j + 1);
|
|
134
|
+
len += 1 + joined.length;
|
|
135
|
+
width = 2;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { length: len, width };
|
|
139
|
+
}
|
|
61
140
|
|
|
62
141
|
/**
|
|
63
142
|
* Visible display width of a string: SGR escapes count 0, tabs advance to the
|
|
@@ -81,9 +160,9 @@ export function visibleWidth(s: string): number {
|
|
|
81
160
|
i += 1;
|
|
82
161
|
continue;
|
|
83
162
|
}
|
|
84
|
-
const
|
|
85
|
-
w +=
|
|
86
|
-
i +=
|
|
163
|
+
const cluster = nextGraphemeCluster(s, i);
|
|
164
|
+
w += cluster.width;
|
|
165
|
+
i += cluster.length;
|
|
87
166
|
}
|
|
88
167
|
return w;
|
|
89
168
|
}
|
|
@@ -130,9 +209,9 @@ export function truncateToWidth(s: string, cols: number): string {
|
|
|
130
209
|
cw = TAB_STOP - (w % TAB_STOP);
|
|
131
210
|
chunk = "\t";
|
|
132
211
|
} else {
|
|
133
|
-
const
|
|
134
|
-
cw =
|
|
135
|
-
chunk =
|
|
212
|
+
const cluster = nextGraphemeCluster(s, i);
|
|
213
|
+
cw = cluster.width;
|
|
214
|
+
chunk = s.slice(i, i + cluster.length);
|
|
136
215
|
}
|
|
137
216
|
if (w + cw > limit) break;
|
|
138
217
|
out += chunk;
|