# shell_nav：App Shell 凍結層交付

> 目前路徑：元件已搬到 `design-system/`；現行組裝入口與檔案位置以 `../COMPONENTS.md` 為準。下文搬家前的 `prototype/*` 是歷史執行路徑；搬家結果另見本檔後段的路徑修正紀錄。

## 結論

[confirmed] 已交付可直接組裝的 App Shell：固定外框、status bar、header、內容 outlet、safe area、四格 nav，以及每個 root tab 各自保留深度與捲動位置的 stack。實作檔為 `prototype/shell.css`、`prototype/shell.js`、`prototype/preview-shell.html`；本輪未修改 `tokens.css` 或其他 worker 檔案。

[confirmed] Nav 僅有「今日 / 自選 / 市場 / 我的」，順序與文字均依 `DECISIONS.md` D4；個股沒有 nav 格。`preview-shell.html` 另平鋪 selected、unselected、pressed、badge、無 badge、icon + label 狀態，badge 使用無數字的灰階圓點，不假造通知數。

[confirmed] 資訊區只放 `--color-skeleton` 骨架；stack 內沒有代定今日選股、個股資訊、設定項或任何金融數字。「我的」只保留決策已定的「筆記彙總」入口。

[coverage gap] 本環境沒有可用的瀏覽器控制實例，因此未完成真實瀏覽器的視覺點擊驗證；已完成 `node --check prototype/shell.js`、token literal 掃描與 DOM 契約靜態核對。不可把這項 coverage gap 寫成「已完成瀏覽器 QA」。

## 證據邊界

- [confirmed] 制度與定案來源：`AGENTS.md`、`DECISIONS.md`。
- [confirmed] H1 可讀來源：`0805-app-architecture-exploration/prototypes/a-guided-brief/index.html`、同目錄 `styles.css`。只重新觀察 CSS 實值，不沿用其中產品文案或結論。
- [coverage gap] H1 `index.html` 另載入 `../shared/base.css`，但該檔不在本規格允許讀取的 H1 目錄內，因此沒有讀取；H1 的全域字體、shell chrome、共用陰影與共用元件值可能仍有未覆蓋部分。
- [confirmed] 0730 可讀來源：`0726-prototype-verdict/0730-stock-content-prototype/app-shell.html` 與同目錄 `app.css`。只把結構當素材重新觀察，不沿用舊 tab 分類、內容或產品結論。

## 1. H1 視覺語言 → tokens.css 對映

### 色彩

| token 變數名 | H1 本地 CSS 實際值 | 建議是否採用 | 證據與判斷 |
|---|---:|---|---|
| `--color-bg` | `--canvas: #f1f4f9` | 不直接採用 | [confirmed] `styles.css :root` 有此值；[inferred] 帶藍灰色相，不符合本輪「除漲跌外以灰階為主」，保留目前中性值。 |
| `--color-surface` | `--surface: #fbfcff` | 不直接採用 | [confirmed] `styles.css :root`；[inferred] 帶輕微藍色相，保留目前中性值。 |
| `--color-surface-raised` | `#fff` | 採用 | [confirmed] `.focus-stock`、`.continue-strip button` 使用純白，符合灰階限制。 |
| `--color-border` / `--color-divider` | `--line: rgba(25, 45, 78, .13)` | 不直接採用 | [confirmed] `styles.css :root`；[inferred] 為藍灰線色，保留目前中性 border/divider。 |
| `--color-border-strong` | 無明確 strong border；literal 有 `#cdd9f2` | 不直接採用 | [confirmed] `#cdd9f2` 只出現在 `.focus-stock`；[unknown] 單一卡片邊線不足以定義全域 strong border。 |
| `--color-text` | `--ink: #17243a` | 不直接採用 | [confirmed] `styles.css :root`；[inferred] 為深藍墨色，不符合本輪灰階限制。 |
| `--color-text-secondary` | `--muted: #68758a` | 不直接採用 | [confirmed] `styles.css :root`；[inferred] 為藍灰文字，保留目前中性值。 |
| `--color-text-tertiary` | 無明確第三層文字 token | 保留現值 | [unknown] H1 本地 CSS 只宣告一個 `--muted`，不可拆成兩層文字語意。 |
| `--color-text-inverse` | 無 inverse text semantic；literal `#fff` 是背景 | 保留現值 | [unknown] 不把白色背景用途改寫為反白文字證據。 |
| `--color-accent` | `--accent: #2458d8` | 不採用 | [confirmed] `styles.css :root`；[confirmed] 彩色 accent 超出本輪唯一彩色例外。Shell 使用目前近黑 accent。 |
| `--color-accent-weak` | `--accent-soft: #e7eeff` | 不採用 | [confirmed] `styles.css :root`；[inferred] 屬藍色互動面，不符合本輪限制。 |
| `--color-up` | `--positive: #ba3030` | 採用 | [confirmed] `styles.css :root`；[confirmed] 符合台股紅漲語意例外。 |
| `--color-down` | `--negative: #087c63` | 採用 | [confirmed] `styles.css :root`；[confirmed] 符合台股綠跌語意例外。 |
| `--color-up-weak` / `--color-down-weak` | 本地 CSS 無值 | 保留現值 | [unknown] H1 沒有正負向弱背景定義。 |
| `--color-flat` | 無明確 flat semantic 值 | 保留現值 | [unknown] H1 本地 CSS 沒有 flat 變數；不可把 `--muted` 直接升格為平盤定義。 |
| `--color-skeleton` | 無 skeleton semantic；相近 literal 有 `#e7e9ee`、`#e9eef7` | 不抄值，保留現值 | [confirmed] literal 分別出現在 `.ticker.muted`、`.continue-strip`；[unknown] H1 沒有宣告它們是骨架色。 |
| `--color-skeleton-shimmer` | 本地 CSS 無值 | 保留現值 | [unknown] H1 本地 CSS 沒有 shimmer 定義。 |
| `--color-scrim` / `--color-focus` | 本地 CSS 無值 | 保留現值 | [coverage gap] 可能位於未讀的 `../shared/base.css`。 |
| 無可採用對映 | `--warning: #9b6509`；literals `#7a4c00`、`#e2a11d`、`#a52d36`、`#176a9d`、`#8a620b`、`#3e4d65`、`#fff2dc`、`#744800` | 不採用 | [confirmed] 都出現在 H1 本地 CSS；[confirmed] 除漲跌外的警示、證據、進度與藍色文字色超出本輪彩色邊界。 |

### 字級、行高、字重

| token 變數名 | H1 本地 CSS 實際值 | 建議是否採用 | 證據與判斷 |
|---|---:|---|---|
| `--font-sans` | 本地 CSS 未宣告 | 保留現值 | [coverage gap] H1 全域 font 很可能在邊界外的 shared CSS。 |
| `--text-xs` | `11px` | 採用 | [confirmed] `.quote-line small`、`.focus-foot`、`.watch-row em` 等重複使用。 |
| `--text-sm` | `13px` | 採用 | [confirmed] `.market-line p`、`.continue-strip b`、`.evidence-list p` 使用。 |
| `--text-md` | `15px` | 採用 | [confirmed] `.fact-strip b` 使用。 |
| `--text-lg` | `17px` | 採用 | [confirmed] `.stock-top b` 使用。 |
| `--text-xl` | `20px` | 採用 | [confirmed] `.tension-block h2` 使用。 |
| `--text-2xl` | 無 `24px` | 保留現值 | [unknown] H1 本地 CSS 的大字為 `21px`、`29px`、`30px`，沒有可直接對映的 `24px`。 |
| 無直接 token 尺寸 | `12px`、`14px`、`18px`、`21px`、`29px`、`30px` | 暫不新增 | [confirmed] H1 本地 selectors 有這些值；[inferred] Shell 不需要新增字級，PO 應等待內容元件的跨 worker 需求再決定是否擴 scale。 |
| `--leading-tight` | 無單一全域值；局部為 `25px/18px`、`27px/20px` | 保留現值 | [confirmed] H1 用 selector 局部指定絕對行高；[unknown] 無法證明全域 tight 比例。 |
| `--leading-normal` | 無單一全域值；局部有 `17px`、`18px`、`19px`、`20px`、`21px` | 保留現值 | [confirmed] 各 selector 不同；不可合併成一個不存在的 H1 定義。 |
| `--weight-regular` | 本地 CSS 未明示 | 保留現值 | [unknown] 無可直接對映值。 |
| `--weight-medium` | 局部有 `700` | 保留現值 | [confirmed] `.market-line span` 使用 `700`；[unknown] 單一 selector 不足以定義全域 medium。 |
| `--weight-bold` | 多個強調 selector 使用 `750`，另有 `800` | 建議 `750`，由 PO 拍板 | [confirmed] `.research-question`、`.focus-foot b`、`.continue-strip button` 等使用 `750`；[inferred] `750` 比 `800` 更接近 H1 的主要強調層。 |

### 間距

| token 變數名 | H1 本地 CSS 實際值 | 建議是否採用 | 證據與判斷 |
|---|---:|---|---|
| `--space-1` | `4px` | 採用 | [confirmed] margin、padding 多處出現。 |
| `--space-2` | `8px` | 採用 | [confirmed] `.market-line p`、`.index-grid > div` 等使用。 |
| `--space-3` | `12px` | 採用 | [confirmed] gap、margin、padding 多處使用。 |
| `--space-4` | `16px` | 採用 | [confirmed] `.focus-stock` padding、`.quote-line` margin 等使用。 |
| `--space-5` | `20px` | 採用 | [confirmed] `.index-grid`、`.source-row` margin 使用。 |
| `--space-6` | 無明確 `24px` 間距 | 保留現值 | [unknown] 本地 CSS 未出現可對映的 spacing 值。 |
| `--space-8` | 無明確 `32px` 間距 | 保留現值 | [unknown] `32px` 只出現在局部 line-height，不是 spacing。 |
| `--space-10` | 無明確 `40px` 間距 | 保留現值 | [unknown] `40px` 出現在 control min-height，不是 spacing。 |
| `--space-12` | 無明確 `48px` 間距 | 保留現值 | [unknown] `48px` 出現在 control min-height，不是 spacing。 |
| 4pt scale 外的局部間距 | `2px`、`3px`、`5px`、`6px`、`7px`、`9px`、`10px`、`11px`、`13px`、`14px`、`17px`、`18px`、`22px` | 不新增 | [confirmed] H1 本地 selectors 有這些 gap/margin/padding；[inferred] 它們屬局部微調，本輪維持既有 4pt token scale，避免把每個 literal 都升格為系統 token。 |

### 圓角與陰影

