Skip to content

feat: 增强工作区分支管理、共享会话状态与审批交互体验#3

Open
liangriyu wants to merge 36 commits intoLeibnizhu:mainfrom
liangriyu:feat/unified-approval-card
Open

feat: 增强工作区分支管理、共享会话状态与审批交互体验#3
liangriyu wants to merge 36 commits intoLeibnizhu:mainfrom
liangriyu:feat/unified-approval-card

Conversation

@liangriyu
Copy link
Copy Markdown

背景

这次 PR 将 Web 端围绕工作区、审批与共享会话的一组能力做了集中增强,目标是让远程使用 Codex Web Local 时,分支操作、变更预览、共享状态感知和审批交互都更完整、更稳定,尤其补齐了移动端和刷新恢复场景下的关键体验缺口。

主要改动

1. 工作区与分支管理增强

  • 支持统一的工作区模型
  • 支持 Git 状态面板与基线分支配置
  • 支持多模式工作区差异面板
  • 支持受限版工作区分支切换与创建
  • 增强基线分支本地自动推导
  • 修正 staged diff 引用方向
  • 恢复新建会话页的分支管理入口

2. 审批链路与展示统一

  • 统一审批请求卡片体验
  • 增加 Web 端审批浮层与升级审批展示
  • 支持 persisted approval 的区分提示与 dismiss
  • 优化审批作用域与工作区守卫一致性
  • 侧边栏摘要支持显示授权数量
  • 优化共享会话状态卡与审批相关提示文案

3. 共享会话能力补齐

  • 增加共享会话快照基础设施
  • 增加共享会话状态总览
  • 增强 shared session reader UI
  • 拆分 approval / attention 语义,避免把所有待处理请求都误标为审批
  • 对 shared session projector / snapshot / bridge / UI 进行了配套补齐

4. 文件变更恢复与预览改进

  • 增加 thread file changes fallback
  • 修复文件变更 fallback 的 turnId 恢复
  • 收敛共享会话与文件变更恢复边界
  • 将文件变更持久化改为摘要级别,避免把完整 diff 长期写入本地存储
  • 阻断无 diff 场景下误打开空 diff 的路径
  • 优化移动端 diff 预览可读性

5. 移动端与交互细节优化

  • 修复移动端输入器样式变形问题
  • 优化手机端深色主题对比度
  • 优化会话消息复制交互
  • 优化审批操作条与若干移动端布局细节

6. 文档与测试补齐

  • 补充了分支管理、差异面板、共享会话、审批链路、移动端布局等多份设计文档与实施计划
  • 新增/更新了审批、共享会话、文件变更 fallback、消息复制、移动端样式等相关测试

影响范围

  • 前端主界面:会话列表、输入区、消息区、代码预览区、审批浮层、共享状态卡
  • 前端状态管理:desktop state、storage、server request handling
  • 服务端桥接层:codex app-server bridge、shared session snapshot/projector/store
  • 协议与 UI 类型:shared session、workspace diff、file change 恢复相关类型

风险说明

这次 PR 范围较大,主要风险点在:

  • 工作区与审批链路交叉后的状态一致性
  • shared session 快照与前端实时状态的同步边界
  • 文件变更摘要恢复与 diff 预览联动
  • 移动端布局在窄屏下的表现

建议 reviewers 重点关注:

  • src/server/codexAppServerBridge.ts
  • src/composables/useDesktopState.ts
  • src/api/codexGateway.ts
  • src/App.vue
  • src/components/content/ThreadComposer.vue
  • src/components/content/ThreadConversation.vue
  • src/components/content/SharedSessionStatusCard.vue

验证建议

建议在合并前至少回归以下场景:

  • 工作区分支切换 / 创建 / 基线分支推导
  • staged / workspace diff 展示
  • live approval + persisted approval 展示与处理
  • shared session 状态卡与侧边栏摘要
  • 文件变更卡片刷新恢复
  • 移动端输入器、项目选择、diff 预览可读性

liangriyu and others added 30 commits March 31, 2026 23:26
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>
Copilot AI review requested due to automatic review settings April 5, 2026 09:27
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +28 to +34
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}`,
)
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +26
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',
]
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread src/api/codexGateway.ts
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 : ''
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
const receivedAtIso = typeof row.receivedAtIso === 'string' ? row.receivedAtIso : ''
const receivedAtIso = typeof row.receivedAtIso === 'string' ? row.receivedAtIso.trim() : ''

Copilot uses AI. Check for mistakes.
Comment thread src/api/codexGateway.ts
Comment on lines +253 to +292
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 : '',
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

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'.

Copilot uses AI. Check for mistakes.
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