Files
ttstd-infinite-canvas/docs/design/workspace-shell-design.mdx
爱笑的毛毛虫 7ec51732f7 feat(editor): AI 编辑助手悬浮面板、插件权限体系与时间线渲染管线(M4–M6.3) (#411)
* feat(editor): 编辑器实施 M0-M1 - 剪辑入口占位与插件 SDK v2 垂直切片

- detail.tsx 新增剪辑成片视图(editor tab,常驻),概览页插入剪辑步骤卡
- plugin-types.ts 新增 v2 类型(EditorSlotKind/EditorPluginPermission/PluginManifestV2),修复权限数组交叉归约 bug
- editor-slot-registry.ts 插槽注册表(优先级排序、幂等注册、订阅机制)
- plugin-registry.ts 注册器 v1/v2 分支校验,v1 路径零改动
- editor-shell.tsx 垂直切片插件:timeline-panel 插槽真实渲染,验证插件链路全通
- 测试:插槽注册表 5 用例 + 命令测试骨架,v1 插件回归 21 例零回归

* feat(editor): 命令协议黄金文件 - editor-commands 纯函数 reducer + 8 个黄金 op

* feat(editor): 快照撤销历史 - editor-history 有界 200 层 undo/redo

* feat(editor): 命令状态机 store - dispatch/手势预览/1.5s 防抖保存/串行保存链

* fix(editor): createEditorStore 返回 zustand hook 类型修复 typecheck

* feat(editor): 手势 echo 最小件 - 时间线面板拖拽移动/裁剪 + store 接线与本地持久化

* docs(editor): M2 完成状态记录

* feat(editor): M3.1 时间线面板选中联动 - store 选中状态 + 片段高亮/点击取消

* feat(editor): M3.2 预览监视器 - preview-renderer 插槽 + 真实媒体帧/播放头/时间码

* feat(editor): M3.3 片段检查器 - inspector 插槽 + 属性编辑入队可撤销

* feat(editor): M3.4 asset-ingest 素材库插件(addClip 命令入轨)

* feat(editor): M3.4 补充 - host 上下文(项目信息+资产)、editor.tsx 接线、asset-ingest 纯函数、死代码清理

* feat(editor): M3.5 subtitle-tools - SRT import/export + rebuild from node snapshot

* feat(editor): M3.6 transcription - deterministic mock ASR to subtitle track

* feat(editor): M3.7 export - render plan + ffmpeg.wasm fallback export

* feat(editor): M3.8 - render all 8 editor slots in three-pane layout + ai-assistant placeholder

* docs(editor): M3 完成状态 - 8 插件 + 全插槽接线,冒烟待确认

* feat(editor): 编辑器媒体与时间线打磨 - 资产来源分组筛选、时间线全宽布局、拖拽末片段修复、OSS 降级本地存储、Concat 风格控制条与检查器

* fix(plugins): 合并上游后修复插件分类筛选对 v2 清单的类型兼容

* feat(editor): 时间线工具态与轨道操作打磨 - 工具按钮选中仅图标变色、监视器 Concat 式控制条、removeTrack 黄金命令与轨道行头移除按钮、maskClosable 迁移

* fix(api): 资源上传超 MaxBytesReader 上限时报中文大小限制而非英文裸 400

* fix(editor): 监视器控制条 - 修复播放按钮 JSX 模板闭合花括号缺失导致的编译错误

* feat(api): 本地媒体导入分片化对齐 Concat - >50MB 视频不再失败 - 分片会话三接口(POST开始/PUT单片/complete合并)、前端超50MB自动切8MB分片并失败整传重试、默认配额放宽(日200MB→2GB、总2GB→20GB)、UploadResourceFile 豁免单文件限

* feat(editor): 轨道行头新增可见性/静音开关按钮 - 复用现有图标资源,按轨道类型显隐;命令层新增 setTrackFlag 黄金命令(flag 白名单 fail-closed)并补单测

* docs(progress): 记录轨道可见/静音开关改动与复测项

* docs(editor): M4 转写与渲染任务设计 - 复用 Task 表与状态机、独立创建 API、worker 领取类型过滤、whisper HTTP provider(OpenAI 兼容 /v1/audio)

* fix(api): OSS 上传失败自动降级本地存储时 body 已被 Transport 关闭 - newOSSRequest 用 NopCloser 包装请求体,服务端提前返回(签名 403)时不再关闭调用方持有的文件,降级分支 Seek 重读生效;补回归测试

* docs(progress): 记录媒体导入根治(分片+OSS 降级修复)复测项

* feat(api): 视频播放副本转码 H.265→H.264 + variant=playback 服务与启动回填 - 上传视频后异步探测 codec,H.265/AV1 走 moov 快速探测并转码 H.264 播放副本;GET /api/user-data/...?variant=playback 就绪时服务副本(独立 ETag :pb),未就绪回退原件并撤销后缀防 304 黑屏;storeResource 触发、启动幂等回填存量;前端播放失败自动轮询副本就绪后刷新

* fix(editor): 轨道至少保留一条 - 不可移除轨道点击 X 时居中浮现原因提示(2.2s 自动消失, aria-live);修正可见性 toggle 传参 (visible===false 语义) ;轨道行内移除按钮改为常驻(不可移除时点击给提示)

* feat(timeline): M4.1 字幕转写后台任务 - whisper.cpp 本地执行 - 新增 timeline_transcription 任务类型,worker 按类型分叉独立执行器(不走计费与模型渠道);输入 resourceId+language,读取用户资源后 ffmpeg 预处理 16k 单声道 wav,POST 本地 whisper.cpp /inference,结果写回 segments+srt+language;未配置 CANVAS_WHISPER_BASE_URL 等失败均落明确终态;feature 门控;进度分阶段;含解析/SRT/快速失败单元测试

* feat(media): 素材卡片时长与媒体面板打磨 - 导入媒体解析时长(前端 media-metadata + 后端 project_asset durationMs 透出)随卡片展示;媒体面板/监视器/时间线相关样式细节收尾

* docs(progress): 阶段 C 监视器播放复检清单 - H.265 播放副本/真实播放驱动/时长链路待浏览器确认

* feat(timeline): M4.1 转写任务创建 API - POST /api/timeline/transcriptions - service CreateTimelineTranscriptionTask:IsDraining 守护 + feature 门控 + 资源归属与可转写类型校验 + 排队计 active 限额(含任务级/项目级存储估算);local/whisper.cpp 标记,任务不经过模型路由与计费;createTaskWithinStorageQuota 复用与 ErrActiveTaskLimit 用户可读映射;测试覆盖排队成功(owner/type/provider 断言)、跨用户资源拒绝、非音视频拒绝

* feat(timeline): M4.3 字幕转写前端接线 - 真实任务替换 mock - 新增 services/api/timeline-tasks.ts(POST /timeline/transcriptions,复用 apiClient+request);EditorTranscription 改为 创建任务→waitForGenerationTask 轮询(25min)→结果 segments 映射 SrtEntry[]→rebuildSubtitleClips 写入字幕轨道;实时展示 stage/progress,失败/空识别/无字幕轨道分别提示;素材选择支持音频与视频(取 storageKey 中的 resource id 提交)

* fix(media): 播放副本状态收敛与轮询终态 - 无ffmpeg/远端存储/编码不可处理落 none 避免重复探测;前端 none 停轮询回原生播放并加 processing 上限

* feat(timeline): M4.2 时间线渲染任务 - POST /api/timeline/renders 创建(buildRenderPlan 校验含可渲染媒体、配额入队、local/ffmpeg、60min 超时);worker 顶部按类型分叉 processTimelineRender;ffmpeg 合成产物写资源存储并经 ResultJSON 记 resourceId;render 计划/结果测试 PASS

* fix(resource): 转码原子抢占与 OSS 请求体加固 - ClaimPlaybackTranscode 空/none→processing 单实例抢占避免重复转码,runPlaybackTranscode defer recover 落 failed;If-Range 与带 :pb 后缀的 serveETag 比较;无请求体的 OSS 请求不再包 NopCloser(nil)(防 Go 1.26 body 探测 nil 解引用)

* style(editor): 修正字幕回写面板缩进

* docs(timeline): M4 设计文档按实现回写 - 单一 worker 池 + processClaimedTask 顶部按类型分叉(决策 3/4),端点统一为复数 /api/timeline/transcriptions 与 /api/timeline/renders(注册于 RegisterTaskRoutes),ResultJSON/feature gate/ffmpeg env 同步;Runbook M4 表格与状态行更新;auth.go API 前缀白名单补 timeline

* fix(service): 修复上游 v1.2.5 公告配图删除保护自锁与测试夹具缺口

上游 #385 资源引用走查器对公告配图/草稿做了硬性引用检查,导致三个问题:
- UpdateAnnouncement 在更新公告前对旧配图走查,必然命中当前公告自身引用而自锁,
  公告替换配图功能实际不可用;DiscardAnnouncementImage 同理被草稿自身引用阻塞。
  修复:ensureResourceHasNoBusinessReferences 支持忽略指定引用(当前公告/当前草稿),
  其余引用仍按原语义拦截。
- admin DeleteAdminResources 将公告草稿引用视为阻塞,但删除事务会级联清理草稿与
  资源绑定,service 层永不进入事务,回滚审计注入测试无法到达预期路径。修复:service
  预检时过滤公告草稿引用,与 repository 级联语义对齐。
- project_settings_test.go 夹具未建 ResourceReferenceSnapshot 走查的 Task/Session/
  Message/TaskLog/Result/Announcement/AnnouncementImageDraft 表,报 no such table。
  修复:夹具 AutoMigrate 补齐。

后端全量 go test ./... 通过。

* feat(editor): 成片导出接入后端渲染任务(M4.2 前端接线)- timeline-tasks 增 createTimelineRenderTask;editor-export 主路径改 POST /timeline/renders 轮询取 resourceId 内嵌预览/下载,ffmpeg.wasm 降级保留

* feat(editor): M5 插件权限执行(fail-closed)- plugin-permission-check 新建(8 插槽最小权限表/判定/断言);SlotStack 渲染前过滤缺权限插槽改诊断条;editor-shell manifest 补 timeline.command/export.run;测试 10 用例含 editor-shell 真实回归

* feat(editor): M6.1 AI 命令 schema + M6.2 时间线摘要 - ai-command-schema(parse/dry-run 校验/12-op 提示词契约/版本化+golden);timeline-summary(确定性中文摘要/折叠上限);各 20/10 用例全绿

* docs(progress): M6 质检清单 - M6.1 schema/M6.2 摘要标记完成(含测试计数),M6.3 待模型源确认;清理 M5.1 重复条目

* feat(editor): AI 命令 schema 支持只读问答 - commands 空数组合法化(无法执行/纯提问时 reasoning 直接作答),新增 AI_EDITING_MAX_COMMANDS=8 上限防失控编辑,提示词同步

* feat(editor): AI 助手重构为右上悬浮对话面板(M6.3)+ 素材入库原子化收尾

- editor-ai-assistant.tsx: 占位面板替换为 OpenAI Canvas 风格右上悬浮对话面板;
  面板内文本模型选择器(effective.textModels,选择即持久化并覆写请求模型),
  消息流 user/assistant/error/preview 四类气泡 + 思考中占位,空态引导示例 chips
  一键发送,底部输入条;顶部工具栏常驻 Sparkles 入口(aria-pressed 切换)。
  修复上一版重复/截断函数声明(appendToEntry 双定义、settleAnswer 三声明等)。
  宿主链路:buildAiEditingSystemPrompt(summarizeTimeline(project)) 喂上下文 →
  parseAiCommandPlan → validateAiCommandBatch dry-run(任一非法整批拒绝)→
  ≤3 条 dispatch 直执行 / >3 条预览确认卡片;宿主拒绝时 failTurn 回填 FAILED_MARK 可重试。
- editor-asset-ingest/editor-preview-monitor/detail/editor.tsx: 配套接线与打磨
  (编辑器顶栏 AI 入口、素材导入面板、预览监视、项目页顶栏整理)。
- backend: LinkProjectAsset 事务内原子创建资产 + 项目链接(OnConflict 幂等),
  资产首次入库不再预落库,失败整体回滚,消除"有资产无链接"孤儿记录。
- docs: pending-test.mdx / runbook M6.3 状态更新(tsc + 测试 + 构建通过,
  浏览器冒烟待真实文本渠道)。
2026-09-04 14:03:33 +08:00

180 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工作区外壳设计沉淀:侧栏 + 主区 + 顶部栏
> 范围:用户工作台外壳(`UserLayout` → `AppWorkspaceShell`)的侧栏、主区卡片与主区顶部栏的设计决策与实现约束。
> 参考:shadcn 风格工作台(侧栏导航 + 主内容区 + 顶栏图标按钮组),`SelectItem` 式紧凑交互(hover 浅背景反馈 + Chevron 指示)。
> 硬约束:不改变任何既有功能入口;明暗主题由三层 token 驱动;不引入装饰性线条(竖杠 / hairline / 默认边框)。
> 状态:`bunx tsc --noEmit` 通过;未运行构建与浏览器实测(真机回归项见文末)。
## 1. 布局架构
工作台外壳为「**侧栏卡片 + 主区卡片**」双卡片布局,顶部栏**位于主区卡片内部顶部**(不再占全宽,视觉上位于侧栏右侧):
```text
┌────────────────────────────────────────────────────────┐
│ .app-workspace-shell padding: var(--workspace-shell-gutter) │
│ ┌──────────────┐ ┌───────────────────────────────────┐│
│ │ 侧栏卡片 │ │ 主区卡片 .app-workspace-stage ││
│ │ 260px 可折叠 │ │ ┌───────────────────────────────┐ ││
│ │ │ │ │ 顶部栏(面包屑…账户) │ ││
│ │ 切换器/搜索 │ │ ├───────────────────────────────┤ ││
│ │ 分组导航 │ │ │ 页面内容 │ ││
│ │ footer │ │ │ │ ││
│ └──────────────┘ │ └───────────────────────────────┘ ││
│ └───────────────────────────────────┘│
└────────────────────────────────────────────────────────┘
```
- 两张卡片共享同一外观规则(**单一来源**):1px 边框 + `--r-xl` 圆角 + 阴影栈,见 §5.1。
- 例外(连体契约):画布编辑器 `/canvas/:id` 与运营后台 `/admin`(`:not(.is-spatial)`)取消外壳留白与卡片圆角,保持全屏连体。
- 侧栏折叠为 0 宽时(`:has(.is-collapsed)`)外壳留白/行间距/主区圆角同步归零,四周贴齐视口。
## 2. 侧栏设计(`.app-workspace-sidebar`)
### 2.1 尺寸与卡片
| 项 | 值 | 说明 |
| --- | --- | --- |
| 展开宽度 | `--workspace-sidebar-width: 260px` | 组件 token;含 1px 卡片边框 |
| 折叠态 | `width: 0 !important`,边框/阴影同步收起 | 内存态,不持久化(沿用 admin-shell 先例) |
| 圆角 | `--r-xl`(14px) | 卡片合同 |
| 背景 | `--workspace-sidebar`(= `--workspace-navigation`) | 与主区 `--workspace-content` 区分 |
### 2.2 纵向结构(自上而下)
1. **工作区切换器** `WorkspaceSwitcher`:品牌 mark(`size-8`、`--r-sm` 圆角、`InfinityIcon`)+「影策」wordmark + 副标题「创作工作台」(`--fs-label`、42% 灰)→ 点击展开切换浮层 `.app-workspace-nav-popover`(无阴影,1px 边框 + 实底背景)。
2. **快速搜索**:`h-9`、`--r-lg` 圆角、`bg-foreground/5` 浅底;占位文字与 `Search` 图标均为 `text-muted-foreground`(语义占位色);hover 加深至 `bg-foreground/[.07]` + 70%;右侧常驻 `⌘K` kbd 提示(复用导航项 kbd 合同);点击打开命令面板。
3. **分组导航区** `.app-workspace-sidebar-scroll-area`(`flex-1` 滚动,滚动条隐藏):
- 无标题分组(首页 / 短剧创作 / 画布 / 任务中心 / 资产…核心入口)**常驻展示,不折叠**;「短剧创作」入口(`/projects`)**常驻**(不随 `shortDramaEnabled` 隐藏,该开关仅作部署级兜底:关闭时由 `RequireFeature` 提示「暂未开放」而非隐藏入口);「任务中心」等按功能开关条件渲染(`workspace-sidebar-nav.tsx` `buildNav`)。
- 有标题分组渲染为 `NavGroup`(见 §2.3)。
4. **Footer**(设置分区 + 退出登录):无分隔线(见 §2.5)。
### 2.3 分组折叠 `NavGroup`
- 触发:有标题的分组(如「工作台管理」)标题变为**按钮**(`.app-workspace-nav-group-toggle`),点击折叠/展开组内导航项。
- 视觉:`min-height: 28px` 紧凑按钮,`--r-sm` 圆角;右侧 `ChevronRight` 指示——展开 `rotate-90`(向下)、折叠水平(向右),200ms 旋转过渡。
- 交互:hover/focus-visible 浅背景 `--surface-hover`;标签文字 36% → 55%、图标 35% → 70%(成对递进,深浅主题一致)。
- 动画:`grid-template-rows: 1fr ↔ 0fr` + opacity,300ms `ease-in-out`(与 NavItem 子项折叠同款,交互一致)。
- 可用性:**激活项所在分组自动展开**(`useEffect` 监听 `hasActive`),保证当前位置可见。
### 2.4 导航项 `NavItem` 状态设计
| 状态 | 表现 |
| --- | --- |
| 默认 | 文字/图标 62% 中性灰(`text-foreground/62`),图标 60% |
| hover | `--surface-hover` 浅底 + 文字 100%、图标 80% |
| 激活 | `is-active` + `font-medium`,文字 100% |
| 子项展开 | `ChevronRight` 旋转 + `grid-rows` 动画 + **引导线**(`(level+1)*12+12.5px`,居中于子项圆点) |
- 行容器:`min-h-9`、`--r-sm` 圆角、`px-2.5`、图标 16px/1.6 描边。
- 子项缩进:`padding-left: ${level * 12 + 10}px`;子项无图标时显示 `size-1.5` 圆点(`opacity-40`)。
- 徽章(钱包余额):`bg-primary/10` + `text-primary` 圆角胶囊,`tabular-nums`。
- 快捷键:`kbd`(`h-5`、描边 `--workspace-border`、浅底、mono、55% 灰),hover 才显示。
- 激活分支自动展开(设置分区子项同款逻辑)。
- 点击既有 `to` 又有子项时:**同时折叠切换 + 导航**(接受的既定交互)。
### 2.5 Footer:无分隔线
- **禁止 `border-t` 分隔线**——层级改用淡背景表达:`bg-foreground/[.04]` + 自身 `rounded-b-[var(--r-xl)]`(侧栏根无 `overflow-hidden`,需自身圆角防背景溢出卡片底角)。
- 内容:设置(6 个 `/settings?section=` 子项,自定义渠道按功能开关过滤)+ 退出登录(`LogOut`,走 `useWorkspaceLogout`)。
## 3. 主区设计(`.app-workspace-stage`)
- 卡片合同与侧栏**共享单一规则**(§5.1);背景 `--workspace-content`。
- 顶部栏在卡片内顶部全宽(见 §4);页面内容容器(`.app-user-content` / `WorkspacePage` / `.app-workspace-grid`)继承卡片背景。
- 空间工作台(`is-spatial`):侧栏与主区**连体**——主区 `border: 0; border-radius: 0; box-shadow: none`,侧栏仅保留 `border-right`(7% 分隔线),无卡片间隙(`gap/padding` 归零)。
- 侧栏折叠后:`:has(.is-collapsed)` → shell `padding: 0`、主区圆角归零、四周贴齐。
## 4. 主区顶部栏设计(`.app-workspace-topbar`)
### 4.1 位置与内容
- 位于主区卡片内部顶部(侧栏右侧),`flex` 两端布局:
- 左:折叠按钮(`PanelLeftClose/Open`)+ 面包屑「影策 / 页面标题」(`--fs-caption`、50% 灰、当前页 100%)。
- 右:公告中心(登录用户)→ 主题切换(`AnimatedThemeToggler`)→ 账户菜单(`WorkspaceAccountMenu`)。
### 4.2 图标按钮合同(`.app-workspace-topbar-icon-button`)
顶栏所有图标按钮(折叠/公告/主题/账户)复用同一合同,保证视觉一致:
| 项 | 值 |
| --- | --- |
| 容器 | 36px(`size-9`)、`--r-md` 圆角 |
| 图标 | 18px、`stroke-width: 1.8`(`> span > svg` 规则匹配占位图标) |
| 默认色 | `color-mix` 58% |
| hover | `--surface-hover` 底 + 100% 前景 |
账户按钮:真实头像 24px(`size-6`);未登录/占位 `CircleUserRound` 撑满(`size-full` → 18px);**无背景容器**(默认态纯图标,反馈由按钮 hover 提供);触发按钮不含徽章(完整徽章在菜单头部)。
### 4.3 展开浮层(账户菜单 / 公告中心)
- **不透明**:AntD 主题 `colorBgElevated` 统一 `alpha 1`(浅色 `#fff` / 深色 `#1f1f20`),杜绝透出下层内容(曾因 0.96 透明度 + AntD 6 类名变更失效而暴露,见 §7 经验)。
- **无阴影**:`.app-spatial-overlays` 下菜单类浮层(`.ant-dropdown-menu`、`.ant-select-dropdown`、`.ant-popover-container`、cascader、mentions)`box-shadow: none`——层级由 **1px 边框 + 背景对比**表达;Modal/Drawer 不受影响。
- 背景/边框/圆角收敛到 `--workspace-overlay-bg-strong` / `--workspace-overlay-border` 等唯一源 token。
## 5. 共享规则与 token
### 5.1 卡片合同(单一来源)
```css
/* globals.css:.app-workspace-sidebar, .app-workspace-stage 共享 */
border: 1px solid color-mix(in srgb, var(--foreground) 7%, transparent); /* 减线:9% → 7% */
border-radius: var(--r-xl);
box-shadow: 0 14px 44px color-mix(in srgb, var(--foreground) 12%, transparent),
inset 0 1px 0 color-mix(in srgb, white 6%, transparent);
/* .dark 覆盖:0 14px 44px rgba(0, 0, 0, 0.45) + inset 白 6%(阴影永远取真黑,禁止用 foreground 推导) */
```
深色阴影必须为 `rgba(0, 0, 0, 0.45)` 级真黑(`var(--foreground)` 在深色下是白 → 发光)。`.dark` 覆盖**必须放在折叠规则之前**(两者 specificity 相同 (0,2,0),源顺序决定胜负)。
### 5.2 关键 token 映射
| Token | 值 | 用途 |
| --- | --- | --- |
| `--workspace-sidebar-width` | 260px | 侧栏展开宽度 |
| `--workspace-shell-gutter` | 6px | 外壳留白与卡片间隙 |
| `--workspace-sidebar` / `--workspace-content` | navigation / page | 侧栏 / 主区背景 |
| `--surface-hover` | 5% / 7% (dark) | 行 hover 反馈 |
| `--r-sm / --r-lg / --r-xl` | — / — / 14px | 行 / 搜索 / 卡片圆角 |
| `--fs-label / --fs-caption / --fs-body` | 11 / 12 / 14px | 标签 / 说明 / 正文 |
| `--motion-state` / `--ease-aceternity` | 180ms / ease-out | 状态过渡 |
| `--workspace-overlay-*` | strong / border 等 | 浮层背景、边框、分隔 |
## 6. 视觉与交互规范(沉淀约束)
- **禁止装饰性线条**:卡片无左侧竖杠、无默认 border/hairline;层级用**明度、间距、淡背景**表达(footer 分隔即用 `bg-foreground/[.04]`)。导航引导线、markdown 引用线属于内容层级,不在此列。
- **不重复声明**:相似组件样式合并共享选择器(侧栏/主区卡片已合并);新增覆盖前先搜索同名规则,回到唯一源修改。
- **色彩角色成对**:主操作 / 普通选中 / 勾选 / 开关各为其政;hover 递进必须成对定义(文字 62→100 与图标 60→80 同步)。
- **状态一致**:持久切换用 `aria-pressed`;`type="primary"` 仅用于当前主命令。
- **可访问性**:键盘导航保留 `:focus-visible`(点击态用 `:focus:not(:focus-visible)` 消除);折叠/展开控件使用 `aria-expanded`。
- **AntD 6 浮层类名**:Popover 背景容器为 `.ant-popover-container`(不再是 v5 的 `.ant-popover-inner`),覆盖选择器必须按实际 DOM 类名书写。
## 7. 关键经验(踩坑记录)
1. **AntD 6 类名迁移**:`.ant-popover-inner` → `.ant-popover-container`,旧选择器静默失配(无报错),导致背景/边框规则全部失效——覆盖第三方组件前必须核对 `node_modules` 内实际类名。
2. **半透明浮层**:`colorBgElevated: 0.96` 在内容滚动时会透出下层 → 浮层一律实底。
3. **深色阴影**:用 `color-mix` 从 `--foreground` 推导阴影在深色主题下变成白色光晕 → 显式 `rgba(0,0,0,.45)`。
4. **specificity 平手靠顺序**:`.dark .x` (0,2,0) 与 `.shell .x.is-collapsed` (0,2,0) 相同时,源顺序决定——覆盖块必须置于折叠规则之前。
5. **编辑防重复**:多 anchor 编辑后行号漂移导致重复内容(搜索占位文本曾出现两份),改后必须复查目标行区域。
## 8. 实现文件清单
| 文件 | 职责 |
| --- | --- |
| `web/src/components/layout/app-top-nav.tsx` | `AppWorkspaceShell` 外壳、侧栏/主区行布局、折叠态类 |
| `web/src/components/layout/workspace-sidebar-nav.tsx` | 切换器、搜索、`NavGroup` 分组折叠、`NavItem`、footer |
| `web/src/components/layout/workspace-top-bar.tsx` | 主区顶部栏:折叠按钮、面包屑、公告、主题、账户 |
| `web/src/components/layout/workspace-account-menu.tsx` | 账户菜单与 `UserAvatar`(按钮图标合同) |
| `web/src/components/layout/workspace-command-palette.tsx` | ⌘K 命令面板(feature-gated) |
| `web/src/hooks/use-workspace-logout.ts` | 登出 hook |
| `web/src/lib/app-theme.ts` | AntD 主题:`colorBgElevated` 不透明、控件色 |
| `web/src/styles/globals.css` | 卡片合同、`NavGroup`/`NavItem` 状态、浮层唯一源规则 |
| 已删除 `workspace-sidebar-footer.tsx` | 无引用,逻辑并入 nav footer |
## 9. 真机回归清单(未运行,待确认后补测)
- [ ] 明暗主题下侧栏/主区卡片边框、阴影、分组折叠展开动画
- [ ] 侧栏折叠(0 宽贴齐)与移动端抽屉
- [ ] 账户菜单、公告中心浮层:不透明、无阴影、边框层级
- [ ] ⌘K 命令面板打开/过滤/快捷键提示
- [ ] 设置分区子项展开 + 引导线对齐 + 激活自动展开