# TVU Consumer Product — Setup Runbook

> 本 runbook 是 plugin 内 `setup-tvu-consumer` skill 的执行手册。
> Skill shim（cwd 安全检查 + idempotency）在 `skills/setup-tvu-consumer/SKILL.md`。
>
> **运行假设**：调用方已通过 Step 1（cwd 安全）+ Step 2（idempotency）检查，确认当前 cwd 是合法消费产品目录、未配置过。

真源仓库变量：`TVU="${CLAUDE_PLUGIN_ROOT:-<plugin-root>}"`（下称 `$TVU`）
模板根：`TEMPLATE="$TVU/templates/consumer-product"`

---

## Step 0. Variant Selection（mandatory · 跳不得）

**先问用户选哪种变体**（详见 [`PROJECT_STRUCTURE.md` § 两种变体](./PROJECT_STRUCTURE.md#两种变体setup-时必选其一)）：

```
你要建哪种变体？
  A · Design + Code（默认）— 标准消费产品，含 vue-app / vite / postinstall sibling link
  B · Design-only（纯设计协作）— 只放 mockup spec / 需求 / 复盘 / Figma 链接，代码住别处
```

**判定启发**（用户没明示时辅助推测，但**仍要确认**——不擅自决定）：
- cwd 已有 `package.json` / `vite.config.{js,ts}` / `src/` → 倾向 A
- cwd 完全空 OR 只有 `docs/` / 设计草稿 → 倾向 B
- 用户已经说过"不写代码"/"代码在别的仓"/"只放 UX 设计" → 锁定 B

**记录 variant**：把用户选择写进 `VARIANT` shell 变量，后续 Step 1-5 据此跳步：

```bash
# 用户选 A:
VARIANT=design-code
# 用户选 B:
VARIANT=design-only
```

**后续 step 适用矩阵**：

| Step | 变体 A | 变体 B |
|---|---|---|
| Step 1 · Vite singlefile 检查 | ✅ 执行 | ⏭ 跳过 |
| Step 2 · TVU DS 联调 (postinstall + vite alias) | ✅ 执行 | ⏭ 跳过 |
| Step 2.7 · mockup handoff conformance gate（零依赖 git hook，F62-b） | ✅ 执行 | ✅ 执行 |
| Step 2.9 · upstream-gate 接线（支柱④） | ✅ 执行 | ✅ 执行 |
| Step 3 · Docs 骨架 + 项目结构 scaffold | ✅ 执行 | ✅ 执行（结构检查跳过"主应用目录"项） |
| Step 4 · 验证 | ✅ 执行（全套） | ✅ 执行（只验 docs 骨架） |
| Step 5 · 报告 + 收尾 | ✅ 全报告 | ✅ 简化报告（无 Vite / TVU DS 联调段） |

---

## Step 1. Vite singlefile 强制配置（部署侧规则）

> **仅 `VARIANT=design-code` 执行**。变体 B 跳过本 step。

**适用条件**：cwd 下（根 + 一级子目录）存在 `vite.config.{js,ts}`。无则跳过。

**为什么强制**：消费产品要部署到 ux_server（`product-demo.tvustream.com`），Caddy 静态托管对子路径资源敏感；deploy-hook 的 singlefile alias 依赖 `dist/` 不含 `assets/` 子目录才会触发——只有 singlefile 满足。详见 `~/Documents/ux_server/SETUP_PROGRESS.md` 的"开新项目"段。

**对每个发现的 vite project 执行**：

```bash
# (a) package.json devDependencies 缺 vite-plugin-singlefile → npm i -D
if ! jq -e '.devDependencies["vite-plugin-singlefile"] // .dependencies["vite-plugin-singlefile"]' package.json >/dev/null 2>&1; then
  npm install -D vite-plugin-singlefile
fi

# (b) vite.config.* 静态检查（必须三要素）:
#     1) import { viteSingleFile } from 'vite-plugin-singlefile'
#     2) plugins 数组里有 viteSingleFile()
#     3) build 里有 assetsInlineLimit: Infinity, cssCodeSplit: false
# 缺任一 → 报告 + 给 diff 建议 + 让用户确认（不自动改 vite.config）
```

**报告格式**（即使全部 OK 也明示）：

```
✅ Vite singlefile 配置检查（<path/to/vite.config.js>）
   - vite-plugin-singlefile 依赖: 已装 / 已为你装
   - viteSingleFile() plugin: 已配置 / ⚠️ 缺失，建议加在 plugins 数组
   - build.assetsInlineLimit: Infinity ✓ / ⚠️ 不是 Infinity
   - build.cssCodeSplit: false ✓ / ⚠️ 没设为 false
```

⚠️ **永远不自动改 vite.config**——只报告 + 提建议，用户确认后由用户或下一轮对话改。

---

## Step 2. TVU DS 依赖联调（本地 sibling auto-link + npm fallback）

> **仅 `VARIANT=design-code` 执行**。变体 B 跳过本 step。

**适用条件**：vite project `package.json` 已声明 `@nancyzeng0210/tvu-design-system` 依赖。无则跳过。

**为什么**：消费产品改 sibling repo 源码时要立刻 HMR 看到效果，但 `package.json` 不能硬写 `file:../../tvu-design-system` 路径（污染 git、他人 clone / CI 没 sibling 直接挂）。双层 fallback：
- 本地有 sibling → postinstall 软链覆盖 `node_modules/@nancyzeng0210/tvu-design-system` + vite alias 把 bare import 重定向到 `src/index.ts`
- 没 sibling → postinstall 静默跳过 + vite 无 alias，runtime 走 npm 发布版 dist

参考实现：消费产品可用 postinstall soft-link + vite alias 条件块实现本地 sibling auto-link with npm fallback。

**对每个匹配的 vite project**：

```bash
# (a) 复制 postinstall 脚本（idempotent，已存在不覆盖）
mkdir -p scripts
[ ! -f scripts/link-tvu-ds.mjs ] && cp "$TEMPLATE/scripts/link-tvu-ds.mjs" scripts/link-tvu-ds.mjs

# (b) package.json 加 postinstall（已含 link-tvu-ds 则跳过）
if ! jq -e '(.scripts.postinstall // "") | contains("link-tvu-ds")' package.json >/dev/null 2>&1; then
  tmp=$(mktemp) && jq '.scripts.postinstall = (
    if (.scripts.postinstall // "") == "" then "node scripts/link-tvu-ds.mjs"
    else (.scripts.postinstall + " && node scripts/link-tvu-ds.mjs") end
  )' package.json > "$tmp" && mv "$tmp" package.json
fi

# (c) package.json TVU 依赖必须是发布版号，不能是 file: 路径
TVU_DEP=$(jq -r '.dependencies["@nancyzeng0210/tvu-design-system"] // ""' package.json)
case "$TVU_DEP" in
  file:*|link:*)
    echo "⚠️  package.json 把 @nancyzeng0210/tvu-design-system 写成 $TVU_DEP，会污染 git。建议改回 ^x.y.z 发布版号，让 postinstall + vite alias 接管联调。"
    ;;
esac

# (d) vite.config 检查 + REPORT（不动手 — 同 Step 4 doctrine）
# 必须包含（参考 $TEMPLATE/vite.config.fragment.js）:
#   1) import { existsSync } from 'node:fs'
#   2) import { fileURLToPath, URL } from 'node:url'
#   3) const tvuDsRoot = fileURLToPath(new URL('../../tvu-design-system', import.meta.url))
#   4) const useLocalTvuDs = existsSync(`${tvuDsRoot}/src/index.ts`)
#   5) resolve.alias 条件块（local 时映射 bare import + style.css）
#   6) server.fs.allow 条件含 tvuDsRoot
# 缺任一 → 报告 + 指向 $TEMPLATE/vite.config.fragment.js 让用户合并
```

**报告格式**：

```
✅ TVU DS 联调配置（<path/to/vite project>）
   - scripts/link-tvu-ds.mjs: ✅ 已就位 / ✨ 已为你创建
   - package.json postinstall: ✅ 已配置 / ✨ 已为你加上
   - package.json TVU 依赖版本: ✅ ^x.y.z / ⚠️ 检测到 file:/link: 路径
   - vite.config alias 条件块: ✅ 已配置 / ⚠️ 缺失，参考 $TEMPLATE/vite.config.fragment.js 合并
```

⚠️ **永远不自动改 vite.config**（同 Step 1 doctrine）。
⚠️ **永远不自动改 dependencies 版本**（涉及构建语义，必须用户主导）。

---

## Step 2.6. TVU 规范强制接线（ESLint + lint-staged + pre-commit）

> **仅 `VARIANT=design-code` 执行**。变体 B（design-only）跳过本 step。
> 真源：[`docs/CONSUMER_AUDIT_SETUP.md`](../../docs/CONSUMER_AUDIT_SETUP.md) §Scenario-aware enforcement + [`docs/internal/ds-enforcement-scenarios-spec.md`](../../docs/internal/ds-enforcement-scenarios-spec.md)。

**为什么**：TVU 的 ESLint 规则（含 `no-native-element` 拦手搓 `<button>/<input>/<select>/<textarea>`）只在**真正接进 lint + pre-commit** 时才生效。"配了 config 但从不跑" = 违例 silent 通过（spec §3 根因）。

### (a) 先定场景（mandatory · 不要默认 strict）

问用户当前任务属哪个 TVU 消费场景（对齐 `code-conventions.md §R7`）：

```
这个产品/任务属于哪种场景？
  3 · 新产品全量 TVU（greenfield）        → STRICT 全量、阻塞
  2 · 已有产品上加/改功能（增量，默认）    → INCREMENTAL：只检查改动文件（lint-staged report-only），不动 legacy
  1 · 非 TVU 视觉还原（按第三方设计像素还原）→ OFF：不接 TVU lint 层
```

> **纪律（spec §2）**：场景 2 **不能**上 strict-TVU 全量闸——会把 pre-design-system 的 legacy 全量误报，并诱导违反 R6（不借机修旧债）。增量场景只检查**改动文件**，且 findings report-only，不阻断旧产品沿用 local 视觉。

### (b) ship ESLint 层（idempotent，已存在不覆盖）

```bash
# 复制 eslint 规范层 fragment（不覆盖用户已有 eslint.config）
[ ! -f eslint.config.fragment.mjs ] && cp "$TEMPLATE/eslint.config.fragment.mjs" eslint.config.fragment.mjs
# 报告：让用户把对应场景的 enforcement 块 merge 进自己的 eslint.config.{js,mjs}（放在 Vue/TS 块之后）
#   场景2：tvuEnforcementIncremental / tvuEnforcement（report-only；允许旧产品本地 SVG / local 视觉复用）
#   场景3：tvuEnforcementStrict（禁止任意 local/CDN SVG icon asset）
```

⚠️ **不自动改用户已有 `eslint.config.*`**（同 vite.config doctrine：配置文件常含自定义逻辑）——只 ship fragment + 报告 merge 指引。

### (c) 按场景接 pre-commit

**场景 2（增量，默认）— 只 gate 改动文件**：

```bash
# lint-staged config（merge 进 package.json，已含则跳过）
if ! jq -e '."lint-staged"' package.json >/dev/null 2>&1; then
  tmp=$(mktemp) && jq '."lint-staged" = {"*.{vue,js,jsx,ts,tsx}": "eslint"}' package.json > "$tmp" && mv "$tmp" package.json
fi
# 提示装 husky + lint-staged + 建 .husky/pre-commit（执行 npx lint-staged）——见 CONSUMER_AUDIT_SETUP.md §5
echo "ℹ️  场景2 增量 gate：pnpm add -D husky lint-staged && npx husky init && echo 'npx lint-staged' > .husky/pre-commit"
```

**场景 3（greenfield）— 额外加全量 lint + audit script**：

```bash
tmp=$(mktemp) && jq '.scripts.lint = (.scripts.lint // "eslint .") |
  .scripts["audit:code"] = (.scripts["audit:code"] // "node node_modules/@nancyzeng0210/tvu-design-system/scripts/audit-product-code.mjs --dir src")' package.json > "$tmp" && mv "$tmp" package.json
echo "ℹ️  场景3 全量：把 pnpm lint + pnpm audit:code 接进 CI + pre-commit（阻塞）"
```

**场景 1**：跳过本 step（不接 TVU lint 层）。

### (d) 报告

```
✅ TVU 规范强制接线（场景 <1|2|3>）
   - eslint.config.fragment.mjs: ✅ 已 ship / 已存在（场景2 merge tvuEnforcementIncremental；场景3 merge tvuEnforcementStrict）
   - 场景2: package.json lint-staged: ✅ 已加 / 已存在；pre-commit: ⚠️ 待装 husky（见提示）
   - 场景3: package.json lint + audit:code script: ✅ 已加；CI/pre-commit 接线待用户
   - 场景1: 跳过（视觉还原，enforcement off）
```

---

## Step 2.7. mockup handoff conformance gate（零依赖 git hook · F62-b）

> **变体 A + B 均执行**（两变体都产 mockup handoff）。
> 真源：[`.githooks/README.md`](./.githooks/README.md)；镜像自 `$TVU/scripts/audit-mockup-handoff-evidence.mjs`。

**为什么**：mockup handoff 文档的真实落点是消费仓库 `docs/handoffs/`，含 `<!-- mockup-handoff -->`
标记的 handoff 必须引用一份真跑过、通过、新鲜的 conformance report——堵住"手打 `Integrity audit: I1=pass`
编造证据"洞。此前这道闸只挂在 DS 自己仓库，消费仓库（尤其无 husky 的变体 B）从没有。本 hook 用 git
原生 `core.hooksPath`，唯一硬依赖是 node，不需要 npm/husky。

### (a) ship hook 目录（idempotent，已存在不覆盖）

```bash
mkdir -p .githooks
for f in pre-commit audit-mockup-handoff-evidence.mjs README.md; do
  [ ! -f ".githooks/$f" ] && cp "$TEMPLATE/.githooks/$f" ".githooks/$f"
done
chmod +x .githooks/pre-commit .githooks/audit-mockup-handoff-evidence.mjs
```

### (b) 按变体启用

**变体 B（Design-only，无 husky）—— 用 core.hooksPath（git 原生，零依赖）**：

```bash
# 只在还没被别的工具占用 hooksPath 时设（不覆盖用户已有配置）
CURRENT_HP=$(git config --get core.hooksPath || true)
if [ -z "$CURRENT_HP" ]; then
  git config core.hooksPath .githooks
  echo "✅ core.hooksPath = .githooks（mockup handoff gate 已启用）"
else
  echo "⚠️  core.hooksPath 已是 '$CURRENT_HP'——手动把 .githooks/pre-commit 的检查段并入（见 .githooks/README.md）"
fi
```

**变体 A（已装 husky，hooksPath 指向 .husky/）—— 追加检查段到 .husky/pre-commit**：

```bash
# husky 占用 .husky/ 作 hooksPath，不能再切 core.hooksPath；把检查段并入 husky pre-commit。
if [ -f .husky/pre-commit ] && ! grep -q 'audit-mockup-handoff-evidence' .husky/pre-commit; then
  cat >> .husky/pre-commit <<'HOOK'

# --- TVU mockup handoff conformance gate (F62-b) ---
STAGED_HANDOFFS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^docs/handoffs/.*\.md$' || true)
if [ -n "$STAGED_HANDOFFS" ]; then
  echo "▶ pre-commit: mockup handoff conformance evidence"
  node .githooks/audit-mockup-handoff-evidence.mjs $STAGED_HANDOFFS || exit 1
fi
HOOK
fi
```

> `core.hooksPath` 是本地 repo 配置，不随 clone 传播——团队每个 clone 各自跑一次 setup（或手跑上面 (b)）。

### (c) 报告

```
✅ mockup handoff conformance gate（F62-b, 变体 A + B）
   - .githooks/{pre-commit,audit-mockup-handoff-evidence.mjs,README.md}: ✅ 已 ship / 已存在
   - 变体 B: core.hooksPath = .githooks: ✅ 已设 / ⚠️ 已被占用（<现值>，手动并入）
   - 变体 A: .husky/pre-commit 追加检查段: ✅ 已加 / 已存在 / ⚠️ 无 .husky（先跑 Step 2.6 装 husky）
   - node 依赖: 已装 ✓ / ⚠️ 未装（gate fail-open 告警，装 node 后自动生效）
```

---

## Step 2.9. upstream-gate 接线（支柱④ 上游理解-分析 gate）

> **变体 A + B 均执行**（design-only 也产 mockup，同样要走上游 gate）。

**为什么**：产品设计（code/mockup）动手前强制走上游理解-分析并产出 `docs/specs/upstream-gate.<feature>.md`，其确定层由 validator 机校、理解层由 owner PR review 兜底。gate 的机械 reach = 消费 repo 是否成为 chokepoint（有 git remote + CI）。

### (a) ship 模板（idempotent，已存在不覆盖）

```bash
mkdir -p docs/specs
[ ! -f docs/specs/_upstream-gate.template.md ] && cp "$TEMPLATE/docs/specs/_upstream-gate.template.md" docs/specs/_upstream-gate.template.md
[ ! -f docs/specs/_product-context.template.md ] && cp "$TEMPLATE/docs/specs/_product-context.template.md" docs/specs/_product-context.template.md
# CI workflow 模板（复制到消费 repo 的 .github/workflows/，Gitea consumer 改等价语法）
mkdir -p .github/workflows
[ ! -f .github/workflows/upstream-gate.yml ] && cp "$TVU/templates/upstream-gate-workflow.yml" .github/workflows/upstream-gate.yml
```

### (b) git-remote 探测（落点自举 —— 消费者已有 remote 永远优先）

```bash
# 只探测 + 建议，绝不强制覆盖已有 remote（护栏 spec §6.2）。
node "$TVU/scripts/scaffold-git-remote.mjs"
```

按脚本输出：
- **已有 remote** → 什么都不用做，upstream-gate CI 直接挂在该 repo。
- **有 repo 无 remote** → 脚本建议默认 Gitea 地址；**用户确认或换成自己的托管**后手动 `git remote add origin <地址>`。
- **无 repo** → 脚本建议 `git init` + remote；用户确认后手动执行。

### (c) 报告

```
✅ upstream-gate 接线（支柱④）
   - docs/specs/_upstream-gate.template.md: ✅ 已 ship / 已存在
   - docs/specs/_product-context.template.md: ✅ 已 ship / 已存在（填好复制成 docs/product-context.md 激活强 gate）
   - .github/workflows/upstream-gate.yml: ✅ 已 ship / 已存在
   - git remote 落点: <use-existing 用现有 / suggest-default 待用户确认 / init-then-suggest 待 git init>
```

---

## Step 3. Docs 骨架 + 项目结构 scaffold

**完全 idempotent**——只建缺的，不覆盖已存在内容。

```bash
# (a) 6 子文件夹（缺哪个建哪个）
mkdir -p docs/{retrospects,handoffs,audits,specs,decisions,scripts}

# (b) docs/README.md（不存在则复制 + 用 cwd basename 替换 {{PRODUCT_NAME}}）
if [ ! -f docs/README.md ]; then
  PRODUCT=$(basename "$PWD")
  sed "s/{{PRODUCT_NAME}}/$PRODUCT/g" "$TEMPLATE/docs/README.md" > docs/README.md
fi

# (c) retrospects 配套（用法 README + Marp 骨架）
[ ! -f docs/retrospects/README.md ] && cp "$TEMPLATE/docs/retrospects/README.md" docs/retrospects/

# Marp 骨架: 复制时把 {{AUTHOR}} / {{TEAM}} 用 git config 真值替换
# 其它占位符 ({{FEATURE}} / {{DATE_RANGE}} / {{AI_MODEL}} / {{FILENAME}}) 是 per-retrospect 填，留原样
if [ ! -f docs/retrospects/_template.slides.md ]; then
  AUTHOR=$(git config user.name 2>/dev/null || echo "{{AUTHOR}}")
  sed -e "s/{{AUTHOR}}/$AUTHOR/g" -e "s/{{TEAM}}/TVU UX team/g" \
    "$TEMPLATE/docs/retrospects/_template.slides.md" > docs/retrospects/_template.slides.md
fi

# (c2) .marprc.yml — 让 Marp 找到 TVU 真源主题（绝对路径引用）
[ ! -f .marprc.yml ] && cp "$TEMPLATE/.marprc.yml.template" .marprc.yml

# (c3) 需求溯源索引脚本 + design-record / handoff frontmatter 模板（idempotent，已存在不覆盖）
# 有了这三件，consumer 收尾跑 `node docs/scripts/build-request-index.mjs` 才能生成 REQUEST-INDEX.md（见 design-process §6）
[ ! -f docs/scripts/build-request-index.mjs ] && cp "$TEMPLATE/docs/scripts/build-request-index.mjs" docs/scripts/build-request-index.mjs
[ ! -f docs/specs/_feature-design-record.template.md ] && cp "$TEMPLATE/docs/specs/_feature-design-record.template.md" docs/specs/_feature-design-record.template.md
[ ! -f docs/handoffs/_handoff.template.md ] && cp "$TEMPLATE/docs/handoffs/_handoff.template.md" docs/handoffs/_handoff.template.md

# (d) gitignore.fragment 追加到 .gitignore（用 marker 防重复）
MARKER="# === TVU consumer product 标准 .gitignore 片段 ==="
if [ -f .gitignore ] && ! grep -qF "$MARKER" .gitignore; then
  echo "" >> .gitignore
  cat "$TEMPLATE/gitignore.fragment" >> .gitignore
elif [ ! -f .gitignore ]; then
  cp "$TEMPLATE/gitignore.fragment" .gitignore
fi
```

**项目结构检查**：按 `$TEMPLATE/PROJECT_STRUCTURE.md` 比对当前 cwd，输出报告（**只报告不动手**）：

- [ ] root 有 `README.md`
- [ ] root 有 `docs/` 骨架（5 子目录 + README + retrospect 模板）

**仅变体 A 额外检查**：

- [ ] root 有**单一**主应用目录（`vue-app/` / `*-app/` / `web-app/` — 只允许一个匹配）
- [ ] root **不**直接放 Vite 项目文件（vite.config.* 必须在子目录）
- [ ] 主应用目录有 `package.json` + `vite.config.{js,ts}`

**变体 B 跳过**主应用目录检查（design-only 不需要应用代码）。

⚠️ 结构不合规 → 报告差异，**不自动迁移文件**（影响面大，必须用户主导）。

**marp-cli 提示**（全局工具，不入项目 deps）：

```bash
if ! command -v marp >/dev/null 2>&1; then
  echo "ℹ️  marp-cli 未全局安装。复盘幻灯片导出需要装一次："
  echo "    npm i -g @marp-team/marp-cli"
  echo "（非阻塞——不装也可以正常用 Claude，仅影响幻灯片导出）"
fi
```

---

## Step 4. 验证

```bash
ls docs/                      # 应看到 5 子文件夹 + README.md
test -f docs/retrospects/_template.slides.md && echo "Marp 模板就位"
```

任一 fail → 报告具体哪步挂 + STOP，不假装成功。

---

## Step 5. 报告 + 收尾

```
✅ TVU consumer setup 完成 @ <cwd>

可用 skills（7）:
  - tvu-design-mockup    （唤醒词 @TVU mockup）
  - tvu-design-code      （唤醒词 @TVU code）
  - design-discovery
  - design-walkthrough
  - design-qa-loop
  - persona-simulation
  - consumer-product-conventions  （消费产品通用工作规则，自动加载）

Docs 骨架（5 子目录已建）:
  - docs/retrospects/    （含 _template.slides.md + README）
  - docs/handoffs/
  - docs/audits/
  - docs/specs/
  - docs/decisions/

Marp 主题配置 (.marprc.yml): ✅ 已 link 到 TVU 真源 tvu-dark.css
Vite singlefile 检查: ✅ / ⚠️ 见上方报告
TVU DS 联调配置 (postinstall + vite alias): ✅ / ⚠️ 见上方报告
TVU 规范强制接线 (ESLint + lint-staged/pre-commit, 场景 <1|2|3>): ✅ / ⚠️ 见上方报告
mockup handoff conformance gate (F62-b, 零依赖 git hook, 变体 A + B): ✅ / ⚠️ 见上方报告
upstream-gate 接线 (支柱④, 变体 A + B): ✅ / ⚠️ 见上方报告
项目结构检查 (PROJECT_STRUCTURE.md): ✅ / ⚠️ 见上方报告
marp-cli (全局): 已装 / 待装（npm i -g @marp-team/marp-cli）

下次新 session 进本项目 cwd 即可直接触发，无需再 setup。
```

---

## 撤销（用户问起时告诉他）

```bash
rm -rf docs/retrospects/_template.slides.md .marprc.yml scripts/link-tvu-ds.mjs
rm -rf .githooks && git config --unset core.hooksPath   # 撤 mockup handoff gate（变体 B）
# 变体 A：手动删 .husky/pre-commit 里的 "TVU mockup handoff conformance gate" 段
# 手动移除 .gitignore 里的 TVU consumer product 标准片段（如需彻底撤销）
```
