Skip to content

feat(nav): show question mark icon when AskUserQuestion is pending - #1541

Merged
limityan merged 2 commits into
GCWing:mainfrom
ArrogHie:feat/ask-user-question-nav-icon
Jul 15, 2026
Merged

feat(nav): show question mark icon when AskUserQuestion is pending#1541
limityan merged 2 commits into
GCWing:mainfrom
ArrogHie:feat/ask-user-question-nav-icon

Conversation

@ArrogHie

Copy link
Copy Markdown
Contributor

Summary

在左侧导航面板的会话列表中,当运行中的会话 Agent 调用 AskUserQuestion 等待用户回答时,将旋转加载图标替换为脉动问号图标(CircleHelp),使用户能直观区分"Agent 正在工作"与"Agent 正在等待你的输入"两种状态。

Type and Areas

Type:

UI

Areas:

Web UI(桌面前端导航面板)

Motivation / Impact

问题:当会话 Agent 调用 AskUserQuestion 时,会话仍处于 PROCESSING 状态,左侧面板显示与普通运行时一致的旋转 Loader2 图标,用户无法区分 Agent 是在自主执行还是在等待回答。

根因

  1. 状态机的工具确认事件(TOOL_CONFIRMATION_NEEDED 等)已定义但从未在生产代码中派发,processingPhase 不会变为 TOOL_CONFIRMING
  2. needsUserAttention === 'ask_user' 仅对非活跃会话设置(handleConfirmationNeeded 中有 sessionId !== activeSessionId 守卫)。
  3. attentionKind 推导有 !isRunning 守卫,运行态时抑制了 attention 标志。
  4. 选择器字符串不含工具项状态,活跃会话的工具项变为 pending_confirmation 时选择器输出不变,flowChatState 不刷新,导航行不重渲染。

解决方案:复用 companion-pet 中已有的 findPendingAskUserQuestion(直接扫描对话轮次中的非终态 AskUserQuestion 工具项),将其提取为共享 util 并添加布尔包装函数。将该检测结果加入选择器字符串以触发重渲染,在图标槽中用 CircleHelp 替代 Loader2

影响:用户在左侧面板即可看到问号图标,无需切换到会话内容区即可知晓某会话正在等待输入。对非活跃会话同样有效。

Verification

检查项 命令 结果
前端类型检查 pnpm run type-check:web 通过(exit code 0)
Companion 单元测试 pnpm --dir src/web-ui run test:run src/flow_chat/utils/agentCompanionActivity.test.ts 11/11 通过(提取 util 后无行为变更)

Reviewer Notes

改动文件

  • 新增 askUserQuestionState.ts — 提取 findPendingAskUserQuestionTRANSIENT_TURN_STATUSES 及新增 hasPendingAskUserQuestion
  • 修改 agentCompanionActivity.ts — 从新 util 导入,删除本地副本,无行为变更
  • 修改 SessionsSection.tsx — 选择器加入 hasPendingAskUserQuestion;渲染 CircleHelp 替代 Loader2
  • 修改 SessionsSection.scssis-ask-user 样式 + bitfun-nav-ask-user-pulse 关键帧 + prefers-reduced-motion 回退

架构说明findPendingAskUserQuestion 直接扫描对话轮次中的工具项,不依赖未接线的状态机事件或仅对非活跃会话设置的 needsUserAttention 标志,因此对活跃和非活跃会话均可靠。--color-warning 为已有 token,无新增颜色。

兼容性风险:低。仅影响导航面板图标渲染,不改变状态机、Store 或后端逻辑。agentCompanionActivity.ts 为纯重构(函数来源迁移),测试全部通过。代码由AI辅助生成。

UI Comparison

befor:
屏幕截图 2026-07-14 103909
after:
屏幕截图 2026-07-14 103955

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

整体结论:建议 Request changes。当前实现存在一个会漏报真实等待状态的状态模型问题,以及两项影响该状态可感知性的无障碍问题。

