Repository navigation
fix(ui): align mobile task inspector modality and focus - #7410
huangruiteng merged 2 commits into
Conversation
Signed-off-by: song <liusongstep@gmail.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Signed-off-by: song <liusongstep@gmail.com>
songoow
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval): APPROVE - exact head 2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19 retains the rendered-position modality fix, passed the freshly rebuilt packaged route smoke, and passed a source-rebuilt mutation sensitivity check.
Reviewer: model_agent · GPT-5 · OpenAI · self_reported
审核对象:PR #7410,exact head 2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19。本结论分别记录本地 exact-head build/smoke 通过、源码 mutation 的预期失败与恢复后通过,以及被继承基线红灯阻断的远程 CI。
动机
窄屏用户打开 Current Todo 详情时,需要键盘和辅助技术只与当前 inspector 交互。此前在不超过 1050px 时,详情虽然显示为固定 overlay,但背景仍可聚焦、页面仍可滚动,焦点可以离开详情,而且任何打开的 modeless dialog 都会误吞 Escape;现在同一视觉边界下,inspector 与 inspector-full 都具有 aria-modal、背景 inert/aria-hidden、滚动锁和焦点陷阱,modal dialog 仍先处理 Escape,modeless dialog 不再阻止 inspector 关闭。1512px 的并排面板继续保持非模态;1024、768、390、320px 的 Todo 详情进入模态路径,跨断点和 inspector/inspector-full 切换保留所选 Todo 与未提交草稿,关闭后焦点回到原 Task,原 opener 消失时回到 Task board,且不产生模型 turn 或 authority write。本次不改变 Todo 权限、持久化、后端 API、视觉布局、其它 drawer 类型或原生桌面安装路径。
改动思路
入口仍是现有 WorkspaceShell 和 ContextDrawer。决定性的输入不是另一套 viewport 常量,而是 Todo selection、drawerMode 和 drawer 元素实际计算出的 CSS position。这保持了 CSS 作为响应式布局唯一事实源,同时让交互骨架与视觉 overlay 使用同一 owner。
现有 WorkspaceShell、ContextDrawer 与 GoalTasksView 已分别拥有布局、焦点和返回目标语义,本次只扩展这些 owner;新增 modal 布尔值完全由实际 CSS position 推导,不引入持久化或第二套视口状态。
| 状态 | 交互边界 | 保留行为 |
|---|---|---|
| 1512px inspector | 非模态并排面板 | 主工作区仍可聚焦和滚动 |
| 1024/768/390/320px inspector | 模态 overlay | 背景 inert、滚动锁、焦点陷阱 |
| 1024px inspector-full | 模态全宽详情 | 切换尺寸不丢失 Todo 或草稿 |
| native modal dialog | 优先于 inspector 处理 Escape | dialog 关闭后 inspector 仍保留 |
| native modeless dialog | 不消费 inspector Escape | modeless dialog 保持打开 |
本 PR 只闭合不超过 1050px Task inspector(含 inspector-full)的模态、焦点、滚动和 Escape 所有权,并保留 1512px 非模态面板,不改变 Todo 权限、持久化、布局系统或安装后的原生应用路径。
具体改动
基线 fb0303f33327c51da434a215f0c757b5d1a5b16a 到 exact head 2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19 共 6 个文件、+241/-14。生产改动集中在 workspace-shell.tsx、context-drawer.tsx 与 goal-tasks-view.tsx;其余是 packaged route smoke、静态 contract assertion 和 Python Tests 的 shard 1 wiring。仓库没有可独立固定的公开 specification;验收依据是维护者提供的 PR-15 前端交互要求,因此这里诚实使用 no spec,而不是虚构 criterion id。
关键代码讲解
useDrawerOverlay(workspace-shell.tsx:6)观察 drawer 的实际 computed position,并用 ResizeObserver 与 window resize 更新本地布尔值;禁用、卸载和 resize 都有清理边界。WorkspaceShell(workspace-shell.tsx:31)只在 derived overlay 为真时把 sidebar/main 设为inert、aria-hidden并锁定 document/body 滚动,cleanup 恢复原值。ContextDrawer(context-drawer.tsx:172与:295)从 Todo selection 和同一 overlay 结果设置aria-modal、聚焦标题、管理 Tab/Shift+Tab;Escape 只把dialog[open]:modal视为优先 owner,并先关闭嵌套 details,再关闭 inspector。closeDrawer(context-drawer.tsx:281)返回原 opener;若 opener 已失效,则聚焦GoalTasksView中tabIndex={-1}的 Task board(goal-tasks-view.tsx:201)或当前 Goal tab,而不是把焦点留在 body。
正向路径:1024px 打开 Current Todo,overlay 被识别为 fixed,背景被隔离、滚动锁定、焦点留在详情;切换到 inspector-full 后仍模态,再切回仍保留 Todo 与草稿。负向路径:modeless dialog 打开时按 Escape,inspector 关闭而 dialog 保持打开;modal dialog 先关闭;嵌套 management details 先关闭;opener 被移除时焦点回到 Task board。
回归敏感性使用真实源码 mutation:把 Escape selector 临时改回 dialog[open],重新构建 chat bundle 后,packaged route smoke 在等待 inspector 隐藏时超时并以 exit 1 失败;恢复 dialog[open]:modal、重新构建后同一 smoke 通过。这直接证明测试区分了历史缺陷,而不是仅依赖 bundle 外改写或测试替身。
对主干的风险
最强回归场景是 modeless dialog 已打开、Todo inspector 仍可见时按 Escape。旧分支会把任意 open dialog 当作 Escape 的优先 owner,导致 inspector 留在屏幕上;新分支只在 native dialog 为 modal 时让出 Escape。影响面限于个人工作区的展示层键盘与焦点路径,不触及 Todo 写入、模型调用、持久化、权限或协议。
在 exact head 上,npm run build 通过,完整 smoke:task-inspector-route 在 1512/1024/768/390/320px 通过;1024px 显式覆盖 inspector 到 inspector-full 再返回,正向、负向 Escape 和 draft resize 均通过。smoke:task-inspector-return、smoke:task-board-scroll、navigation sorting、Goal workspace craft smoke、personal-workspace contract 与 tests/test_python_ci_workflow.py(549 passed)也通过。远端 kernel-static-checks 仍以顶层模块 145 到 146 失败,真正新增的顶层模块是已被 earlier merged change 引入的 loopx/chat_history.py;日志末尾的 workflow_skill_install.py 只是排序后的 offender tail,不是根因。该失败在 immutable base 与 exact head 呈现同一签名,故不能归因给本 UI diff;它仍会阻塞 merge readiness,并使 chat-bundle-browser skipped。
本 PR 不改变布局、字体、颜色、卡片、间距或视觉层级,只改变既有 overlay 的模态与焦点语义,因此截图不是本次质量判断的决定性证据。未验证范围:未验证安装后的原生应用、真实屏幕阅读器朗读、跨浏览器视觉回归以及 exact-head 远程 browser job;本地 exact-head Chromium packaged smoke 已覆盖本次交互不变量。
我的整体评价
Long-horizon 影响为 improved:模态判断跟随实际 rendered position,避免 CSS breakpoint 与另一份 JavaScript viewport 状态长期漂移。User-experience 影响为 improved:窄屏背景、滚动、焦点与 Escape 现在符合用户看到的 overlay,1512px 并排工作流保持非模态。架构判断为 retain;现有 WorkspaceShell、ContextDrawer 与 GoalTasksView 已分别拥有布局、焦点和返回目标语义,本次只扩展这些 owner;新增 modal 布尔值完全由实际 CSS position 推导,不引入持久化或第二套视口状态。本 PR 只闭合不超过 1050px Task inspector(含 inspector-full)的模态、焦点、滚动和 Escape 所有权,并保留 1512px 非模态面板,不改变 Todo 权限、持久化、布局系统或安装后的原生应用路径。
改动比例与代码量成立:问题需要同步修正两个交互 owner、一个安全返回目标、一个 packaged regression 与 CI wiring,没有增加权限、持久化、provider 或公共协议面。源码 mutation 失败、恢复后 exact-head smoke 通过,进一步覆盖了最关键的 Escape 回归。当前结论为 APPROVE;远端 CI 红灯与 merge-state 仍是独立的 merge-readiness hold,不代表本 PR 回归,也不授予合并或部署权限。
English verdict: APPROVE - exact head 2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19 implements rendered-position-derived modal semantics, keeps the wide panel non-modal, passes the rebuilt packaged route smoke and source mutation sensitivity check, and leaves the inherited module-budget red attributed separately as a merge-readiness hold.
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · declaration_source=runtime_reported · reasoning_effort=max.
评审对象:#7410,完整 head 2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19;对照基线 fb0303f33327c51da434a215f0c757b5d1a5b16a。
未发现阻塞问题。当前结论为 APPROVE;远端合并准备度仍需单独核验。
动机
受影响的是在窄屏打开任务详情、使用键盘查看要求并返回原任务的工作区用户。 旧版详情已经覆盖页面,焦点却能退回背景,菜单上的 Escape 也会直接关闭整块详情;候选让这些交互跟随实际覆盖布局,同时保留宽屏并排浏览。 本次可观察结果是窄屏只操作当前详情,内层菜单或模态窗口先退出,跨尺寸保留草稿,最后返回原入口或任务列表。 本 PR 不改变 Todo 权限、后端写入、模型执行或页面布局,也不代表已安装原生 App、真实屏幕阅读器或所有平台已经验证。
用户的直接任务是打开要求、查看内容、退出内层操作并回到原任务,不需要多做一次设置或确认。本评审用实际基线缺陷和仓库已接受的界面规范定义验收;作者自评中提到的私有 PR-15 原文未取得,因此不把它描述为已独立核验的公开 specification。
改动思路
同一实际 CSS position 已经决定详情是否覆盖背景,复用它推导模态并接回原有 shell、drawer 和返回目标,避免新增第二套断点或弹层框架。 本 PR 只闭合现有任务详情的模态、焦点、滚动锁与退出恢复;写入和模型执行仍由原 owner 决定,宽屏并排详情保持非模态。
采用现有布局的计算结果,而不是另写一个 JavaScript 屏宽阈值:任务详情实际成为 fixed overlay 时,shell 隔离背景并锁滚动,drawer 负责标题焦点和 Tab 边界。Escape 先尊重已经处理过的事件、真正的原生模态窗口和打开的任务菜单,再关闭详情。宽屏并排路径继续允许主区交互。
| 触发 | 原版 | 当前 head |
|---|---|---|
| 390px 从标题 Shift+Tab | 焦点进入背景附件菜单 | 焦点回到详情末项 |
| 管理任务菜单按 Escape | 整个详情关闭 | 先关闭菜单、保留详情 |
| 1512px 并排详情 | 非模态 | 非模态 |
| 原入口已消失 | 焦点可能留在无效目标 | 回到当前任务列表 |
具体改动
整个 base-to-head 差异为 6 个文件、+241/−14。
三个生产文件分别接回布局、键盘/焦点和可安全聚焦的 Task board;另三个文件扩展现有 route smoke、静态契约检查,并把既有 smoke 接入 CI shard。没有改 CSS 布局、Todo 后端、授权或模型调用。
按变更前已接受的 docs/development/design.md 验收,固定 spec_revision fb0303f33327c51da434a215f0c757b5d1a5b16a:
- Accessibility:已覆盖。Support visible keyboard focus and keyboard navigation in the actual interface. raw-CDP actual390 Tab/ShiftTab/Escape; opener and lost-opener readback
- Responsive Behavior:已覆盖。Maintain usable narrow interactions and readable full content. packaged320/390/768/1024/1512; long bilingual native Task read; inspector/full and draft resize
- Populated Page Review:已覆盖。Inspect populated default and expanded/error whole pages and preserve context on return. actual1512/390 default, sixteen bilingual paragraphs, menu and404 screenshots, native SQLite no mutations
关键符号与调用方向:
- export function useDrawerOverlay(
apps/presentation/dashboard/src/features/personal-workspace/workspace-shell.tsx:6):Derive overlay from rendered layout;computed fixed position becomes interaction input。关键条件是 No duplicated breakpoint; disconnect observer on disable/unmount,由 both existing shell and drawer invoke the same hook 调用,结果交给 WorkspaceShell + ContextDrawer。 - export function WorkspaceShell(
apps/presentation/dashboard/src/features/personal-workspace/workspace-shell.tsx:31):Isolate background and scrolling;narrow Task joins existing layout owner。关键条件是 only fixed Task overlay inerts sidebar/main and locks body/root,由 PersonalWorkspacePage renders current shell 调用,结果交给 existing workspace layout。 - const closeDrawer = useCallback(
apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx:281):Close and restore context;original opener attempt now has safe fallback。关键条件是 Return to original connected focus target; otherwise Task board/tab,由 close button and document Escape 调用,结果交给 GoalTasksView tabIndex=-1。 - function onKeyDown(event: KeyboardEvent)(
apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx:296):Respect nested keyboard owner;Escape checks consumed event/modal/details before drawer; narrow Tab trap。关键条件是 inner menu/modal first; wide Todo no Tab trap,由 document keydown hook within ContextDrawer 调用,结果交给 existing dialog interaction。
正向路径:390px 模态隔离背景并正确环绕 Tab;1024px inspector-full 保持模态;390/1024/1512px 切换保留同一 Todo 和草稿;宽屏保持非模态。
反向/恢复路径:原版390px Shift+Tab 从详情标题逸出到背景,菜单 Escape 关闭整个详情;候选先关闭菜单或真实快速导航,再关闭详情,并在原按钮消失时聚焦任务列表。缺失 Task 的重试和退出可见。
对主干的风险
最强风险是焦点陷阱错误地覆盖宽屏或抢走内层窗口的键盘事件。实际打包页面中,1512px 主区仍可交互;390px 的 Task 正反向 Tab、原生快速导航内的 Tab/Escape、管理菜单关闭顺序、1024px inspector-full 和跨尺寸草稿均观察通过。缺失 Task 的 404 仍展示原因和重试,Escape 能退出;删除原 opener 的反例回到 Task board。两组隔离 SQLite 来源各 5 个文件完全未变,HTTP 变更请求、action apply 和模型 Turn 均为零。
基线与 head 的真实 build:chat、workspace contract、task-board scroll 均通过。标准 source-checkout premerge 的 4 项直接检查和 4 项选择检查通过;当前质量回执为 pass(cqr_cd9727e81194855d8d9b)。作者的另一个源码 mutation/完整 Playwright smoke 结果只作为作者报告,未冒充本次独立执行。没有查询或等待远程 CI。
我的整体评价
默认看板仍按状态分区;窄屏长要求在固定返回栏与操作栏之间完整滚动,管理菜单和缺失任务提示均可读,关闭后回到原任务语境。本次改善的是交互归属,未声称重做版式。 未验证已安装原生 App、真实辅助技术朗读、跨浏览器与远程 CI;第一次被工具重置为宽屏的键盘/菜单记录已排除,结论使用随后逐步核对尺寸的真实390px记录。
架构与改动比例成立:修复同时接回现有 shell、drawer 和原任务列表,调用链在打包页面与真实 Task reader 上连通。默认页、长内容、菜单、错误页及返回路径均可使用;没有引入新的用户设置或额外确认。本次完成的是现有任务详情的交互修复,结论 APPROVE。合并准备度、代码所有者权限、安装和发布仍由各自流程决定,本评审不执行这些动作。
English verdict: APPROVE — head 2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19 aligns the fixed Task inspector with modal focus/background/scroll semantics while preserving the wide non-modal path, nested Escape ownership, draft identity and return focus. Independent packaged/native SQLite checks, exact-scope CQR and standard premerge pass; installed App, spoken AT and hosted CI remain outside this verification.
Goal And Delivered Outcome
mainatfb0303f33327c51da434a215f0c757b5d1a5b16a.Author Declaration
Implemented against
useDrawerOverlay,ContextDrawersmoke:task-inspector-routeWorkspaceShell,ContextDrawerWorkspaceShellinert/aria-hidden/scroll cleanupContextDrawerkey handler<details>, modal<dialog>, and modeless<dialog>assertionscloseDrawer,GoalTasksViewtsc --noEmit, production build, contract tests, workflow contract tests and packaged browser scenarios pass. The responsive route smoke is scheduled inPython Tests / chat-bundle-browsershard 1. This is a source checkout using a synthetic public fixture, not installed native-app qualification.Scope And Continuation
loopx/chat_history.pywas introduced. A separate focused prerequisite repair is tracked in #7415; as of this audit it is open and unmerged, and PR fix(ui): align mobile task inspector modality and focus #7410 has not been synced or rerun, so the inherited exact-head CI failures are not claimed fixed and this PR does not absorb that unrelated move. Installed/native-app qualification remains unverified.Validation
2f5352e87e1fb6baeb7a8dab206b5e5184c2ef19staticpassednpx tsc --noEmit; productionnpm run build(existing large-chunk warnings only)unitpassedpersonal-workspace-contract.test.mjs;smoke:task-board-scrollunitpassedtests/test_python_ci_workflow.py: 549 passed after wiring the route smoke to shard 1integrationpassedsmoke:task-inspector-routeat 1512×844, 1024×768, 768×1024, 390×844 and 320×844: modal/non-modal state, 1024px inspector/full-width toggle, inert background, scroll lock, Tab/Shift+Tab, modal and modeless dialog Escape order, breakpoint resize, draft preservation and focus cleanupreal_entrypointpassedtask-inspector-returnandnavigation-sortingscenarios on the exact source bundleregression_paritypassedstaticpassedintegrationfailedkernel-static-checksstops on the inherited base module-budget failure described above; dependent browser jobs are skipped. This is not claimed as a PR-local pass. Installed native-app behavior was not tested.Frontend / Visual Evidence
inspector-full, has matching aria-modal/inert/focus/scroll behavior; at 1512px it remains the same non-modal side panel. Nested menus and modal dialogs take Escape first, while modeless dialogs do not trap it; close returns to the originating Task or the Task list when that opener no longer exists.Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
N/A — no TypeScript control-plane migration, shared Goal Authority, persistence, or provider-routing change.
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).Signed-off-bytrailer (git commit -s).