Files
ts-mobile-go/docs/UI重构设计规范_审查报告.md
sansenandClaude Fable 5 8716b391f6 工程改进:跨语言契约固化、日志脱敏、状态模型收敛
依据 docs/项目工程改进方案.md 实施(第五梯队 CI/协作除外)。

安全性
- Release 剥离 Log.v/d/i/w(-assumenosideeffects),保留 Log.e
- 全量频道/成员 dump、消息正文、发送者 UID 改为 BuildConfig.DEBUG 保护
  (实测 release dex 中 "Channel: id=" / "Client: id=" / selfId= 等均为 0 命中)

构建产物体积
- .gitignore 补 *.a(保留 .opus/lib 四个 ABI 预编译库)、.opus/install、
  app/libs/*.aar、dnn/torch
- git rm --cached 除名 AAR、.gradle、.opus/{build,install}、dnn/torch
  跟踪体积 50MB → 17.4MB,工作区文件不受影响

跨语言契约(本方案核心)
- 新增 go/teamspeak/contract.go:字段名常量 + BridgeContractVersion 单一事实来源
- 新增契约测试:反射断言常量与 struct tag 一致;Go/Kotlin 共用 golden 样本
- 新增 BridgeContract.kt:启动校验 AAR 契约版本,不匹配则阻止连接
- 修正文档:channel_order 是前驱频道 ID(链表指针)而非排序权重,
  按它数值排序会打乱频道树;ChannelInfo.order 在 Go 侧不存在

状态模型
- ConnectionState 增 Idle 取代 null 编码,connectionState 不再可空
- applyClients 改为按频道差分更新,避免全量刷新导致频道树整体重组
- 频道数据 freshness 统一由 Repository 维护,修复 ViewModel 与 Repository
  两份时间戳不同步导致的重复刷新

UI
- 修复 collectAsState() 在参数位置调用导致的 isSelf 快照失效
- 消除 9 处 !! 断言(ChannelListScreen)
- 硬编码 24.5/14.5dp 与 depth*24 收敛到 UiTokens.Spacing,缩进加 4 层上限

测试
- Kotlin 测试 7 → 106;Go 契约测试新增 24 个用例
- 各覆盖 InputValidator、消息送达确认、僵尸会话过滤、频道差分、
  频道顺序语义、错误分类

其他修复
- classifyError 提取为纯函数并补测试;修复 too many clones (id=521)
  未识别导致英文原文直接暴露给用户

版本号提升至 1.0.13(versionCode 13)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 14:58:15 +08:00

28 KiB
Raw Permalink Blame History

《TS Mobile UI / UX 重构设计规范》审查报告

审查对象:E:\MyProject\ts-mobile-go\docs\TS Mobile UI UX 重构设计规范.md(47 章) 交叉参照:

  • E:\MyProject\ts-mobile-go\docs\UI架构设计.md
  • E:\MyProject\ts-mobile-go\docs\流程\00_总览.md ~ 09_EventBus架构.md
  • E:\MyProject\ts-mobile-go\docs\implementation\00_实施总览.md ~ 12_主题与收尾.md
  • E:\MyProject\ts-mobile-go\CLAUDE.md(已知 SDK 限制)
  • Android 实现:android/app/src/main/java/com/tsmobile/app/(Models、Repository、NavGraph、UiTokens、Shapes、SemanticColors、TSBridge、各 ViewModel 与组件)

审查结论:方向正确,但当前版本不具备落地条件。

"Server 是工作空间 + Current Channel 是核心上下文 + Chat/Channels 只是视图"这个模型判断是对的,比现有的三页跳转(server_config → channel_list → chat)更贴合 TeamSpeak 的使用心智。但文档停留在"理念陈述"层面:状态模型与代码不符、多个核心组件缺数据来源、异常路径几乎空白、Design Token 与现有实现两套命名并存。按此文档开工,实现者会在第 3 天卡住。


一、阻断级缺陷(不补齐无法开工)

A1. 顶层状态模型与代码不一致,且漏掉必然出现的中间态

文档第 3 章只定义 Disconnected / Connecting / Connected 三态,并断言"不应该存在 Connected but no channel"。

实际 data/Models.kt 的 ConnectionState 是 5 态:

文档 代码实际
Disconnected Disconnected(reason, wasKicked)
Connecting 无独立态(连接中由 ViewModel 的 isConnecting 类标志表达)
Connected Connected
— 未定义 Disconnecting
— 未定义 Reconnecting(attempt, maxAttempts, reason)
— 未定义 WaitingForNetwork(since)

更关键的是:"Connected 但没有频道"在真实链路上必然存在,文档把它当成不该出现的状态,但它不是 UI 设计问题,是协议时序问题:

  1. ConnectionState.Connected 之后还有独立的第二层状态 SyncState(ChannelViewModel:Unsynced / Syncing / Synchronized / SyncFailed)。首次同步期间频道树是空的,UI架构设计.md 2.2 节为此专门设计了"正在同步服务器数据..."加载态。新文档完全没提这一层。
  2. Repository.currentChannelId 初值就是 "0";NavGraph.kt 里存在兜底逻辑——当 currentChannelId <= 0 时改用 Repository.getSelfChannelId() 从客户端列表反查。这说明 GetChannelID 失败是已知的真实场景。

需要补的规格:Connected 之后的完整状态矩阵,至少覆盖 Connected × {Unsynced, Syncing, Synchronized, SyncFailed} × currentChannelId ∈ {有效, "0", 反查失败}, 并明确每种组合下 ServerHeader / CurrentChannelBar / Content 区各显示什么(骨架屏?占位名?"正在同步"?)。这是全文档最大的空洞——CurrentChannelBar 被称为"最重要的 UI 组件",却没有定义它在数据未就绪时的样子。

A2. 成员状态系统缺数据来源,且未列入任何优先级

文档第 16 章要求 8 种成员状态:Online / Idle / Away / Muted / Deafened / Speaking / Server Muted / Disconnected。

实际数据能力:

  • ClientInfo 字段仅有 id / nickname / uid / channelId / serverGroups / isSelf——没有 away、idle、muted、deafened、serverMuted、talkPower 任何一项。
  • TSBridge 回调只有 onClientSpeaking(clientID, speaking)(Go 侧判定,Kotlin 不得自行从 PCM 推断)。
  • setRemoteClientMuted 是本地静音远端,不等于对方的 Muted 状态。

也就是说 8 种状态里只有 Speaking 有数据支撑,其余 6 种(Online 可由在场推断)需要改 go/teamspeak/bridge.go、扩展 getClientsJSON 字段、重新编译 AAR。文档第 40~44 章的重构方向和 P0/P1/P2 优先级里完全没有"需要改协议层"这一项,会让排期严重失真。

需要补:每个状态标注「数据已具备 / 需扩展 bridge / 本期不做」,并把 bridge 改造单列为 P0 前置项。

A3. Avatar 体系凭空出现,无任何数据来源与降级规格

文档第 7、15、16、31、39 章大量依赖头像:CurrentChannelBar 的 ● ● ● ● +8、Member Item 的 [Avatar] Alex、Speaking 的 Avatar Ring、第 31 章还定义了三档尺寸令牌(32/40/56dp)。

实际情况:TeamSpeak 协议没有用户头像。ServerInfo.iconId 和 ChannelDetailInfo.iconId 是服务器图标 / 频道图标,不是客户端头像;代码中不存在任何 avatar 相关实现。

文档从头到尾没有说明:头像内容从哪来?无头像时显示什么(昵称首字母?固定图形?服务器组图标?)?头像是否可上传(TS 无此能力)?

这不是细节问题——CurrentChannelBar 的成员预览、Member Item 的主视觉、Speaking 的主要表达方式全部建立在头像上。必须先定义"无头像时的 Avatar 替代方案",否则第 7、15、16 章的示意图无法实现。

建议:改为首字母色块(昵称首字符 + 由 UID 哈希出的稳定背景色),Avatar Ring 表达 Speaking,并把"服务器组徽章"作为唯一的真实图形来源。

A4. PTT 在最终结构里没有位置

文档第 19 章说"PTT 是语音 UI 的核心操作",第 20 章专门定义 PTT 状态,第 39 章组件树里有 VoiceStatus。

但第 38 章"主页面最终结构"的两张完整示意图(Chat / Channels)里没有任何语音或 PTT 元素,第 5 章的 Server Space 结构图里也只有 Current Content。

同时文档第 18 章否定了 Voice 独立页面,第 19 章又把 Voice UI 画成一个大卡片(含 🎙 / ● Connected / [PTT] / Mic / Speaker)——这个卡片挂在哪一层?常驻还是弹出?与底部 BottomNavigation 如何共存?现状 UI架构设计.md 是"底部语音控制栏常驻所有页面 + 展开为语音卡(BottomSheet)",新文档既没说保留也没说取消。

核心操作缺失布局定义,属于必须补齐的阻断项。

附带三个未定义问题:

  • PTT 触发方式(屏内按钮 / 悬浮窗 / 音量键 / 声控)一个都没指定。
  • App 在后台或锁屏时如何发言——全文未涉及后台场景,但代码有 ConnectionService 前台服务与 keepalive 通知,说明后台常驻是真实需求,PTT 的后台可用性是移动端语音客户端的关键设计点。
  • 第 20 章的 Idle → Pressed → Speaking 与代码 VoiceState(Idle / Transmitting / Blocked(reason))不匹配:文档漏了 Blocked(未连接、采集失败、发送异常)及其 UI 表现;且 Speaking 在代码语义里指远端用户在说话(onClientSpeaking),文档却把它当作自己 PTT 的第三态,概念混用需澄清。

A5. Chat 范围收窄会砍掉私聊与服务器聊天,文档未声明取舍

文档第 9 章把 Chat 定义为"当前频道的文字消息流",第 41 章 ServerSpaceState 只有单个 chatMessages,第 46 章 Current View = Chat or Channels。

实际现有能力覆盖三种会话:ChatMessage.targetMode 为 1=私聊 / 2=频道 / 3=服务器;Repository 按 "${targetMode}_${targetId}" 分档归档(每会话上限 500 条);ServerViewModel.sendMessageNotification 会区分标题"频道消息"/"私聊消息";MainActivity.DeepLinkAction 携带 targetMode/targetId,通知点击可直达指定会话。

新架构里私聊和服务器聊天没有任何入口。这会直接导致:

  • 系统通知点击 → 跳转会话的 deep link 断链;
  • Repository 的多会话归档能力被闲置;
  • 从 Member Action Menu 发起私聊的路径消失(第 17 章的菜单里也只有 Poke / Copy / Move / Kick)。

必须明确写:本期是否支持私聊与服务器聊天?若不支持,通知 deep link 如何降级?若支持,入口在哪(Drawer?Member Menu?第三个 Tab?会话列表?),ServerSpaceState 需改为会话集合而非单个 chatMessages。


二、与现有文档 / 代码的直接冲突(需裁决,否则三套规范并存)

# 冲突点 新规范 现状
B1 断线重连 第 33 章 Reconnecting → Warning、第 35 章推荐 Reconnect banner 动画 UI架构设计.md 5.4 为顶部横幅不阻塞页面、最多 5 次递增重连;代码实际是 ReconnectOverlay 全屏遮罩;CLAUDE.md 又写明"自动重连默认禁用,频繁重连会触发服务端限流/封禁"。三方不一致,新文档未裁决
B2 头部是否显示地址 第 6 章"不建议长期显示 server.example.com:9987" UI架构设计.md 1.2 与 AppTopBar(subtitle=...) 明确显示 192.168.1.1:9987。文档未说明这是有意变更;且服务器重名时地址是唯一辨识信息,第 24 章 Recent Servers 又显示地址,自相矛盾
B3 端口输入 第 23 章把 Port 拆为独立输入框 InputValidator.validateServerAddress 支持 address:port 一体式并校验 1..65535;ServerConfig / RecentConnection 只有 address 字段;TSDNS 地址本身可能含端口。第 24 章 Recent Servers 又写成一体式
B4 连接页字段 第 23 章表单无 Password、无默认频道 第 3 章 Disconnected 明确列了 Password;ServerConfig 有 password / defaultChannel / defaultChannelPassword
B5 被踢路径 第 26 章 Disconnect 流程只有 Server Space → Disconnected → Connection Screen 代码有 Disconnected(wasKicked=true) → Routes.KICKED → KickedScreen(重连 / 返回主页)。流程图漏了这条已实现的路径
B6 成员状态是否显示文字 第 15 章"正常状态不要一直显示文字" UI架构设计.md 与现有实现用文字标注;纯颜色/圆点表达会降低可访问性(见 D4)
B7 Drawer 条目 第 21 章:Server Info / Voice / Notifications / Appearance / Settings / Disconnect 第 4 章信息架构:Server Info / Voice Settings / Notifications / Appearance / Disconnect。同一文档内两处条目不同(Settings 有无、Voice vs Voice Settings)
B8 CurrentChannelBar 位置 第 38 章放在 Header 之下(顶部) 第 5 章、第 8 章结构图放在 Content 之下、Tab 之上(底部)。同一文档内位置矛盾
B9 主题切换入口 第 21 章移入 Server Drawer → Appearance 现状在服务器配置页右上角,未连接时也可切换。移入 Drawer 后未连接状态无法切换主题,属交互回退,文档未察觉
B10 频道切换语义 第 27 章"本质上只是 currentChannelId 发生变化" 代码是异步 ClientMove + ChannelSwitchState 等服务端确认,可能因密码 / 满员 / 权限 / talkPower 失败(mapMoveError)。按文档描述实现会做成无回滚的乐观更新

文档治理问题(最需要先解决的一条):项目已有 UI架构设计.md(3 页 3 卡)、docs/流程/00~09、docs/implementation/00~12 三套体系,新文档没有版本、日期、作者、适用范围、变更记录,也没有声明它与上述文档的关系——是取代还是补充?冲突以谁为准?不明确这一点,上表 10 处冲突会全部变成实现期的反复拉扯。

建议在文档开头加一节「本文档效力」:明确取代 UI架构设计.md 的哪些章节、保留哪些、docs/流程 作为行为权威源不变。


三、覆盖缺口(现有功能在新规范里消失)

新规范描述的是一个"理想晴天的正常路径"。以下已实现或已设计的能力在 47 章中一次都没出现:

异常与错误态(现状代码已有对应实现,文档全部丢失)

  • 连接失败 6 类错误分类与文案(ServerViewModel.classifyError:密码错误 / 昵称冲突 / 网络不可达 / 地址无效 / 超时 / 服务器满)
  • 首次同步失败与重试(SyncState.SyncFailed)
  • 频道切换失败(ChannelSwitchState / mapMoveError)、密码频道输入(ChannelPasswordDialog)、频道确认(ChannelConfirmDialog)
  • 消息发送失败与重试(MessageSendState.Failed / MessageDeliveryState.FAILED / 10 秒超时)
  • 空状态(EmptyStateView:空频道、无消息)
  • 网络丢失等待(WaitingForNetwork)
  • 应用更新提示(UpdateChecker / UpdateBanner / UpdateCheckDialog)在 Server Space 中的位置

已存在的 Dialog / Menu 全部未纳入规范 第 28 章只抽象地说 Dialog 用于"确认 / 危险操作 / 输入",但没有清单。代码中已有:ChannelPasswordDialog、ChannelConfirmDialog、DisconnectConfirmDialog、PokeDialog、ClientActionMenu、MessageContextMenu、UpdateCheckDialog、PokeNotification。规范应逐个给出触发条件、内容、按钮、危险等级。

Poke 现状是核心交互(三个入口 + 顶部气泡 + 系统通知 + App.POKE_CHANNEL_ID)。新文档只在第 17 章 Member Action 里出现一次 "Poke",未定义接收端表现。

权限与降级 第 17 章要求 Move / Kick from Channel / Kick from Server,但没定义无权限时的表现(隐藏 / 置灰 / 点击后报错)。TS 的权限由服务器组与频道组决定(ServerInfo.defaultServerGroup / defaultChannelGroup、ChannelDetailInfo.neededTalkPower),这是必须定义的分支。

Recent Servers 管理 第 24 章只给了两行卡片示意,缺:最多 10 条、按时间倒序、长按删除、清空、成功/失败标记(这些在 UI架构设计.md 2.1 有),以及密码的存储安全规范(RecentConnectionsStore 持久化了 password,文档未涉及是否加密、是否用 Keystore)。

文件与富文本消息 代码有 Bbcode.kt、FileMessageMeta(图片 / MyTS 文件)、EmojiMapper。文档第 10 章讲消息合并、第 11 章说"不要把 Emoji / 文件 / 图片放一级 UI",但没说明接收侧这些内容如何渲染。"不放到一级输入 UI"和"能不能收发"是两件事,当前表述容易被误读为砍功能。

后台与生命周期 前台服务、keepalive 通知、NetworkMonitor、进程被杀后的状态恢复、connectionGeneration 防止陈旧回调——移动端语音客户端的核心体验,文档零覆盖。


四、Design Token 章节(29~34)的具体问题

D1. 与现有 Token 两套命名并存

第 29 章给 xs=4 sm=8 md=12 lg=16 xl=24,代码 UiTokens.Spacing 已是 None=0 ExtraSmall=4 Small=8 Medium=12 Large=16 ExtraLarge=24 Huge=32。文档说"建议继续强化现有体系",却给了一套新命名,且漏了 None 和 Huge。照文档写会出现 Spacing.sm 与 Spacing.Small 并存。 → 直接引用现有名称,补上 None / Huge。

D2. 用区间代替确定值,等于没有约束

  • 第 30 章 Radius:List Item 8~12dp、Card 12~16dp、Floating Card 16~20dp
  • 第 32 章 Typography:Title 16~20sp、Body 14~16sp、Secondary 12~14sp、Caption 11~12sp
  • 第 31 章:List Item 52~56dp

给区间意味着两个开发者会各选一端,最终仍然不统一——这正是文档第 31 章想避免的"14.5dp / 17dp 局部修正值"问题的另一种形式。 → 规范应给确定映射,并复用代码已有档位:Shapes(extraSmall 4 / small 8 / medium 12 / large 16 / extraLarge 24)、UiTokens.Size(TouchTarget 48 / ControlHeight 40 / AppBarHeight 56 / Icon 16-20-24)、MaterialTheme.typography(AppTopBar 已用 titleMedium)。文档自建的 Title/Body/Secondary/Caption 与 M3 的 display/headline/title/body/label 是两套体系,未给映射关系。

D3. Semantic Colors 与代码不匹配

第 33 章列 Primary / Accent / Success / Warning / Error / Muted / Disabled / Speaking;代码 SemanticColors 只有 success / warning / info 三组(各含 on / container 变体)。差异:

  • 缺 accent / muted / disabled / speaking 四组,需明确新增字段
  • Primary / Error 在 M3 colorScheme 里已有(primary / error / errorContainer),文档另立一份会造成双源
  • info 在代码里有、文档没提
  • Accent 与 M3 的 tertiary 关系未定义;Speaking → Accent 与 Primary Action 同色会削弱第 37 章的视觉层级
  • 未给出 onXxx 前景色与 container 变体,而代码现有 token 都是成组定义的

D4. 状态仅靠颜色区分,缺冗余编码

第 33~34 章把 Connected/Speaking/Reconnecting/Disconnected 全部映射到颜色,没有要求形状、图标或文字的第二重编码。这违反 WCAG 1.4.1(不能仅用颜色传达信息),对色觉障碍用户不可用。 第 15 章"正常状态不要一直显示文字"进一步削弱了这一点。 → 建议规定:颜色 + 图标形状(如 Speaking 用波形而非仅高亮环)双重编码;纯装饰性状态点可只用颜色。

D5. 暗色主题规范完全缺失

代码有 ThemeMode.kt / ThemePreferences.kt,UI架构设计.md 4.4 定义了主题切换与持久化。新文档第 21 章列了 Appearance、第 29 章提了 Theme,但全文没有一条暗色规格:Semantic Colors 在暗色下的取值、Speaking 高亮的对比度、第 37 章 Background/Surface/Elevated Surface 三级层级在暗色下如何区分(暗色主题通常靠 tonal elevation 而非阴影)。 → 第 33/34/37 章均需给出亮/暗双套取值,或明确引用 M3 tonal palette 的生成规则。

D6. 动画规范无数值

第 35 章只说"短时、低幅度",没有时长、easing、可打断性、并发规则。代码现状已有具体值(tween(340/320/280/240, FastOutSlowInEasing))。 → 给出 duration token(如 Fast=150ms / Normal=250ms / Slow=350ms)与 easing token,并说明是否沿用现有值。同时缺"系统开启减弱动效时如何降级"。


五、组件级设计漏洞

E1. CurrentChannelBar(第 7 章,自称最重要组件)缺规格

  • 成员头像预览的排序规则未定义(说话者优先?加入时间?权限高低?)
  • 折叠阈值未定义(几个之后显示 +N)
  • 点击行为未定义(跳 Channels Tab?弹 Member 列表 Sheet?)
  • 缺变体定义:第 38 章 Channels 视图里它被简化为 # General 12,第 7 章却是含头像的完整版。同一组件两种形态,正文未说明,也无变体命名
  • 与 Channels Tab 的 Channel Tree 成员列表信息重复:Channels 视图下当前频道成员会出现两次
  • 数据刷新抖动:CLAUDE.md 说明 client enter/leave 会触发全量 clientlist 刷新(因 notifycliententerview 的 ChannelID 不可靠),头像预览会整组重排。第 35 章讲动画却没考虑这个高频刷新场景

E2. Channel Tree(第 12~14 章)缺关键规格

  • 嵌套缩进上限未定义。TS 频道可深层嵌套,窄屏 5 层以上必然溢出。需要规定最大缩进层级、超限处理(截断缩进 / 横向滚动 / 面包屑)
  • 排序规则未定义(ChannelInfo.order 字段存在但文档未提)
  • 默认展开策略未定义(全部折叠?只展开当前频道路径?)
  • 展开状态在切 Tab / 切频道后是否保持
  • 大量频道时的性能要求(LazyColumn、key 稳定性)
  • 实时性假设不成立:CLAUDE.md 明确 SDK 无 channel create/update/delete 事件,频道列表 5 分钟陈旧或切换前才刷新。第 12 章把它当实时树来设计,需注明刷新时机与"数据可能滞后"的表达

E3. 消息合并(第 10 章)缺规则

  • 合并的时间窗口未定义(跨 5 分钟还合并吗?)
  • 合并后时间戳如何显示
  • 系统消息(MessageType.SYSTEM)是否打断合并
  • 合并组内单条消息的 deliveryState(PENDING/SENT/FAILED)如何展示、失败重试入口在哪

E4. 用 emoji 充当设计规格

第 13 章 ▼ 🎙 General 12、示意图中的 ☰ ⋮ ● ▼ ▶ 💬 🎤 🔒 🔇。emoji 跨设备渲染不一致,且与第 36 章"Low Decoration / 不是 Material 3 Demo"的诉求冲突。 → 应指定 Material Icons 具体图标名,并补一张「频道类型 → 图标」映射表。文档提到"Channel Type"但从未枚举 TS 实际的频道属性(默认 / 密码 / 永久 / 半永久 / 临时 / codec / neededTalkPower / maxClients),而这些字段 ChannelInfo 和 ChannelDetailInfo 里都有。

E5. 触控目标低于平台标准

第 31 章 Small Button 40dp。Android 与 M3 推荐最小触控目标 48dp(代码 UiTokens.Size.TouchTarget = 48)。视觉高度 40dp 可以,但必须规定用 padding 补足到 48dp 命中区——文档未说明,实现者会直接做成 40dp 命中区。

E6. 第 28 章 Sheet/Dialog/Drawer 分层规则不完整

规则本身合理(避免 BottomSheet → Dialog → BottomSheet 链),但缺:

  • Bottom Sheet 的档位(半屏 / 全屏 / 拖拽行为),现状语音卡是可拖拽半屏
  • 系统返回键在每种容器上的行为
  • 多层容器同时存在时的层级与遮罩规则
  • 键盘弹起时 Dialog / Sheet 内输入框的避让(现状 UI架构设计.md 2.3 有键盘联动规格,新文档丢失)

六、可访问性与适配(全文零覆盖)

一份移动端 UI/UX 重构规范里没有以下任何一项:

  • 无障碍语义:contentDescription 规范、状态变化的 liveRegion 声明。Speaking 是纯视觉高频状态,TalkBack 用户完全无法感知谁在说话——这对语音客户端是严重问题
  • 字体缩放:全用 sp 定义字号,但第 31 章的固定 dp 高度(Input 52 / List Item 52~56)在大字号下会截断,需定义最小/最大缩放下的行为
  • 横屏 / 平板 / 折叠屏:全文只有一种窄屏竖版布局。Compose 项目大概率会跑在平板上,Server Space 是否用双栏(左频道树 + 右内容)?未提
  • 对比度:第 33/37 章定义颜色与层级,无任何对比度要求(WCAG AA 4.5:1)
  • 减弱动效:第 35 章无降级方案
  • 单手可达性:PTT、发送、Tab 都在底部是对的,但 CurrentChannelBar 按第 38 章在顶部,未讨论可达性

七、实时性能约束缺失

对一个语音客户端,这是不该缺的一章:

  • Speaking 状态由 Go 侧 400ms 静音超时判定,属高频变化。第 16 章要求 Avatar Ring + 状态点 + 音量指示器三种同时表达,12 人频道会引发成员列表大范围重组
  • 文档未规定重组边界(derivedStateOf、稳定 key、把高频状态下推到最小 composable)
  • 未规定 Speaking 指示的更新节流(是否需要 100ms 级去抖)
  • 未规定频道树全量刷新时避免闪烁的策略(结合 CLAUDE.md 的"client enter/leave 触发全量 clientlist 刷新")

八、文档形式与可执行性

F1. 信噪比过低

25KB / 47 章中大量是把一两个词包进 ```text 代码块(如单独一行的 currentChannelId、Chat、Channels)。真正的规格信息(确定数值、状态枚举、交互规则)占比很小。同样的内容可以压缩到 1/3 篇幅且更有用。

F2. 措辞无约束力

"推荐"约 20 次、"建议"约 25 次、"不要/避免"若干。规范里全是建议就无法作为验收依据。 → 改用 MUST / SHOULD / MAY(或 必须 / 应当 / 可以),并对每条给出可检验的判据。

F3. 缺规范应有的载体

没有:组件规格表(尺寸/间距/状态/交互的确定值)、状态枚举表、Do/Don't 对照、验收清单、视觉稿或线框图链接。ASCII 示意图只能表达布局意图,无法表达规格。

F4. 术语不统一

  • Client / Member / User 混用:代码是 ClientInfo / ClientActionMenu / ClientItem,文档第 15~17 章叫 Member,第 40 章目录又叫 client/
  • Channel Tree / Channels / Channel List 混用
  • Server Space / 服务器工作空间(第 2 章)混用 → 加一节术语表,锁定唯一名称(建议跟随代码用 Client)。

F5. P0/P1/P2 不可排期

第 42~44 章的优先级只有条目名,缺:工作量、依赖关系、验收标准、是否需要改 Go 协议层。按 A2/A3 的分析,成员状态与头像都需要先改 bridge.go 并重编 AAR,这必须是 P0 前置项,但文档把它归到了 P1(Member Item)和未提及(Avatar)。

F6. 缺迁移映射

第 40 章给了目标目录结构,但没有"现有文件 → 新结构"的对照表。当前实现是 4 路由(server_config / channel_list / chat / kicked)+ 独立 Screen;目标是 Server Space 单容器 + 内部 Tab。这需要重写 NavGraph.kt(含 deep link、BackHandler、LaunchedEffect 导航、ReconnectOverlay 与 PokeNotification 全局覆盖层),是本次重构成本最高的部分,文档一句未提。

建议补一张迁移表,例如:

现有 目标 处理
AppTopBar server/ServerHeader 改造(增加状态与在线数)
ChannelListScreen ServerSpace + channel/ChannelTree 拆分
ChatScreen chat/ChatView 降级为视图
VoiceCard + VoiceControlBar + PTTButton voice/* 合并(需先定 A4)
ServerDetailCard server/ServerDrawer 形态从 BottomSheet 改 Drawer
NavGraph 4 路由 2 路由(Connection / ServerSpace)+ Tab 重写
KickedScreen ? 文档未定义(见 B5)

九、优化建议:文档结构重组

按当前 47 章平铺结构,实现者无法按需查阅。建议重组为 6 部分:

  1. 范围与效力(新增,最高优先):版本 / 日期 / 适用模块 / 与 UI架构设计.md、docs/流程、docs/implementation 的取代关系 / 冲突裁决规则 / 术语表
  2. 模型与状态(现第 24、2627、41 章):连接状态机(补齐 5 态 + SyncState + currentChannelId 就绪态)、频道切换的异步语义与失败回滚
  3. 信息架构与导航(现第 5、8、28、38、39 章):Server Space 布局、Tab 规则、容器分层、迁移映射表
  4. 组件规格(现第 67、925 章):每个组件一节,统一模板 = 结构 / 尺寸 token / 状态枚举 / 交互 / 数据源 / 异常态 / 无障碍 / 变体
  5. 视觉与 Token(现第 29~37 章):确定值映射到现有代码 token,亮暗双套,对比度要求,动画数值
  6. 横切规范(新增):异常与错误态全集、空态、后台与生命周期、实时性能约束、可访问性、多窗口适配、凭据存储安全

另外两条具体建议:

  • 把"数据可用性"作为每个组件规格的必填字段。本次审查中 A2(成员状态)、A3(头像)、E2(频道实时性)三处阻断问题的共同根因,是文档从 UI 效果倒推、没有先核对协议层能提供什么。建议在每个组件规格里强制填「数据源 / 是否需改 bridge / 数据未就绪时的表现」。
  • P0 之前先做一次"数据能力对齐":列出 bridge.go 当前导出的全部字段与事件,标出规范中依赖但缺失的项,形成 bridge 改造清单。这一步不做,P0 的 CurrentChannelBar 和 Voice 都只能做成半成品。

十、总体评价

维度 评价
产品模型判断 好。Server Space + Current Channel 上下文的方向正确,优于现有三页跳转
与代码现状的吻合度 差。状态机、成员数据、头像、会话范围四处脱节
完整性 不足。异常路径、后台、可访问性、暗色、性能基本空白
可执行性 不足。区间值代替确定值、"建议"代替约束、无迁移映射、无验收标准
文档治理 缺失。无版本信息,未声明与既有三套文档的关系
内部一致性 有 4 处自相矛盾(B3/B4/B7/B8)

建议处理方式:不要按当前版本直接进入实现。先补三样东西——① 效力声明与冲突裁决(第二节表格逐条定调);② 数据能力对齐清单(Go bridge 现有字段 vs 规范依赖字段);③ 状态矩阵(A1)。这三项完成后,第 40 章的组件重构才有确定的输入。其余缺口可在各组件规格细化时逐个补齐。