1. [P2] 应按状态机跟踪的 turn 判断 AskUserQuestion

问题
SessionsSection.tsx:235-236:962-964 都检查 dialogTurns 的最后一项。但生产逻辑明确允许较新的 turn 已由 composer 预先追加,而 currentDialogTurnId 仍指向正在执行的旧 turn;现有 agentCompanionActivity.ts 也已经通过 currentDialogTurnId 解析 tracked turn。

风险
当 turn A 正等待 AskUserQuestion、turn B 已排队追加时,两处判断都会检查 turn B 并返回 false,导航继续显示 Loader。非活跃会话因此无法提示用户需要回答,直接违背本 PR 的目标。

建议
让订阅 fingerprint 与渲染共用同一个 tracked-turn resolver:优先按 currentDialogTurnId 找 turn,缺失时再回退最后一项,并确保 tracked id/revision 的变化能触发订阅刷新。增加 focused regression test:turn A pending Ask、turn B queued,断言问号出现,并在等待结束后移除。

2. [P2] 等待输入状态需要可被读屏实时感知

问题
SessionsSection.tsx:1063-1069 只给异步插入的 SVG 设置了 aria-label。当焦点停留在聊天区或编辑器时,Loader 替换为问号不会自动被辅助技术播报。

风险
读屏用户无法知道后台会话已经阻塞并需要输入。该变化属于需要在不移动焦点的情况下可感知的状态消息。

建议
使用集中、去重的 aria-live=polite / role=status 区域播报“会话 X 需要你的输入”,并处理等待结束状态;图标本身可保持装饰性,避免多个会话行重复播报。参考 WCAG 4.1.3:https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html

3. [P2] Light 主题下问号图标对比度低于 3:1

问题
SessionsSection.scss:201-204 使用 --color-warning。在内置 Light 主题下,#c08c42 对导航背景 #f3f3f5 的对比度只有约 2.68:1;pulse 的 0.85 透明度低点约 2.28:1,hover/active soft 背景下最低约 2.06:1。

风险
这个 14px 细线图标是区分“仍在运行”和“等待用户输入”的关键状态图形,低于有意义 UI 状态图形的 3:1 阈值。当前 theme color audit 只检查 token 治理,不检查实际对比度。

建议
使用经过 Light/Dark 全主题验证的高对比前景,或将 warning 与 --color-text-primary 混合并提供足够对比的底色/边框;动画全过程也应保持 3:1。参考 WCAG 1.4.11:https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html

验证说明

  • 审查 head:81b54d6245d8586fddfd31172a11675868d00b5d
  • GitHub 当前 4 个 CI job 均通过。
  • 本地 pnpm run type-check:web、15 个 focused tests、pnpm run theme:color-audit:allgit diff --check 均通过。
  • 现有测试没有覆盖多 turn 状态机与 SessionsSection 导航渲染,上述问题不会被当前绿色 CI 捕获。
  • 修复后请按仓库规则 rebase 到最新 main。

@ArrogHie
ArrogHie requested a review from limityan July 14, 2026 07:12

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核结论:tracked-turn 选择和主题对比度已修复,但新的 aria-live 实现仍有 3 个 P2 问题,暂不建议合并。

1. [P2] 多个 SessionsSection 会重复播报同一个全局事件

问题
SessionsSection.tsx:257-266 在每个组件实例中遍历全部 flowChatState.sessions,并在 :973-975 各自创建 live region。SessionsSection 会按工作区多实例挂载,折叠只改变样式和 isVisible,不会卸载组件;当前 effect 也没有按工作区、远程连接或可见性过滤。

风险
任一会话等待输入时,多个工作区的 live region 会重复播报。隐藏的 transient/subagent 会话也可能产生“有会话需要输入”,但当前列表中没有可操作的会话行。

