# FR-047 SPEC 手冊站台 — 側欄/CSS/JS 外部共用檔重構 SUMMARY

| 項目 | 內容 |
|------|------|
| 日期 | 2026-07-20 |
| 觸發 | 新增 Release Notes 頁面那次 commit 一次動了 323 個檔案，暴露 build 機制的結構問題 |
| Tag | `v1.9.0`（沿用既有版號，不視為新 release，`pyproject.toml` 仍 `1.9.0`；未 push） |
| Commits | `dfc6ecc6`（重構本體）、`852b0e55`（analysis 文件 + docstring） |

## 為何做

`scripts/deliverables/render_html.py` 原本把全站 CSS（206 行）、lightbox JS（85 行）、
以及完整側邊欄 `<nav>` HTML 全部內嵌進每一個輸出的 `.html` 頁面。三個版本目錄
（`current`/`v1.9.0`/`v1.8.0`）合計 273 個頁面，每個都帶一份幾乎一模一樣的複本。

任何「側欄結構」變化（新增一頁、調整 NAV 分組）都會讓該版本目錄下**全部**頁面被
重新 render——因為側欄 HTML 是 build time 算好、字串拼接進每一頁的。這次觸發點是
新增 Release Notes 頁面那次 commit 動了 323 個檔案，邏輯上很小的改動卻在 git diff
上難以核對「是不是只有側欄段落變了」。

## 做了什麼

把 CSS、lightbox JS、側欄渲染邏輯抽成外部共用檔，側欄改成瀏覽器端 JS 動態渲染：

- 新增 `docs/specs/_shared/site.css`（跨三版本共用的靜態全站樣式）
- 新增 `docs/specs/_shared/site.js`（lightbox 邏輯原樣搬移 + 新側欄渲染邏輯：
  fetch `nav-data.json` → 動態組 DOM → 綁收合事件；`file://` 開啟時 `fetch()`
  會被 CORS 擋下，此時側欄顯示明確可見的錯誤提示，非沉默空白）
- `render_html.py`：
  - `build_nav()` 拆成 `nav_data(pages, src)` 純資料版（無 HTML 字串、無
    current 高亮——同一份會被同版本目錄下所有頁面共用，current 判斷交給瀏覽器
    端 JS 依 `data-current` attribute 比對）
  - `TEMPLATE` 的 `<nav class="sidebar">` 改成空殼，只帶 `data-current` /
    `data-navdata` 兩個 attribute；`<style>` 改 `<link rel="stylesheet">`；
    尾部內嵌 `<script>` 改 `<script src="">`
  - `build()` 每次執行對該 `src` 呼叫一次 `nav_data()`，寫出
    `<src>/html/nav-data.json`（per-version，因為三個版本目錄頁面清單不同）
  - `CSS` / `NAV_JS` 兩個 Python 常數整段移除（內容已搬到 `_shared/`）
- 三個版本目錄（`current`/`v1.9.0`/`v1.8.0`）重新 build，`docs/specs/index.html`
  （`build_version_index()`）未變動
- 補 `docs/analysis/2026-07-20-spec-site-shared-assets-extraction.md`（問題假設 /
  考慮過的選項 / 選擇理由 / 被排除選項與原因 / 未來反悔條件），`render_html.py`
  檔頭 docstring 同步補新機制說明

## 行為差異

| 項目 | 重構前 | 重構後 |
|------|--------|--------|
| 側欄內容來源 | build time 內嵌進每頁 HTML | 瀏覽器端 fetch `nav-data.json` 動態渲染 |
| 改側欄結構（新增一頁 / 調分組）的異動範圍 | 該版本目錄下全部頁面 `.html` | 只有 `nav-data.json`（+`render_html.py`） |
| CSS / lightbox JS | 每頁各帶一份複本 | 三版本共用一份 `_shared/` 靜態檔 |
| current 高亮判定時機 | build time | 瀏覽器端（依 `data-current` attribute） |
| `file://` 直接雙擊開啟 | 正常（側欄內嵌） | 側欄顯示明確錯誤提示，要求改用 HTTP server（新限制，已附 fallback UI） |
| Cloudflare Pages / 本地 HTTP server 開啟 | 正常 | 正常（無差異） |

## 驗證結果

- 三版本 build 均**零**「⚠️ nav 未涵蓋」警告；`index.html` 正常重生
- 瀏覽器實測（Chrome DevTools MCP，`docs/specs/` 為 webroot 起 `python3 -m
  http.server`）：
  - 側欄完整渲染，樣式與互動跟重構前一致（234 個 nav 項目全部正確含 collapse
    button / current class / ↺ 重複掛載標記）
  - 點連結導頁正常、current 高亮正確、當前頁鏈自動展開
  - 收合功能：點擊收合 + reload 後 localStorage 記得住（測了「當前頁鏈強制
    展開」與「非當前頁群組記憶收合」兩種情境）
  - `file://` 直開：CSS/JS 正常載入，`fetch()` 被 CORS 擋下時側欄顯示可見警告框
    + console 印清楚訊息
  - 三版本 × 多深度頁面（首頁、SSP 巢狀子頁、Release Notes 動態子頁、↺ 重複
    掛載頁）全部零 console error
  - lightbox 縮放/拖曳/Escape 關閉功能正常
- 效果驗證：模擬新增一頁並掛進 NAV_STRUCTURE，重 build 後 `git diff --stat`
  顯示只有 `nav-data.json`（+7 行）與 `render_html.py`（+1 行）異動，**零**既有
  `.html` 頁面被觸動。已 revert 模擬變更。

## 已知限制 / follow-up

- `file://` 直接雙擊開啟本機 `.html` 檔時側欄無法載入（`fetch()` CORS 限制），
  這是重構引入的行為差異（重構前側欄內嵌，`file://` 可正常開）。已在 `site.js`
  加明確可見錯誤提示，判斷此限制優先度低（部署面本來就是 Cloudflare Pages，
  本地預覽建議用 `python3 -m http.server`）。反悔條件見 analysis 文件末段。
- 若未來 Cloudflare Pages 靜態檔快取造成 `nav-data.json` 更新後沒有即時反映，
  可能需要加 cache-busting（目前尚未觀察到此問題，暫不處理）。
- 未 push（`v1.9.0` tag 也未 push），push 時機等 user 明示。

## 規範文件更新狀態

- ✅ `docs/analysis/2026-07-20-spec-site-shared-assets-extraction.md`（新增）
- ✅ `render_html.py` 檔頭 docstring（補新機制段落）
- ✅ 本 SUMMARY
- ✅ `docs/features/FR-047-2607-feature-spec-handbook/tracker.md` Session 紀錄補一行
- ✅ memory `project_fr047_spec_handbook.md` 補一段
- ✅ Notion 任務清單補一條