| token 變數名 | H1 本地 CSS 實際值 | 建議是否採用 | 證據與判斷 |
|---|---:|---|---|
| `--radius-sm` | 無 `6px` | 保留現值 | [unknown] 本地 CSS 無直接對映。 |
| `--radius-md` | `10px` | 採用 | [confirmed] `.continue-strip button`、`.sheet-limit` 使用。 |
| `--radius-lg` | `16px` | 採用 | [confirmed] `.focus-stock` 使用。 |
| `--radius-pill` | `50%` 僅用於圓點 | 保留現值 | [confirmed] `.progress-dot` 是圓形；[unknown] 不能據此推定所有 pill 的固定半徑。 |
| 未對映 radius | `8px`、`11px`、`12px`、`13px` | 暫不新增 | [confirmed] 分別出現在 quote snippet、ticker、search field、continue strip；[inferred] Shell 可由現有 `10px/16px` 層級完成，新增 token 應由 PO 看三份 worker 需求後一起決定。 |
| `--shadow-sm` | 無直接對映 | 保留現值 | [unknown] 本地 CSS 沒有可證明為 small elevation 的共用值。 |
| `--shadow-md` | `0 8px 22px rgba(38, 68, 118, .08)` | 不直接採用 | [confirmed] `.focus-stock` 使用；[inferred] 幾何可參考，但藍色陰影不符合本輪灰階限制。 |
| `--shadow-nav` | 本地 CSS 無值 | 保留現值 | [coverage gap] H1 nav 樣式可能位於未讀 shared CSS。 |
| 非 elevation shadow | `0 0 0 4px rgba(226, 161, 29, .16)` | 不對映 shadow token | [confirmed] `.progress-dot` 用作彩色進度光環，不是 surface elevation；且不符合本輪彩色邊界。 |

## 2. Shell 外框：0730 採用與捨棄

| 0730 結構觀察 | 本輪處理 | 證據等級與理由 |
|---|---|---|
| `.app-shell` 內依序放 `.statusbar`、`.app-header`、`.viewport`、`.bottom-nav` | 採用固定 chrome + 單一內容 viewport 的責任分界，改名為 `.lila-shell`、`.shell-statusbar`、`.shell-header`、`.shell-content`、`.shell-nav` | [confirmed] 來源為 0730 `app-shell.html`；[inferred] 此分層讓內容 worker 只需掛載 slot，不重做 shell。 |
| `.viewport` 固定夾在 header 與 bottom nav 之間，`.view` 自己垂直 scroll | 採用責任概念，不沿用 magic inset | [confirmed] 0730 `.viewport` / `.view` CSS；本輪改用 grid row 與 tokens 計算可用高度。 |
| `.bottom-nav` 固定四欄並處理 `env(safe-area-inset-bottom)` | 採用四欄、safe area、icon + label、active state | [confirmed] 0730 `.bottom-nav` CSS 與 HTML；tab 名稱改按本輪 D4。 |
| 0730 desktop phone 為 `393 / 852`，status bar `27px`、header `48px`、nav `70px` | 不沿用數值 | [confirmed] 0730 `app.css`；[confirmed] 本輪 `tokens.css` 已鎖 `390 / 844`、`44px / 44px / 49px / 34px`，worker 只使用 token。 |
| `.journey-strip` | 捨棄 | [confirmed] 0730 有額外旅程列；[inferred] 本輪未定 stack 實際內容與旅程，不應在 chrome 代定固定步驟。 |
| dark theme、amber accent、radial background | 捨棄 | [confirmed] 0730 `app.css :root/body`；[confirmed] 本輪灰階為主，唯一彩色只允許漲跌語意。 |
| 舊 nav「今日 / 大盤 / 自選 / 更多」 | 捨棄 | [confirmed] 0730 `app-shell.html`；[confirmed] 本輪已定「今日 / 自選 / 市場 / 我的」。 |
| `.stock-overlay iframe` 全頁覆蓋 | 捨棄 | [confirmed] 0730 個股用 overlay；[confirmed] 本輪 D5/D6 要的是可 back 的 stack，個股應從 root stack push，不是獨立 nav 或 opaque iframe。 |
| 0730 真實文案、股票、行情與流程 | 全部捨棄 | [confirmed] 本輪內容 OPEN；資訊位只留骨架。 |

本輪 Shell 尺寸完全依 token：`--shell-width`、`--shell-height`、`--statusbar-height`、`--header-height`、`--navbar-height`、`--safe-bottom`。`shell.css` 沒有寫死顏色、間距或字級 literal。

## 3. Nav bar 四格狀態契約

| 狀態 / 變體 | class / attribute | 組裝規則 | 證據等級 |
|---|---|---|---|
| selected | `.shell-tab.is-selected` + `aria-selected="true"` | icon 容器出現弱 accent 面，icon/label 用 accent；tabIndex 為 `0` | [confirmed] 已實作於 CSS/JS。 |
| unselected | `.shell-tab` + `aria-selected="false"` | tertiary 文字；tabIndex 為 `-1` | [confirmed] 已實作於 CSS/JS。 |
| pressed | `.shell-tab:active`；展示時可加 `.is-pressed` | 短暫縮放並顯示弱 accent 面 | [confirmed] 已實作於 CSS。 |
| 有 badge | `.shell-tab.has-badge`，`.shell-tab-badge` 移除 `hidden` | 只用無數字灰階圓點；可呼叫 `instance.setBadge(tab, true)` | [confirmed] 已實作於 CSS/JS；[inferred] 不假造 badge 數字，也不挪用漲跌色。 |
| 無 badge | badge 保持 `hidden`，tab 不加 `.has-badge` | 預設狀態 | [confirmed] 已實作。 |
| icon + label | `.shell-tab-icon > svg` + `.shell-tab-label` | 兩者均必要；SVG 使用 `currentColor` | [confirmed] 已實作。 |

[confirmed] tab 固定順序：`today / watchlist / market / profile`，顯示為「今日 / 自選 / 市場 / 我的」。[confirmed] 個股不在 `.shell-nav`；要由任一 root 內的 `data-shell-push` 進 stack。

## 4. Stack 導航機制

- [confirmed] `data-shell-root` 各自建立 `{ stack, scrollPositions }`，切 tab 只切 active root，不重建或清空其他 root。
- [confirmed] `data-shell-push="頁面名"` 會在目前 root 內找同名 `data-shell-page`，執行由右往左的 push；下一層 header 自動顯示 back 與骨架 title。
- [confirmed] `data-shell-back` 只 pop 目前 root；回到 root 時 back 變為不可見且 disabled。
- [confirmed] `.shell-depth` 依目前 stack 長度動態產生層級點，並更新可讀的 `aria-label`。
- [confirmed] 每頁 `.shell-page-scroll` 的 `scrollTop` 在 push、back、切 tab 前保存，回到該頁後恢復。
- [confirmed] tablist 支援滑鼠 / 觸控點擊與鍵盤 `ArrowLeft`、`ArrowRight`、`Home`、`End`。
- [confirmed] 每次 tab / push / back 都送出 bubbling event `lila:shell-change`，detail 包含 `action`、`activeTab`、`activePage`、`depth`、`stacks`。
- [coverage gap] 因沒有瀏覽器實例，以上是由實作與語法檢查確認，尚缺真實瀏覽器 event/transition 執行證據。

最小初始化：載入 `shell.js` 後會自動 mount 所有 `[data-shell]`。如為動態插入 DOM，可呼叫 `window.LILAShell.mount(shellElement)`；回傳 instance 提供 `switchTab(tabName)`、`push(pageName)`、`back()`、`setBadge(tabName, visible)`、`getState()`、`destroy()`。

## 5. 元件清單（下一個 agent 的組裝說明書）

| 元件名（class） | 用途 | 狀態清單 | 變體清單 | 必要 DOM 結構 | 依賴 token |
|---|---|---|---|---|---|
| `.lila-shell` | App 固定外框與四列版位 | 預設 | 裝置內嵌、全高容器 | `[data-shell] > statusbar + header + content + nav` | shell 六個尺寸 token、bg/text、radius、z-index |
| `.shell-statusbar` | status chrome 與 safe top | 預設 | mark + 系統 icon | `.shell-statusbar > .shell-status-mark + .shell-status-icons` | statusbar height、space、text/bg |
| `.shell-header` | root / pushed page 共用 header | root、depth > 1 | 文字 title、骨架 title | `back + [data-shell-header-title] + [data-shell-depth]` | header height、divider、shadow、z-header、space |
| `.shell-header-back` | pop 目前 stack | `.is-root`、可用、`:active` | icon button | `button[data-shell-back] > svg` | space、radius、text、accent-weak、duration/ease |
| `.shell-header-title` | 顯示 root 名；次層不代定內容時顯示骨架 | 預設、`.is-placeholder` | 文字、骨架 | `h1[data-shell-header-title]` | text-lg、weight-bold、skeleton、radius-pill |
| `.shell-depth` / `.shell-depth-dot` | 呈現目前層級深度 | current、previous | 動態數量 | `[data-shell-depth] > .shell-depth-dot*`，JS 生成 | space、border-strong、accent |
| `.shell-content` | 所有 root 的固定 viewport / clipping 邊界 | 預設 | 無 | `main[data-shell-outlet] > .shell-root*` | surface、z-base |
| `.shell-root` | 一個 root tab 的獨立 stack 容器 | active、hidden | today/watchlist/market/profile | `.shell-root[data-shell-root] > .shell-stack` | 無額外視覺 token |
| `.shell-stack` | 管理同一 root 的 page 疊層 | 預設 | 無 | `.shell-stack > .shell-stack-page*` | surface |
| `.shell-stack-page` | 一個可 push/back 的頁面層 | `.is-active`、`.is-under`、`.is-preparing`、`.is-exiting` | root title、`data-shell-title-mode="skeleton"` | `section[data-shell-page] > .shell-page-scroll` | duration/ease、surface、z-base/z-push |
| `.shell-page-scroll` | 每一頁唯一的垂直 scroll owner | 當前、隱藏 | 無 | `.shell-page-scroll[data-shell-scroll] > .shell-slot` | 無額外視覺 token |
| `.shell-slot` | 內容 worker 的 mount seam | 空、已掛內容 | edge-to-edge、內含自有 padding wrapper | `.shell-slot[data-shell-slot]` | 本身只需 min-size |
| `.shell-nav` | 固定四格 root 導覽與 safe bottom | 預設 | 無 | `nav[data-shell-nav][role=tablist] > .shell-tab × 4` | navbar height、safe-bottom、divider、shadow-nav、z-nav、space |
| `.shell-tab` | 單一 root tab | selected、unselected、pressed、focus | 有 badge、無 badge | `button[data-shell-tab][role=tab] > icon + label` | text/accent/tertiary、accent-weak、radius、text-xs、duration/ease、space |
| `.shell-tab-icon` | tab icon 容器 | selected、unselected | 任一 SVG icon | `.shell-tab-icon > svg`，badge 可同層放入 | space、radius、accent-weak |
| `.shell-tab-label` | tab 固定文字 | selected、unselected | 今日/自選/市場/我的 | `.shell-tab-label` | text-xs、leading-tight、weight-medium |
| `.shell-tab-badge` | 無數字通知狀態 | hidden、visible | dot | `.shell-tab-icon > .shell-tab-badge` | space、bg、accent、radius-pill |
| `.shell-placeholder-page` | preview 用骨架版面；正式內容可整個替換 | 預設 | root、detail | 放在 `.shell-slot` 內 | space |
| `.shell-skeleton-line` | 文字資訊骨架 | 預設、`.is-short` | 長、短 | `span` | skeleton、space、radius-pill |
| `.shell-skeleton-block` | 大型資訊區骨架 | 預設 | 無 | `span` | skeleton、space、radius-lg |
| `.shell-skeleton-card` | 清單 / 卡片資訊骨架 | 預設 | 可含 line / icon | `span.shell-skeleton-card > skeleton children` | skeleton、surface-raised、border、shadow-sm、space、radius |
| `.shell-skeleton-icon` | 圖像資訊骨架 | 預設 | 無 | `span` | skeleton、space、radius-md |
| `.shell-skeleton-group` | 多個骨架的排列容器 | 預設 | 無 | wrapper > skeleton components | space |
| `.shell-placeholder-action` | preview 中觸發 push 的結構控制 | default、pressed、focus | 進入次層、再深入、筆記彙總入口 | `button[data-shell-push] > span + svg` | surface-raised、border、text、accent-weak、space、radius、text-sm、duration/ease |
| `.shell-preview-layout` / `.shell-preview-device` / `.shell-preview-specimens` | 僅供本 worker preview 展示，不是 app runtime component | 預設 | Shell demo、nav state gallery | 見 `preview-shell.html` 外層 | shell size、surface、border、shadow、space、radius |

