Skip to content

room-share: the joined notice and the copied pane line are cut on UTF-16 units, and the pane line is unbounded #1933

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: 287704d751b440548bfbd9049eaa67aa
Branch: dev/2-p285-room-share-unbounded
Base: 77c9bc6
At: 2026-10-31T12:31:47Z

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

Written-by: FlyBot Lead

Two bounds on text written by the other side of a share still count UTF-16 units instead of code points, so a cut can leave half a surrogate pair in the room. Both were found while reviewing #1929 and left out of it on purpose.

1. The joined notice — core/src/host/room-membership.js, noticeOf: data.notice.replace(…).trim().slice(0, 2000) cuts at 2,000 UTF-16 units. A friend's notice whose 2,000th unit lands inside an emoji or ZWJ sequence is stored and rendered half cut.

2. The copied pane line — same file, applyShareHistory: the line written to room.log goes through cut(String(entry.text), HISTORY_TEXT), but the same loop then calls showShareLine({ text: entry.text }) with the raw sender string, and showShareLine applies no bound at all. The pane line is therefore unbounded (it can carry the transport's full 4,000 units) and can also end inside a pair, so the pane and the log disagree about the same line.

Expected.

  • Both bounds cut on code point boundaries and keep their current values: the notice stays at the envelope's own notice bound (NOTICE_MAX, 2,000) and the copied pane line uses the same HISTORY_TEXT the adjacent cut already uses. No new number is introduced; the two sides must share one constant so they cannot drift apart.
  • The pane line equals the stored prefix: what showSharedLine receives is the same bounded text that went into room.log.
  • The line operation is untouched: its text legitimately rides the transport bound (4,000), so the bound belongs at the applyShareHistory call site, not inside showShareLine.

Acceptance (red first, one test per site).

  1. A join whose notice is 'a'.repeat(1999) + '😀' + 'b'.repeat(400) parses and stores a notice that is well formed (no lone surrogate) and is the expected prefix.
  2. A copied history entry whose stored line is already a 1,999-unit prefix must be shown as exactly that prefix — not the sender's raw 2,405 units, and not a half pair.
  3. Mutation, one at a time: put .slice(0, 2000) back in noticeOf → only case 1 red; pass entry.text back to showShareLine → only case 2 red.
  4. fitJoin's notice shrink loop is not touched by this change.

Scope. core/src/host/room-membership.js (two call sites) + core/test/remote-room.test.js (the two cases) + the mirrors under patches/ds-bot/files/.

Dedupe: checked open issues — #1932 covers fitJoin dropping the notice when the roster is full (a different site: envelope fitting, not code-unit cutting), #1930/#1928 are closed or unrelated. No open issue covers these two.

Activity

  1. HugoXK commented on Oct 31, 2026

    @HugoXK
    ContributorAuthor

    Heartbeat

    Written-by: FlyBot Lead
    Still-working: 287704d751b440548bfbd9049eaa67aa
    At: 2026-10-31T12:37:31Z

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