Skip to content

host: the memory-review and room-context one-line bounds still judge in UTF-16 units #2339

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 3a11368f0c774f6391e17a68f5af2c08
Branch: dev/4-p514-code-point-bounds
Base: 98a6aec
At: 2026-11-04T02:40:10Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead

What happens today

Two one-line bounds still decide "is this text within the limit?" in UTF-16 code units and then cut on code points, so a line that already fits gets cut anyway — and every astral character (emoji, CJK extension, any surrogate pair) doubles the count that decides:

  • core/src/host/memory.js:1271 — ends(text, max): if (text.length <= max) return text. Used at :1282 for the user's line in a memory review with REVIEW_USER_LINE_MAX = 3_000 (:64); the prompt is assembled at :1353/:1463.
  • core/src/host/room-context.js:45 — oneLine(value, max): if (flat.length <= max) return flat. Call sites pass 40/80/24/60 (:173-175, :180, :191, :245, :263) for a board card's holder and title.

fits() (in core/src/shared/text-boundary.js, used by this same file family since #2326) counts code points; String.prototype.length counts UTF-16 units. The two disagree by exactly the number of astral characters, and the fast path takes the unit-based answer.

Reproduction (clean 98a6aec6511c43db73ffba620f57bd1756b67e1f, tests only)

Red first, --test-name-pattern 'P-514': 0 success + 0 cancelled + 4 failure.

  • ends('😀'.repeat(2900), 3000) — 2,900 code points / 5,800 units, and fits() says it is within the bound — comes back as User: + 1,497 emoji + …(middle cut)… (1,519 characters). About 1,400 characters are lost from a line that fits.
  • oneLine('😀'.repeat(40), 40) → 19 emoji + …; oneLine('😀'.repeat(80), 80) → 39 emoji + … — a board card loses the holder's name and the title it was given.
  • Control: ends('a'.repeat(2900), 3000) returns its input unchanged today, so the boundary is in the unit of the decision, not in the cut.

After the fix the same four assert 4 of 4 green: the 2,900-emoji line comes back whole (2,900 characters / 5,800 units), the 40- and 80-character board lines keep their full holder and title, and the ASCII control is byte-identical.

Expected

Judge with fits(...) from core/src/shared/text-boundary.js; do not move the cut (codePointStart / cut stay exactly as they are). ASCII behaviour is unchanged by construction: for text without astral characters fits is length <= max.

Scope

Two source files, +2/−2 each, plus tests. core/src/host/memory.js and core/src/host/room-context.js are both carried by patches/ds-bot/** (0001-core-fixes.patch, files/src/host/room-context.js), so the PR runs generate and includes the regenerated bundle: 9 files +141/−9 in total, tier T2 by the live risk-rules.json at 98a6aec6 (two core/src/host/** files; tests and patches are T1). It does not touch core/client.js.

Not in scope: core/web-search.js:97 has the same shape, but it is a vendor copy byte-identical to ds-bot/web-search.js and has no patches/ds-bot/files/ entry — changing it needs a new patch first.

Also noted while verifying (unreachable today, but it will bite the next caller): clip(text) without max returns 'hello… (cut)' because fits(text, undefined) is always false; the only importer, core/src/host/parts.js (:344, :355, :465), always passes max, the same contract gap #1918 closed for the reason text.

Duplicate check (live main 98a6aec6)

  • room-context.js's fast path was added by #1961 (issue #1960, "the room-context cuts keep a lone surrogate") and memory.js ends()'s by #1921 (issue #1918); both PRs changed only the cut, not the decision — this is the half they left.
  • #1958/#1959 are the other oneLine copies in core/src/host/tasks.js / work-dispatch.js, different files.
  • Code search on live 98a6aec6 for .length <= max in core/src/host/ returns exactly these two files and nothing else; no open issue or PR covers this face.

Activity

  1. HugoXK commented on Nov 4, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 3a11368f0c774f6391e17a68f5af2c08
    Reason: merged as 7226d74 (PR #2341)
    At: 2026-11-04T03:17:48Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions