# 2026-05-09 AITerms.tw 全日 changelog 與 17 條踩坑教訓

Updated: 2026-05-09

> 本文件給接手 AITerms.tw 的工程師與 AI 在動工前讀。
> 所有規則 / 教訓的詳細描述見 `docs/LESSONS-LEARNED.md` 章節 L–BB。
> 機器可讀清單見 `/content-quality/known-bad-patterns.json` `architecture_rules`。

---

## 1. 上線項目（5 個 deploy）

| Deploy | Version ID | 範圍 |
|---|---|---|
| #1 Layout Phase 1 | `e0fa9c91` | global.css 加 13+ utility class（page / section-stack / heading-row / badge-spec / stat-grid / alert-card 等） |
| #2 Layout Phase 3 | `cf5cf08f` | /ipas/L2/scope, strategy, terms 三頁套規範 |
| #3 Layout Phase 6 | `0e3f311c` | /tools, /catalog, /terms, /ipas/L1 全套規範 + Phase 5 Playwright 200/200 |
| #4 Learning Flow + Catalog | `9294a27f` | Learning Flow Engine 11 頁 + 9 API + 47 vitest pass + 30 工具 catalog 內容修正 |
| #5 /terms 稽核 | `af16ace8` | filter SSR + .md 多行 + /llms.txt(1015 行) + 6 個 content-quality 規範文件 + renderer 防護 |

---

## 2. 17 條踩坑教訓（章節 L–BB）

### 並行 / 部署紀律

**L. 多 fork 同檔 race condition** — NEVER 派平行 fork 改同一檔。Edit 工具用 file-state cache，多 fork 並行寫同檔 → 互相覆蓋 → race。同檔多工作 MUST 序列或單 fork 內部分批。

**M. safe-deploy.sh 擋 dynamic 化的固定頁** — 4 個 static page 錨點（`/`, `/ipas`, `/ipas/L1`, `/ipas/L2`）MUST 保持靜態。動態 state 用 vanilla JS client-side fetch + DOM 注入，NEVER 把整頁改 SSR。`scripts/safe-deploy.sh` 第 27 行 hardcode 這 4 個錨點。

**N. /llms.txt 線上空檔** — 所有 `text/markdown` / `text/plain` / `application/json` 公開路由 MUST `export const prerender = true`。runtime SSR 在 CF Worker prod 環境失敗會回空。

### 排版紀律

**O. .stat-card padding 14px 違反 LAYOUT-SPEC** — 所有卡片元素（`.card-spec`, `.stat-card`, `.alert-card`, `.info-card`）padding MUST ≥ 16px（含手機版 clamp 最小值）。

**P. Playwright card-safe-padding 測試 false-positive** — 測試 CARD_SELECTORS MUST 區分「真正卡片容器」與「layout-only utility」。`.info-card-layout` / `.heading-row` / `.section-stack` / `.block-stack` / `.card-stack` / `.page` 不能進 CARD_SELECTORS。

### 內容渲染紀律

**Q. /terms/{slug}.md Content-Type = text/plain** — 所有 .md 路由 Content-Type MUST `text/markdown; charset=utf-8`，輸出 MUST 多行（≥20 行 + H1 + 至少一個 H2，不得含 escaped \`\\n\`）。Build-time 加 sanity check throw error。

**R. Markdown 渲染外洩 raw `**`** — renderer MUST 包 `fixUnpairedBold` + `dedupeConsecutiveHeadings` + `warnBlockquoteTable`，由 vitest 覆蓋（見 `tests/unit/markdown-renderer.test.ts`）。

**S. 「台灣第一個」絕對宣稱無依據** — 絕對 claim（最強 / 第一 / 業界領先 / 完全免費 / 保證可商用 / 無版權風險）MUST 含 `evidence_url + last_checked_at`，否則改中性表述。違反 = No-Go。

### Lib / API 結構紀律

**T. Decision Engine 想 import 未生成的 database.types.ts** — 純函數 lib（`src/lib/learning-engine/` 等）MUST 用 inline TypeScript types，NEVER import `src/lib/supabase/database.types.ts`。整合層（API route）才用 generated types。

**U. API route 沒包 try/catch** — 所有 `src/pages/api/**/*.ts` MUST 把 Supabase query 包 try/catch + 回 safe fallback default，never let DB error 進到使用者。

### Catalog / Terms 內容紀律

**V. catalog 內容絕對宣稱沒過 lint** — `catalog-content.ts` 內容 MUST 過 `/content-quality/known-bad-patterns.json` 檢查（banned_phrases + banned_claims），違反 = No-Go。Build pre-step 跑 grep 守門。

**W. 重複 canonical 詞條** — 1 個概念 = 1 個 canonical active 頁，slug 結尾 `-1` `-2` MUST status='redirect'，sitemap / search / related 只輸出 canonical。同義詞放 `aliases[]`。

**X. iPAS frequency_percent 無來源** — iPAS 百分比 MUST 含 `sample_size + exam_year_range + methodology_url + calculated_at`，否則只寫 `relevance: high|medium|low|unknown` + 文字理由。

**Y. sources 同 URL 不同 title** — 每個 source MUST 獨立 URL（或註明 `same_doc_section: true`）+ `type: official|paper|standard|regulation|documentation` + `supports[]: ["exam_question_1"]` + `last_checked_at`。

### SSG filter / 診斷紀律

**Z. /terms 索引 filter 完全失效** — SSG 頁面的 query-driven filter MUST 用 inline JS + URLSearchParams + active filter banner（顯示「目前篩選：X，共 N 個」+ 清除連結）+ 0 結果才 unhide empty-state（預設 hidden）。

**AA. 「.md 被壓單行」誤判** — 診斷「Markdown 壓縮」前 MUST 先 `curl -sIL <url> | grep HTTP` 確認 200，再看 Content-Type，再看內容。1 行 + text/html = 404 偽裝。

**BB. Phase 2 元件成僵屍檔** — 新建元件 MUST 跟 1 個實際使用例子（`pages/` 或文件範例）一起 commit。沒用例的元件 → Astro tree-shake 掉 → 完全沒包進 dist。

---

## 3. Hans 待做清單（10 條，本 fork 無權處理）

### A. Supabase 資料層（必須 Hans 親手做）

1. **Apply Learning Flow migration**
   ```bash
   cd ~/aiterms-tw
   supabase db push --dry-run    # 看 DDL 順序
   supabase db push                # 實際執行
   npx supabase gen types typescript --linked > src/lib/supabase/database.types.ts
   ```
   Migration 檔：`supabase/migrations/20260509203727_learning_flow_phase1_schema.sql` + `20260509203827_learning_flow_phase1_rls_fix.sql`

2. **修 quiz_events RLS 漏洞**（已寫在 migration 第 2 支：`DROP POLICY quiz_events_session_read`）

3. **DB term_data 內容修正**（/terms 稽核未動的部分）：
   - P0-03 CI/CD 拆兩 row + slug redirect
   - P0-04 / P0-05 overfitting-1 / underfitting-1 redirect + 刪「這張卡」字串
   - P0-06 / P0-10 ipas_stats / sources schema migration
   - P1-01 LLaMA 改 open-weight / community license
   - P1-02 Semantic Kernel 更新 plugins / agents / function calling
   - P1-03 LangChain FAQ 移除通用模板
   - P1-04 Deep Learning 容易混淆改寫
   - P1-05 Graph RAG 移除「優於傳統向量檢索」
   - P1-06 MLOps / Machine Learning Operations 合併
   - P1-07 No Code / Low Code 重新命名
   - P1-08 SEO title 統一格式

4. **Supabase ai_tools 表 tagline 同步**（30 個工具）— 清單見 catalog fork 1+2 回報內

### B. 拍板決策

5. **「台灣第一個」全站 6 處替換**（constants.ts / llms.txt.ts / feed.xml / BaseLayout / about.astro / cheatsheet）— 影響 SEO / 品牌

6. **validate-terms 是否掛 build pipeline**（目前 standalone 跑不了因 `cloudflare:` import）

7. **Step 1-3 schema 重做**（canonical_slug / status / license / ipas / sources 結構化欄位）— 大工程，下次另開 SDD

### C. 驗證

8. **手機驗證 5 截圖病徵**真的修了

9. **Phase 5 Playwright 掛 CI**（目前手動跑 `npm run test:e2e:layout`）

10. **題庫 backfill 執行**（migration SQL 內 commented-out block）

---

## 4. AI 接手指引

接手 AITerms.tw 的 AI（含本地 Claude / Cursor / Codex）動工前 MUST 讀以下檔案：

| 順序 | 檔案 | 用途 |
|---|---|---|
| 1 | `CLAUDE.md` | 全域規則（27 條老規則 + 28-44 條 2026-05-09 新加） |
| 2 | `docs/LESSONS-LEARNED.md` | 章節 A–K 老踩坑 + L–BB 2026-05-09 新踩坑（共 28 章） |
| 3 | `/content-quality/known-bad-patterns.json` | 機器可讀的 banned_phrases / banned_claims / architecture_rules |
| 4 | `/content-quality/term-page-spec.md` | 詞條頁 7 條 hard rule |
| 5 | `/content-quality/source-policy.md` | 來源政策 |
| 6 | `/content-quality/ai-generation-rules.md` | AI 生成 7 步流程 |
| 7 | `/content-quality/today-2026-05-09.md` | 本檔（最近改動 + 待辦） |
| 8 | `/llms.txt` | 站台對 AI crawler 的入口 |
| 9 | `docs/LAYOUT-SPEC.md` | LAYOUT-SPEC v1（2026-05-09 寫） |
| 10 | `openspec/changes/` | SDD 進行中的 change（layout-system-overhaul / learning-flow-engine） |

### 改動前的 4 個 grep 自查

```bash
# 1. 同檔有沒有其他 fork 在改？
grep -l "<target_file>" /tmp/claude-*/*.output 2>/dev/null

# 2. 預備加 prerender = false？確認不是 4 個 static 錨點
grep -n "prerender" src/pages/index.astro src/pages/ipas/index.astro src/pages/ipas/L1.astro src/pages/ipas/L2.astro

# 3. 寫絕對宣稱前 grep banned_claims
grep -E "最強|業界領先|完全免費|台灣第一|保證可商用" src/data/catalog-content.ts

# 4. 改 catalog 工具：source code 與 Supabase ai_tools 表都要改
echo "Hans: 記得同步 Supabase ai_tools 表 tagline / logo_url / official_url"
```

### 改動後的 4 個驗證

```bash
# 1. astro check 0 errors
npx astro check 2>&1 | tail -5

# 2. vitest 全 pass
npx vitest run tests/unit

# 3. Playwright 200/200（如改 layout）
npm run test:e2e:layout

# 4. 線上驗證（不靠本地 grep）
curl -sIL https://aiterms.tw/<path> | grep HTTP
```

---

*本檔每次重大改動後 update。違反任何 17 條教訓 = No-Go。*