建议
把 live region 上移到唯一的 Nav owner,由一个集中状态源去重;如果必须保留在 SessionsSection,至少复用列表的工作区/远程连接/transient/subagent 过滤,并受 isVisible 控制和正确重置 scope。

2. [P2] 连续等待和部分解除会丢失或保留错误公告

问题
SessionsSection.tsx:268-280 只处理 addedCount 和“全部集合清零”。A 已等待后 B 再等待时,两次都设置相同单数文案;React 会跳过相同字符串状态更新,live region 没有 DOM 变化。A、B 同时等待后仅 A 解除时,代码也不会更新公告。

风险
第二个等待事件可能完全不被播报;部分解除后 live region 仍可能保留“2 个会话需要输入”等过期内容。

建议
显式计算 added/removed IDs,以会话名称和当前总数生成每次可区分的消息,或使用带事件序号的集中公告队列。增加连续单新增、部分解除、等待会话交换的组件级测试。

3. [P2] 读屏用户仍无法定位具体等待会话

问题
SessionsSection.tsx:1098-1106CircleHelp 设为 aria-hidden 本身没有问题,但会话行没有对应的 sr-only 状态或 aria-describedby;全局公告也只有数量,不包含会话标题。

风险
用户即使听到“有会话需要输入”,随后遍历会话列表也无法判断是哪一个会话需要操作,状态也无法被重新查询。

建议
为每个等待行增加本地化的隐藏状态文本,并与行的可访问名称/描述关联;live region 消息应包含发生变化的会话标题。

已确认修复与验证

  • tracked-turn resolver 已覆盖 turn A 等待、turn B queued 的场景。
  • Light 主题对比度修复后约为 8.23:1,pulse 最低点约 6.33:1。
  • pnpm run type-check:web、i18n audit、theme audit、diff check 通过。
  • focused tests 22/22;Web 全量 tests 1515/1515;lint 0 errors。
  • 审查 head:3048e5a6d0404f7658d20d7c6bff9435c92e6ab7
  • 该 head 的 GitHub CI run 当前为 action_required,没有 job 实际运行,不能视为 CI 已通过。
  • 分支落后当前 main 8 个提交,虚拟合并无冲突;修复后请按仓库规则 rebase。

@ArrogHie
ArrogHie requested a review from limityan July 15, 2026 02:17

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再次复核最新提交 9ca611f 后,仍有一项需要修改的问题:

[P2] 最终 merge 恢复了分区级 aria-live,导致重复播报

问题

NavPanel 已经挂载单一的全局 AskUserAnnouncer,但最终 merge 又在 SessionsSection 中恢复了 liveAnnouncement、prevWaitingIdsRef、对应 effect,以及局部的 role=status aria-live=polite。这部分旧逻辑在 19bae86 中已经删除,却被 9ca611f 重新合入。

风险

每个已挂载的 SessionsSection 都会扫描全部 session,而不是只处理所属 workspace。任一 AskUserQuestion 进入或退出等待态时,全局 announcer 与多个 workspace announcer 会同时播报;多 workspace 或多会话切换时,还可能播报不同的数量信息。这会让屏幕阅读器收到重复或相互冲突的状态提示,违背本次修复的 single-owner 目标。

建议

  1. 删除 SessionsSection 中的 liveAnnouncement、prevWaitingIdsRef、对应 effect 和局部 live region,只保留全局 AskUserAnnouncer。
  2. 增加最终组件树的集成测试:同时挂载多个 SessionsSection,断言整个 NavPanel 只有一个 AskUser live region,并且一次状态变化只更新一次消息。

另外,当前净变更约 +610/-55,但 PR 包含 7 个提交及 2 个 merge commit,并存在重复 patch。请按仓库约定基于最新 main rebase,并压缩到最多 2 个提交。此次 live-region 回归正是交叉 merge 拓扑带回旧实现造成的。

本地验证:Web 全量测试 250 文件 / 1538 项通过;type-check:web、i18n:audit、theme:color-audit:all、git diff --check 均通过。GitHub CI 当前也已全部通过,但现有测试仅覆盖消息计算函数,没有覆盖上述最终组件集成回归。

@ArrogHie
ArrogHie force-pushed the feat/ask-user-question-nav-icon branch from 9ca611f to 19bae86 Compare July 15, 2026 04:38
@ArrogHie
ArrogHie requested a review from limityan July 15, 2026 04:41
@ArrogHie
ArrogHie force-pushed the feat/ask-user-question-nav-icon branch from 5cdb083 to 9146c96 Compare July 15, 2026 06:39

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再次复核最新 head 9146c96 后,提交已压缩为 1 个 commit,上轮的重复提交/merge 拓扑问题已经修复。但仍有一项新增能力覆盖缺口,暂不建议合并。

[P2] 新增的 AskUser 状态播报没有覆盖存量 Toolbar Mode

问题

本 PR 新增的 AskUserAnnouncer 只挂载在 NavPanel。存量 AppLayout 在 Toolbar Mode 下会提前返回,只渲染 ToolbarMode,整个 WorkspaceBody/NavPanel 子树都会卸载。

这不是本 PR 改坏了 Toolbar Mode:Toolbar Mode 的布局分支是历史代码。准确的问题是,本 PR 新增“单实例 AskUser 状态播报”时没有覆盖这个既有、仍可创建/切换/运行 FlowChat 会话的宿主。Toolbar Mode 当前只把 AskUserQuestion 当作普通 processing/streaming 状态,也没有对应的 waiting live region。

风险

在 Toolbar Mode,特别是折叠状态下,当前或后台会话进入 AskUserQuestion 等待态时,读屏用户不会收到“需要输入”或“等待已解除”的状态播报,界面仍可能只表现为 processing/stop,容易让用户一直等待。

建议

将 AskUserAnnouncer 提升到 AppLayout 模式分支之外的共同常驻 owner,例如 ToolbarModeProvider 内、LazyAppLayout 外;或者让 ToolbarMode 复用相同的 AskUser waiting 状态源。补充一个集成测试:切换到 Toolbar Mode 后触发和解除 AskUserQuestion,断言仍只有一个 live region 且消息正确更新。

已扩大检查存量宿主:正常 ChatPane、FloatingMiniChat、AssistantConfig、ACP、远程/Peer 与折叠导航均和 NavPanel 同根挂载;Agent Companion 有自己的 aria-live/activity bridge。除 Toolbar Mode 外,未发现第二个同类高置信遗漏。

合并准备

提交数量现已符合要求,但 9146c96 的父提交仍是旧基线 e9d94f1,当前 GCWing/main 为 71ceb38,分支落后 13 个提交。请按仓库约定 rebase 最新 main 后再更新 PR。

验证

  • 审查 head:9146c96bfde8b4641420f582b2104d000753470e
  • Web 全量测试:249 files / 1532 tests 通过
  • type-check:web、i18n:audit、git diff --check 通过
  • 本轮 force-push 后 GitHub 尚无 CI check 结果

ArrogHie added 2 commits July 15, 2026 16:49
Replace the spinning Loader2 icon with a pulsing CircleHelp icon in the left nav session list when a running session has a pending AskUserQuestion tool call, so users can distinguish 'waiting for your input' from 'agent is working'.

- Extract findPendingAskUserQuestion into a shared util (askUserQuestionState.ts) with a boolean wrapper hasPendingAskUserQuestion
- Add hasPendingAskUserQuestion to the SessionsSection selector string so the nav row re-renders when the pending status changes (fixes active session not refreshing)
- Render CircleHelp (is-ask-user class, warning color + pulse animation) instead of Loader2 when isRunning && isWaitingForUserAnswer
- Add prefers-reduced-motion fallback and hover-scale exclusions for the new icon class

fix(nav): use tracked turn, aria-live, and contrast fix for AskUserQuestion icon

Address three P2 review findings:

