# 清单格式、Funnel 与存档区铁律（2026-07-27 血案汇总）

## 1. Tailscale Funnel 端口配置陷阱

### 症状
API 调用 `https://macbook-air-2.tail6864e8.ts.net/api/visit-card` 不响应，但本地 `curl http://127.0.0.1:8000/api/visit-card` 正常。

### 根因
Funnel 转发端口与服务器监听端口不一致：
- 服务器运行在 `0.0.0.0:8000`
- Funnel 转发到 `127.0.0.1:8765`（旧端口）

### 检测
```bash
tailscale serve status
# 看 Funnel 条目 → 本地端口
lsof -i :8000  # 确认服务器在哪个端口
```

### 修复
```bash
# 旧语法 tailscale funnel <port> 已废弃
tailscale funnel --bg http://127.0.0.1:8000
curl -s --max-time 10 https://macbook-air-2.tail6864e8.ts.net/api/visit-card \
  -X POST -H "Content-Type: application/json" -d '{"customer":"test"}'
```

### 铁律
服务器启动后四步验证：本地端口 → Funnel 状态 → Funnel URL 外网可达 → 页面 API 正常。缺一不可。

---

## 2. file_manifest.json 是 dict 不是 {files: []}

`regenerate_manifest.sh` 输出格式：
```json
{
  "/客户管理/拜访卡/广州万孚_拜访卡_20260727.html": {
    "name": "广州万孚_拜访卡_20260727", "type": "visit", "path": "..."
  }
}
```

前端不能 `manifest.files`（不存在）：
```javascript
// ❌ var files = manifest.files || [];
// ✅ 遍历对象键
var entries = [];
Object.keys(manifest).forEach(function(k) {
  var v = manifest[k];
  if (v && v.name) entries.push(v);
});
```

---

## 3. name 字段不含扩展名 → `.endsWith('.html')` 陷阱

manifest 脚本 `name = f.rsplit('.', 1)[0]` 剥离了 `.html`。`entries=30` 但 `visitCards=0` 的根因。

```javascript
// ❌ n.endsWith('.html')  — name 不含扩展名
// ✅ 仅关键词
var n = (f.name||'').toLowerCase();
return n.indexOf('_拜访卡') > 0 || n.indexOf('_市场调研卡') > 0 || n.indexOf('_访前背调') > 0;
```

---

## 4. 存档区按销售员隔离

从文件名提取客户名 → 与 `getVisibleCustomers()` 交叉匹配：
```javascript
var myCustNames = getVisibleCustomers().map(function(c){
  return c.name.replace(/[\(\)（）]/g,'').toLowerCase();
});
visitCards = visitCards.filter(function(card){
  var fn = (card.name||'').toLowerCase();
  var separators = ['_拜访卡','_市场调研卡','_调研_','_访前背调','_拜访作战卡'];
  var custName = fn;
  for (var si=0; si<separators.length; si++) {
    var idx = fn.indexOf(separators[si]);
    if (idx > 0) { custName = fn.substring(0, idx); break; }
  }
  return myCustNames.indexOf(custName) >= 0;
});
```

---

## 5. IMA API 笔记创建后不可立即搜索

`import_doc` 返回 `code:0, note_id:xxx`，但 `search_note_book` / `list_note_by_folder_id` 返回 0 结果。索引有延迟。
- `get_doc_content(doc_id)` 可立即验证
- `list_note_folder_by_cursor` 对多数账号返回空（API 限制）
- 笔记本需用户在 IMA App 手动创建，API 无法创建 notebook
- KB `search_knowledge` 仅搜标题，不搜内容

---

## 6. 请求队列 localStorage → API 文件双写

访前准备"生成拜访卡"提交时：
1. 前端写 `localStorage`（`bior_visit_queue`）——即时显示"处理中"
2. `fetch` POST 到 API（Funnel URL）——落文件到 `数据/visit_requests/`
3. API 返回 `{ok:true, queued:true}`
4. Hermes Agent 轮询队列文件处理

若 Funnel 不通 → 请求只留在 localStorage，Agent 看不到。用户需点"清除旧请求"后重试。

---

## 7. 拜访卡 CloudBase 部署——手机端访问必备

**症状**：赵云浩在手机飞书/平板点存档区"打开"链接 → 404。

**根因**：拜访卡存在 Mac 本地，存档区用的是本地 Funnel 链接。Funnel 依赖 Mac 在线。

**修复**：每次生成拜访卡后**同步部署到 CloudBase**，存档区用相对路径（CloudBase CDN 自动解析）：
```bash
tcb hosting deploy "客户管理/拜访卡/{file}" "客户管理/拜访卡/{file}" -e bier-sales-d0gatbvlx288724e9
```

**存档区链接**：用相对 `path`（如 `/客户管理/拜访卡/广州万孚_拜访卡_20260727.html`），CloudBase 自动解析。不要硬编码 Funnel URL。

## 8. 存档区 `<a>` 标签 vs `window.open`

**症状**：飞书 WebView 中 `onclick="window.open(...)"` 可能被拦截或重定向。

**修复**：用标准 `<a href="..." target="_blank">打开</a>` 标签。飞书 WebView 对 `<a>` 标签的处理优于 `window.open`。