## 6. 與 read_swipe / feed_list 的接縫契約

1. [confirmed] 內容掛載點固定是 `.shell-slot[data-shell-slot]`。內容 worker 不應把元件插到 `.lila-shell`、`.shell-content` 或 `.shell-stack` 的直屬層，否則會破壞 chrome 與轉場。
2. [confirmed] 可用高度由 `.shell-content` 的 grid row 決定：Shell 總高扣除 `max(--statusbar-height, safe-top)`、`--header-height`、`--navbar-height`、`max(--safe-bottom, safe-bottom env)`。內容 worker只拿到這個剩餘高度，不應再扣一次 header/nav。
3. [confirmed] 垂直 scroll 由 `.shell-page-scroll[data-shell-scroll]` 唯一負責；`read_swipe` 與 `feed_list` 的最外層不得再設整頁 `height: 100vh` 或整頁 `overflow-y: auto`。元件內局部水平 swipe / carousel 可以自行負責，Shell 不攔截手勢。
4. [confirmed] `.shell-slot` 不自帶水平 padding，讓 edge-to-edge K 線或 swipe 可以貼齊 viewport；需要頁面內距時，由內容 worker 的 wrapper 使用 spacing token。preview 的 padding 來自可移除的 `.shell-placeholder-page`，不是 slot 本身。
5. [confirmed] 新增 root page 的必要結構是 `.shell-root[data-shell-root] > .shell-stack > .shell-stack-page[data-shell-page] > .shell-page-scroll[data-shell-scroll] > .shell-slot[data-shell-slot]`。root 的第一個 `data-shell-page` 會成為該 tab 初始 stack。
6. [confirmed] 新增次層時，把同 root 下的 page 先放好，再讓觸發元件使用 `data-shell-push="完全相同的頁面名"`。header/back/depth 由 Shell 管理，內容 worker不要再放第二套全頁 header 或 back。
7. [confirmed] `feed_list` 預計放入 `today-root`、`watchlist-root`、`market-root` 的 `.shell-slot`；`read_swipe` 預計放入從上述 root push 的次層 `.shell-slot`。這只是接縫位置，不代表任何 feed 邏輯或個股內容已定案。

## OPEN / 不得升格

- [unknown] 要讓新手看懂籌碼 K 線的哪一段；本 Shell 沒有回答。
- [unknown] 「今日」Feed 的選股邏輯；本 Shell 只有容器。
- [unknown] 各條 stack 的實際資訊內容與頁名；次層 header 故意使用骨架。
- [unknown] 「我的」設定細項；本 Shell 只有空殼與已定的筆記彙總入口。
- [coverage gap] H1 shared base CSS 未在可讀邊界內，不能聲稱目前 token map 已覆蓋 H1 全部視覺值。
- [coverage gap] 尚缺真實瀏覽器互動與視覺 QA；PO 整合後應以 `preview-shell.html` 驗收「今日 push 至第三層 → 切市場並 push → 切回今日仍在第三層 → back 兩次回 root」。

## 追加：fill 模式 stack page（PO 指派的 seam 修正）

### 結論

[confirmed] 已新增 opt-in 的 `data-shell-scroll-mode="fill"` stack page 變體：預設 page 仍由 `.shell-page-scroll` 垂直捲動；只有明確宣告 fill 的 page 會關閉 Shell 捲動、取得確定的全高 `.shell-slot`，讓 `.read-swipe` 保持唯一縱向 scroller。既有 tab、push、back、depth 與預設 scroll 行為沒有改變。

[confirmed] 此追加段落取代前文接縫契約第 3 條對所有頁面一體適用的描述：該條仍適用於預設模式；`data-shell-scroll-mode="fill"` 是明確例外，縱向 scroll 改由 slot 內的 child 負責。

### 1. 宣告方式與命名

[confirmed] 宣告方式是把 `data-shell-scroll-mode="fill"` 放在 `.shell-stack-page[data-shell-page]`：

```html
<section
  class="shell-stack-page"
  data-shell-page="stock-reader"
  data-shell-scroll-mode="fill"
>
  ...
</section>
```

[inferred] 使用 attribute 而非新的 modifier class，與現有 `data-shell-page`、`data-shell-title-mode`、`data-shell-scroll` 命名一致，也讓 JS 可以讀取同一份 DOM 契約。`fill` 表示 Shell 提供完整可用高度，不表示內容元件必須採用哪種 scroll 技術。

[confirmed] 未宣告 `data-shell-scroll-mode` 就是原本的預設捲動模式；沒有新增 token，也沒有改動原 class 名。

### 2. fill 模式 CSS 契約

| 節點 | fill 模式屬性 | 原因 | 證據等級 |
|---|---|---|---|
| `.shell-stack-page[data-shell-scroll-mode="fill"] > .shell-page-scroll` | 保留既有 `width: 100%`、`height: 100%`；新增 `min-height: var(--space-0)`、`overflow: hidden`、`overscroll-behavior: none` | page 本身已由 absolute inset 取得確定高度；scroll wrapper 撐滿該高度但不捲，也允許在高度鏈中收縮 | [confirmed] 已寫入 `shell.css`。 |
| `.shell-stack-page[data-shell-scroll-mode="fill"] > .shell-page-scroll > .shell-slot` | `width: 100%`、`height: 100%`、`min-height: var(--space-0)`、`overflow: hidden` | 把確定高度傳到內容 seam；覆蓋預設 `.shell-slot { min-height: 100%; }`，避免 auto/min-content 撐高與第二層捲動 | [confirmed] 已寫入 `shell.css`。 |
| slot 內的 `.read-swipe` | 沿用 `read.css` 的 `block-size: 100%`、`min-block-size: 0`、`overflow: hidden`；其 `.read-swipe__viewport` 保持 `overflow-y: auto` 與 `scroll-snap-type: y proximity` | `height/block-size: 100%` 現在有確定父高度可解析，reader viewport 成為唯一縱向 scroller | [confirmed] 已唯讀核對 `read.css`；本輪未修改 reader 檔案。 |

[confirmed] 預設 `.shell-page-scroll` 仍是 `height: 100%; overflow-y: auto`，預設 `.shell-slot` 仍是 `min-height: 100%`；fill selector 只命中 opt-in page，root page 與既有一般次層不受影響。

### 3. shell.js 的 fill 判斷

[confirmed] 新增 `isFillPage(page)`，只在 `page.dataset.shellScrollMode === "fill"` 時回傳 true；`getScrollRegion(page)` 遇到 fill page 直接回傳 `null`，其餘頁面仍回傳 `[data-shell-scroll]`。

| 路徑 | fill 模式處理 | 證據等級 |
|---|---|---|
| mount / constructor | 初始 `sync({ restoreScroll: true })` 若目前 page 是 fill，`restoreScroll()` 由共同 guard 安全跳過 | [confirmed] mount 流程沒有另開分支，預設初始化不變。 |
| push 到 fill | 先保存離開中的預設 page `scrollTop`；新 fill page 的 restore 因沒有 Shell scroll region 而跳過 | [confirmed] `push()` 仍走原 transition、stack 與 chrome 更新。 |
| 從 fill back | `saveScroll()` 對 fill page 不寫入 `scrollPositions`；上一個預設 page 仍恢復原 `scrollTop` | [confirmed] `back()` 的 pop、transition、depth 更新未改。 |
| fill 狀態切 tab | 離開 fill 時 save 安全跳過；各 tab 的 stack 仍保留；切回 fill 時 restore 安全跳過 | [confirmed] `switchTab()` 與 root state 結構未改。 |
| depth / header | 不判斷 scroll mode，仍只依目前 root 的 `stack.length` 與 active page 更新 | [confirmed] fill 不改導航層級語意。 |

[inferred] 把判斷集中在 `getScrollRegion()`，避免 mount、push、back、切 tab 各自複製條件；這個窄改同時保證預設路徑仍取得原本的 scroll element。

### 4. 實際檢查與預設模式回歸邊界

- [confirmed] 執行 `node --check prototype/shell.js`，通過，無 JavaScript 語法錯誤。
- [confirmed] 執行 DOM/CSS 契約檢查：預設 `.shell-page-scroll` 仍有 `overflow-y: auto`；fill wrapper 為 `overflow: hidden`；fill slot 有 `height: 100%` 與 `min-height: var(--space-0)`。
- [confirmed] 同一檢查確認 `today-root` 沒有 fill 宣告、`today-detail-one` 有 `data-shell-scroll-mode="fill"`，且 preview 內的 push target 全部能對到既有 `data-shell-page`。
- [confirmed] 確認四個 root tab 仍精確為 `today / watchlist / market / profile`，所有 `shell.css` token 引用皆存在於 `tokens.css`，沒有新增 literal color 或新 token。
- [confirmed] 執行 HTML tag nesting 靜態檢查，`preview-shell.html` 無未關閉或錯序標籤。
- [confirmed] `preview-shell.html` 的 `today-detail-one` 現在展示 fill page：Shell wrapper 不捲，`height: 100%` 的 `.shell-fill-sample` 內部 viewport 自己做縱向 proximity snap；仍可由其中按鈕 push 到第三層，以覆蓋 fill → push 的 DOM 路徑。
- [coverage gap] 本環境沒有可用瀏覽器控制實例，因此沒有把上述靜態檢查寫成真實互動驗證；尚待 PO 整合後以瀏覽器實測 push / back / 切 tab / depth 與 reader snap 手感。

### 5. 組裝者最小範例

[confirmed] root page 不加 mode，維持 Shell 預設垂直 scroll；push 進去的 reader page 才加 `data-shell-scroll-mode="fill"`：

```html
<section class="shell-root" data-shell-root="today">
  <div class="shell-stack">
    <!-- root：預設由 Shell 捲動 -->
    <section
      class="shell-stack-page is-active"
      data-shell-page="today-root"
      data-shell-title="今日"
    >
      <div class="shell-page-scroll" data-shell-scroll>
        <div class="shell-slot" data-shell-slot>
          <!-- feed_list 元件 -->
          <button type="button" data-shell-push="stock-reader">進入次層</button>
        </div>
      </div>
    </section>

    <!-- pushed page：Shell 給確定高度但不捲 -->
    <section
      class="shell-stack-page"
      data-shell-page="stock-reader"
      data-shell-scroll-mode="fill"
      data-shell-title-mode="skeleton"
      aria-hidden="true"
    >
      <div class="shell-page-scroll" data-shell-scroll>
        <div class="shell-slot" data-shell-slot>
          <!-- read.css 已定義 block-size: 100%; min-block-size: 0 -->
          <article class="read-swipe">
            <div class="read-swipe__viewport">
              <!-- reader cards；此 viewport 是唯一縱向 scroller -->
            </div>
          </article>
        </div>
      </div>
    </section>
  </div>
</section>
```

[confirmed] 組裝規則只有一個例外點：需要 child 自有全頁縱向 scroll 時，在該 `.shell-stack-page` 加 `data-shell-scroll-mode="fill"`；不要刪除 `.shell-page-scroll` 或 `.shell-slot`，因為 Shell 的高度鏈、stack transition 與 JS 查找仍依賴這兩層。

## 追加：index.html 與 phone.html 組裝

### 結論

[confirmed] 已完成兩個可由本機直接開啟的入口：`prototype/index.html` 是三個 domain preview、review 契約與 token 尺規的總索引；`prototype/phone.html` 只組合既有 Shell、Lists、Read Swipe 元件，完成四條 root stack、三條股票入口到 fill reader，以及「我的」到筆記彙總骨架的路徑。

[confirmed] `phone.html` 沒有複製或重寫任何 domain CSS，依序載入 `tokens.css`、`shell.css`、`lists.css`、`read.css`，並載入 `shell.js`、`read.js`。金融資訊位全部是既有 skeleton class；唯一具體圖形是從 `preview-read.html` markup 沿用的 `.read-kline` inline SVG 假資料幾何。

[confirmed] `index.html` 的元件數量採各 worker review「元件清單」表的契約列機械計數：Shell 25、Read Swipe 10、Lists 33；這些是目前檔案中的元件／元件群數，不是產品使用量或驗證數據。

### 1. phone.html 組裝路徑

| root | root page（預設 Shell 捲動） | push 觸發 | push 目標 | 目標 scroll mode |
|---|---|---|---|---|
| `today` | `today-root`：`.filter-strip` + `.feed-list` + `.feed-card` + 共用 `.stock-row` | 股票列 `.stock-row__primary` 或右側 `.row-action[data-shell-push]` | `today-reader` | `fill` |
| `watchlist` | `watchlist-root`：`.list-edit-toolbar` + draggable / selected `.stock-row` + `.batch-bar` | 股票列 `.stock-row__primary[data-shell-push]` | `watchlist-reader` | `fill` |
| `market` | `market-root`：`.segmented-control` + `.list-section` + 共用 `.stock-row` | 股票列 `.stock-row__primary` 或右側 `.row-action[data-shell-push]` | `market-reader` | `fill` |
| `profile` | `profile-root`：`.profile-shell` + `.profile-shell__placeholder` + `.destination-row` | 「筆記彙總」`.destination-row[data-shell-push]` | `profile-notes` | 預設 Shell 捲動 |

[confirmed] 四條 root 均維持 Shell 的必要骨架：

```html
<section class="shell-root" data-shell-root="today">
  <div class="shell-stack">
    <section class="shell-stack-page" data-shell-page="today-root">
      <div class="shell-page-scroll" data-shell-scroll>
        <div class="shell-slot" data-shell-slot>
          <div class="list-stack">
            <div class="list-stack__content">...</div>
          </div>
        </div>
      </div>
    </section>
  </div>
</section>
```

[confirmed] 三條股票 reader page 各自位於原 root 內，因 Shell 的 `getPage()` 只在 active root 尋找 push 目標；它們使用相同的既有 reader template，但各自建立獨立 `ReadSwipe` instance：

```html
<section
  class="shell-stack-page"
  data-shell-page="today-reader"
  data-shell-scroll-mode="fill"
  data-shell-title-mode="skeleton"
>
  <div class="shell-page-scroll" data-shell-scroll>
    <div class="shell-slot" data-shell-slot data-reader-host>
      <!-- 啟動前由本檔的 template clone 放入一個既有 .read-swipe -->
    </div>
  </div>
</section>
```

[confirmed] `.read-swipe` 的最小實際結構為 progress + 唯一 viewport + 三個 direct child cards + loading / empty state；`read.js` 依 `[data-read-card]` 自動建立可點擊 progress segment。reader markup 只寫一份在 `<template id="reader-template">`，在載入 `read.js` 前 clone 到三個 `data-reader-host`，避免三份 reader DOM 手工漂移。

### 2. 組裝時發現的落差

| 落差 | 涉及檔案 | 本次繞過方式 | 建議 owner / 後續 |
|---|---|---|---|
| Lists 是純 CSS 元件，`.filter-chip`、`.segmented-control`、編輯／批次按鈕沒有狀態 controller | `lists.css`、`preview-lists.html` | [confirmed] `phone.html` 只展示既有 selected / editing / batch 狀態；股票主要按鈕另以 Shell 原生 `data-shell-push` 接 reader，不假造篩選或編輯業務規則 | [inferred] 若下一輪要評估清單操作流程，派 `feed_list` 補 opt-in controller；本輪不應由組裝檔代定選股、分類或批次規則。 |
| `read.js` 的 `initAll()` 是私有函式，只在載入／DOMContentLoaded 掃描一次；動態插入 reader 後沒有公開 mountAll | `read.js` | [confirmed] 先 clone `<template>`，再載入 `read.js`，讓既有自動初始化看得到三個 instance | [inferred] `read_swipe` 可考慮公開 `ReadSwipe.mountAll(root)`；目前也能對單一 root 使用 `new ReadSwipe(root)`，不是 blocker。 |
| `read.css` 有未 scope 的 `html`、`body`、`button` 與 `.visually-hidden` 基礎規則；載入順序會覆蓋 page-level body 樣式 | `read.css`、`shell.css` | [confirmed] App 實際 surface 都由 `.lila-shell`、`.shell-content`、`.list-stack` 明確設定；phone host 使用既有 `.shell-preview-layout`，因此本次渲染沒有破版 | [inferred] `read_swipe` 應把 preview-only body 規則 scope 到 `.preview-page`，共用 visually-hidden utility 則由 PO 決定是否另立 shared utilities。 |
| `phone.html` 明確載入 `tokens.css`，而三份 domain CSS 各自也 `@import` 同一檔 | `shell.css`、`lists.css`、`read.css`、`phone.html` | [confirmed] 保留規格要求的 link 順序；重複載入同一本機資源不改變 cascade 結果 | [inferred] 非阻斷；若 PO 未來建立 bundling，可統一由入口載入 token，domain CSS 是否保留 standalone import 需一起決定。 |
| `.visually-hidden` 實作只存在於 `read.css`，但組裝 Lists 骨架的可及文字也需要它 | `read.css`、`phone.html` | [confirmed] `phone.html` 必須載入 `read.css`，所以目前 class 可解析 | [inferred] 這是跨 domain utility 耦合；建議 PO 未來放進 shared utilities，而不是放進 `tokens.css`。 |
| Lists 的 mobile media query 會在手機寬度隱藏 `.stock-row__sparkline` | `lists.css` | [confirmed] `phone.html` 保留 sparkline DOM 與骨架，但實際手機畫面依既有 CSS 不顯示；完整變體仍可在 `index.html` 的 Lists iframe 查看 | [inferred] 這是 feed_list 已定的 responsive 行為，不在組裝檔覆寫；若手機 demo 必須看見 sparkline，應回派 `feed_list` 評估，而非在 phone 寫 override。 |
| Reader page 是否應隱藏 bottom nav 尚未有元件契約 | `DECISIONS.md`、`shell.css`、`read_swipe.md` | [confirmed] 使用既有 Shell，reader push 後 bottom nav 維持可見；沒有在組裝檔新增 nav 隱藏變體 | [unknown] 這是產品／shell 決策，不是組裝者可代定；由 PO 決定是否另派 shell 變體。 |

[confirmed] 沒有發現 class 同名衝突會改寫三個 domain 的核心元件；機械比對顯示 `phone.html` 使用的 class 都能在既有 `shell.css`、`lists.css`、`read.css` 找到。[confirmed] Shell slot 不加 padding，Lists 只由 `.list-stack__content` 加一次 padding，reader fill slot 不加 padding，因此沒有重複 page padding。[confirmed] `.read-swipe` 自帶 `isolation:isolate`，progress z-index 留在 reader stacking context，實際靜態渲染沒有與 Shell header / nav 疊在同一版位。

### 3. read:dismiss → Shell back

[confirmed] `shell.js` 載入後，以公開 API `window.LILAShell.mount(shellElement)` 取得 idempotent navigation instance；監聽 Shell 內 bubbling `read:dismiss`，直接呼叫 instance 的 `back()`：

```js
var shell = document.querySelector("[data-shell]");
var navigation = window.LILAShell.mount(shell);

shell.addEventListener("read:dismiss", function () {
  navigation.back();
});
```

[confirmed] 這個 bridge 不讀 progress DOM、不直接改 page class，也不另造 navigation state；實際 pop、header back 狀態、depth 與 root stack 保存仍完全由 `LilaShellNavigation.back()` 負責。

### 4. 實際檢查

- [confirmed] 完整讀取 `preview-read.html` 與 `preview-lists.html` markup，再依它們的必要 DOM 組裝；沒有以猜測縮寫 reader / list 結構。
- [confirmed] 對 `index.html`、`phone.html` 執行 tag stack 與 nested button 靜態檢查：無未關閉、錯序或巢狀 button。
- [confirmed] 機械核對所有相對 `src` / `href`：CSS、JS、三份 preview 與三份 review 路徑均存在。
- [confirmed] 機械核對四個 root / tab 精確為 `today / watchlist / market / profile`；每個 `data-shell-push` 在同一 root 都有對應 `data-shell-page`。
- [confirmed] 機械核對有三個 fill page、三個 reader host；reader template 有三個 direct card，且 `read:dismiss` bridge 呼叫 `navigation.back()`。
- [confirmed] 機械比對 `phone.html` 所有 class 都存在於三份既有 domain CSS；`index.html` 與三份 CSS 的 `var(...)` 都可解析到既有 token，沒有新增 token。
- [confirmed] `node --check prototype/shell.js` 與 `node --check prototype/read.js` 均通過；兩段 inline classic script 也通過 JavaScript 語法編譯檢查。
- [confirmed] 以 macOS Quick Look 直接對兩個本機 HTML 產生實際 render：`index.html` 頁首、Shell iframe 與連結區可見；`phone.html` 的 status/header、今日 Feed 骨架與四格 nav 可見。這項只證明靜態渲染與本機資源載入，不是互動驗證。
- [coverage gap] 本環境沒有可用的 Browser 實例，因此未實際點擊 tab / push / back / progress，也未以真實 wheel/touch 驗證 reader proximity snap；不能把 DOM、語法或 Quick Look 結果寫成互動已通過。

### 5. file:// 相容性

[confirmed] 兩個入口只使用相對路徑的 `<link>`、classic `<script src>`、`<iframe src>`、review `<a href>` 與 inline SVG；沒有 module script、dynamic import、`fetch()`、XHR、CDN、外部字體、圖片或網路請求，因此沒有引入常見的 `file://` module CORS blocker。

[confirmed] `phone.html` 的 reader template clone 是同步 inline classic script，且發生在 `read.js` 載入前；不依賴 HTTP server 或跨檔案 fetch。`shell.js` / `read.js` 都用普通 script 載入，可在本機協定執行。

[confirmed] Quick Look 直接渲染本機 `index.html` 時，內嵌的 `preview-shell.html` iframe 已可見，支持同目錄相對 iframe 在此 macOS renderer 可載入；三份 iframe 本身也保留「在新視窗開啟」連結，若某瀏覽器限制 file iframe，使用者仍可直接開各 preview。

[coverage gap] 尚未在實際 Chrome / Safari 的 `file://` 分頁逐一操作；不同瀏覽器對 local file origin 的安全政策仍可能不同。現有程式沒有主動觸發 CORS 的 API，但瀏覽器別的互動與 iframe 行為仍需 PO 端實開驗收。

## 追加：design system catalog

### 結論

[confirmed] `prototype/index.html` 已依 `DECISIONS.md` D13 改為唯一的 canonical design system catalog，使用單頁錨點目錄分成 Foundations、Components、Patterns、Compositions、Decision Status 五層；catalog 的 specimen 直接讀現有 token 或嵌入既有 preview，沒有複製 domain CSS，也沒有把 catalog 變成第二份 source of truth。

[confirmed] `prototype/phone.html` 只新增定位橫幅與明確的 CSS 載入順序。橫幅說明它是 composition playground、不是 design system，並連回 `index.html` 與 `DECISIONS.md`；既有四 root、push、fill reader、`read:dismiss` bridge 與 script 順序未重構。

[confirmed] Decision Status 完整照錄 D13 的狀態盤點；nav 四格與「個股不佔格」標為 Approved（D4），目前 K 線只在判讀卡、清單迷你走勢為骨架的配置標為 OPEN（O9），沒有因實作已存在而升格。

[coverage gap] 本環境的 Browser runtime 沒有可用瀏覽器實例，未完成真實瀏覽器的 push／back／progress／swipe 操作驗證。macOS Quick Look 已實際產生兩頁縮圖，但那只算靜態 renderer 證據，不算瀏覽器互動證據。

### 1. 參考頁連線與借用邊界

[confirmed] 已唯讀連上使用者指定的 `https://invoicemanager-screens-dev-557076811903.asia-east1.run.app/design-system.html`，取得實際 HTML／CSS。參考頁使用黏性頂部錨點導覽、單頁分區、分組標題、可掃描卡片／表格與 live specimen；本 catalog 只借用這五種資訊組織手法。

[confirmed] 沒有借用參考頁的發票產品意圖、文案、命名、色票、token、元件、圖像素材、吉祥物、產品概念、copy-to-clipboard 行為或任何採用結論。LILA catalog 的內容與狀態只來自本工作區的 `tokens.css`、`utilities.css`、domain CSS／JS、`COMPONENTS.md` 與 `DECISIONS.md`。

### 2. Foundations 如何從 tokens.css 實際渲染

[confirmed] `index.html` 直接 `<link>` `tokens.css` 與 `utilities.css`；所有 73 個現有 token 都有 specimen 與 `data-token-readout`，機械比對結果為 73／73，沒有在 HTML 寫死 token 實值。頁面載入後以 `getComputedStyle(document.documentElement).getPropertyValue(tokenName)` 顯示當下的 resolved value；改 `tokens.css` 後，sample 與讀值會一起變。

| 類別 | 直接渲染方式 | 來源標示 |
|---|---|---|
| 色彩 | `background: var(--color-*)` 的色票 | 每張標 `[H1]` 或 `[PO]`；依 `tokens.css` 註解與本 review 第 1 節對映 |
| 字體／字級／行高／字重 | sample 分別套 `font-family`、`font-size`、`line-height`、`font-weight: var(...)` | `[H1]`／`[PO]` |
| 間距 | bar 的 `inline-size: var(--space-*)` | `[H1]`／`[PO]` |
| 圓角 | sample 的 `border-radius: var(--radius-*)` | `[H1]`／`[PO]` |
| 陰影 | sample 的 `box-shadow: var(--shadow-*)` | `[PO]` |
| 轉場 | hover sample 的 `transition-duration`／`transition-timing-function: var(...)` | `[PO]` |
| z-index | layer sample 的 `z-index: var(--z-*)`，並顯示即時 resolved value | `[PO]` |
| Shell 尺寸 | 尺規 bar 的 `inline-size: var(--shell-width)` 等 | `[PO]` |
| 其他契約 token | disabled opacity、骨架寬度、gesture threshold 由 `getComputedStyle` 直接讀值 | `[PO]` |
| utilities | 明確載入 `utilities.css`，用真實 `.visually-hidden` specimen 與最小 markup 說明 | `DECISIONS.md` D13 盤點的 Candidate 補充 |

[confirmed] 一個實際例子是 `--color-up`：HTML 只寫 token 名與 `[H1]`，色票 CSS 是 `background: var(--color-up)`，文字讀值由 JavaScript 從目前 stylesheet 取得；HTML 沒有抄入 `tokens.css` 的色碼。

### 3. Patterns 與最小 HTML 骨架

[confirmed] catalog 收入五個 pattern。判斷標準是「至少跨兩個 component，且包含可重複的 DOM／attribute／事件接縫」；單一 `.stock-row` 或 `.read-card` 仍留在 Components，不因有多個 child 就升格成 pattern。

1. Root stack 必要骨架（Candidate：D5、D6）：

```html
<div class="lila-shell" data-shell>
  <header class="shell-header">back + title + depth</header>
  <main class="shell-content" data-shell-outlet>
    <section class="shell-root" data-shell-root="today">
      <div class="shell-stack">
        <section class="shell-stack-page" data-shell-page="today-root">
          <div class="shell-page-scroll" data-shell-scroll>
            <div class="shell-slot" data-shell-slot>...</div>
          </div>
        </section>
      </div>
    </section>
  </main>
  <nav class="shell-nav" data-shell-nav>...</nav>
</div>
```

2. 預設捲動 vs fill（Candidate：D11）：

```html
<section class="shell-stack-page" data-shell-page="list">
  <div class="shell-page-scroll" data-shell-scroll><div class="shell-slot" data-shell-slot>list</div></div>
</section>
<section class="shell-stack-page" data-shell-page="reader" data-shell-scroll-mode="fill">
  <div class="shell-page-scroll" data-shell-scroll><div class="shell-slot" data-shell-slot><section class="read-swipe">...</section></div></div>
</section>
```

3. 股票列 push 到判讀層（Approved：D4、D8；Candidate：D6；OPEN：O7、O9）：

```html
<article class="stock-row">
  <button class="stock-row__primary" data-shell-push="stock-reader">骨架 slots</button>
</article>
<section class="shell-stack-page" data-shell-page="stock-reader" data-shell-scroll-mode="fill">
  <div class="shell-page-scroll" data-shell-scroll><div class="shell-slot" data-shell-slot><section class="read-swipe">...</section></div></div>
</section>
```

4. 清單載入／空狀態（Approved：D2；OPEN：O2）：

```html
<section class="feed-list" aria-busy="true"><article class="stock-row stock-row--loading">骨架</article></section>
<section class="empty-state"><span class="empty-state__mark"></span><span class="skeleton-line"></span></section>
```

5. 自選編輯態與批次態（Candidate：D12；OPEN：O8）：

```html
<div class="list-edit-toolbar">...</div>
<article class="stock-row stock-row--draggable">drag + primary + delete</article>
<div class="batch-bar">summary + actions</div>
```

### 4. Decision Status 與 DECISIONS.md 對應

[confirmed] Decision Status 層照 `DECISIONS.md` D13 的「目前的狀態盤點」分類，沒有重新判斷：

| 狀態 | catalog 收錄代號 |
|---|---|
| Approved | D1、D2、D3、D4、D8 |
| Candidate | D5、D6、D7、D9、D10、D11、D12、D13 |
| OPEN | O1、O2、O3、O4、O5、O6、O7、O8、O9 |

[confirmed] D13 另把 `utilities.css` 與「tokens 的 H1 值採用」列為 Candidate，但沒有給它們獨立 D 代號；catalog 明寫「D13 盤點、無獨立代號」，沒有自行發明代號。

[confirmed] 每個 Pattern 與 Composition 都至少有一個狀態標籤與連回 `DECISIONS.md` 的 D／O 代號；混合了已定接縫與未定產品配置的 composition 同時列出多個狀態，不用單一 Approved 掩蓋 OPEN。

[confirmed] Decision Status 警語明寫「頁面完整度不等於方向已採用」，並說明沒有狀態標籤時，版位、K 線位置與互動會被頁面完整度默默升格；O1–O9 是刻意留白，不是待辦。

[confirmed] 組裝時發現一個文件落差：`COMPONENTS.md` 的 OPEN 表目前只列 O1–O7，沒有 D13 已新增的 O8、O9。catalog 依硬規則採 `DECISIONS.md` 的 O1–O9；未修改 `COMPONENTS.md`，建議由 PO 同步組裝索引。

### 5. phone.html 橫幅與動線回歸邊界

[confirmed] 橫幅使用原生 `<details open>`：預設明顯展開，使用者點 `<summary>` 即可收起；它在 `[data-shell]` 外、位於正常 document flow，沒有 fixed／sticky／overlay、沒有拿 shell z-index，也不攔截手機畫面手勢。Quick Look 靜態渲染可見橫幅在手機外框上方，沒有覆蓋 shell。

[confirmed] CSS `<link>` 已改成明確順序：`tokens.css → utilities.css → shell.css → lists.css → read.css`。橫幅自己的樣式只用既有 token，沒有新增 token。

[confirmed] 靜態回歸核對仍有 4 個 `data-shell-root`、4 個 `data-shell-tab`、3 個 fill reader page、3 個 reader host，以及 1 個 `read:dismiss` → `navigation.back()` bridge；沒有 module script 或網路依賴。兩段 inline classic script、`shell.js`、`read.js` 皆通過 JavaScript 語法檢查。

[coverage gap] 沒有真實瀏覽器可點擊，因此不能宣稱 push、back、progress 跳段或上下滑已在本工單實際操作通過；本次能支持的是 DOM／API 接縫未改、banner 不覆蓋 shell，以及 Quick Look 的靜態 layout 沒有破版。

### 6. 實際檢查與 file://

