创建时间:2026-05-24 最后更新:2026-05-26(✅ Phase A-H 全部完成并归档) 父计划:
refactor-closeout.md(Phase 7 视觉线;同属仍在进行的 phase-7b macOS 平台视觉,见总控板 Phase 7 行) 产品思考见 docs/insights/macos-visual-profile.md 技术验收见 docs/handover/macos-visual-profile.md Phase 7c 章节
Phase 7b 把 macOS native visual profile 推到了 Round 33。视觉上四张卡片基本成型,但三个观感问题反复出现:
- 卡片底部圆角"脏"(边缘抗锯齿不齐)
- Hover 拖动分割线时,line 没在两张相邻卡片的 gap 几何中心
- topbar 上方留白比 cards 底部多,视觉重心偏下
Round 34 试图一刀切修这三个问题,结果误把 4 个 panel 文件改成不一致结构 + 大范围动了 wrapper / handle / surface 关系,导致 Mac 适配整片失效(dataPlatform=null)。已回退到 commit 1202fef。
Codex 复审给出根因:这三个问题本质是一个问题 — floating card 缺一个 layout primitive。当前 sidebar / main / workspace / file-tree 四个区域各写了一套 wrapper / handle / surface 结构,因此:
- ResizeHandle 在 file-tree 里被包进了
card-frame(应该在 frame 外) - ResizeHandle 在不同 panel 里的
-ml-1几何含义不同(线落点错位) - shell padding 一把梭
padding: 16px,topbar 上下不对称
继续在各 panel 里局部修每次都会打歪另一个地方。这次必须抽出共享 primitive。
data-app-content-row(横向 flex,children 扁平)
├─ CardFrame(kind="sidebar") ← shadow + radius + isolation + layout
│ └─ CardSurface(kind="sidebar") ← bg + clip-path + backdrop-filter + content slot
│ └─ <ChatListPanel/SettingsSidebar inner content/>
│
├─ ResizeGutter (only if sidebar && main 都可见)
│
├─ CardFrame(kind="main")
│ └─ CardSurface(kind="main")
│ └─ <main/>
│
├─ ResizeGutter (only if main && workspace 都可见)
│
├─ CardFrame(kind="workspace") (chat detail only)
│ └─ CardSurface(kind="workspace")
│ └─ <TabBar/><TabPanel/>
│
├─ ResizeGutter (only if workspace && fileTree 都可见, 或 main && fileTree 都可见)
│
└─ CardFrame(kind="fileTree") (chat detail only)
└─ CardSurface(kind="fileTree")
└─ <FileTreePanel inner content/>
硬性边界:
- CardFrame 只投影、不裁剪。
overflow: visible、isolation: isolate、border-radius用于让 shadow 沿圆角 paint,但自己不 clip 任何东西。 - CardSurface 只裁剪和承载内容。
clip-path: inset(0 round R)+background(按 kind 决定 translucent / opaque)+ optionalbackdrop-filter。自己不投影。 - ResizeGutter 只存在于"左右两张可见 CardFrame 之间"。是 row-level child,绝不出现在某个 CardFrame 内部。视觉 line 必须落在 row gap 几何中心 ±1px 以内。
- panel 业务组件(ChatListPanel / SettingsSidebar / TabBar+TabPanel / FileTreePanel inner)继续自己管 width state(localStorage 持久化 + min/max clamp + delta math),通过 prop 把
onResize/onResizeEnd/onReset传给<ResizeGutter>。layout 几何由 AppShell 统一编排,panel 不再 wrap 自己的 frame / handle。 - CardSurface 的
kindprop 自己映射 data attribute(data-platform-sidebar/-main-content/-workspace-sidebar/-file-tree),调用方不手写。
| Phase | 内容 | 状态 | 备注 |
|---|---|---|---|
| A | 新增 card-primitives.tsx (CardFrame + CardSurface + ResizeGutter) + 单元测试 |
done | commit 98d2d8d |
| B | AppShell + 左侧 ChatListPanel/SettingsSidebar 切到 CardFrame + CardSurface | done | commit f6f95ef |
| C | WorkspaceSidebar 同 B:移除自己的 aside / wrapper | done | commit be5a3c0 |
| D | PanelZone + FileTreePanel 同 B/C | done | commit b991d1e |
| E | shell padding 拆开(padding-top 单独算)+ trafficLightPosition.y 重算 |
done | commit e7b5a44;y=21 起点,待 Electron 截图微调(D-3) |
| F | ResizeHandle → ResizeGutter 切换(3 处替换,content-row gap 改 0) | done | commit 1261bc4;当时临时保留 sidebar wrapper(已于 2026-05-26 收口删除,tech-debt #29 误判已撤销,sidebar 改为 row-level card) |
| G | 验收:四张卡片 DOM 结构记录、gutter 中心测量、light/dark Electron 截图、console 干净 | done | 写入 docs/handover/macos-visual-profile.md Phase 7c 章节;3 个 gutter 全部 offset=0 |
| H | 收口:删 sidebar wrapper(row-level card)+ AssistantPanel 接入 CardFrame/CardSurface(新增 assistant kind)+ 真实 DOM gutter 几何 e2e + 修 globals.css stale trafficLight 注释 |
done | 2026-05-26;Codex review 4 findings;Electron diag dataPlatform=darwin / opaqueElementCount=0 |
提议过 "row 4 cards + 3 gutters" 扁平结构。Codex 修正:gutter 必须只在左右两张可见 card 都存在时出现,否则会出现 sidebar hidden 但 gutter 还挂在 main 左边 之类的视觉残留。
决议: AppShell 用条件渲染 {showSidebar && showMain && <ResizeGutter .../>} 控制每个 gutter;或者抽 renderCardSequence(cards: VisibleCard[]) helper。
提议过 "panel 只暴露 inner content,width state 上提到 AppShell"。Codex 修正:业务状态(width / min / max / persistence)跟内容紧耦合,强行上提会让 AppShell 接管太多。
决议: panel 继续自己管 width state;只把 width / onResize / onResizeEnd / onReset 透传给 AppShell 的 <ResizeGutter>。AppShell 负责 layout 几何,panel 负责行为。
提议过 trafficLightPosition.y = padding-top + h-10/2 - 7。Codex 修正:AppKit / Electron 在 macOS 26 上对 traffic light 有细微偏移(红绿灯实际中心跟 dot 几何中心可能差 1-2px)。
决议: Phase E 落 padding 改动后,立刻用 Electron 截图比对红绿灯三个圆心 vs topbar 控件中心线。公式给一个起点(21px 试),但接受截图反馈调整 ±2px。
之前回退步骤建议过 git reset --hard 1202fef。Codex 警告:worktree 里还有用户改动(debug 文件 / 临时 patch),hard reset 会破坏未提交的工作。
决议: 每个 Phase 独立 commit。失败时回退本步用 git revert HEAD 或 git reset --soft HEAD~1(保留改动到工作区),不默认 --hard。
ResizeGutter 的"line 在 gap 正中"不能只靠肉眼。
决议: 加一个 phase-7c-gutter-geometry.test.ts,在 jsdom 里挂载 AppShell(或 mock parent),用 getBoundingClientRect() 断言 gutter rect 的 centerX 等于相邻 card.right + gap / 2 ± 1px。Electron 阶段额外用截图二次确认。
每个 Phase 结束跑:
npm run test— typecheck + 单元测试,必须全绿(含 Phase A 新加的 gutter 几何测试)npx electron .启动 → 检查[macos-vibrancy-diag]日志:dataPlatform=darwin、dataShell=electron、surfaceSidebarToken≠transparent、opaqueElementCount=0- Phase B 之后每步额外检查:DOM 里
[data-platform-card-frame]数量 === 当前可见 card 数;[data-platform-sidebar/-main-content/-workspace-sidebar/-file-tree]数量 === 各 panel 渲染数 - Phase E 之后必须 Electron 截图(light + dark),人眼对齐红绿灯三圆心 vs topbar 控件中心线
- 最终 Phase G 把结构图、gutter centerX 测量结果、light/dark 截图、console 日志写进
docs/handover/macos-visual-profile.md
- anti-FOUC script 失效复现路径 — Round 34 改完后 dataPlatform=null。已定位(2026-05-26,tech-debt #29 收口):根因是 React 放弃 SSR hydration、从 RootLayout 重渲染
<html>,抹掉<head>script 设的data-platform,由组件树中任意位置的 hydration mismatch / render error 触发,与某个 layout wrapper 无关。切换 AppShell 结构时仍建议单独提交并立刻重启 Electron 验证 diag log;但若 dataPlatform 变 null,应去查 hydration mismatch / render error(控制台的 hydration 报错、server/client 渲染分叉),不要再怪某个 wrapper。 - clip-path + translucent fill + backdrop-filter 三层叠加的 sub-pixel 锯齿 — Round 30 的根因,目前用
inset 0 0 0 1px highlightring 视觉掩盖。Phase A 设计 CardSurface 时考虑改用border: 1px solid+box-sizing: border-box(Chromium 把 border 跟 border-radius 当一个 anti-aliased outline)。这是个单独验证项:CardSurface 加 storybook-style demo 页面,对比 inset shadow vs border 两种方案的圆角清晰度。 - trafficLight 跨 padding 变化 — 任何 shell padding 变动都必须同步
electron/main.ts的 trafficLightPosition.y。建议在 main.ts 注释里链回这份计划,提醒未来改 padding 的人。