# DS-merge Follow-up — Sync A 弃掉的 5 旧演示重建到标准运行时

> 承接 2026-07-23/24 两套 DS 合并（`docs/_archive/superpowers/plans/2026-07-23-ds-merge-two-systems.md`）。
> Phase 2–5 已 shipped（commit `b2e91d8d`）。本文件是 follow-up 的交接：把 Sync A(`bfca04be`) Phase 5 弃掉的 5 个旧演示重建到标准 `window.TvuDS` 运行时。
> **Owner 裁决（2026-07-24）**：走**方案一 = 先做 2 个 mockup，3 个 dc-runtime template 待定**。
>
> ## ✅ 2 mockup 已完成并上传 Sync A（2026-07-24）
>
> - **TopBar UserMenu Demo** + **TVU Command Center v2** 均重写到标准运行时（引 `../styles.css`〔@imports `_ds_bundle.css`+fonts〕+ `../_vendor/react.js`〔合并 React+ReactDOM〕+ `../_ds_bundle.js`〔自注册 WC + `window.TvuDS`〕，去 CDN）。
> - **canonical 修正**（真源核实）：UserMenu `items→actions[]`+`languages[]`、去 `signOutLabel/defaultOpen`；`Icon`/`Logo` 走 `window.TvuDS.Icon/.Logo`（内联 SVG，无 spritePath）；MenuList 用 `onSelect`；TopBar `logo/left/menu/rightContent` slot。
>   - **⚠️ handoff 原指引一处纠偏**：`onLanguageChange`/`onSignOut` **不是** drift——bundle 实测 react-pilot 把 Vue emit `languageChange`/`signOut` 暴露为 `onXxx` React prop（`grep _ds_bundle.js` = onLanguageChange×5 / onSignOut×6 / onAction×108），保留它们才维持 i18n 切换交互。真正的 drift 只有 `items`/`signOutLabel`/`defaultOpen`。
> - **验证**：Playwright headless（**http 本地服务器**，非 file://——file:// 把每个文件当独立 origin 会误报外部 sprite `<use>` 跨源错误）两 mockup 均 PASS：`tvu-top-bar`/`tvu-user-menu`/`tvu-menu-list`/`tvu-logo` shadowRoot 均有内容、**零 console error**、零请求失败；截图视觉正确。
> - **上传**：`finalize_plan`（2 写 0 删）→ `write_files`（localPath 直传）→ 逐 `get_file` 亲验（`truncated:false`、运行时+canonical 标记齐、零 drift/CDN 标记）→ `list_files` 复核落地 `mockups/{TopBar UserMenu Demo,TVU Command Center v2}.html`、无多余写入。
> - **源留档**：重写后的两 mockup 源在 `scratch/ds-followup/mockups/`（未进 git，scratch 工作区）。**建议 owner 定夺是否把这两份源纳入 repo 常驻**——本 follow-up 起因正是"remote-only 演示被删后源丢失"，留一份 in-repo 副本可防再丢（但会改变"remote-only 演示"模型，故留 owner 拍）。

## Baseline（已核实，勿重查）

这 5 个演示是 **remote-only（不在仓库）**，Phase 5 已从 Sync A 删除。源内容可恢复性：

| 演示 | 类型 | 源状态 | 恢复路径 |
|---|---|---|---|
| **TopBar UserMenu Demo** | mockup（旧全局+drift API）| 只在当时 session 上下文 | **本文件下方已存全文** |
| **TVU Command Center v2** | mockup（旧全局，仅 TopBar 走 bundle）| remote 已删 | `get_file bfca04be versions/v2/mockups/TVU Command Center v2.html`（备份在）|
| operations-dashboard | dc-runtime x-dc template | remote 已删 | `get_file bfca04be versions/v2/templates/operations-dashboard/OperationsDashboard.dc.html`（备份在）|
| live-device-monitor | dc-runtime x-dc template | **源已丢** | ❌ **放弃**（owner 定 2026-07-24，不找回、不重建）|
| design-retrospective | dc-runtime x-dc template | **源已丢** | ❌ **放弃**（同上）|

> **教训（已记 memory）**：删 remote-only 内容前必须先 `get_file` 全部文件落地备份。本次只 fetch 了辅助文件、漏了 3 个 `.dc.html` → 丢 2 个。

## 方案一 = 本次要做（2 mockup → canonical window.TvuDS）

**只做 2 个 mockup**，重写到标准运行时 + 重新上传 Sync A + get_file 亲验。3 个 template **暂缓**（见末尾）。

两个 mockup 的运行时改法（都在 `mockups/`，root 上一级）：
- head 去掉 CDN React，改引 **`../_vendor/react.js`**（合并版，给 `window.React` + `window.ReactDOM`）+ **`../_ds_bundle.js`**（标准，给 `window.TvuDS` + 自注册 WC）+ 保留 `../styles.css`。
- `whenReady`/轮询从 `window.TVUNetworksDesignSystem_bfca04` 改为 **`window.TvuDS`**。
- **修 drift 到 canonical**（真源 = `src/canonical/*.vue` + `scripts/data/component-card-demos.mjs` 里已 verified 的用法）：
  - `UserMenu`：`items:[...]` → **`actions:[...]`**；加 `languages:[{label,value}]`；props = `name/email/role/color/lang/actions/languages`（**无 items/defaultOpen/signOutLabel/onSignOut/onLanguageChange** 这些旧写法——canonical UserMenu 见 `src/canonical/UserMenu.vue` + demos.UserMenu）。
  - `Icon`：**去掉 `Icon.spritePath`**（canonical Icon 内联 SVG、按 `name` 从 registry 取，`size` 是自由 number）。
  - `TopBar`：props `tag/title/showMenu/showSearchBox/logo/menu/rightContent`（见 demos.TopBar）；`menu` 传 `{c:'MenuList',p:{orientation:'horizontal',items:[...]}}`；`logo` 传 `{c:'Logo',p:{type:'tvu',size:32}}`。
  - `MenuList`：`orientation` + `items:[{label,value,active?,disabled?}]`（**item 无 type 字段**）。
- 渲染仍用 `React.createElement(window.TvuDS.<Name>, props, children)`（Phase 1 裁决）。
- **验证**：Playwright headless 载入，确认 `<tvu-*>` shadowRoot 有内容、零 console error（NOTES §CRITICAL：用了组件≠渲染对了）。

**上传纪律（血泪，见 memory）**：
- DesignSync **只主 session**；`finalize_plan`（贴 owner 确认）→ `write_files`（localPath 读盘）→ 逐 `get_file` 亲验字节保真（别信面板 updatedAt）。
- `delete_files` **不支持 glob**、只删显式路径；删后必 `list_files` 亲验剩余、别信 `deleted:N`。
- 这 2 个 mockup 上传路径 = `mockups/TopBar UserMenu Demo.html` + `mockups/TVU Command Center v2.html`（覆盖式写；它们已删，等于新增）。
- push 以 `git ls-remote` 为准（GitHub phantom reject）。

## 3 个 dc-runtime template（✅ owner 2026-07-24 全部放弃 — follow-up 收口）

- 它们**不用 `window.TvuDS` 组件全局**，而是 **x-dc 模板 + dc-runtime harness（`support.js` = DCLogic 类，来自不在本仓的 `dc-runtime` 工具链 `cd dc-runtime && bun run build`）+ 光 DOM CSS 类（`.topbar`/`.topbar--after-login`/`.tvu-icon` 等，来自 Phase 5 已删的 `components.css`）**。
- 迁移它们 = 重建 dc-runtime 工具链 + 恢复那批组件 CSS 类，是**独立工程**，非"改全局名"。
- **三个全部放弃**（owner 定 2026-07-24）：
  - `ldm` + `design-retrospective`：源已丢、不找回、不重建。
  - `operations-dashboard`：源在 `versions/v2/templates/operations-dashboard/`（`get_file` 可取），但**同类构件**——迁移仍需 dc-runtime 工具链（不在仓库，自查已核实 repo 无 `dc-runtime` 目录/`DCLogic`/`x-dc` 引用）+ 已删的 `components.css`。为单个 discard-tier 演示复活一整条工具链 ROI 不成立；有价值的组件演示已被方案一 2 mockup 覆盖。**源能取回 ≠ 值得复活承载它的工具链**。
- **若日后要 dc-runtime 展示能力 → 另行独立立项**（评估是否把 dc-runtime 工具链纳入仓库），不再挂在本 follow-up 下。

## ✅ Follow-up 整体收口（2026-07-24）

- **方案一 2 mockup = 最终交付**，已 shipped 到 Sync A（`bfca04be`）+ commit `4f9cee1f` 三端一致。
- **3 template 全部 discard**（owner ack）。
- **2 mockup 源已纳 repo 常驻**（防再丢，owner ack）：`docs/internal/_demos/{TopBar UserMenu Demo,TVU Command Center v2}.html`（与 Sync A `mockups/` 1:1、文件名一致）+ `docs/internal/_demos/README.md`（provenance + 维护约定）。这些是 Sync A 镜像源，改演示以 DesignSync 为准、改完同步回这里。
- 本 handoff 到此为 **DONE**，无后续 open 项。

## ★ 2026-07-24 追加修复：Sync A 卡片索引 bug（owner 肉眼发现）

- **现象**：owner 在 claude.ai 看 `TVU Networks Design System`（`bfca04be`）面板，基础卡全 `file not found` + 组件远不到 35。
- **根因**：clean-slate 换了整套卡片，但 app 编译的 `_ds_manifest.json`（面板据以渲染的卡片索引）是合并前的旧缓存、从没重建——它指向旧路径（`colors-brand.html`/`components/Icon/` 等）+ 只登记 6 组件。**面板读 manifest 当权威清单 + 校验存在性剔除，不实时扫 @dsCard**；DesignSync API 写入不触发重编译。原 merge "verified" 口径不实（只验单文件字节 + 2 mockup 渲染，没验面板索引）。
- **修法**：写 `_ds_needs_recompile` 哨兵（`finalize_plan` writes=[哨兵]/deletes=[] → write_files）+ owner 重开项目 → SPA/CLI self-check 重编译并消费哨兵。`get_file _ds_manifest.json` 亲验：`cards[]`=41 真实路径卡（2+35+4）/ `namespace`=TvuDS / `templates`=[] / `globalCssPaths` 去 `components.css` / `source` spa→design-sync-cli。
- **防复发**：`.design-sync/NOTES.md` 新增 §「Card index (`_ds_manifest.json`) must be RECOMPILED after any card-set change」——改卡片集后必触发重编译 + 亲验 manifest（文件上传≠卡片渲染）。
- **另注**：同名项目有两个——`bfca04be`（我的 Sync A）+ `019dfb04`（Eduardo 的，绝不碰）；排查时确认 owner 看的是前者。

## ★ 2026-07-24 卡片打磨（滚动条 + UserMenu 内容）

owner 反馈面板卡片瓦片有滚动条 + UserMenu 下拉裁切/缺项。实测结论 + 落地（Sync A 全量重生成上传）：
- **瓦片高度**：面板按 manifest `viewport` 定瓦片尺寸；`@dsCard` 支持 `viewport="<W>"` 属性且重编译会保留（实测 `viewport="1000x1400"`→固定高有空白；`viewport="1000"` **只给宽度→自适应高度**）。两个生成器模板（`component-card-template.mjs`/`foundation-card-template.mjs`）的 `@dsCard` 头统一加 `viewport="1000"`。**注意**：自适应只到面板高度上限，超高内容仍滚动（面板硬约束，无法从我们侧消除）——owner 接受现状。图标卡内部 `.ds-icon-grid` 另有意为之的 `max-height` 内滚。
- **UserMenu demo**（`component-card-demos.mjs`）：actions 精简为**真实两项** `Purchase Tokens` + `Manage Subscription Seats`（去掉编造的 Account settings/Billing/API keys，Figma 组件里没有）；标准 UserMenu 卡的交互示例**右对齐**（`justify-content:flex-end` 包裹），使点击展开的面板向左落进可视区（镜像真实 TopBar 头像在右的布局，否则左对齐会把面板裁出卡片）；TopBar demo 的 UserMenu 实例同步补齐 actions+languages。
- 测试：`tests/gen-foundation-cards.test.mts` 的 @dsCard 正则更新以允许 `viewport` 段（96 pass）。
- **Sync B / TVU UX(`406a39da`)** 同类问题走系统 `.ds-sync` driver（卡片格式不同），修法另议——**未做**。

## ★ 2026-07-24 Typography 卡对齐 Figma 字阶 + Badge 字号漂移发现

owner 反馈 Typography 卡出现 10px/11px（Figma 最小 12px）、行高非 150%。
- **根因**：Typography 卡数据源是 `atomicAssets.typographyStyles`（一份"代码实际用了哪些字号"的审计清单，含 `source`/`note` 指向 .vue + 实现缺口），**不是** Figma 字阶 token。它如实记录了**已漂移的实现值**（Badge 用 11/10px、Title 行高 normal）。
- **修复**（Sync A 已上传 `preview/type.html`，纯内容改动无需重编译）：`gen-foundation-cards.mjs` 新增 `figmaTypeScale` = 7 档 `--text-style-*` token（12/14/16/18/20/22/24），`buildTypeCard` 改用它。owner 定：**tips 12/16=133% 是 Figma 真源的例外，其余全 150%，token 本体不改**；只展示 Figma 已有字体（去掉 Compact 13 / Metric 10·11）。测试 20 pass。
- **⚠️ 未解 · 报 owner**：`src/components/Badge/Badge.vue` 硬编码 `font-size:11px`（m）/`10px`（s），**违反 canonical `src/canonical/Badge.vue` 的 `var(--text-style-tips)`=12/16**。这是**组件本体漂移**（改动影响 npm 包 + bundle + Badge 卡渲染），未擅自改，待 owner 定是否纳入。
- **待做**：(1) 同规则应用到 **Sync B/TVU UX**（driver 机制，另查）；(2) 全面审计两系统其他"无中生有"元素（已启动只读子代理查 Sync A demos/foundation vs canonical）。

## ★ 2026-07-24 审计结果 + owner 决策（下个 session 执行）

**只读子代理审计 Sync A（35 demo + 基础卡 vs canonical）结论**：无凭空组件、35 demo 枚举值全命中 canonical、`designAssetData.ts`（颜色/图标）程序化派生自活源无漂移。漂移只在手写的 `atomicAssets.ts`：
- ✅ **已修**：Radius XXL 100px→20px（commit `1f8adb8b`，token 真源 2026-06-12 已 20px）；Typography 去 10/11/13px 改 Figma 7 档（commit `7e5ef912`）。两卡已上传 Sync A（纯内容、无需重编译）。
- ⚠️ 待验：8 个输入/分页/Tab demo 用 `value` 而非 canonical `modelValue`。

**owner 决策（2026-07-24，下个 session 做）**：
1. **Badge 字号漂移 → 只留 12px 一档**（`src/components/Badge/Badge.vue` 现 m=11px/s=10px 两档，改成全部 `var(--text-style-tips)`=12px，对齐 canonical `src/canonical/Badge.vue`）。⚠️ **组件本体改动 = 动 npm 包 + bundle**：改后需 `pnpm build`（或 sync 重建 `_ds_bundle.js`）+ 重传受影响卡；改前核 s 档 16px 高塞 12px 是否溢出（Figma 真源为准）。同步把 `atomicAssets.typographyStyles` 的 Metric 10/11 条目清掉（Badge 不再用它们）。
2. **`value` demo 本地渲染实测**：起本地 http + Playwright 载入某输入卡（如 Input/InputBoxFilled/Pagination），确认填值示例真显示值还是空。空=react-pilot 不认 `value`→改这 8 处为 `modelValue`（canonical 真名）重生成上传；正常=保留（react-pilot 把 Vue modelValue 暴露为 React value，则 `value` 正确）。8 个组件：Input/InputBoxFilled/InputNumber/Pagination/Tab/TabList/SelectBoxFilled/SelectBoxLine。
3. **Sync B/TVU UX(`406a39da`) 同步**：把 Typography Figma 7 档字阶 + width-only 自适应 + 上述修复同步过去。**先查它的卡片机制**——它走系统 `.ds-sync` driver（卡格式 `components/general/*` + 自己的 `preview/type-scale.html` 等），不是本仓生成器，需先 `list_files`/`get_file` 勘察 driver 产物结构再定改法。

**关键约束（沿用本 session 血泪）**：改卡片集/新增卡 → 写 `_ds_needs_recompile` 哨兵 + SPA 重开触发重编译 + `get_file _ds_manifest.json` 亲验；纯内容改现有卡路径 → 直接传、无需重编译。DesignSync 只主 session。Eduardo `019dfb04` 绝不碰（同名项目，认准 owned 的 `bfca04be`）。push 以 `git ls-remote` 为准（GitHub phantom reject）。

---

## ★ 2026-07-24 三项已执行（commit `381f6be3`，双 remote ls-remote 验）

> **贯穿教训：owner/handoff 对①③的前提描述被 live 核实推翻**——「先读活源再下结论」（[AGENTS.md 硬规则 #10](../../../AGENTS.md)）当场救了两次不必要的 build/重传。

1. **Badge 字号 12px — premise 错误，真实范围远小于预估**。核实：`src/index.ts` 导出的是 `canonical/Badge.vue`（**一直** `font:var(--text-style-tips)`=12px）；`_ds_bundle.js` 里 Badge = `.canonical-badge`（12px，legacy `.badge--m/--s` 根本不在 bundle）；Badge 卡 demo 渲染 canonical API（color/fill/type，已 12px）；DS Typography 卡早已改用 `figmaTypeScale`（commit `7e5ef912`），不消费 `atomicAssets.typographyStyles`。**10/11px 只剩两处死角**：legacy `src/components/Badge/Badge.vue`（仅 `/legacy.html` 演示）→ 统一 12px（weight 600 保留计数可读性）；`atomicAssets.ts` Metric 11/10 两条 → 删。**⇒ 无需 `pnpm build`、无需重传 Badge 卡**（handoff 原写「组件本体=动 npm 包+bundle+卡渲染」三点全不成立）。改前核 `--badge-circle-size`=24px、`--text-style-tips`=12/16，无溢出。
2. **`value` demo — 实测「正常」，保留不改**。react-pilot wrapper（`react-pilot/src/wrappers/*.tsx`，由 `generate-react-bindings.mjs` 生成）刻意把 React 端 prop 命名为 **`value`**、内部 `el.modelValue = value`（Vue v-model 惯例映射）。Playwright http 实测 8 组件：Input/InputBoxFilled/InputNumber 输入框显真值；Pagination `value:3`→"21-30 of 120"；SelectBoxFilled/Line 显选中项文案；TabList `value`→active tab。**若按 owner 决策②改成 `modelValue`，wrapper 不认 → 反而渲染空**。⇒ 保留 `value`，无代码改动。（证据截图 `scratch/task2-shots/`）
3. **Sync B/TVU UX(`406a39da`) — foundation preview 已上传，待 owner 重开验 manifest**。机制勘察结论：Sync B 的 `preview/*.html` 由本仓**同一个** `gen-foundation-cards.mjs` 生成到 `ds-bundle/preview/`（`components/general/*` 才是 `.ds-sync` driver 产物，不碰）；merge 时上传的是**修复前**旧版（type 带 10/11/13px、radius XXL 旧值、全无 viewport）。修法 = 用当前生成器 `node scripts/gen-foundation-cards.mjs --out=ds-bundle` 重生成 → 上传 6 preview + `_card.css`。`@dsCard` 头 group/name/subtitle 与远端 manifest 完全一致（只 +viewport），重编译不会改名/换组。**Badge/value 修复对 Sync B 不需要**（同 Sync A：Badge 卡渲染 canonical 12px、value 正确）。已 `finalize_plan`(8 写 0 删)→`write_files`→`get_file type.html` 字节验（`truncated:false`/7 档/`viewport="1000"`/radius XXL 20px）+`list_files` 核哨兵落地。**⚠️ 未完成的唯一一步**：owner 在 claude.ai 重开/硬刷 Sync B 项目 → SPA 重编译 manifest + 消费哨兵 → 我 `get_file _ds_manifest.json` 亲验 6 张 foundation 卡 `viewport:"1000"`（当前 manifest 对这些卡**无** viewport 字段，故瓦片非 width-only，重编译才生效）。

---

## 附：TopBar UserMenu Demo 旧源（唯一只在上下文、已抢救于此；重写时作参照，勿照抄 drift props）

```html
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TVU — TopBar / UserMenu Demo</title>
<link rel="stylesheet" href="../styles.css">
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" crossorigin="anonymous"></script>
<script src="../_ds_bundle.js"></script>
<style>
  html, body { margin: 0; background: var(--bg-layer1); min-height: 100vh; }
  .demo-body { max-width: 1120px; margin: 0 auto; padding: var(--sp-xxl) var(--sp-xl); }
  .demo-hint { font: var(--text-style-body); color: var(--text-tips); margin: 0 0 var(--sp-l); }
  .cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-l); }
  .card { background: var(--bg-layer2); border: 1px solid var(--line-deep); border-radius: var(--r-l); padding: var(--sp-l); }
  .card .k { font: var(--text-style-body); color: var(--text-tips); margin: 0 0 var(--sp-xs); }
  .card .v { font: var(--text-style-large-title-22); color: var(--text-heading); }
  .card .d { font: var(--text-style-tips); color: var(--brand); margin-top: var(--sp-xs); }
  .section-title { font: var(--text-style-headline-1); color: var(--text-heading); margin: var(--sp-xxl) 0 var(--sp-m); }
  .toast { position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%); background: var(--bg-layer4); color: var(--text-heading); font: var(--text-style-body); padding: 10px 18px; border-radius: var(--r-m); box-shadow: 0 8px 24px rgba(0,0,0,.5); opacity: 0; transition: opacity .2s ease; pointer-events: none; z-index: 2000; }
  .toast.show { opacity: 1; }
  #boot { color: var(--text-tips); font: var(--text-style-body); padding: 24px; }
</style>
</head>
<body>
<div id="root"><div id="boot">Loading design system…</div></div>
<div class="toast" id="toast"></div>

<script>
function toast(msg){
  var el=document.getElementById('toast'); el.textContent=msg; el.classList.add('show');
  clearTimeout(window.__tt); window.__tt=setTimeout(function(){el.classList.remove('show');},1600);
}
// OLD (drift): polled window.TVUNetworksDesignSystem_bfca04. REWRITE → window.TvuDS
function whenReady(cb){
  var n=0, t=setInterval(function(){
    var ns=window.TVUNetworksDesignSystem_bfca04;
    if(ns&&ns.TopBar&&ns.UserMenu&&ns.MenuList){clearInterval(t);cb(ns);}
    else if(++n>60){clearInterval(t);document.getElementById('boot').textContent='bundle 加载失败';}
  },80);
}

whenReady(function(ns){
  var e=React.createElement;
  var TopBar=ns.TopBar, UserMenu=ns.UserMenu, MenuList=ns.MenuList, Icon=ns.Icon;
  if(Icon) Icon.spritePath='../assets/icons/sprite.svg';   // OLD drift: canonical Icon has NO spritePath

  var T={
    en:{overview:'Overview',sessions:'Sessions',network:'Network',alerts:'Alerts',
        activeSessions:'Active Sessions',bandwidth:'Total Bandwidth',health:'Fleet Health',
        tokens:'Purchase Tokens',seats:'Manage Subscription Seats',signout:'Sign out',
        section:'Live Metrics',hint:'点击右上角头像 TW 展开账号菜单，切换 EN / 中文 看整页文案随之变化。',
        d1:'+12 today',d2:'▲ 4.1% vs 24h',d3:'all systems normal'},
    zh:{overview:'总览',sessions:'会话',network:'网络',alerts:'告警',
        activeSessions:'活跃会话',bandwidth:'总带宽',health:'设备健康度',
        tokens:'购买 Tokens',seats:'管理订阅席位',signout:'退出登录',
        section:'实时指标',hint:'点击右上角头像 TW 展开账号菜单，切换 EN / 中文 看整页文案随之变化。',
        d1:'今日 +12',d2:'▲ 4.1% 对比 24h',d3:'系统全部正常'}
  };

  function App(){
    var ls=React.useState('en'), lang=ls[0], setLang=ls[1];
    var ns2=React.useState('overview'), nav=ns2[0], setNav=ns2[1];
    var tr=T[lang];

    var navItems=[['overview',tr.overview],['sessions',tr.sessions],['network',tr.network],['alerts',tr.alerts]]
      .map(function(m){return {label:m[1], active:nav===m[0], onClick:function(){setNav(m[0]);toast(m[1]);}};});

    var logo=e('svg',{width:26,height:26,'aria-hidden':true,style:{color:'var(--brand)'}},
      e('use',{href:'#brand/logo-tvu',xlinkHref:'#brand/logo-tvu'}));

    // OLD drift: UserMenu items:[...] → canonical actions:[...] + languages:[...]
    var right=e(React.Fragment,null,
      Icon?e(Icon,{key:'n',name:'status/notification',size:20,color:'var(--icon-default)'}):null,
      Icon?e(Icon,{key:'s',name:'setting/preferences',size:20,color:'var(--icon-default)'}):null,
      e(UserMenu,{key:'u',name:'Tim Wong',email:'twong@tvunetworks.com',role:'Owner',lang:lang,
        onLanguageChange:function(v){setLang(v);toast(v==='zh'?'语言：中文':'Language: English');},
        items:[{label:tr.tokens,onClick:function(){toast(tr.tokens);}},
               {label:tr.seats,onClick:function(){toast(tr.seats);}}],
        signOutLabel:tr.signout,onSignOut:function(){toast(tr.signout+' — demo');}})
    );

    var bar=e(TopBar,{tag:'After Login',logo:logo,showMenu:true,
      menu:e(MenuList,{orientation:'horizontal',items:navItems}),rightContent:right});

    var body=e('div',{className:'demo-body'},
      e('p',{className:'demo-hint'},tr.hint),
      e('div',{className:'section-title'},tr.section),
      e('div',{className:'cards'},
        e('div',{className:'card'},e('p',{className:'k'},tr.activeSessions),e('div',{className:'v'},'248'),e('div',{className:'d'},tr.d1)),
        e('div',{className:'card'},e('p',{className:'k'},tr.bandwidth),e('div',{className:'v'},'18.6 Gbps'),e('div',{className:'d'},tr.d2)),
        e('div',{className:'card'},e('p',{className:'k'},tr.health),e('div',{className:'v'},'99.2%'),e('div',{className:'d'},tr.d3))
      )
    );

    return e(React.Fragment,null,bar,body);
  }

  ReactDOM.createRoot(document.getElementById('root')).render(e(App));
});
</script>
</body>
</html>
```

**重写要点**：`whenReady` 轮询 `window.TvuDS`；引 `../_vendor/react.js`+`../_ds_bundle.js` 去掉 CDN；`UserMenu` 的 `items/signOutLabel/onSignOut/onLanguageChange` → canonical `actions[]`+`languages[]`（对照 `scripts/data/component-card-demos.mjs` 的 `demos.UserMenu`）；去掉 `Icon.spritePath`；其余布局/i18n/toast 保留。
