Skip to content

core: a peer display name keeps a lone surrogate — cleanPeerName has no toWellFormed() #1940

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: b8a2488115044cdd8d49283bbd413c4f
Branch: dev/2-p294-clean-peer-name
Base: 564a8bc
At: 2026-10-31T14:44:10Z

core/src/shared/remote-label.js:6 cleanPeerName is the twin of cleanName: same sanitise chain (String(...) → strip C0/C1 controls and \u2028\u2029 → strip []"\<>→ collapse whitespace →trim) and the same cut(text, 60), but the chain has **no .toWellFormed()**. #1938added the repair tocleanName and to the share envelope (parseRoomShare→ notice / roster / history). Every peer string that does **not** travel throughparseRoomShare` still reaches a display label with a lone surrogate inside it.

Reproduction (pure function, no fixture)

On live main 12936d207cfb89546d12a68fa9bac71d36d7d82e:

import { cleanPeerName } from './core/src/shared/remote-label.js'

cleanPeerName('Ann\ud83dX')                    // 'Ann\ud83dX'   → isWellFormed() === false
cleanPeerName('a'.repeat(59) + '\ud83d')       // 60 units, last one a lone high surrogate → still false

cut(text, 60) does not split a whole pair, but it also does not repair a lone half, so the half travels on. For contrast, cleanName in delivery.js returns 'Ann\uFFFDX' (well formed) after #1938.

Why it matters

cleanPeerName is the display-name normaliser for peer-controlled strings: client/face-seats.js, client/at-menu.js, client/cells.js, client/user-message.js, host/face-lead.js, host/room-membership.js (roster name/lead/owner, inviterName). Ill-formed UTF-16 reaches visible text and, on the roster/inviter path, text that is assembled next to prompt-facing material — the same class of damage #1937 fixed one layer up, minus the envelope.

Fix direction

Insert .toWellFormed() on the String(name ?? '') result, before the replace chain — exactly what #1938 did in cleanName. cut(text, 60) stays as is: one UTF-16 unit becomes one U+FFFD, so the 60-unit bound and the existing "never split a whole pair" behaviour do not move. Well-formed input must come back byte-identical.

Mirrors that regenerate from core/: core/client.js (bundle), patches/ds-bot/0008-client-bundle.patch, patches/ds-bot/files/src/shared/remote-label.js. Test in core/test/group-bot-label.test.js: lone half → U+FFFD; length preserved; 60-unit bound unchanged; whole pair at the boundary still dropped as a pair; well-formed input unchanged.

Dedupe (live main, 2026-10-31)

Searched open and closed issues for cleanPeerName, toWellFormed, remote-label, surrogate: #1927, #1908, #1917, #1930 (closed) are the cut-on-code-point-boundary fixes; #1937/#1938 (open) is the parseRoomShare + cleanName repair. Nothing covers cleanPeerName's missing repair — this is the remaining sibling.

Scope

core/src/shared/remote-label.js (+1), core/client.js mirror (+1), patches/ds-bot/0008-client-bundle.patch (+1/−1), patches/ds-bot/files/src/shared/remote-label.js, core/test/group-bot-label.test.js (~+20), one changelog.d/ fragment. No behaviour change for well-formed input, no constant or bound touched.

Timing

Branch dev/2-p294-clean-peer-name is cut from main at 564a8bcf6025c6aec6051968f061ee04fd80b3c1 — the merge of #1938 (the #1937 envelope + cleanName repair) — so this one rebases on nothing; #1938 did not touch remote-label.js, so the reproduction above is unaffected by it.

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