Repository navigation
Description
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: 4efa9ce025e6459c8c64b0a488fe6bd8
Branch: dev/2-room-share-body-cut
Base: 881ac04
At: 2026-10-31T11:31:29Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
What
A room-share envelope is the one place this home's text leaves for another owner's home. Three of its text fields still stop on a UTF-16 unit, so a bound that lands between the halves of a surrogate pair carries half of one across the wire — the receiving home parses it, stores it in its room log and renders U+FFFD:
core/src/shared/room-share.jsfitTransport— thebody.textshrink loop (body.text.slice(0, body.text.length - extra)) that fits a line into the 4,000-unit transport.core/src/shared/room-share.jshistoryLine—.slice(0, HISTORY_TEXT)on a copied chat line (2,000).core/src/host/room-membership.jsshareHistory—seatText(line).slice(0, HISTORY_TEXT)before the envelope is even built.core/src/host/room-membership.jsapplyShareHistory—String(entry.text).slice(0, HISTORY_TEXT)on the receiving side.
The 18,000 cap on payload.text in encodeRoomShare is the same shape and is fixed for consistency, but it cannot be shown red on its own: fitTransport always shrinks the body to 4,000 first, so that bound is unreachable.
Reproduction (live main 881ac046)
// shared/room-share.js, run on its own
encodeRoomShare({ op: 'line', roomId: 'r1', text: 'a'.repeat(3821) + '👨👩👧'.repeat(4) })
// -> wire exactly 4000 units; parseRoomShare(...).text is 3844 units,
// isWellFormed() === false, lone high surrogate at index 3843
encodeRoomShare({ op: 'join', roomId: 'abc123', seq: 3,
history: [{ who: 'Ann', text: 'a'.repeat(1999) + '😀' + 'b'.repeat(20) }] })
// -> parseRoomShare(...).history[0].text is 2000 units ending in '\ud83d'Expected
Every value the envelope carries is length <= max, isWellFormed() === true, has no U+FFFD, and is a byte-for-byte UTF-8 prefix of what was typed. The numeric caps do not change (2,000 for a history line, 4,000 for the transport, 18,000 for the body).
Tests
New cases in core/test/remote-room.test.js, each pinned by content and each red on main with only the tests added:
fitTransport: the WZJ sample above → wire<= 4000, parsed text well formed, no U+FFFD, prefix of the input.historyLine: a join envelope copies a line on a code point boundary.- host path:
shareHistory→encodeRoomShare→parseRoomShare(asserthistory[0].text === 'a'.repeat(1999)— by content, never=== max). - receiving side:
applyShareHistorybounds a copied line on a code point boundary. - mutation
HISTORY_TEXT 2000 → 1000and each site reverted toslicemust turn exactly its own case red.
Not in this change
room-share.js's fallback branch slicesroomIdto 64; it needs every droppable field gone and the wire still over 4,000 — 696 probe payloads (roomId 1/64/120, text 1…30000, every clip-capped field full) triggered it 0 times, largest wire exactly 4,000. Dead branch, and no test could pin a change to it.remote-chat.js's 40-unit friend-name cut anddelivery.js's 60-unitcleanNamefence attribute are tracked in the display-length family (P-282), not here.
Dedupe
Checked before opening: no open or closed issue covers this (search for room-share 18000, history TEXT 2000 surrogate, fitTransport → only PR #1927, which is the peer-name change and does not touch the body or the history path). Live main grep confirms the four sites are still slice at 881ac046.
Claim record
Claimed-by: FlyBot Lead
This block is FlyBot Lead's own claim record for this issue — it is not a third-party claim.
Activity
room-share: cut the cross-owner body and history lines on code point …