# baoyu-workbench-frontend-pitfalls — deploy cdn

## 114. 浏览器里 CDN 页面不对，先用 curl 复核再下结论

同一个 CDN 链接，浏览器可能拿到 CloudBase 的**中间页/“风险提醒”页**（整页只有一条“我是开发者，如何去掉当前页面？”的链接），而 `curl` 拿到的是真实内容。因此：
- 判断“部署有没有生效”一律以 `curl -H 'Cache-Control: no-cache'` 拿到的内容为准（查特征串 + 本地/线上逐字节比对），**不要凭浏览器看到的页面判定部署失败**。
- 浏览器看到中间页时，那是托管侧的中间页设置，与页面本身无关：记下现象告知用户（可在 CloudBase 控制台关掉中间页），**不要去改页面代码**。
- 反向同理：链接点击类问题要在**本地同文件**上复现和验证（功能逻辑一致），不要被托管侧差异带偏。



## 52. 缓存破坏代码插进 `function loadData()` 定义导致语法错误

**症状**：登录页下拉框展开后完全空白（无用户列表），JS 全部崩溃。

**根因**：为绕过 CDN 缓存，用 Python 执行 `c.replace("loadData()", "loadData(); console.log('v=...')")`，但 `loadData()` 同时出现在函数定义 `function loadData() {` 中，导致：
```javascript
function loadData(); console.log('v=1785142324') {  // ← 语法错误！
```

**检测**：`node --check` 会报告 `SyntaxError: Unexpected token ';'`。`grep -n "loadData();" 工具/index.html` 检查是否有残留。

**铁律**：CDN 缓存用 URL 参数 `?v=` 破坏，**不要**改代码来破坏缓存。



## 17. Tunnel URL 更新：必须更新 3 处以上

**2026-07-26 起已切 Tailscale Funnel 固定域名，本节仅在未来迁移域名时适用**（如买自有域名后切 Cloudflare Named Tunnel）。

Tunnel 重建后 URL 变化，HTML 中需要同步更新至少 3 处（不是 2 处）：
- `var AUTH_URLS[0]` — 飞书免登回调（`feishuAutoLogin` 函数内）
- `var apiUrls[0]` — 登录认证（`doLogin` 函数内）
- `var API_BASE_URLS[0]` — MCP 数据拉取

`replace_all=true` 可安全用于 Tunnel URL（域名唯一），但改完仍需 `grep -c <old-url>` 确认全部清零。



## 20. CDN 缓存验证 vs Origin 文件

CloudBase CDN 缓存可能阻挡部署验证。区分方法：

```bash
# CDN 边缘节点（可能缓存旧版）
curl -s "https://<cloudbase-url>/index.html" | grep -c '<new-feature>'

# Origin 实际文件
tcb hosting download index.html /tmp/verify.html -e <env-id>
grep -c '<new-feature>' /tmp/verify.html
```

两者不一致 = CDN 未刷新（等 5-10 分钟）。部署后立即用 `tcb hosting download` 验证 Origin。

### CDN 验证



## 29. 审计无本地源的 CloudBase 页面：先下载建源再改（2026-07-26）

`/工具/index.html` 曾只存在于云端、本地无源文件。标准流程：
```bash
curl -s "<线上URL>" -o /tmp/page.html                                     # 1. 下载
cp /tmp/page.html ~/Desktop/Hermes输出-工作类/工具/index.html              # 2. 建立本地源（唯一真相）
cp index.html index_backup_$(date +%Y%m%d).html                           # 3. 备份
# 4. patch 修复 → 括号平衡验证（第25条清单）
tcb hosting deploy index.html /工具/index.html -e bier-sales-d0gatbvlx288724e9  # 5. 显式云路径部署
# 6. CDN 验证：curl -H "Cache-Control: no-cache" <线上URL> | grep -c '<新代码标记>'
# 7. 实测验证：browser_navigate → 真实登录 → 逐 Tab switchTab 并收集 window.onerror
```
逐 Tab 验证 snippet：
```javascript
window._errs=[]; window.onerror=function(m){window._errs.push(String(m));};
['customers','orders','todos'].forEach(function(t){try{switchTab(t);}catch(e){window._errs.push(t+':'+e.message);}});
```



## 89. app.html 缺失 helper 函数 → script 块中断 + 功能崩溃（2026-08-23 血案）

**症状**：AI 助手「真实 AI 回答」链路跑不通——`esc`/`CHAT_APIS` 全部 undefined，点发送报 `esc is not a function`；客户/客诉 Tab 筛选下拉 `renderMultiSelect is not defined`；KPI 环形图 `clamp is not defined`。此前被误判为「Funnel 网络偶发卡住」，实际是代码 bug。

**根因**：app.html 是「双线分拆」重构版，从 index.html 拆分时**漏了 4 个 helper 函数**，但调用点全保留：
1. `debounce`（顶层 `var debouncedSearch = debounce(...)` / `var _debouncedOrderRender = debounce(...)`）——**缺失导致 script 块在首个调用处直接中断**，其后所有 `var` 赋值（含 esc/CHAT_APIS/CHAT_TIMEOUT_MS）全部不执行。这是 AI 回答链路跑不通的真正根因。
2. `clamp`（KPI donut 环形图调用）
3. `showToast`（15 处：AI 问答保存/归档/转待办 + 拜访卡 + 待办）
4. `multiSelect`/`renderMultiSelect`/`toggleMs`/`toggleMsOpt`/`toggleMsAll`（客户/客诉筛选下拉）+ `.ms-*` CSS

