Skip to content

[finding] PresenceAvatars 的状态圆点对联合类型外的 status 无颜色兜底(statusColors 查空 → backgroundColor 缺失) #3447

Description

@yinlianghui

实现 #3440(PR #3445)时顺带发现,不改 —— 与该单的 i18n 范围无关,单独记一笔。

packages/collaboration/src/PresenceAvatars.tsx 的状态圆点直接查一张按联合类型建的表:

const statusColors: Record< PresenceUser['status'], string > = {
  active: '#22c55e',
  idle:   '#f59e0b',
  away:   '#94a3b8',
};
// …
backgroundColor: statusColors[user.status],

user.status 落在 'active' | 'idle' | 'away' 之外,查表得 undefined,React 直接省略该行内样式 —— 圆点变成一个 8px 的白边透明圈,不是崩溃,但也不是任何一种「状态」。

为什么这不是纯理论:presence 用户不是本包造的,而是宿主实现的 PresenceSource(PresenceProvider.tsx,WebSocket/SSE 一类传输)推进来的,subscribeTenant/subscribeRecord 的回调只做了 Array.isArray(next) 检查,没有逐条校验 status。类型标注管不到运行时的传输层。#3440 的单据正文本身就猜成了 online/away —— 恰好是一个会漏出联合类型的拼法。

PR #3445 在 tooltip 那一侧显式处理了这件事(未映射的 status 原样渲染成原始字符串,不臆造标签),但颜色这一侧仍无兜底。两侧口径不一:同一个未知 status,文字有话说,圆点没有。

为什么标 finding 而不是缺陷:今天仓内没有任何 PresenceSource 实现(默认是 NOOP_SOURCE),所以现在没有用户撞得到;它随第一个真实 presence 传输接进来才会被激活。严重度交给 triage 判,不由发现者预判。

可能的修法(供 triage 参考,不代表已定):

  • A. 给 statusColors 加一个中性默认色(如 #cbd5e1),statusColors[user.status] ?? DEFAULT。最省事,但等于在消费端加宽容兜底 —— 与 contract-first 相冲。
  • B. 在 PresenceProvider 的订阅回调里把 status 归一化(未知值一律映到 away 或丢弃该用户),让联合类型在进入 React 树之前就真正成立。producer 侧收口,和 PR fix(collaboration,i18n): PresenceAvatars 的三处硬编码英文接入 i18n (#3440) #3445 的显示层兜底不冲突(那一处兜的是「显示原始值而非臆造」,不是「让非法值合法」)。
  • C. 什么都不做,等真实传输落地时连同校验一起设计。

我倾向 B:类型声明的联合应当由一处校验来兑现,而不是让每个下游查表点各自 ?? 一个默认值(那是第二份事实上的契约)。但这属于会改公开行为的取舍,不该由 #3440 顺手带上。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions