Conversation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
# Conflicts: # .gitignore
There was a problem hiding this comment.
Pull request overview
本 PR 集中增强 Codex Web Local 的工作区能力(分支/差异面板/基线分支)、审批链路(统一审批卡 + persisted approvals 处理)与移动端主题可读性,并补齐多组 Node node:test 回归测试来降低回归风险。
Changes:
- 引入统一的审批请求展示模型与 UI 卡片相关配套(含 i18n 文案与测试)。
- 增强工作区侧能力:persisted server requests 拉取/忽略、workspace diff mode API/归一化、base branch 本地持久化等。
- 建立主题语义 token 并迁移部分高频 UI 组件样式,同时新增主题 token 回归测试;新增消息复制 util 与回归测试。
Reviewed changes
Copilot reviewed 50 out of 51 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/theme-contrast-tokens.test.mjs | 新增主题 token/消费点回归测试,防止深浅色语义变量与高频组件样式回退。 |
| tests/serverRequests.test.mjs | 覆盖 persisted server requests 按 workspace 聚合的选择逻辑。 |
| tests/messageCopy.test.mjs | 覆盖消息复制 util(Clipboard API + textarea fallback)与 assistant 连续消息合并复制语义。 |
| tests/codexAppServerBridge.test.mjs | 覆盖 bridge 在 persisted request 转换链路上的时序/落盘一致性。 |
| tests/approvalRequestUi.test.mjs | 覆盖统一审批卡组件接线与文案 key 存在性。 |
| tests/approvalRequestDisplay.test.mjs | 覆盖审批 request -> 展示模型的归一化逻辑与 decision 映射。 |
| src/utils/messageCopy.ts | 新增复制文本能力与 assistant 连续消息合并复制 payload 生成。 |
| src/utils/approvalRequestDisplay.ts | 新增审批请求展示模型构建与选项/decision 映射。 |
| src/types/codex.ts | 扩展 persisted server request / workspace diff / workspace model 等 UI 类型。 |
| src/style.css | 引入浅色/深色语义色 token,并让全局容器消费 token。 |
| src/i18n/uiText.ts | 补充分支菜单、diff panel、审批卡、消息复制等中英文文案。 |
| src/composables/desktop-state/storage.ts | 增加 base branch 按 cwd 的 localStorage 持久化读写。 |
| src/composables/desktop-state/server-requests.ts | 增加 persisted requests 的 workspace 聚合选择器。 |
| src/components/sidebar/SidebarThreadTree.vue | sidebar 样式迁移为语义 token(提升深色可读性一致性)。 |
| src/components/sidebar/SidebarThreadControls.vue | sidebar 控件样式迁移为语义 token。 |
| src/components/sidebar/SidebarPrimaryNav.vue | 主导航项样式迁移为语义 token。 |
| src/components/sidebar/SidebarMenuRow.vue | 菜单行图标颜色改为语义 token。 |
| src/components/layout/DesktopLayout.vue | layout 容器背景/前景改为语义 token。 |
| src/components/icons/IconTablerCopy.vue | 新增复制图标。 |
| src/App.vue | 串联 workspace 模型/分支操作/diff mode/base branch 更新/错误提示等页面编排。 |
| src/api/codexRpcClient.ts | 新增 persisted requests 拉取、dismiss、以及 workspace diff mode HTTP 封装。 |
| src/api/codexGateway.ts | 增加 fetchJson 通用封装、persisted requests 归一化、workspace git/branch/diff mode API 与归一化。 |
| docs/plans/2026-04-03-unified-approval-card-implementation-plan.md | 统一审批卡实现计划与执行回填。 |
| docs/plans/2026-04-03-unified-approval-card-design.md | 统一审批卡体验设计文档。 |
| docs/plans/2026-04-03-theme-contrast-plan.md | 主题对比度修复计划与执行回填。 |
| docs/plans/2026-04-03-message-copy-implementation-plan.md | 消息复制实现计划与执行回填。 |
| docs/plans/2026-04-03-message-copy-design.md | 消息复制体验设计文档。 |
| docs/plans/2026-04-01-workspace-model-unification-plan.md | 工作区模型收敛计划与完成进度回填。 |
| docs/plans/2026-04-01-workspace-model-unification-design.md | 工作区模型收敛设计文档。 |
| docs/plans/2026-04-01-workspace-guard-phase2-persisted-approvals-plan.md | persisted approvals 阻塞/恢复方案计划与回填。 |
| docs/plans/2026-04-01-workspace-guard-phase1-plan.md | workspace guard phase1 方案计划与回填。 |
| docs/plans/2026-04-01-workspace-guard-hardening-plan.md | workspace guard hardening 计划。 |
| docs/plans/2026-04-01-workspace-guard-hardening-design.md | workspace guard hardening 设计。 |
| docs/plans/2026-04-01-workspace-guard-consistency-design.md | workspace guard 一致性设计。 |
| docs/plans/2026-04-01-persisted-approval-dismiss-plan.md | persisted approvals dismiss 计划与回填。 |
| docs/plans/2026-04-01-persisted-approval-dismiss-design.md | persisted approvals dismiss 设计文档。 |
| docs/plans/2026-04-01-git-status-base-branch-plan.md | Git Status + base branch 配置化计划与回填。 |
| docs/plans/2026-04-01-git-status-base-branch-design.md | Git Status + base branch 配置化设计文档。 |
| docs/plans/2026-04-01-diff-panel-implementation-plan.md | diff panel modes 计划与回填。 |
| docs/plans/2026-04-01-diff-panel-design.md | diff panel 设计文档。 |
| docs/plans/2026-04-01-base-branch-auto-detection-plan.md | base branch 本地自动推导计划。 |
| docs/plans/2026-04-01-base-branch-auto-detection-design.md | base branch 本地自动推导设计。 |
| docs/plans/2026-03-31-branch-management-design.md | 分支能力设计文档。 |
| docs/plans/2026-03-31-branch-capability-phase1-plan.md | 分支能力 phase1 实施计划与执行回填。 |
| .gitignore | 增加 playwright 相关忽略目录。 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const token of requiredTokens) { | ||
| assert.match(css, new RegExp(`${token}:`), `missing light token ${token}`) | ||
| assert.match( | ||
| css, | ||
| new RegExp(`html\\[data-theme='dark'\\][\\s\\S]*${token}:`), | ||
| `missing dark token ${token}`, | ||
| ) |
There was a problem hiding this comment.
The “light token” assertion currently uses new RegExp(${token}:), which will also match dark-mode token definitions. That means the test could still pass even if the token is missing from the light/default theme (as long as it exists in the dark block). Consider restricting the light-mode search to the :root and/or html[data-theme='light'] blocks so the test actually verifies both theme definitions independently.
| const requiredTokens = [ | ||
| '--color-bg-app', | ||
| '--color-bg-surface', | ||
| '--color-bg-elevated', | ||
| '--color-text-primary', | ||
| '--color-text-secondary', | ||
| '--color-text-muted', | ||
| '--color-border-default', | ||
| '--color-code-bg', | ||
| '--color-code-text', | ||
| '--color-chip-bg', | ||
| '--color-chip-text', | ||
| '--color-link', | ||
| '--color-link-hover', | ||
| ] |
There was a problem hiding this comment.
requiredTokens omits tokens that are already asserted as being consumed elsewhere in this test suite (e.g. --color-bg-subtle is matched in the App/layout assertions). As written, a missing definition for such a token would not be caught (usage would still match). Consider adding the actually-consumed tokens (like --color-bg-subtle, --color-bg-muted, etc.) to requiredTokens to ensure they are defined for both light and dark themes.
| const row = value as Partial<UiPersistedServerRequest> | ||
| const id = typeof row.id === 'number' && Number.isInteger(row.id) ? row.id : null | ||
| const method = typeof row.method === 'string' ? row.method.trim() : '' | ||
| const receivedAtIso = typeof row.receivedAtIso === 'string' ? row.receivedAtIso : '' |
There was a problem hiding this comment.
receivedAtIso is accepted without trimming, so a whitespace-only string would be treated as “present” and the record would be normalized as valid. Trimming (and re-validating non-empty) would make the normalization consistent with method/cwd and prevent invalid timestamps from entering UI/state sorting.
| const receivedAtIso = typeof row.receivedAtIso === 'string' ? row.receivedAtIso : '' | |
| const receivedAtIso = typeof row.receivedAtIso === 'string' ? row.receivedAtIso.trim() : '' |
| function normalizeWorkspaceDiffMode(value: unknown): UiWorkspaceDiffMode { | ||
| const allowed: UiWorkspaceDiffMode[] = ['unstaged', 'staged', 'branch', 'lastCommit', 'gitStatus'] | ||
| return typeof value === 'string' && allowed.includes(value as UiWorkspaceDiffMode) | ||
| ? (value as UiWorkspaceDiffMode) | ||
| : 'unstaged' | ||
| } | ||
|
|
||
| function normalizeChangedFiles(value: unknown): UiWorkspaceDiffSnapshot['files'] { | ||
| if (!Array.isArray(value)) return [] | ||
| return value | ||
| .map((file) => { | ||
| if (!file || typeof file !== 'object' || Array.isArray(file)) return null | ||
| const row = file as Partial<UiWorkspaceDiffSnapshot['files'][number]> | ||
| const path = typeof row.path === 'string' ? row.path.trim() : '' | ||
| if (!path) return null | ||
| return { | ||
| path, | ||
| additions: typeof row.additions === 'number' && Number.isFinite(row.additions) ? Math.max(0, Math.trunc(row.additions)) : 0, | ||
| deletions: typeof row.deletions === 'number' && Number.isFinite(row.deletions) ? Math.max(0, Math.trunc(row.deletions)) : 0, | ||
| diff: typeof row.diff === 'string' ? row.diff : '', | ||
| } | ||
| }) | ||
| .filter((file): file is UiWorkspaceDiffSnapshot['files'][number] => file !== null) | ||
| } | ||
|
|
||
| function normalizeWorkspaceDiffSnapshot(value: unknown, fallbackCwd: string, fallbackMode: UiWorkspaceDiffMode): UiWorkspaceDiffSnapshot { | ||
| const row = value && typeof value === 'object' && !Array.isArray(value) | ||
| ? (value as Partial<UiWorkspaceDiffSnapshot>) | ||
| : {} | ||
| const files = normalizeChangedFiles(row.files) | ||
| const totalAdditions = typeof row.totalAdditions === 'number' && Number.isFinite(row.totalAdditions) | ||
| ? Math.max(0, Math.trunc(row.totalAdditions)) | ||
| : files.reduce((sum, file) => sum + file.additions, 0) | ||
| const totalDeletions = typeof row.totalDeletions === 'number' && Number.isFinite(row.totalDeletions) | ||
| ? Math.max(0, Math.trunc(row.totalDeletions)) | ||
| : files.reduce((sum, file) => sum + file.deletions, 0) | ||
| return { | ||
| mode: normalizeWorkspaceDiffMode(row.mode ?? fallbackMode), | ||
| cwd: typeof row.cwd === 'string' && row.cwd.trim().length > 0 ? row.cwd.trim() : fallbackCwd, | ||
| label: typeof row.label === 'string' ? row.label : '', |
There was a problem hiding this comment.
normalizeWorkspaceDiffMode defaults invalid values to 'unstaged'. When used from normalizeWorkspaceDiffSnapshot, this means a backend bug/typo in mode could silently rewrite a requested snapshot (e.g. branch) into unstaged, affecting UI state and caching keys. Consider making normalizeWorkspaceDiffSnapshot fall back to fallbackMode when row.mode is invalid, rather than hard-defaulting to 'unstaged'.
背景
这次 PR 将 Web 端围绕工作区、审批与共享会话的一组能力做了集中增强,目标是让远程使用 Codex Web Local 时,分支操作、变更预览、共享状态感知和审批交互都更完整、更稳定,尤其补齐了移动端和刷新恢复场景下的关键体验缺口。
主要改动
1. 工作区与分支管理增强
2. 审批链路与展示统一
3. 共享会话能力补齐
4. 文件变更恢复与预览改进
5. 移动端与交互细节优化
6. 文档与测试补齐
影响范围
风险说明
这次 PR 范围较大,主要风险点在:
建议 reviewers 重点关注:
src/server/codexAppServerBridge.tssrc/composables/useDesktopState.tssrc/api/codexGateway.tssrc/App.vuesrc/components/content/ThreadComposer.vuesrc/components/content/ThreadConversation.vuesrc/components/content/SharedSessionStatusCard.vue验证建议
建议在合并前至少回归以下场景: