Platform Name Drift Check
A fail-closed CI gate that keeps platform-name rendering on one shared helper. On
a platform with localized display names (Twitch's CJK feature) a chatter can
carry a display name like こさむん while their login stays the ASCII handle
kosamun. The right behaviour — render こさむん (kosamun) only when the name
"really looks like that", and only on a platform where the display name is a
freely-editable field over an immutable login (Twitch today), and never on a
normal Cruex / cruex chatter — lives in one place: @lumio/ui's
formatPlatformName / isLocalizedPlatformName (ZAF-1452). A hand-rolled
displayName ?? login fallback re-introduces the bug this helper fixes: it either
shows the login on every chatter (noise), drops it entirely (loses the handle a
moderator needs), or renders こさむん (こさむん) on Kick/YouTube.
The check lives in scripts/check-platform-name-drift.mjs (pure Node, no
dependencies) and runs as the Platform Name Drift job in
.github/workflows/ci.yml on every PR.
The invariant
No source file under
apps/{web,admin,stats}/srcmay collapse a platform display name and its login with a raw??/||expression (displayName ?? login,display_name || username, and the mirror order). Use@lumio/ui'sformatPlatformName({ displayName, login, platform }, t)for the composed string, orisLocalizedPlatformName(displayName, login, platform)when you only need the decision.
Comparisons (!==, ===), ternaries and helper calls are never flagged — only a
raw ?? / || collapse between a display-name token and a login token, in either
order. A member-access prefix on the right operand (?? user.login) is tolerated.
Always pass the source platform. The bracket is gated on a third ANDed
condition (founder ruling ZAF-1452, 2026-09-27): the trigger fires only when
platform is on the helper's LOCALIZED_DISPLAY_NAME_PLATFORMS allowlist —
["twitch"] today. An unknown or absent platform is fail-closed (no
bracket). This is why the platform must be threaded through: a localized display
name only exists where the platform lets a user edit it independently of the login
(Twitch). On YouTube "channel title ≠ handle" is the normal case (ZAF-1478), so
bracketing there would fire on every non-Latin-titled channel — the exact "nicht
bei jedem chatter" the founder rejected. Kick writes one value into both name
fields and is a permanent no-op.
The decorateLoginForPlatform @ prefix (こさむん (@kosamun)) is retained in
the helper but currently unreachable: while the allowlist is Twitch-only the
YouTube bracket never triggers. Adding "youtube" to the allowlist would restore
it with no other change — the @ lives only in the helper, never in the
stored login (ZAF-1478).
Why a guard and not just a sweep
Migrating N call sites by hand and hoping the (N+1)-th uses the helper is a snapshot, not a state. The guard makes "app-wide" a state: a new collapse fails the PR.
The ratchet allowlist
The pre-existing collapse sites are baselined in
scripts/platform-name-drift-allowlist.json, keyed by { file, expr } (the
matched text with internal whitespace collapsed). The guard fails only on a
new { file, expr } pair that is not on the baseline; the baseline may only
shrink.
- Migrating a site (ZAF-1452 sweep tasks): replace the collapse with the helper and delete its allowlist entry in the same change.
- A genuinely out-of-scope site — machine-consumed login (filter matching,
@-mention insertion, dedupe keys) or a Lumio-account identity that has no platform login (ZAF-1452 §D4/§D6) — stays listed with a correctedreason. - Never add a new entry to unblock new code. Use the helper.
Running it locally
node scripts/check-platform-name-drift.mjs --self-test # exercise the detector
node scripts/check-platform-name-drift.mjs # check the tree
Exit codes: 0 clean, 1 a new collapse was found, 2 internal/format error.