Repository navigation
Commit ae7ea46
authored
Cut the five text bounds in turns.js on code points, not UTF-16 units (#1985)
Fixes #1984
Owner: Dev-3
Written-by: Dev-3
Reviewer: Birfy
Claim-Id: ac124b1661744f348c1a09cabe4a35ca
Tip: 3837d5c
Base: 5b72400
Tree: 22f824b
## What changed
Five places in `core/src/host/turns.js` bounded text with a bare
`String.prototype.slice`, which cuts
on UTF-16 units: `turnFailure` (:36, 500), `failureLine` (:45, 200), the
owner-visible step line
(:303, 79 + `…`), an approval card's description and command (:363, 300
/ 400) and the reason a failed
turn leaves in the inbox (:405, 160). A bound that landed between the
halves of a surrogate pair stored
`U+D83D` at the tail — `U+FFFD` once encoded — in state, in the line the
owner reads, and in the reason
the next turn sees. Each now goes through `cut(text, max)` from
`core/src/shared/text-boundary.js`
(`host/text.js` already re-exports it, so the existing `textOf` import
gained a name).
No budget moved: 500, 200, 79, 300, 400, 160 are unchanged. The step
line still renders at 80 columns;
its `…` is now appended only when `cut` actually dropped something
(`cut().cut`) instead of being
guessed from `line.length > 80`, so a line that measures 80 but loses a
pair no longer claims to be
whole. `:630` is the same field as `:45` cut a second time and is
untouched, as is everything else in the
file.
Three tests, one per reading path: `diagnostics.test.js` drives the
exported `turnFailure` /
`failureLine`; `group-status.test.js` drives the real route (`openTurn`
+ `tool/call` + `askApproval`,
then reads `state.work.bots.b.step` and
`state.work.approvals[0].detail`); `dead-turn.test.js` drives
`turn/end` and reads `state.work.ended[0].failed`. Each asserts the same
three things — `isWellFormed()`,
a code-point prefix of the input, and the unchanged length budget — and
each carries a control case.
`docs/subsystems/core-host.md` (owned by `core/src/host/`) gains one
sentence naming the five bounds;
`changelog.d/1984-turns-code-point-bounds.fixed.md` is the fixed entry.
## Red first (base `5b72400c`, source untouched, tests applied only)
Inputs put the cut exactly on the high surrogate (`'a'.repeat(N-1) + '😀'
+ 'tail'`, N = the site's
budget), driven through the real functions and routes, never a private
helper:
```
diagnostics.test.js # tests 18 # pass 16 # fail 2
not ok 12 - a failure message cut at its 500-unit bound keeps the pair whole
the message is not well formed: "aaa\ud83d"
not ok 13 - a failure line cut at its 200-unit message bound keeps the pair whole
the line is not well formed: "aaa\ud83d"
group-status.test.js # tests 25 # pass 23 # fail 2
not ok 2 - an approval card cuts its description and command at their bounds without splitting a pair
the description is not well formed: "aaa\ud83d"
not ok 20 - a step line cut at its bound keeps the pair whole, and the ellipsis only shows when it cut
the step is not well formed: "aa\ud83d…"
dead-turn.test.js # tests 6 # pass 5 # fail 1
not ok 3 - a failed turn records its reason cut at 160 units without splitting a pair
the stored reason is not well formed: "aaa\ud83d"
```
Five failures, five sites. With the source fix applied and nothing else
changed the same three files are
`18/18`, `25/25`, `6/6`.
## Reverting one fix at a time (base `5b72400c`, fix applied except the
site shown)
| site | what was reverted | red |
| --- | --- | --- |
| 1 | `:36` → `error.message.trim().slice(0, 500)` | `diagnostics` not
ok 12 only |
| 2 | `:45` → `failure.message.slice(0, 200)` | `diagnostics` not ok 13
only |
| 3 | `:303` → `{ text: line.slice(0, 79), cut: line.length > 79 }` |
`group-status` not ok 20 only |
| 4 | `:363` → `text.trim().slice(0, 300)` | `group-status` not ok 2
only |
| 5 | `:405` → `(failure.message \|\| failure.code).slice(0, 160)` |
`dead-turn` not ok 3 only |
Each revert reddens exactly its own case and nothing else, so no case
passes on another site's fix.
## Verification at the tip
Head SHA: `3837d5c72c60f11a323a946ffa2376a933e4b2f8` — tree
`22f824b4343ac2526c7c31d1bb8e13c6b689d5f9`.
The run below happened on that same tree at
`d8c7d6d85cbf0e3b89414d3f91d179cefcc55051` (the pre-push commit, clean
working tree);
the push helper rebuilt the commit on base `5b72400c` and proved the
tree identical
(`tree ok 22f824b (9 paths)`), so the pushed head carries exactly
what was verified.
Ran:
```
node scripts/ds-bot-patches.mjs generate # rewrote the series from core/ (0001, 0007, files/test/dead-turn.test.js)
node scripts/ds-bot-patches.mjs check # 3 lines ok: pin, 10 patches apply in order, core = ds-bot + patches
node scripts/ds-bot-patches.mjs canonical # ok — generate is a no-op
node .github/scripts/changelog.mjs check # 13 fragments well formed
TZ=UTC ARCH_DOCS_BASE=5b72400c6fef51c9a2145b78de9530c338853b74 npm run verify # exit 0
```
`verify` at that tip: core **2387/2387** (`base 5b72400` measures
**2382**, so this branch is exactly
+5 tests), social 541, tree 127, timeline 135, workflow 336,
`.github/scripts` 563, interop 37,
live-e2e 24 — 0 failures anywhere. Core lint 73 warnings / 0 errors;
plugin lint 36 / 9 / 2 / 16
warnings, 0 errors; `arch-docs: OK (9 changed file(s))`.
Not run (and why):
- the smoke test: it needs a `../deepseek-harness` checkout or
`DSH_HARNESS`, and this machine has none.
The change is confined to bounds inside `src/host/turns.js`; nothing in
the module list or prompt path
changes shape.
- CI's `package` job (pack check, typecheck, actionlint): not part of
`npm run verify`.
- Windows: this branch touches no path handling.
## Checkpoint
<!-- flybot:checkpoint -->
```json
{
"pr": 1985,
"claim": "Dev-3",
"issue": 1984,
"summary": "The five text bounds in core/src/host/turns.js now cut through the shared cut helper instead of a bare slice. A bound that landed inside a surrogate pair used to leave a lone high surrogate in state, in the owner-visible step line and in a failed turn's stored reason. Each site keeps its original budget and now yields a well-formed code-point prefix of its input. The step line's ellipsis follows the helper's cut flag rather than a length guess.",
"files": ["core/src/host/turns.js", "core/test/group-status.test.js", "core/test/diagnostics.test.js", "core/test/dead-turn.test.js", "changelog.d/1984-turns-code-point-bounds.fixed.md"],
"checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=5b72400c npm run verify (exit 0, core 2387/2387)", "result": "green", "at": "3837d5c72c60f11a323a946ffa2376a933e4b2f8" },
"next": "none — ready for review at this tip"
}
```
<!-- /flybot:checkpoint -->1 parent 5b72400 commit ae7ea46
9 files changed
Lines changed: 211 additions & 21 deletions
File tree
- changelog.d
- core
- src/host
- test
- docs/subsystems
- patches/ds-bot
- files/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
| 36 | + | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
300 | 300 | | |
301 | 301 | | |
302 | 302 | | |
303 | | - | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
304 | 306 | | |
305 | 307 | | |
306 | 308 | | |
| |||
360 | 362 | | |
361 | 363 | | |
362 | 364 | | |
363 | | - | |
| 365 | + | |
364 | 366 | | |
365 | 367 | | |
366 | 368 | | |
| |||
402 | 404 | | |
403 | 405 | | |
404 | 406 | | |
405 | | - | |
| 407 | + | |
406 | 408 | | |
407 | 409 | | |
408 | 410 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
104 | 117 | | |
105 | 118 | | |
106 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
234 | 234 | | |
235 | 235 | | |
236 | 236 | | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
237 | 260 | | |
238 | 261 | | |
239 | 262 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
135 | 158 | | |
136 | 159 | | |
137 | 160 | | |
| |||
447 | 470 | | |
448 | 471 | | |
449 | 472 | | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
450 | 491 | | |
451 | 492 | | |
452 | 493 | | |
| |||
0 commit comments