- [confirmed] 唯讀連線取得參考頁實際 HTML／CSS，核對其單頁導覽與 specimen 組織方式。
- [confirmed] 機械核對 catalog 五個 layer anchor 都存在；5 個 Pattern 與 4 個 Composition 每個都有狀態標籤。
- [confirmed] 機械核對 Components 表的 Shell／Lists／Read 分別有 25／33／10 列，與三份 review／`COMPONENTS.md` 的元件數一致；每列都有 class、用途、狀態／變體、token 依賴與 review section 連結。
- [confirmed] 機械核對 `tokens.css` 的 73 個 token 全部在 Foundations 有 specimen／readout，CSS 中的 `var(...)` 都能解析到 token 或 catalog 內部 sample 變數；`index.html` 沒有硬編 hex 或 px token 值。
- [confirmed] 機械核對 `index.html` 的 149 個本機 `href`／`src` 與 `phone.html` 的 9 個本機 `href`／`src` 目標都存在；沒有外部函式庫、CDN、外部字體／圖片或網路請求。
- [confirmed] `node --check prototype/shell.js`、`node --check prototype/read.js` 通過；`index.html` 的 1 段與 `phone.html` 的 2 段 inline classic script 均通過語法編譯檢查。
- [confirmed] macOS Quick Look 實際產生 `index.html` 與 `phone.html` 縮圖；catalog 頁首、錨點目錄、Foundations 色票，以及 phone 的展開橫幅與既有手機 shell 都有渲染。這是靜態 renderer 驗證。
- [confirmed] 兩頁只用相對路徑的 stylesheet／classic script／iframe／anchor 與 inline SVG，沒有 `type="module"`、`fetch()`、XHR 或 dynamic import，因此沒有新增常見的 `file://` module CORS blocker；橫幅的收合是原生 HTML，不依賴額外 JavaScript。
- [coverage gap] Browser runtime 回傳沒有可用瀏覽器實例，無法在 Chrome／Safari 的 `file://` 分頁操作 iframe、錨點、details、push／back／progress／swipe；Quick Look 與靜態檢查不能替代這項互動驗證。

## 追加：工具搬家後的連結修正

### 結論

[confirmed] 搬家後兩個入口的本機資源連結已全部修通。`design-system/index.html` 共改 141 個 `href`／`src`：71 個 contract 連結改到同層子目錄 `./contracts/`，另依實際存在位置修正 1 個 `COMPONENTS.md`、66 個 `DECISIONS.md`、3 個 LILA `phone.html` 連結。`LILA_v2/prototype/phone.html` 的 5 份 stylesheet、2 份 classic script 與 catalog 入口已改到 sibling `../../design-system/`，DECISIONS 與同頁 SVG 錨點維持不變。

[confirmed] 路徑採相對寫法，不使用使用者專屬的 `/Users/chiu/...` 絕對路徑。搬動整個 `Cmoney/` 資料夾時，`design-system/` 與 `LILA_v2/` 只要保持 sibling 關係，兩頁仍可解析；絕對路徑反而會綁死帳號與機器。

[coverage gap] 本環境沒有可用 Browser 實例，沒有在 Chrome／Safari 實際點擊 contract／DECISIONS／phone anchor，也沒有重跑 push／back／swipe。已完成檔案存在性、語法、載入順序與 macOS Quick Look 靜態渲染；不可把這些寫成瀏覽器互動驗證。

### 1. index.html 修改數量與本機連結核對

[confirmed] 修改前的失效本機連結共有 141 處，分解如下：

| 原路徑 | 處數 | 新路徑 | 搬家後角色 |
|---|---:|---|---|
| `../outputs/component-baseline/reviews/` | 71 | `./contracts/` | 三份元件 contract 已搬進工具目錄 |
| `../COMPONENTS.md` | 1 | `./COMPONENTS.md` | 組裝索引與 catalog 同層 |
| `../DECISIONS.md` | 66 | `../LILA_v2/DECISIONS.md` | LILA 產品決策留在 LILA_v2 |
| `./phone.html` | 3 | `../LILA_v2/prototype/phone.html` | composition playground 留在 LILA_v2 |

[confirmed] 替換後機械掃描 `design-system/index.html` 的每一個非網路、非同頁錨點 `href`／`src`：共 149 個引用、18 個 unique path，逐一用 `path.resolve(dirname(index), refWithoutFragment)` 與 `fs.existsSync()` 核對，missing 為 0。另反向搜尋確認舊 contract prefix、舊 COMPONENTS／DECISIONS／phone attribute 都為 0；新 `./contracts/` 精確為 71 處。

### 2. phone.html 選擇相對路徑的理由

[confirmed] 選擇相對路徑。從 `LILA_v2/prototype/phone.html` 到工具 root 的既有 sibling 關係可精確表示為 `../../design-system/`，所以載入順序是：

```html
<link rel="stylesheet" href="../../design-system/tokens.css">
<link rel="stylesheet" href="../../design-system/utilities.css">
<link rel="stylesheet" href="../../design-system/shell.css">
<link rel="stylesheet" href="../../design-system/lists.css">
<link rel="stylesheet" href="../../design-system/read.css">
```

[confirmed] `shell.js`／`read.js` 與 catalog 入口也使用同一 prefix；`../DECISIONS.md` 仍解析到 LILA_v2 根目錄，沒有改動。橫幅文字已明寫 catalog 位於獨立的 `design-system/` 工具目錄，不再暗示它與 phone 同層。

[inferred] 相對路徑比絕對 `/Users/chiu/Documents/...` 更可靠，因為它保留 repo bundle 的可搬移性，也不依賴特定 macOS 使用者名稱；本工單已用實際 filesystem resolution 與 Quick Look 證明目前 sibling 結構可解析。

### 3. file:// 跨目錄風險

| 類型 | 本輪結論 | 證據邊界 |
|---|---|---|
| stylesheet | [confirmed] 5 個相對目標與各 CSS 的本機 `@import` 全部存在；Quick Look 中 phone 完整套用 shell／lists 樣式 | [coverage gap] 未在 Chrome／Safari 比較各自的 local-file policy |
| classic script | [confirmed] 2 個目標存在，且沒有 `type="module"`、`fetch()`、XHR 或 dynamic import，因此沒有新增常見的 file module CORS blocker | [coverage gap] Node 只驗語法與 DOM 接縫，沒有執行瀏覽器事件 |
| iframe | [confirmed] catalog 的三份同目錄 preview 與跨 sibling 的 phone target 都存在；parent 不讀 iframe DOM，因此不依賴跨 file origin 的 DOM 權限 | [coverage gap] 瀏覽器可能把不同 local file 視為 opaque origin，實際是否顯示仍需 Chrome／Safari 點開核對 |
| anchor | [confirmed] HTML page、DECISIONS、COMPONENTS、contract 檔案都可解析到存在的本機 target；`#phone-icon-*` 是同文件 SVG reference，未改動 | [inferred] contract 的 `.md#heading` 能開到檔案，但純 `file://` 瀏覽器不一定把 Markdown heading 轉成 HTML id，因此 fragment 精準跳節屬瀏覽器／Markdown renderer 能力，不是路徑存在性保證 |

### 4. 實際檢查

- [confirmed] 修改前後以精確字串計數：舊 contract prefix 71 → 0，新 `./contracts/` 0 → 71；另外三類搬家失效路徑也都歸零。
- [confirmed] 機械解析兩頁所有本機 `href`／`src`：catalog 149／149 存在，phone 9／9 存在，missing 都是 0。
- [confirmed] 機械核對 phone stylesheet 順序精確為 tokens → utilities → shell → lists → read；classic script 順序維持 shell → read。
- [confirmed] `index.html` 的 1 段與 `phone.html` 的 2 段 inline classic script 均通過 JavaScript 語法編譯；搬家後 `shell.js`、`read.js` 通過 `node --check`。
- [confirmed] 唯讀掃描 `design-system/` 的 4 份 HTML，所有本機 `href`／`src` missing 為 0；掃描 5 份 CSS，所有 `@import` missing 為 0。
- [confirmed] macOS Quick Look 對搬家後 `design-system/index.html` 與 LILA `phone.html` 都成功產生縮圖；phone 橫幅與完整 shell 樣式可見，支持跨 sibling stylesheet 的靜態載入。這不是互動驗證。
- [coverage gap] Browser runtime 沒有可用實例，未在真實 Chrome／Safari 點擊 catalog anchors、展開 iframe、切 tab、push／back 或滑 reader。

### 5. A／B 範圍外的搬家落差

[confirmed] 沒有發現其他功能性 HTML／CSS 資源失效：四份工具 HTML 的本機引用與五份 CSS 的 import 都存在。

[confirmed] 另發現的是「非超連結的歷史文字路徑」：`design-system/COMPONENTS.md` 仍有 9 個 `prototype/...` code span；`contracts/shell_nav.md` 原有段落有 13 個、`read_swipe.md` 有 2 個、`feed_list.md` 有 6 個。它們不是 live `href`／`src`，所以不影響頁面載入，但下一個 agent 若直接複製舊命令或檔案位置會找不到搬家後的 flat path。

[confirmed] 依邊界沒有改 `COMPONENTS.md`、沒有重寫三份 contract 的歷史內容，也沒有修改 preview／CSS／JS；本節只留下使用回饋。建議 PO 另行決定要把這些歷史交付敘述保留為 provenance，或新增一段「搬家後 canonical path」覆蓋舊路徑。
## 追加：catalog 呈現層重做（v2）

### 結論

[confirmed] `design-system/index.html` 已依 D14／D15 重做為「視覺在前、規格在後且預設收起」的雙讀者 catalog：人類預設路徑為總覽（完整手機）→ Shell／清單／判讀的元件與狀態 → Patterns → Foundations → Decision Status；68-row implementation 索引、73 個 token specimen、5 個 Pattern 原碼、contract 與完整 D／O registry 都仍在 DOM，沒有刪除 agent 所需資訊。

[confirmed] 本輪只改 catalog 呈現層，沒有修改任何 token、utility、domain CSS／JS、preview、phone、COMPONENTS 或 DECISIONS。資訊位仍是骨架；沒有替 O1、O2、O5、O6、O7、O8、O9 填答案。清單 chip、編輯態與批次態明標「僅狀態展示」，沒有偷補 controller。

[confirmed] 在 Playwright 內建 Chromium 以 `file://` 實際載入最終檔，4 個 iframe 都完成載入；sticky nav、`<details>`、程式碼與 token 複製、Shell preview tab、Read preview loading control 與 progress mount 均通過，console／page error 為 0。

[inferred] 實作已具備人類 gate 所要求的可掃視結構，但「不讀文字的人是否真的辨認出三個 domain」仍需真人看頁確認，不能用工程檢查代替使用者理解。

### 1. 頁面順序與 sticky nav

[confirmed] 頁面實際 DOM 順序如下：

1. Hero：兩句白話說明 + Approved／Candidate／OPEN 三態 legend；source of truth 與五層索引收進「給實作者」`<details>`。
2. 總覽（`#overview`，Compositions metadata）：先嵌完整手機 `phone.html`（`#full-phone`），再放 Shell／清單／判讀三個 representative specimen。
3. Shell（`#shell`，Components metadata）：selected／unselected 並排樣本 → live `preview-shell.html` → 收合的 25-row implementation reference。
4. 清單（`#lists`，Components metadata）：loading／selected／編輯態並排樣本 → live `preview-lists.html` → 收合的 33-row implementation reference。
5. 判讀（`#read`，Components metadata）：ready／loading／empty 並排樣本 → live `preview-read.html` → 收合的 10-row implementation reference。
6. Patterns（`#patterns`）：5 個組合 pattern；每個先顯示關係圖與用途，HTML 原碼收進「查看組裝 HTML」。
7. Foundations（`#foundations`）：token 實際樣本、用途、token 名與 resolved value；provenance 與實作資訊收合。
8. Decision Status（`#decision-status`）：完整 Approved／Candidate／OPEN registry 與「頁面完整度不等於方向已採用」警語。

[confirmed] Sticky nav 的可見錨點依工單精確為：總覽／Shell／清單／判讀／完整手機／Foundations／決策。scroll handler 依各 target 的實際 document offset 更新 `aria-current="location"` 與 `.is-current`；Playwright 點「清單」後實測 URL hash 為 `#lists`，current link 為「清單」。

[confirmed] 五層分類沒有刪除：Foundations、Components、Patterns、Compositions、Decision Status 仍以 `data-layer` 與「給實作者」索引存在；D15 只改人類預設閱讀順序，沒有把 metadata 改成新的產品分類。

### 2. 人類 gate

[confirmed] 一個不讀 class 名的人進頁後，前三個看到的內容是：

1. 「可以看到 Shell／股票清單／滑動判讀，以及哪些 preview 可操作」的兩句白話 hero，下面立即給三態 legend。
2. 從實際 `phone.html` 渲染的完整手機，卡面直接標「可操作：切 tab、push／back、上下滑與 progress 跳段」。
3. Shell／清單／判讀三張代表樣本：各自先看到實體元件，再看到一句用途、狀態與「可操作／僅狀態展示」。

[confirmed] 高頻狀態已在同卡並排：nav selected／unselected、stock row loading／selected／編輯態、reader ready／loading／empty。股票列外部另有 identity／quote／action／reason anatomy 標籤；判讀有 progress／viewport／content／state 標籤。這讓骨架表達結構，不需要在 UI 填假價格、漲跌、理由或未定內容。

[confirmed] 實際視覺樣本都在契約表之前；三份 preview iframe 也都在各 domain 的 implementation reference 之前。sample、用途、狀態與操作性是預設可見，row-level table、原碼與 provenance 預設收起。

[inferred] 以上結構符合 D14 的 human-facing gate；是否真的讓指定使用者「第一眼辨認」仍是人類理解判斷，應由使用者直接開頁驗收。

### 3. Agent gate 與 truth 邊界

[confirmed] 每個 component／pattern／composition 卡片都保留一個精準 contract 連結；必要 DOM、狀態／變體與 token 依賴回到三份 canonical contract。D／O 代號與 DECISIONS 深連結在同卡 `<details>`；收起只影響人類預設顯示，不影響 agent 讀原始 HTML。

[confirmed] 68-row 完整索引仍原樣存在於三個 `Implementation reference`：Shell 25、Lists 33、Read 10。這些表格含 class、用途、狀態／變體、token 依賴與 row-level contract 深連結，沒有因收斂可見連結而刪除。

[confirmed] Foundations 沒有複製值：73 個 specimen 仍以 `var(--token)` 渲染，73 個 `data-token-readout` 由 `getComputedStyle(document.documentElement)` 取得 resolved value。色票用途在前，token 名與值在第二層，`[H1]`／`[PO]` provenance 進「來源」details。

[confirmed] 三份 domain preview 與 phone composition 都直接 iframe 實際檔案；本頁雖明確依 tokens → utilities → shell → lists → read 載入 live state specimen，仍沒有複製 domain CSS。Source of truth 與 `COMPONENTS.md` 入口留在 hero 的實作者 details。

[confirmed] Decision Status registry 沿用既有分類，未重新判斷：Approved 為 D1、D2、D3、D4、D8；Candidate 為 D5、D6、D7、D9、D10、D11、D12、D13，以及 D13 盤點中無獨立代號的 utilities／H1 token 採用；OPEN 為 O1–O9。D14／D15 只作本工單依據，沒有自行改寫 registry 分類。

### 4. `--doc-*` 文件尺度與隔離

[confirmed] 文件 chrome 的 scoped 變數定義在 `index.html` 的 `.design-catalog`，實際值如下：

| 類別 | `--doc-*` | 值 |
|---|---|---:|
| 字級 | `--doc-display-size`／`--doc-page-title-size`／`--doc-section-size` | 36px／28px／22px |
| 字級 | `--doc-card-title-size`／`--doc-body-size`／`--doc-meta-size`／`--doc-caption-size` | 17px／15px／13px／11px |
| 行高 | `--doc-leading` | 1.6 |
| 頁面／導覽 | `--doc-page-max`／`--doc-nav-height` | 1200px／56px |
| 節奏 | `--doc-section-gap`／`--doc-card-gap`／`--doc-card-pad` | 64px／16px／20px |
| 展示舞台 | `--doc-specimen-min`／`--doc-preview-height` | 160px／720px |
| 手機 iframe | `--doc-phone-height`／`--doc-phone-width` | 760px／430px |
| 文件圓角 | `--doc-radius-card`／`--doc-radius-chip` | 16px／999px |

[confirmed] 同一段 scoped CSS 有明確註解：「這是文件 chrome，不是產品 token，不得被任何元件引用。」所有 `--doc-*` 只被 `.doc-*`／catalog wrapper 使用；實際 `.shell-*`、`.stock-row*`、`.read-*` 元件仍由 domain CSS 引用產品 token。

[confirmed] 機械掃描 `shell.css`、`lists.css`、`read.css` 的 `--doc-` 引用為 0；`tokens.css` 沒有修改，也沒有新增 token。文件 chrome 的 px 值沒有寫成產品 token 說明文字，Foundation 產品值仍由 `var()` 與 `getComputedStyle` 取得。

### 5. `file://` 一鍵複製

[confirmed] Playwright Chromium 以 `file://` 實測時，`window.isSecureContext === true`、`navigator.clipboard` 存在、`document.execCommand` 存在。實際點擊 Pattern 的「複製 HTML」與 `--color-up` 色票後，分別得到「程式碼已複製」與「--color-up 已複製」的可見 `aria-live` 回饋；不是按了無反應的按鈕。

[confirmed] fallback 順序是：先試 `navigator.clipboard.writeText()`；若 API 不存在、secure context 不成立或 Promise reject，建立唯讀 textarea 並呼叫 `document.execCommand("copy")`；再失敗時用 `window.prompt` 顯示完整內容供手動複製。色票可直接 click／Enter／Space 複製，另保留明確的「複製值」按鈕；程式碼各有自己的「複製 HTML／程式碼」按鈕。

[coverage gap] 上述成功路徑是本機 Playwright Chromium；Safari 與使用者實際 Chrome 的 local-file policy 沒有逐一實測。即使瀏覽器限制 Clipboard API，`execCommand` 與 prompt 兩層 fallback 仍存在，但不同瀏覽器的最終可用性須在該瀏覽器點擊確認。

### 6. Audit 差距表 15 條逐項回報

| Audit 維度 | 回報 | 本輪處理 |
|---|---|---|
| 入口承諾 | [confirmed] 已處理 | Hero 改為兩句白話；source of truth、載入契約與 agent 索引移入「給實作者」details。 |
| 第一個 App component | [confirmed] 已處理 | Hero 後立即是 `phone.html` 完整手機，再接三個 representative specimen；Foundations 移到後段。 |
| Component 呈現單位 | [confirmed] 已處理 | 每張卡固定為實體樣本 → 一句用途 → 狀態／操作性 → 一個精準 contract；完整 iframe 另作 live card。 |
| 狀態比較 | [confirmed] 已處理 | Nav selected／unselected、stock row loading／selected／編輯態、reader ready／loading／empty 同卡並排。 |
| 互動可見性 | [confirmed] 已處理 | 每張 specimen 明標「可操作」或「僅狀態展示」；Lists 依 O8 不補 controller。 |
| 程式碼位置 | [confirmed] 已處理 | 5 個 Pattern code block 全部進預設收合 `<details>`，summary 精確為「查看組裝 HTML」。 |
| 契約連結 | [confirmed] 已處理 | 可見卡面收斂為一個精準連結；68-row／71 個 row-level 深連結完整留在收合的 Implementation reference。 |
| 版面節奏 | [confirmed] 已處理 | 文件 section gap 為 64px；specimen／pattern 使用 auto-fit grid，卡內順序一致，可先掃圖。 |
| 字級階層 | [confirmed] 已處理 | 建立 scoped `--doc-*`，文件主層級為 36／22／13px，元件仍使用產品 token。 |
| 導覽 | [confirmed] 已處理 | 改為七個具體錨點，加入 scroll current-section 高亮與 `aria-current`；Chromium 實測通過。 |
| 視覺／文字比例 | [confirmed] 已處理 | Phone、live iframe 與 160px 以上樣本舞台在前；表格、code、provenance 都 progressive disclosure。 |
| 色票 | [confirmed] 已處理 | 漲跌語意色先於基礎色；保留 `var()`／resolved value，新增人話用途、點擊複製，provenance 收合。 |
| 骨架可理解性 | [confirmed] 已處理 | 加入 identity／quote／action／reason 與 progress／viewport／content／state 外部標籤；未填金融資訊。 |
| Decision Status 位置 | [confirmed] 已處理 | Hero 下先放三態 legend；每張卡放 badge + 已定／未定白話，D／O 深連結收合，完整 registry 留頁尾。 |
| 五層線性順序 | [confirmed] 已處理 | 五層保留作 metadata；人類預設改為總覽 → 元件與狀態 → Patterns → Foundations → Decision Status。 |

[confirmed] 15 條皆為「已處理」，沒有「未處理」或「不同意」項目；本輪沒有推翻 audit 判斷。

### 7. 實際檢查

#### 靜態與機械檢查

- [confirmed] 解析最終 HTML 的 start／end tag stack：錯誤 0、未關閉 0；1 段 inline classic script 通過 JavaScript 語法編譯。
- [confirmed] 以部署位置解析 170 個本機 `href`／`src`，missing 為 0；外部 `http(s)`／data href/src 為 0。Stylesheet 順序精確為 tokens → utilities → shell → lists → read。
- [confirmed] Sticky nav 7 個標籤與 DOM 閱讀順序機械核對通過；3 個 Implementation reference 與 5 個 Pattern code details 都沒有 `open` attribute。
- [confirmed] 68-row 索引仍是 Shell 25 + Lists 33 + Read 10；73 個 foundation card 與 73 個 token readout 都存在；4 個實檔 iframe 都存在。
- [confirmed] 高頻狀態字樣、可操作／僅狀態展示標示、Decision warning、O1–O9 刻意留白警語、copy 三層 fallback 均存在。
- [confirmed] 精確掃描三份 domain CSS 的 `--doc-` 引用為 0；沒有修改 `tokens.css`，也沒有在 catalog 填假價格、漲跌、成交量、理由或 Feed 邏輯。

#### 真實渲染與互動檢查

- [confirmed] Playwright 內建 Chromium 以 `file:///Users/chiu/Documents/Cmoney/design-system/index.html` 載入，main page response 為 200；`phone.html`、`preview-shell.html`、`preview-lists.html`、`preview-read.html` 四個 file iframe 都完成載入。
- [confirmed] 瀏覽器中 73 個 resolved value 全部有值；5 個 Pattern code 與 3 個 implementation table 預設關閉。
- [confirmed] 點 sticky nav「清單」後 `#lists` 與 current-section 同步；Pattern details 可展開，程式碼與 token 色票點擊都產生成功回饋。
- [confirmed] Shell iframe 實際點「自選」後 `aria-selected="true"`；Read iframe 可切 loading，再切回 ready，progress segments 已 mount。
- [confirmed] 最終 run 的 browser console／page error 為 0，並產出 full-page screenshot。macOS Quick Look 另成功渲染首屏，可見文件字級階層、legend、收合的實作者資訊與 phone composition；Quick Look 只算靜態 renderer。

[coverage gap] 沒有測 Safari、Firefox、窄螢幕的每個 viewport，也沒有做人類辨識測試；因此不能把 Chromium 通過寫成跨瀏覽器保證，或把結構改善寫成使用者已明確同意新呈現。
## 追加：file:// iframe 限制的 graceful degradation

### 結論

[confirmed] `index.html` 的 4 個 preview iframe 已改成延後指定 `src`：在 `file://` 直接顯示說明卡，不再讓瀏覽器先嘗試內嵌本機檔案，因此不會出現破掉的 iframe 或 Chrome 錯誤頁；在 `http://127.0.0.1:8765/design-system/index.html` 則仍載入原本 4 個實際檔案，畫面與互動不改。

[confirmed] Fallback 只有原因、top-level 新分頁連結與本機 server 指引，沒有複製 phone／preview markup，也沒有新增 token、外部資源或產品內容。上一輪的資訊順序、progressive disclosure、文件字級與 Decision Status 均未改。

[confirmed] 使用者實際 Chrome 的錯誤已修正證據邊界：檔案存在與權限正常，但 Chrome 的 local-file origin policy 擋住 iframe。上一輪「Playwright Chromium 內 4 個 file iframe 載入」只代表該測試執行環境，不能支持使用者 Chrome；本追加以 `[coverage gap]` 明確更正該外推。

### 1. 偵測方式與誤判防護

[confirmed] HTML 不再靜態寫 iframe `src`，改把 4 個真實路徑放在 `data-preview-src`，並以 `data-preview-check` 指定成功頁面必須存在的 marker：phone／Shell 為 `[data-shell]`、Lists 為 `body.lists-preview`、Read 為 `[data-read-swipe]`。這可避免 bottom script 執行前，Chrome 已先發起會被擋的 file iframe 導航。

[confirmed] Protocol 分支在 `setupEmbeddedPreviews()` 內使用：

```js
var isLocalFile = window.location.protocol === "file:";

if (isLocalFile) {
  showFallback("file-protocol");
  return;
}
```

[confirmed] `file:` 分支完全不設定 `src`；iframe 被隱藏並標記 `data-preview-state="fallback"`／`data-preview-fallback-reason="file-protocol"`，同位置插入 fallback。這不是先等錯誤發生後遮住錯誤，而是根本不建立會被 Chrome 擋掉的子頁導航。

[confirmed] 非 file 協定同時保留三種失敗判斷：

1. `load` 後以 try／catch 讀取 `contentDocument`、`contentWindow.location.href` 與指定 marker；讀不到、仍是 `about:blank` 或 marker 不存在，標成 `inaccessible-content`。
2. iframe `error` event 標成 `load-error`。
3. 進入可視範圍後 15 秒仍未成功，標成 `timeout`。

[confirmed] 為避免把 HTTP 慢載入誤判為失敗，`loading="lazy"` 的三個 domain iframe 只有在 `IntersectionObserver` 進入 `600px` root margin 後才啟動 15 秒 timer；尚未捲到的 iframe不計時。即使 timeout 已暫時顯示 fallback，後續若收到有效 load，`showFrame()` 仍會移除 fallback、恢復同一個 iframe，沒有永久鎖死慢載入。非 lazy 的 phone iframe 才在指定 `src` 後立即計時。

[confirmed] 每個 iframe 獨立保存 timer、observer、fallback 與狀態；一個 preview 失敗只降級該卡。HTTP 模擬把 `preview-shell.html` 回成缺少 `[data-shell]` 的 HTML 時，實測只有 Shell 進 fallback，其餘 preview 不受影響。

### 2. Fallback 卡內容

[confirmed] Fallback 卡在原 iframe 位置呈現，實際文案與操作如下：

- 標題：「內嵌預覽暫時無法顯示」
- 原因：「瀏覽器的本機檔案限制讓內嵌預覽無法載入，不是檔案壞了。」
- 明顯主動作：「在新分頁開啟」，使用原本 `data-preview-src`，`target="_blank" rel="noopener"`；top-level 仍導到同一份 canonical preview／phone 檔。
- Server 說明：「要看完整內嵌預覽，請雙擊 design-system/serve.command，或在 Terminal 執行：」
- 可選取、可複製指令：`cd ~/Documents/Cmoney && python3 -m http.server 8765`
- 「複製指令」按鈕沿用既有 Clipboard API → `execCommand` → prompt fallback 與 `aria-live` 回饋。

[confirmed] 卡片使用 `.doc-iframe-fallback*` 文件 chrome class 與既有 `--doc-*`／產品 token；沒有建立 domain component、沒有抄 preview DOM，也沒有修改 `serve.command`。

### 3. `file://` 與 `http://` 實際驗證

#### `file://`

[confirmed] 以 Playwright 內建 Chromium 實際載入 `file:///Users/chiu/Documents/Cmoney/design-system/index.html`：4 個 `iframe[data-preview-src]` 都保留作位置契約，但 `iframe[src]` 為 0；4 張 fallback 都存在，原因皆為 `file-protocol`，console／page error 為 0。整頁截圖中 4 個位置皆為說明卡，沒有瀏覽器錯誤頁。

[confirmed] 4 個「在新分頁開啟」都解析為 `file://`；實際點第一個連結會開新頁並到達 `file:///Users/chiu/Documents/Cmoney/LILA_v2/prototype/phone.html`。Fallback 三項必備內容與複製指令按鈕皆在實際 DOM；點「複製指令」得到「程式碼已複製」回饋。

#### `http://`

[confirmed] PO 的本機 server URL `http://127.0.0.1:8765/design-system/index.html` 實際回 200。Playwright 載入後逐一捲到四個 preview：fallback 為 0，4 個 iframe 都是 `data-preview-state="ready"` 且都有 `src`。

[confirmed] 4 個 child frame 的實際 URL 分別是 `/LILA_v2/prototype/phone.html`、`/design-system/preview-shell.html`、`/design-system/preview-lists.html`、`/design-system/preview-read.html`；console／page error 為 0。HTTP 整頁截圖仍顯示原本 phone 與三份 preview，支持「HTTP 下完全不顯示 fallback」。

[confirmed] 額外以 HTTP route 將 Shell preview 回成缺少 marker 的 HTML，`load` 驗證實際標成 `inaccessible-content` 並只顯示 1 張 fallback；這覆蓋 protocol 以外的 load 失敗分支。Timer／晚到恢復由程式分支靜態核對，沒有刻意等待 15 秒製造慢網路。

[coverage gap] 以上兩種協定均在 Playwright 內建 Chromium實際載入；使用者實際 Chrome 的「file iframe 會被擋」以使用者回報為 confirmed，但修正後的 fallback 與 top-level 新分頁連結仍應由使用者在同一個 Chrome profile 再看一次。不能因 Playwright 通過就宣稱所有 Chrome policy 已驗收。

### 4. `file://` 下剪貼簿、anchor、details

| 功能 | 本輪實際結果 | 證據邊界／修正 |
|---|---|---|
| 剪貼簿 | [confirmed] Playwright Chromium 的 `file://` 為 secure context，Clipboard API 與 `execCommand` 都存在；複製指令實際出現成功回饋 | [coverage gap] 使用者 Chrome 未逐一點擊。既有三層 fallback 保留，因此 API 被 policy 擋時會降到 `execCommand`，再失敗則顯示 prompt，不會無回饋 |
| Top-level preview link | [confirmed] Playwright 實際開出新的 `file://` 分頁並載入 phone.html | [confirmed] 這是 top-level navigation，不使用 iframe；使用者規格亦明確確認此路徑允許 |
| Anchor | [confirmed] `#lists` 導航與 hash 可用；fallback 改變頁高後，第一次實測 current-section 仍停在 Shell | [confirmed] 已修正 cutoff：從固定 `nav height + 8` 改為讀取實際 `scroll-margin-block-start`，最終實測 hash `#lists` 與 current「清單」一致 |
| `<details>` | [confirmed] Pattern details 在 `file://` 可展開／收起，無協定差異 | 原生 HTML 行為，不依賴跨 origin 存取 |

[confirmed] 除 iframe local-file policy 與被 fallback 頁高揭露的 current-section cutoff 外，未發現其他需要在本工單修改的協定落差。Decision links／contract links是 top-level navigation；Markdown fragment 是否由瀏覽器 renderer 精準定位仍沿用既有 coverage gap，本輪沒有越界改文件。

### 5. 後續 agent 的驗證教訓

[confirmed] **Playwright Chromium 通過不等於使用者的 Chrome 通過。** 上一輪 Playwright 的「4 個 file iframe 都完成載入」在該 harness 內確實發生，但內建 Chromium 的啟動參數、sandbox、local-file origin policy、profile 與企業政策都可能不同；因此它不能外推成使用者 Chrome 的 local-file 相容性。正確寫法應是「測試 harness 通過；使用者目標瀏覽器為 coverage gap」，直到同一目標瀏覽器／profile 實際操作。

[inferred] 後續凡涉及下列 browser policy 的驗證，一律不得只靠 Playwright 就宣稱使用者環境通過：

- `file://` 的 iframe／跨本機檔案 origin、CORS、module script、`fetch()`／XHR。
- Clipboard、下載、popup／top-level navigation、檔案系統與權限 prompt。
- cookie、localStorage／IndexedDB、service worker、第三方內容與企業政策。
- 相機／麥克風／通知、跨 origin isolation、mixed content 等瀏覽器權限或安全策略。

[inferred] Playwright 仍適合確認我們自己控制的 DOM 分支、狀態、事件、same-origin HTTP 組裝與回歸；但凡結論依賴瀏覽器 policy，報告必須同時寫明瀏覽器產品、版本／執行方式、協定、profile／flags，以及是否就是使用者的實際環境。缺任何一項時，對使用者環境只能標 `[coverage gap]`，不可把「自動化能跑」升格成「使用者一定能用」。
