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: d1fcaf770d55487d93286a2e455134c8
Branch: dev/2-p299-roster-text-bound
Base: 7f7b42d
At: 2026-10-31T15:33:37Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
#1912 gave the host one exported cut(text, max), #1916 swept nine remaining code-unit cuts, and #1933 / #1938 / #1940 cleaned the outbound share envelope and the peer labels. The two storage writers for a group's notice and name were on none of those lists: both still slice on UTF-16 units, so a pair that straddles the bound is written into state.json as a lone surrogate.
What and where (live main 7f7b42d6)
| file:line | cut |
|---|---|
core/src/host/roster.js:265 |
const cleanNotice = text => String(text ?? '').trim().slice(0, ROOM_NOTICE_MAX) — 2000, trim then slice, no toWellFormed |
core/src/host/roster.js:268 |
unquote(text).replace(/\s+/g, ' ').slice(0, ROOM_NAME_MAX) — 60, same shape |
Limits are ROOM_NOTICE_MAX = 2000 / ROOM_NAME_MAX = 60 in core/src/host/constants.js:46-47. roster.js already imports cut (:5) and uses it at :89, :203; only these two writers still cut raw. Call sites: :334 (create-room join) and :405 (updateRoom's patch.notice / patch.name).
#1938's repair runs on the outbound envelope copy only (core/src/shared/room-share.js:233), so the value kept in rooms[].notice / rooms[].name is never made well formed; a guest then receives a body cut one unit short and it never matches the host's.
Reproduce (on the base, three failures, all red for the right reason)
updateRoom({ notice: 'a'.repeat(1999) + '😀' + 'tail' })→ stored2000units ending61 d83d,isWellFormed() === false,JSON.stringify→"…aaa\ud83d", UTF-8 tailef bf bd. The same input throughcut(text, 2000).textis1999units and well formed.updateRoom({ name: 'r'.repeat(59) + '😀' })→ stored60units ending72 d83d, ill formed;cut(name, 60).textis59units and well formed.- The same two through the
createRoom/update_grouppath.
Reachable from two everyday surfaces: the group-name field in core/src/client/details-drawer.js:317-322 has no maxLength (saveName → actions.updateRoom → channel.js:371 → roster.js:405), and the notice field in core/src/client/group-settings.js:200 counts code units (maxLength: 2000), as does a Bot's update_room (core/src/tools.js:550).
Expected
- Both writers store
cut(...).text: a stored notice is at most 2000 units and well formed, a stored name at most 60 and well formed, and the stored value is a code-point prefix of the input (one unit shorter when a pair straddles the bound — the pair is dropped, never split). - Everything else in the two functions unchanged: the two limits, the
trim, the?? ''fallback, the whitespace collapse, theunquote, and the duplicate-name rejection. - A well-formed input is stored byte-identical to today.
- Tests must cover both writers and both paths (
updateRoomandcreateRoom/update_group), each with a red on base and green on the fix, plus the two positive cases (1999a + 😀 + TAIL→1999a;59r + 😀→59r).
Out of scope
ingestSeatShare'stextwriters (core/src/host/room-membership.js, around:1990/:2003/:2022) — same family, separate card.- Reporting the truncation back to the caller (
changesgains a "shortened to N" line) — enhancement, separate card. - The outbound share envelope — already repaired by
#1938.
Dedup
Checked live main 7f7b42d6 (grep -n 'slice(0' core/src/host/roster.js → the two rows above) and the closed issues: #1916 swept nine cuts and neither roster writer is among them; #1933, #1937, #1938, #1940 are the envelope/label side; #1923 is peer-name bounds. Open issues carry no same-topic card.
Scope
Two lines in core/src/host/roster.js, tests, the patches/ds-bot mirrors and one changelog fragment. XS.
Activity
HugoXK commentedon Oct 31, 2026
Heartbeat
Written-by: FlyBot Lead
Still-working: d1fcaf770d55487d93286a2e455134c8
At: 2026-10-31T15:46:06Z