# 2026-06-03 — TopBar scoped sync + "有组件 ≠ 接进产物" export-coverage 审计

> 触发：用户"把 topbar 同步更新一下，我刚刚发布了"。看似一行同步，牵出 3 个可复用工程教训。
> Commits：`45e9d25c`（topbar 高度 + Logo 导出 + docs 修复）· `fe527c15`（audit:export-coverage）。

## 做了什么

1. **TopBar 高度同步** After Login 56→64（两变体现等高）。code 侧 `--topbar-height-before-login` 泛化为 `--topbar-height: 64px`，两变体共用；通用 `--sp-xxxl`（56，别处在用）不动。
2. **docs 页裁切 bug 修复**：`docs-demo-grid--single` 被引用但**从未定义 CSS** → 成员卡片排 2 列窄卡 + `overflow:hidden` 把 1920px 顶栏的菜单/右侧内容裁掉。补单列全宽 + 横向滚动 + 菜单补到 Menu 1-7。
3. **TVU logo 占位 → 真组件**：docs 用手搓 `<span class="brand-pill">TVU</span>`，换成真 `<Logo type="tvu">`。
4. **Logo 公开导出**：`src/components/Logo/Logo.vue` 早已存在却没从 `src/index.ts` 导出 → 消费者 import 不到。本次导出 + install 注册。
5. **新建 `audit:export-coverage`** 防同类 gap，挂 pre-commit。
6. **Alert** 标 internal-only（无 Figma 源、被 Notification/PromptMessage 取代），加审计白名单。

## 教训 1：单组件更新 ≠ 整库同步（我连犯 2 次）

起手把"同步 topbar"当成"同步 Figma 库"触发词，跑了 `pnpm sync:figma-library --with-extract`。它一次 `getFile` 拉**整份** ~193MB 设计文件，**慢且偶发 `UND_ERR_SOCKET: other side closed`**（这次就断在 192MB）。用户两次纠正"我只让更新一个组件，没必要拉全部"。

**真相**：`extract.mjs` 本就内置 `extractComponentByNodeId`（`--component-node-id=`），用 `getNodes([id])` **只拉那一个节点**，秒级。正确路径：scoped extract → 本地 regen（`normalize-component-tokens` + `generate-manifest`，纯本地不联网）→ count 一致性自然保持。

**为什么 scoped 不违"禁 partial subset"**：那条规则（2026-05-27 事故）针对的是**跳过 normalize→manifest→count 链让 normalized 层 stale**；scoped extract 仍写完整 raw + 跑完整下游 regen，count 不变。区别只在"网络拉多少"，不在"下游 regen 多少"。memory `feedback_figma-sync-canonical-sequence` 已补此区分。

## 教训 2：有组件 ≠ 接进产物（本次主收获）

用户问"TVU logo 为什么没显示正确？我理解它应该做成 Code 组件了，可以直接导进去"——一针见血。`Logo` 组件**造好了，但没接进 `src/index.ts`**，所以：消费者 `import { Logo }` 拿不到、docs 契约又禁止从 `src/components/` 导 → 这个组件谁都用不了，只能手搓占位代替。

**为什么没被任何审计发现**（三层全漏，已实证）：
- `IconUsageConformance.test.ts` 只扫 `src/components` + `src/canonical`，**`playground/docs/` 不在范围** → docs 占位永不报。
- 占位是纯文字 `<span>`，**连 inline-svg 检查都不触发**。
- TopBar slot 驱动，slot 内容"不可审计"（2026-05-29 保真度审计已记的盲区）。

**这与 INFRA-F37 同类**：组件/写法躲过审计覆盖盲区。

**机制修复（含一次自我纠偏，见教训 4）**：`audit:export-coverage` —— 从 `src/index.ts` 做 import-graph BFS 收集可达 .vue，凡 `src/components|canonical` 下**不可达且不在 `INTERNAL_ALLOWLIST`** 的组件 → 报警。
- **单信号 = 可达性**。base 组件因被 canonical wrap 而可达，无需第二信号。
- **负向测试**：把 Logo 的 import+export 全删（模拟修复前）→ 审计精确报 Logo orphan；恢复 → PASS。
- 纯可达性扫出 **2 个真 orphan**：`components/Alert/Alert.vue`（无 Figma 源）+ `components/Badge/Badge.vue`（canonical 自包含重写，base 悬空）。两者均只被 legacy `playground/App.vue` 引用 → 立 CANONICAL-030 统一（而非白名单养着）。

## 教训 3：审计范围声明要显式

icon 审计 `TARGET_DIRS` 写死 `src/components` + `src/canonical`，把 `playground/docs/` 排除在外是**隐式**的——没人声明"docs 不审"，于是 docs 占位长期无人管。新审计同类问题要在脚本头注释里写清扫哪、为什么不扫别处，避免下一个盲区。

## 协作流程实证

- **scoped sync 触发 pre-commit 双 gate**：figma-data raw-write guard（靠今日 `figma-sync-report-*.md` 放行 → 补跑 `sync-diff-report.mjs` 生成）+ visual-commit-approval（`.vue/.css` 需 `VISUAL_COMMIT_APPROVED=1`，用户已审视觉效果后合法设置）。
- **并行 session**：全程另一 session 在推进 INFRA-F39 select/dropdown，STATUS.md/backlog.md 是它的领地；本 session 只 commit 自己的文件，STATUS narrative 不动避免 clobber（[[user_parallel-sessions]]）。

## 教训 4：统一优先于兼容规则（用户纠偏 → 立 working-principles 原则 8）

我第一版 `audit:export-coverage` 用了"可达 OR basename 命中导出名"**双信号**——因为 base Badge 不可达（canonical 自包含重写）会被误报，我加第二信号去兜。用户一针见血：**"看起来有 2 种暴露方式，为什么不统一呢？这样不需要添加额外的规则。"**

这正是反模式:**双信号是在固化"两种暴露形式"的不一致,而不是消除它**。而且双信号更隐蔽地有害——它会让**任何**未来"canonical 自包含重写 + 留 orphan base"的情况静默通过（名字匹配即放行），盲点反而扩大。

**纠偏**：① 立 [`working-principles.md` 原则 8](../working-principles.md)（单一统一方法优先，不为分叉形式加兼容规则，尤其 Figma 组件库 Code 化）；② 审计回退到**单信号 = 可达性**；③ 2 个真 orphan（Alert/Badge）走 CANONICAL-030 统一，白名单仅作"显式登记 + backlog 追踪"的临时例外。

**元教训**：写审计/工具时，"加个分支让它通过"往往是在掩盖底层不一致。先问"能不能统一底层"，统一不了再（显式）兼容。

## 后续

- **CANONICAL-030**：统一 Alert/Badge orphan（repoint legacy `playground/App.vue` → 删 base → 清空 `INTERNAL_ALLOWLIST`）。涉及 App.vue 视觉/API，独立小 refactor。
- `audit:export-coverage` 可考虑纳入 AGENTS Sprint Self-Audit 协议（D/E/F/G/H 系列）作第 I 类。本次未改 AGENTS（避免与并行 session 冲突），留作 follow-up。