**检测**：双文件对比缺失函数——index.html 有但 app.html 缺，且在 app.html 中被调用：
```python
# 提取两个文件 function X() + var X = function 集合做差集
# 对 app 缺失的每个 name，正则查 app_js 里是否有 name( 调用（无定义 → 真缺失）
```

**诊断手法**：`'name' in window`（true 但 `typeof name` 是 undefined）→ 变量「声明了但没赋值」，即 script 中段抛错中断；`typeof name` 完全 undefined 且函数声明也消失 → 语法错误。两者区分开能秒定位。

**修复**：从 index.html 复制缺失函数 + CSS 到 app.html（debounce/clamp/showToast/multiSelect 组件 + `.ms-*`/`.upload-toast`）。

**铁律**：app.html 与 index.html 是两份独立代码，改一个必须 diff 检查另一个是否缺同名函数。任何「某函数 undefined 但同块其它 function 正常」的症状，先怀疑顶层 `var X = undefined_func()` 在 script 中段中断（var 提升让变量存在但未赋值）。



## 92. Desktop 目录文件读取卡死（iCloud 优化存储）→ 用 tcb hosting download 绕过（2026-08-30）

**症状**：`ls ~/Desktop/` 30-180s 超时；`cat`/`head`/`grep`/`wc -l` 读 Desktop 下文件（如 `工具/app.html`）全部卡死；`read_file` 返回 "File not found"；但 `ls ~/`、`ls -ld ~/Desktop`、`stat <文件>` 都秒回。`grep -r` 递归 `数据/` 目录也卡死。

**根因**：Desktop 开启 iCloud「优化 Mac 存储」后，文件被驱逐到云端（本地仅占位符），**读内容**触发按需下载卡住；`stat` 只读元数据所以快。

**判别**：`stat -f "%N size=%z blocks=%b" <文件>` 秒回、但 `head -c 100 <文件>` 卡 → 就是这个问题。

**绕过（首选，线上是权威版本）**：用 `tcb hosting download` 从 CloudBase 拉线上副本到 /tmp，再用 read_file/grep 分析，完全绕过本地 Desktop：
```bash
tcb hosting download workbench/index.html /tmp/wb.html -e bier-sales-d0gatbvlx288724e9
tcb hosting download "工具/app.html" /tmp/app.html -e bier-sales-d0gatbvlx288724e9
```

**新源文件写入位置**：Desktop 读卡顿，但新建 HTML 源文件写 `/Users/liuxinyuan/work/`（非 iCloud、非 TCC 保护）再部署，避免踩同一坑。

**勿用**：`ls ~/Desktop/`、`grep -r` 递归 Desktop/数据 目录（会卡死）；`search_files` 精确到 Desktop 下单个文件也会超时。



## 94. app.html 双线分拆遗漏整套布局 CSS（2026-08-30）

**症状**：客户 tab 搜索框位置错乱（跑到侧边栏下方/页面顶部附近），侧边栏/底部导航样式异常。用户报「搜索框位置不对」。

**根因**：app.html 从 index.html 做「双线分拆」时，遗漏了整套布局 CSS——`.app-sidebar`、`.app-bottom-nav`、`.nav-item`、`.nav-divider`、`.bottom-nav`、`#custToolbar`、`.search-box` 全都没有样式定义（只有 HTML `<aside>`/`<nav>` 结构 + JS 填充，无 CSS）。这是 #89（漏函数）、#91（漏变量）的同类 bug，这次是**漏 CSS 选择器**。缺 CSS 时 aside/nav 退化成块级元素占文档流，把 custToolbar/main 挤下去。

**判别**（一行命令）：`grep -c "\.app-sidebar{" 工具/app.html` 返回 0，但 `grep -c "app-sidebar" 返回 >0`（有 HTML 引用、无 CSS 定义）。

**修复**：从 index.html 复制整套布局 CSS 到 app.html，适配 app.html 的 header 高度（app.html 硬编码 54px，index.html 用 `var(--header-h)` 58px）：
1. `:root` 补 `--header-h: 54px; --sidebar-w: 230px;`
2. 补 `.app-sidebar`（`position:fixed;top:var(--header-h);width:var(--sidebar-w)`）、`.app-bottom-nav`、`.nav-item`、`.nav-divider`、`.bottom-nav`、`#custToolbar`、`.search-box` 全套
3. `.app-main` 从 `max-width:1100px;margin-left:auto;margin-right:auto` 改为 `margin-left:var(--sidebar-w)`
4. `@media(max-width:640px)` 补移动端：`.app-sidebar{display:none}` + `.app-bottom-nav{display:block}` + `.app-main{margin-left:0;padding-bottom:90px}`

**铁律**：app.html 与 index.html 是两份独立代码，拆分/改动后除了 diff 函数（#89）和变量（#91），**还要 diff CSS 选择器**——`<selector>{` 在 index.html 有但 app.html 无，且 app.html 的 HTML/JS 用到了该 class，就是漏 CSS。

**⚠️ 多步 Python 替换的坑（本次踩到）**：同一脚本里先 `replace(anchor, newA)` 再 `replace(anchor, newB)`，第二次的 `anchor` 已被第一次换掉 → `str.replace` 静默不生效（无异常），且后续 `assert html.count(old)==1` 会因中间态文件已被前一步改写而误报。**每次 replace 前先 re-read 文件确认当前真实内容，或一个锚点只 replace 一次**；改完必须 `grep -c` 逐个确认每个 CSS 选择器真的落盘（本次曾因顺序错漏，layout CSS 未写入，靠计数 0 才发现）。