1. Use state-machine tracked turn (currentDialogTurnId) instead of last
   dialogTurn to detect pending AskUserQuestion. When a newer turn is queued
   while the tracked turn still has a pending question, the last-turn check
   would miss it. Added resolveTrackedTurn helper and included tracked turn
   ID in the selector fingerprint.

2. Add aria-live=polite status region so screen readers announce when
   sessions start/stop waiting for user input without focus movement. The
   CircleHelp icon is now decorative (aria-hidden); announcement is
   centralized and deduplicated.

3. Fix Light-theme contrast: replace pure --color-warning with
   color-mix(text-primary 70%, warning 30%) to exceed the 3:1 non-text
   contrast threshold. Raised pulse minimum opacity from 0.85 to 0.9.

Added regression tests for tracked turn resolution with queued turns.

fix(a11y): single-owner aria-live, distinguishable messages, per-row status

Address three P2 accessibility review findings:

1. Move aria-live region from per-workspace SessionsSection (multi-
   instance) to a single AskUserAnnouncer component rendered once in
   NavPanel. Eliminates duplicate announcements across workspace instances
   and filters out transient/subagent sessions.

2. Rewrite announcement logic: explicitly compute added/removed session
   IDs with titles. Single add announces session name; multi-add
   announces count; partial resolution announces resolved name + remaining
   count; all-resolved announces clearance. Uses clear-then-rAF DOM
   manipulation to force re-announcement even with identical text.
   Added computeAnnouncementMessage pure function with 7 tests covering
   consecutive add, partial resolution, swap, and no-change scenarios.

3. Add per-row sr-only status text ('Needs your input') inside each
   waiting session row so screen-reader users can identify which session
   needs action when navigating the list. CircleHelp icon stays aria-hidden.

Added i18n keys (ariaNeedsInputWithName, ariaInputResolvedRemaining) to
en-US, zh-CN, zh-TW locales. Code by AI.
The AskUserAnnouncer was mounted only in NavPanel, which is unmounted in
Toolbar Mode (AppLayout returns early with only <ToolbarMode />).
Toolbar Mode still runs FlowChat sessions and can receive
AskUserQuestion calls, so the live region must persist across both
modes.

Move <AskUserAnnouncer /> from NavPanel to App.tsx, inside
ToolbarModeProvider as a sibling of LazyAppLayout. This makes it a
single-instance persistent sibling that stays mounted in both normal
and Toolbar Mode layouts.

Also rebased on origin/main (71ceb38) to pick up 13 missing commits.
@ArrogHie
ArrogHie force-pushed the feat/ask-user-question-nav-icon branch from 9146c96 to 628589b Compare July 15, 2026 08:56
@ArrogHie
ArrogHie requested a review from limityan July 15, 2026 08:59

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再次复核当前 head 628589b8,此前阻塞问题已解决:全局 AskUserAnnouncer 在普通布局和 Toolbar Mode 中均保持单实例挂载,companion pet 窗口没有重复实例;等待状态识别、订阅清理和现有渲染路径未发现新的正确性问题。聚焦测试、类型检查、Web 构建、i18n/theme 审计均已通过,因此批准本次变更。

以下为非阻塞的后续优化建议:

  1. aria-live 当前为每次状态变化单独注册 requestAnimationFrame。极短时间内连续出现多个等待事件时,浏览器或辅助技术可能合并同一帧内的更新;后续可考虑按帧合并或串行播报,并补一个可控 rAF 的组件级测试。
  2. AskUserAnnouncer 注释仍写着 “Rendered once in NavPanel”,可改为实际的 App.tsx 全局挂载位置;未使用的 ariaNeedsInput 翻译键也可一并清理。
  3. 建议更新 PR 描述,使其覆盖全局 announcer、i18n、Toolbar Mode 支持和扩展后的验证范围,避免描述与最终 diff 不一致。

@limityan
limityan merged commit 643277e into GCWing:main Jul 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants