Skip to main content

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}/src may collapse a platform display name and its login with a raw ?? / || expression (displayName ?? login, display_name || username, and the mirror order). Use @lumio/ui's formatPlatformName({ displayName, login, platform }, t) for the composed string, or isLocalizedPlatformName(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 corrected reason.
  • 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.