System Log
Internal · 系統級變更紀錄。來源檔 docs/governance/system-log.md,新條目寫在該檔最上面。
DS site 公開化第 6 步:中英雙語切換(全部公開頁)
plan 第 6 節裁決 1「中英雙語可切換」落地第一輪。navbar 右上角、dark / light 切換鈕旁新增 EN / 中 切換,選擇記在瀏覽器的 localStorage(ds-locale),預設維持繁中。刻意不引 i18n 框架:每句 authored 文案就地存成 { zh, en } 兩份。
- **工具**:
src/providers/locale.tsx一支檔案——LocaleProvider(掛在 root layout,同步<html lang>)、useLocale()回傳locale/setLocale/t();t({ zh, en })取當下語言,不是成對物件的值原樣通過。另有<T zh en />給 server component 用:Changelog 要在建置時讀檔,維持 server component,兩種語言的字串都送到 client,由<T>挑一份 - **切換鈕**:兩顆
@olab/componentsButton(skin=secondary、size=sm),目前語言為active——同 production DurationSwitch 的寫法;pressed 回饋來自 skin 本身(未選active:bg-secondary-pressed、已選active:brightness-90),light / dark 皆可見。外層role="group",每顆帶aria-pressed - **共用元件**:
ComponentPage的 title、Section的 title、SubSection的 label、On this page 的 label、組件頁骨架的SpecRow(part / value)、ComponentUsage的 Do / Don't、ComponentSpec的說明都接受{ zh, en };status 標籤說明(NAV_STATUS_META.summary)改存兩份,頁首、側欄 tooltip、Overview 的 Status 表一起切換。欄名、群組名、頁名、status 標籤名本來就是英文,不動 - **翻譯範圍:全部公開頁**。先做站殼 + Overview + Guidelines + 第 5 步四頁(9 頁);user 看到組件頁仍是中文後,同一個 PR 擴大到其餘 41 頁:Foundations 6 頁、Components 22 頁、Patterns 12 頁、Templates 3 頁(另有 13 頁 Patterns 與 4 頁 Templates 原本就是英文)。不讀檔的 server component 頁(Usage、Data Viz、Mobile、Spacing、Radius、Shadow)改成 client component 直接用
t() - **驗證方式**:用瀏覽器以 EN 模式逐頁打開全站 75 頁、數畫面上剩下的中文字,69 頁為 0;剩下 6 頁是刻意不翻的(Internal 5 頁 + Changelog 的檔案資料)。另掃一次英文版裡的全形標點,清到 0
- **不翻的**:Internal 欄(維持繁中);
src/components/product/裡產品組件自己的文字(例如 Asset Dashboard 裡 K 線圖佔位的一行說明),本輪只動頁面檔;從 repo 檔案讀出來的資料——system-log 標題、鏡像同步紀錄、最近更新清單的頁名——維持來源語言,不機器翻譯。token / 組件 / class 名在英文版照原樣保留 - 已知限制:伺服器端一律先渲染繁中,選了 EN 的訪客重新整理時會先閃一下中文再換成英文(要消除得讓伺服器知道語言,例如改用 cookie,本輪不做)
- 驗證:
verify-previewsOK、next build通過、tsc無新增錯誤;數頁於 1440 寬以 EN / 中 × dark、加 light 全頁截圖
DS site 公開化第 5 步:新增 Usage / Changelog / Data Viz / Mobile 四頁,側欄 Recently Updated 改成 Changelog 入口
plan 第 4.3 節列的四頁新內容落地,並完成第 2 步順延過來的一項:側欄 Getting Started 欄底下的「Recently Updated」標籤與「Component」資料夾拿掉,收進新的 Changelog 頁。四頁的內容全部取自 repo 既有文件與 production 原始碼(OpinionLabs/frontend@cb792f9,2026-10-26 逐檔查證),沒有來源的部分不寫,列在 PR 的缺口清單。
/usage(Getting Started):production 怎麼載入 token(src/index.css的兩行 @import)與組件(@olab/components、RSC 用/server入口);Tier 1 / Tier 2 兩層與三條禁止;dark 為預設、light 靠<html data-opinion-theme="light">切換;本 repo 的.live-scope與單檔原型的寫法;鏡像同步指令/changelog(Getting Started):三段都在建置時讀既有紀錄,不手寫——最近更新(recent-updates.generated.ts,分組件 / 頁面)、組件鏡像同步(docs/mirror-sync.md的同步紀錄表)、DS 更新紀錄(本檔,排除 System 類,標題連到/system-log對應錨點)/live/foundations/data-viz(Foundations):10 個--color-data-*role 的角色表(色票隨模式即時解析)、token 示意圖、顏色契約(中性、只有最重要的記號上data-fill-focus)、雙編碼形態判準、圖表數字字體、實際引用這組 token 的組件。data-fill-accent目前沒有任何組件引用,表上照實標註/live/guidelines/mobile(Guidelines):960px 斷點、手機 chrome(header 48 / tab bar 68 + 安全區 / 內容區 12px 內距 / 文字 footer 只在桌機)、分類捲動列規則、手機沒有 hover(連到 Interaction States)、本 repo 參考實作_shared.tsx:移除 recent 兩區的插入邏輯、群組名小字、標籤樣式與相關排除判斷;NAV_SECTIONS直接等於nav-sections.ts。各頁原位的橘點保留- 新頁不套組件頁的七段骨架:Usage / Changelog 是說明頁,Data Viz / Mobile 與其他 Foundations / Guidelines 頁一樣只講 token 與規則、不放組件 demo,因此不適用 Interactive Demo + State Matrix。文案集中在每頁頂端的
COPY物件,方便第 6 步雙語切換時抽出 - 驗證:
verify-previewsOK、next build通過;四頁於 dev server 以 dark / light 截圖
DS site 公開化第 4 步(第一批):組件頁統一骨架
23 個 @olab/components 組件頁原本各長各的:16 頁只有 Demo + Matrix + 一兩個名字各異的範例段,5 頁沒有靜態狀態對照表(Popover、Tooltip、Navigation、Pagination、Table),Button 沒有獨立的 Interactive Demo,只有 Button 與 TextLink 連到 docs/live-ds-spec/ 的實測規格。骨架經 user 2026-10-26 認可後,先做五頁當樣板:Button、Input、Card、Tooltip、Checkbox。
- **骨架(固定順序與錨點 id)**:Overview → Interactive Demo → State Matrix → Examples(選填,收各頁原本的 Block Mode / With Suffix / Composition 等)→ Spec → Usage(Do / Don't)→ Links。Preview Completeness(Interactive Demo + Static State Matrix)照舊是每頁必備
_shared.tsx新增骨架小工具:COMPONENT_SECTIONS、componentOnThisPage()、ComponentOverview、ComponentSpec+SpecTable、ComponentUsage、ComponentLinks。各頁只填內容,段落標題、順序、連結格式由共用元件決定- **Spec 段的資料原則**:尺寸與數值取 live-ds-spec 的 dark 實測(標明量測日期);source class 一律對照**目前**的元件原始碼,不照抄規格文件——規格文件量於 2026-06,之後 token 已改名(例如 Input focus 現在是
border-white不是border-brand、Card 是bg-primary/border-secondary)。色票直接吃 CSS 變數,隨 dark / light 即時解析,頁面上不寫死 hex - **不放 Props 表**:子資料夾 CLAUDE.md 明文禁止 preview 頁放 Prop / Type / API 表,plan 第 2 節提到的 Props 表不做。Storybook 連結暫不放(線上版過期、新版只在本機)
- Button:新增 Interactive Demo(skin / size / disabled / loading / selected 可切換的真按鈕);New Tier 2 Tokens、brand-subtle 重新設計(含 matrix 裡那一列 sim 與比較 demo)、matrix 說明裡的 Proposal B 段、Implementation Notes 四塊移到
docs/live-ds-spec/button.md新增的第 7 節,頁面不再顯示(brand-subtle 不在本地元件裡,放在狀態對照表會被誤認為現有 skin)。頁面 1,399 → 約 830 行 - Input / Checkbox:Implementation Notes 移除,內容在各自 live-ds-spec 的 Conflict 清單已有(Input 那段的「placeholder 寫死
text-gray-3」已過期,現為placeholder:text-tertiary) - State Matrix 補洞:Tooltip 新增靜態對照(四個方向 + 無箭頭 + 長文字;用同 class 的就地複製品,因為真 Tooltip 走 Portal、z-index 會蓋過站殼);Card 補 hover 模擬格;Checkbox 補 hover 兩列與 disabled + indeterminate;Input 補 focus 模擬,並把原本同時帶
readOnly的 Disabled 樣本改成只有disabled(規格 Conflict #5:disabled 本身不變色,舊樣本會讓人誤以為有變淡樣式) nav-sections.ts:五頁的onThisPage同步成骨架順序- 順手:plan 第 3 節 Concept 那列「待 user 決定」改為 2026-10-17 的裁決(CDS 三頁留公開站、標 Concept);目標 IA 的 Components 骨架文字更新為認可版
- 驗證:五頁於 dev server 以 1440 寬 dark / light 全頁截圖;主控台無錯誤;Button demo 切換 skin / size 生效。其餘 18 頁分兩批
DS site 公開化第 3 步:每頁標上它跟 production 的關係
Patterns 與 Templates 混著三種身分的東西,外人分不出哪個是 production 真的在用、哪個是照線上重建、哪個只是概念。這一步給每一頁一個 status,並且**每一筆都以 production repo(OpinionLabs/frontend@main,0ac828f)的實際檔案為證據**,不憑記憶:一次拉下 production 的完整檔案清單在本機比對,production 欄位記的就是對應檔案路徑。
- 分類結果:Adopted 14(Asset 10 個組件、LiveGamesModule、Finance、Asset Dashboard、Market Resolution)· Reference 15(Market 11 個、SportsGameCard、Markets、Sports、Market Detail)· Concept 3(MarketCdsRow、MarketCdsHeroCard、Market Detail (CD))· Internal 欄五頁。Proposed 目前沒有任何一項
- 與 plan 的差異:plan 原把 Market Resolution 列 Proposed,但 production 已有
src/components/ResolutionDashboard/(重寫後上線),改標 Adopted;plan 把 MarketCds 兩個組件算在「照線上重建的 Reference」,但 production 完全沒有 CDS 產品,改標新增的 Concept 狀態。依 plan「概念稿不公開」的原則,這三頁是否保留要 user 決定,本步不動 - LiveGamesModule:本 repo 2026-04-20 先設計,games-module PRD 標明 04-29 隨 production 上線,因此是 Adopted 而非 Reference
nav-sections.ts:新增NavStatus、NAV_STATUS_META(標籤與一句說明),NavItem加status/production,NavSection加預設status(Internal 欄)_shared.tsx:新增StatusBadge;側欄每項顯示標籤(Internal 欄不重複標);ComponentPage依網址自動在大標題下顯示狀態、說明與 production 檔案連結,30 多頁不需逐頁改- 標籤配色:文字一律用 mode-aware 的
text-primary,狀態色只放在淡底與小圓點。初版直接用text-success-solid/text-warning-solid當字色,淺色模式下只有約 2:1;style.css 沒有分模式的 success / warning 文字 token,新增 token 要走完整定義流程,不在本步處理 - 21 段工作紀錄(11 段 Provenance & Gaps、5 段 Audit Notes、5 段 Foundations 的 Future Audit Notes)統一改名為 Implementation Notes,錨點 id 不變;內容不動。把缺口細節移到 docs 的工作併入第 4 步組件頁骨架統一
- 首頁新增「Status 怎麼讀」說明區,Full catalog 卡片顯示標籤
- 驗證:
verify-preview-coverageOK;tsc在改到的範圍無新錯誤;dev server 上側欄 32 項有標籤、AssetRow 頁首顯示 Adopted 與 production 路徑;用網站切換鈕量深淺兩模式,四種標籤文字對比皆 ≥ 5.49:1;主控台無錯誤
DS site 公開化第 2 步:側欄改成六欄公開 + 一欄 Internal
照 2026-10-16 的四件裁決重排資訊架構(plan 見 repo 根 docs/ds-site-public-plan.md)。原本三欄(Overview / Live DS / Contributing)是依「工作狀態」排的:進行中的 Asset Prediction 與 Market Resolution 佔了首欄,Base / Application 是 Untitled UI 的分法,Live DS 這個名字只在 Claude DS 還在時有意義。新結構依「讀者要找什麼」排:Getting Started → Foundations → Guidelines → Components → Patterns → Templates → Internal。所有項目原封不動搬家,只改分類、群組與標籤;第 1 步封存的治理五頁依裁決 2 以 Internal 身分接回。
nav-sections.ts:NavCategory改為七欄;NavSection新增authoredOrder,取代_shared.tsx寫死群組名的AUTHORED_ORDER_GROUPS;group為空字串時項目直接列在欄位標題下(不包同名資料夾)- 搬家:Layout 從 Base Components 移到 Foundations;Base + Application 合併成 Components(22 項);Product Components 依領域拆成 Patterns · Market / Sports,Asset Prediction 群組的 10 個組件併成 Patterns · Asset;Pages、Asset Prediction 的兩頁與 Market Resolution 合成 Templates(7 頁)
- 標籤:Color (In production) → Color、Finance (Page) → Finance、Asset Dashboard (Page) → Asset Dashboard、交付 & Demo → Market Resolution、Market Detail (Single) → Market Detail、Market Detail · CD Variant → Market Detail (CD);30 頁大標題拿掉「(Live)」後綴,與側欄一致
- Internal:從 git 歷史接回
/workflow、/workflow/authority、/workflow/entities、/workflow/decisions、/system-log與workflow.ts、governance-registry.ts。/system-log與/workflow/decisions改成建置時讀本檔(新的src/content/workflow/system-log.ts只負責解析,不再存資料);docs/governance/的workflow.md、governance-registry.md兩份副本移除,避免同一份內容兩個寫入點,對照表見docs/governance/README.md _shared.tsx:Recently Updated 兩區改接在 Getting Started 欄最後;「這筆更新是不是組件」改看分類(Components / Patterns);麵包屑帶上分類名(Design System / Patterns / Asset / AssetRow)- 首頁 Full catalog:標題改用分類名(Patterns 帶群組),不列 Getting Started 與 Internal,改讀
nav-sections.ts避免 Recently Updated 讓組件重複出現 - 與 plan 的差異:Recently Updated 尚未改成 Changelog 入口(Changelog 頁是第 5 步才有);
/dispute-portal(結算列表 v1)沒有放進 Patterns,它跟 Templates 的 Market Resolution 是兩代設計,哪個是現行答案要先釐清 - 驗證:
verify-preview-coverageOK(28/30);tsc在改到的範圍無新錯誤;dev server 上側欄七欄正確、Patterns 依當前頁自動展開、Internal 五頁皆 200、/system-log讀到 65 條
DS site 公開化第 1 步:封存不該對外的 11 條路由,治理內容離開站點
把站點收成一個可對外公開的 Design System 網站的減法步驟(plan 見 repo 根 docs/ds-site-public-plan.md)。三類東西不該出現在對外站上:已結案的交接頁(Asset Prediction Handoff Spec,production 已收下)、已定案產品線的探索展台(Market Resolution 版本探索 / 組合 Playground / 手刻概念稿)、以及「Claude 怎麼跟這個 repo 協作」的維護者文件(Contributing 四頁 + System Log)。/live 索引頁一併拿掉——它的文案還在說「Base Components 是 Claude DS 的方向探索版本」,Claude DS 07-04 已封存,這段是錯的。原 plan 列了 14 條路由,實作時發現 /standalone/asset-prediction-page、/standalone/asset-dashboard-page、/standalone/market-resolution-dashboard-rows 是 /live 三個整頁預覽的 iframe 來源(ViewportFrame),不是重複品,保留;實際封存 11 條。
- 封存至
../opinion-labs-archive/site-pages/(不刪除):/live、/live/asset-prediction-handoff、/live/market-resolution-variants、/live/market-resolution-playground、/standalone/market-resolution-concepts(含 5 個_*.tsx)、/standalone/market-resolution-variants、/workflow、/workflow/authority、/workflow/entities、/workflow/decisions、/system-log;連帶只服務這些頁的components/product/market-resolution/variants/三檔與public/market-resolution-playground.html - 治理內容
src/content/workflow/*.ts五檔封存;內容以一次性腳本轉成docs/governance/{workflow,agent-workflow,design-process,governance-registry,system-log}.md。Decision Ledger 目前沒有任何decisions[]條目,不另出檔 nav-sections.ts:NavCategory移除Contributing;拿掉 About / Governance 兩群組與三個 item;Asset Prediction 群組現在以 Finance (Page) 開頭、Market Resolution 群組只剩「交付 & Demo」一項(第 2 步 IA 重排會再動)_shared.tsx:AUTHORED_ORDER_GROUPS移除 Governance- 兩層 CLAUDE.md 的 system-log 規則改指
docs/governance/system-log.md recent-updates.generated.ts重新產生;verify-preview-coverageOK(28/30,2 composition-only)
DS site 重整第 4 步:首頁重寫 + 新增 Guidelines 區
站點加法。首頁原本的文案停在 token migration 時代(「正把 token 收斂…改名不改色」「Claude DS 是校準層」),Claude DS 已於 07-04 封存、migration 已結案,整段重寫成:這個站是什麼、跟 production 的關係(token 真相 = style.css、組件 = @olab/components 本地 mirror 與同步日期)、裡面有什麼、怎麼開始用、不是什麼。Quick tour 四格改成 Foundations / Components / Patterns / Guidelines。另外把散在兩層 CLAUDE.md 裡、設計師與工程師都要守的規則搬上站成為 Live DS 底下的 Guidelines 群組——專業 DS 站一定有這一區,而我們之前只有 CLAUDE.md 與 memory 知道。頁面只講既有規則、用真組件示意,不新增規則。
src/app/page.tsx:hero 與「這個站是什麼」整段重寫;Rules at a glance 的 token 範例由舊名(text-white、bg-green-1)改為 style.css 現有 semantic token(text-primary、bg-secondary、border-primary、outcome-yes-solid);第三條規則改為 Preview = QA surface;MIRROR_SYNCED_ON 常數標示 mirror 最後同步日期(2026-07-20)nav-sections.ts:Live DS 新增 Guidelines 群組(Foundations 之後、Base Components 之前),手寫順序:Color Semantics → Numbers → Interaction States → Icons;_shared.tsxAUTHORED_ORDER_GROUPS 加入 Guidelines/live/guidelines/color-semantics:Outcome / Team 雙色系統(兩套互斥、怎麼選、@olab Button 五個 family skin 真組件示意、Tier 1/2 token 對照表、Trend ≠ Outcome、對齊外部視覺只能改 anchor、禁止事項)/live/guidelines/numbers:T4 Numeric 三階規則(font-number tier-1 / tabular-nums tier-2 / fail)、同一欄數字三種寫法並排示意、交易 UI 與圖表各用哪一階/live/guidelines/interaction-states:pressed ≠ selected 二維模型、active 禁用、Pressed 必須可見、手機沒有 hover、禁止事項/live/guidelines/icons:只用 Untitled UI line icon、三個尺寸、寫法、禁 emoji 與第二套 icon 庫
DS site 重整第 3 步:封存已結案的 migration 交接頁,Governance 移到 Contributing
站點收斂成「Design System / Component Library」的減法步驟。Token migration、Button migration、focus-visible pilot 這幾條線都已經結案(Claude DS 於 07-04 封存、Button 於 07-19 確認為目標設計、pilot 於 07-06 撤除),但它們的交接頁還掛在導覽上,外人會把「歷史交接文件」讀成「現行規格」。這批頁面連同 Claude DS 時代的兩個殘留頁(color-target 展示的是已不存在的目標、everything-cds 是 UUI 探索層大雜燴)一起封存到 repo 外隔壁的 ../opinion-labs-archive/site-pages/,不刪除。同時把 Governance 群組(workflow / authority / entities / decisions / system-log)從首欄 Overview 移到新分類 Contributing——那是維護者讀的協作流程,不是 DS 內容,不該佔第一眼的位置。
- 封存 8 頁至
../opinion-labs-archive/site-pages/:整個 Migration Plan 群組(/live/foundations/color-token-alignment、/live/state-pilot-report、/live/button-migration)、/live/foundations/color-target、/everything-cds、/pressed-proposal(未掛導覽的提案頁)、/deferred-components(Coming Soon 空殼)、/trending(與 /markets-page 同用 MarketFeedLayout,保留導覽裡的後者) nav-sections.ts:NavCategory 新增Contributing並排在最後;Governance 群組改歸該分類;移除上述頁的 slugscripts/verify-preview-coverage.mjs:market-feed-layout 的 composition-only 理由改為只 /markets-page- 首頁 Quick tour 的 Components 入口原本連到不存在的 /button(Claude DS 時代路徑),改指 /live/button;/live/button 與兩個 CDS 組件頁裡指向已封存頁的文字改寫
- 第 1 步(根目錄未追蹤檔清場)不需要 PR;第 2 步(歷史探索層搬出 repo)見 PR #198
Market Resolution 交接定案:production patch 交付 + 歷程頁封存
完整頁定案後,這條產品線從「探索」切換到「交接」。交付形式不是文件對照表,而是**在 OpinionLabs/frontend 的本地 clone 上實際接好、驗證過的兩個 git patch**(基底 = production main a37cc8e):真 @olab/components、真 token、真 route,git am 套上就是一條可直接開 PR 的 feature branch。實接過程順帶把先前「Dashboard × Rows · 真組件版」頁只能推測的缺口逐一坐實或補掉——font-number 在 production 確實不存在(已在 patch 內補 --font-number 指向既有 --font-secondary)、TextLink 以新元件進元件庫、既有 DisputeModal / MarketResolution 確認是互補不是衝突。歷程頁五頁(列表頁 v1 / Split flows v2 / 接真資料 / Dashboard × Rows 真組件版 / 真資料三缺口)連同只服務它們的 standalone / demo 雙生路由一併封存——完整頁定案後它們的內容都已被吸收,留著只會誤導「現行答案在哪」。
- production patch 兩枚放進
public/handoff/(0001 主體:ResolutionDashboard 組件 + TextLink 新元件 + --font-number + /resolution route 與 SEO metadata;0002:移除 StepTabs 的 group-active:scale-95 pressed state——只縮文字不動底色,與 interaction state 規範不符),已在乾淨 main checkout 驗證 git am 可完整重放 /live/market-resolution-full新增「交接 · Production patch」一節:patch 下載、套用步驟(git am + 元件庫重 build)、18 檔內容清單、與既有 DisputeModal / MarketResolution 的關係、唯一待拍板的視覺偏離(placeholder text-quaternary vs 既有 #86868D)- 封存 12 檔至 repo 根目錄
_archived-market-resolution-pages/:live 五頁(market-resolution / -split / -api / -dashboard-rows / -real-data)+ standalone 與 demo 的 market-resolution / -split / -api 雙生頁 + public/market-resolution-real-data.html;standalone 與 demo 的 dashboard-rows 仍被完整頁引用故保留 - nav-sections 的 Market Resolution 群組由八項收斂為三項(完整頁 / 版本探索 / 組合 Playground)
Prototype 可接 production 真資料:Market Resolution 接上公開 Resolution OpenAPI
結算的公開 OpenAPI 上線(三支 GET 全部無 API key / 無錢包 / 無簽章),MarketResolutionSplitPage 因此多了一個 mock / live 的資料來源開關。這是本 repo 第一個會讀 production 真資料的 prototype,所以定了它的規矩:接真資料的目的不是把原型變成產品,而是**讓 mock 與真實資料的落差在畫面上現形**——翻譯不出來的欄位一律登記在 API_GAPS 並印在頁面說明卡上,不靜默補值。live 之下再分兩個模式:**唯讀**(需要錢包的動作停用加 tooltip,不做任何假提交)與 **demo**(user 同日裁決加入:用一顆模擬錢包讓整條 flow 走得完,好把連結分享出去)。demo 的模擬性質必須寫在畫面上,不能讓人誤讀成真交易。實測撞到的四個落差:finalized 18,442 筆對 proposed 31 筆、in review 0 筆(歷來 dispute 共 2 筆),finalized 每頁四分之三是價格預言機自動結算的每小時市場,選項多半是隊伍名而不是 YES/NO,多選市場的母題標題 API 拿不到。
resolution-api.ts(新增):API 型別、三支 GET 的取數、row → 腿的翻譯,以及API_GAPS缺口登記表。disputeOutcome的 overturned/upheld/timeout → accepted/rejected 對映由 marketId 5760 實測反推並在註解留下推導依據use-live-resolution.ts(新增):useLiveStage/useLiveDisputes,都吃enabled旗標,mock 模式完全不發請求。三個階段的總數刻意依序而非並行——這支 API 擋突發流量,並行會吃 429live-data-notice.tsx(新增):資料來源切換 + 說明卡(當前請求 URL、當頁觀察到的資料現實、可展開的缺口清單)ResolutionListView新增 optionalremoteprop:有帶就是真資料模式,階段 / 頁碼 / 總數改由外部接管(server 端分頁),分類與 Traded markets 一律停用,Dispute 按鈕在唯讀模式停用;不帶時行為與先前完全相同MyDisputesView新增 optionaldisputorAddress/readOnly/hrefForLeg:唯讀模式改以某個真實 disputor 的身分演這個 flow(公開端點連別人的卷宗都讀得到,與 PRD v3 §5「僅本人可見」不一致,待確認);demo 模式則是模擬錢包自己。地址比對改為大小寫不敏感leg-derive.ts新增groupLegsInOrder:真資料的排序在 server 端做完了,頁內再用自己的鍵重排會讓這一頁跟前後頁對不上ResolutionLeg新增五個 optional 欄位(proposedOutcomeLabel/finalOutcomeLabel/outcomeLabels/slug/isPriceOracle),皆只有真資料會有值。具名選項(隊伍名)改用中性色顯示——染 YES 綠會把 Team Identity 與 Outcome 兩套互斥色系混在一起;outcomeLabels存的是完整 slot → 名字對照,倒數歸零把結果翻面時要印**另一隊**的名字,只存贏家會退回印 YES / NO 並染上極性色- Demo 模式(
simulateActions):模擬錢包 + 壓縮成 90 秒的審理窗,讓提交 → 審理 → 定案 → 領獎走得完。做法是把本地那幾筆**疊**在真資料上(真資料唯讀,API 那邊這些腿還停在 Proposed)——公開列表扣掉已 dispute 的腿並插入落在當前階段的本地腿,tabs 數字與分頁總數同步加減,LocalDispute連整支腿的快照一起存(server 端逐 phase 分頁,腿一離開那一頁就再也拿不到它的 row) - Dispute Detail modal 的具名選項改印原始標籤並走中性顏色表——用的是 vendored 組件自己開的
outcomeColorClassMap缺口,沒有動它的內部 getJson對 429 加一次 1.2 秒退讓重試:撞到的典型情境是同一頁掛兩三個 frame,錯開就過了;重試完仍 429 才顯示錯誤態- 新增三條 route:
/standalone/market-resolution-api(預設 demo,?readonly=1切唯讀)、/demo/market-resolution-api(可分享的 demo,不放資料來源切換避免誤按到 mock)、/live/market-resolution-api(demo flow 逐步說明 + 落差說明)。既有三條market-resolution-splitroute 行為不變,資料來源開關預設關閉
data-viz token 層補 halftone ink role(--color-data-ink / -strong)
Asset Prediction 全範圍稽核(真組件 / 顏色 / 字體 / 子組件四路)抓到 token 層一個 role 錯位:兩張半色調圖的網點是按「亮度」借 token 而不是按角色挑——default 網點借了 --color-data-point(定義是序列節點 / timeline 圓點),value area 借了 --color-data-fill-accent(定義是「整張圖只有一個」的單點標記,但 VA 是多列)。病灶在 light 端最明顯:data-point 與 fill-accent 在 light 同為 -600,網點的強調層級整個消失。成因是 fill 系(-800/-700)back 的是「一塊底板」,對網點這種細碎筆畫太暗,而 role 表裡沒有「halftone ink」這一角色。user 裁決補 role 而非修訂舊 role 定義。
packages/configs/style/style.css:新增 --color-data-ink(dark -400 / light -600)與 --color-data-ink-strong(dark -200 / light -800),並更新三行 @source 防 prune 清單。light 端 ink-strong 走 -800 讓「更重要」的知覺差在白底上重新存在(借用時代兩者同值)DistributionChart/VolumeProfileChart:網點 class 由借用的 bg-data-point / bg-data-fill-accent 遷移到 bg-data-ink / bg-data-ink-strong;data-point 與 fill-accent 回歸各自原本的 role 定義,既有其他消費者(EventTimeline 圓點、PositioningAxis 記號)不動- 兩份 recharts 對照版顏色 contract 同步回 2026-07-28 focus 裁決(modal / POC → data-fill-focus;此前仍停在全中性版,檔頭「same colour contract」聲明已不實);solid 柱對 halftone ink 的對映關係寫進檔頭
AssetPriceTimelinehover chip 的價格由 font-number 改 Manrope + tabular-nums(T4 tier-2)——遵守 data-viz 字體裁決(該組件在射程內),且 chip 需要較小的字體,Space Mono 在這個字級下太寬- handoff 頁 token 表補 data-ink 兩列;/live/distribution-chart 顏色契約段同步改寫
圖表引擎收斂到 production 在用的那套(recharts)+ N1 / N8 落地、N10 轉規格交付
用 gh CLI 逐檔讀 OpinionLabs/frontend@main 原始碼盤點 production 的圖表現況,據以決定本 repo 的圖表引擎。兩個查證結論推翻了原本的施工假設:① production **全域零 candlestick、零柱狀圖**(search/code?q=candlestick 0 筆)——TopicChart/getOptions.ts 建的是 type:"line" 平滑折線,AdvancedChart 只是包 TopicChart,datafeed.ts 是 TradingView 時代殘留。所以「照 production 做 K 線」這個前提不存在,要 candle 就是新增一種 production 沒有的圖表語言。② **echarts 吃不下 CSS 變數**:預設 canvas renderer,ctx.fillStyle="var(--color-…)" 是無效色字串,而 getOptions.ts 的 lineStyle.color 與 axisLabel.color 餵的正是 var()——線上那兩行八成沒生效,走的是 echarts 預設色盤(全 repo 唯一設 renderer 的是那支已死的 chart.tsx)。recharts 相反:SVG,var() 原生解析,MiniLineChart 就是這樣寫的。裁決(user 2026-07-28):N10 只出 spec 交給 dev,因此 **echarts 不進本 repo 依賴**;其餘全走 recharts 或手刻。
src/components/product/asset-price-timeline/:新增 AssetPriceTimeline(N1)——照 productionMultiTopicChart/MultiLineChart的架構做(ComposedChart 管尺度、記號全走 custom render prop、hover 走外部 store 而非圖表 state),原型 383 行手刻 createElementNS 塌進這個形狀。五個機制全保留:入口歸入口 / hit area 是時間帶不是點 / 導航前先揭露 / 系列控制是分組入口 / 跨組件同時只允許一個面板open-panel-store.ts:module store(非 context)——規則是頁面級的,timeline 放哪都遵守,不需要記得包 provider。形狀比照 production 的createHoverStore/createVisibilityStoreAssetSparkline改寫為 recharts,對齊 productionMiniLineChart(ResponsiveContainer + LineChart + Line + CSS var stroke),虛線基準改 ReferenceLine- ⚠ 實作中撞到並修掉三個真 bug:① recharts draw-on 動畫卡在第 0 幀,path 維持
stroke-dasharray: 0px,<len>永不上色(關 isAnimationActive)② grid line 以格式化後字串當 React key,ETH 兩階都格成 "$1.9K" 造成重複 key 掉線(改用 index)③ Ark Popover 在 mount 時就回報onOpenChange(false),14 顆系列 select 同頁時每顆都把共用 store 清空(改成只收回自己那個 panel) - ⚠ 架構坑記錄:ResponsiveContainer 量到寬度後才畫圖,那次 render 發生在**它自己的 subtree**,父層不會重繪——所以從 dot renderer 收集記號座標時,用 ref + 無依賴 useEffect 同步是**行不通的**(effect 永遠不會再跑)。改由 renderer 自己排 microtask flush,並以「值相同就回傳原物件」收斂避免迴圈
src/components/product/{distribution-chart,volume-profile-chart}/*-recharts.tsx:新增兩個**對照版**(不是可消費組件),並排進各自 preview 頁的「引擎對照」節供裁決。實測結論寫進頁面:窄容器 238px 下 recharts 版七個軸標籤共需約 299px、相鄰 6 對全部重疊(SVG <text> 無 truncation,字數上限擋不住像素);volume profile 的 spot 線契約要求畫在兩階之間,而 ReferenceLine 在 category 軸只吸附 band 中心,只能改手算 overlaysrc/app/live/asset-price-timeline/page.tsx:preview 頁(Interactive Demo + Static State Matrix + Provenance & Gaps)。matrix 的「開啟態」由共用 store 真的驅動,不是靜態仿畫src/components/product/asset-dashboard-page/:N8 水平價格軸落地為**頁內一次性 SVG**(user 裁決:只出現一次、無第二消費者,過不了「≥2 個頁面」的 Product Component 門檻,故不進組件庫也不另開 preview 頁)。全中性配色零 raw hex——level 是 OI 與 IV 曲面推出來的估計,wall 不是好事 floor 不是壞事;spot 是唯一白色記號。標籤避讓走貪婪逐列打包,字寬用字數估算並刻意偏保守(實測 "Zero gamma $65.4K" 估 104px vs 實測 102、"Put wall $64K" 估 79 vs 68)。--color-data-band-edge 與 --color-data-grid 至此才有第一個真消費者docs/asset-forecasting/n10-candle-chart-spec.md:新增——N10 改以規格交付 dev,本 repo 不實作(user 裁決)。含引擎三選項取捨、資料契約、11 層圖層順序、zoom/pan/crosshair 交互規格、逐圖元 token 對照(dark 實值逐條對過 style.css)、驗收清單。特別記錄三條參考線(spot / VWAP / Now)**靠線型與方向分而不是靠顏色**:VWAP 原型值 #8b949e 在 ramp 上剛好等於 --color-data-point,但那是點的 role,拿來畫線是誤用;--color-data-reference 的定義本身已寫明涵蓋 VWAPsrc/content/nav-sections.ts/CLAUDE.md:註冊 AssetPriceTimeline,在兩個柱狀圖的 Constraints 標明 recharts 檔案是對照版不可消費,並更新 AssetDashboardPage 的 N8 / N10 現況
Asset Prediction / BTC Dashboard 兩頁真組件重組 + N5 / N7 圖表落地
把 prototypes/assets-forecasting/ 的兩份手刻 standalone HTML 用真組件重組成 product component(/live/asset-prediction-page、/live/asset-dashboard-page)。原則:production 有的一律用 production 的,不自己發明;production 有但沒 vendor 進本 repo 的,刻意缺席而非重刻。data-viz token 拍板後 N5 機率分佈柱狀圖與 N7 volume profile 柱列一併建出(兩者在原型即為 div bars,非 SVG,故不引入圖表庫——本 repo 雖有 recharts,但它會自帶 SVG chrome 與自己的顏色 prop,得再一層層覆蓋回 token,換來多一層依賴與少一層 token 控制權)。仍為佔位的只剩 N10 K 線圖與其疊層、N8 水平價格軸(後者組件歸屬未裁決,先用 MetricReadout 呈現同一組數值,不猜幾何);N1 asset price timeline 亦未建,trending 卡只畫歷史線並在卡上標明。
src/components/product/distribution-chart/:新增 DistributionChart(N5)——四種柱況 default / modal / thin(斜線=薄書)/ selected(▲,唯一非中性墨水);柱子一律中性,機率分佈無極性src/components/product/volume-profile-chart/:新增 VolumeProfileChart(N7)——spot 線畫在夾住 spot 的兩階之間,禁止插值到某一階(分桶資料不得宣稱桶內精度)src/components/product/asset-prediction-page/:新增 AssetPredictionPage + AssetSparkline + mock-datasrc/components/product/asset-dashboard-page/:新增 AssetDashboardPage + mock-datasrc/app/live/{distribution-chart,volume-profile-chart,asset-prediction-page,asset-dashboard-page}/page.tsx:四個 preview 頁;兩個圖表頁含完整 Interactive Demo + Static State Matrix,兩個整頁含組件對照表與缺口清單packages/configs/style/style.css:⚠ light 端 data-fill / data-fill-strong 由 100/200 改 200/300。原推導照「階數鏡射」,接上真圖表後實測不對稱——柱對卡片底 ΔE dark 10.61、light 僅 5.55(白底上同樣差一階的知覺差距小得多),改後 light 為 16.58 / 21.54。屬推導錯誤修正,非 light mode 手動調校- 修正 DistributionChart 版面 bug:外層原用 items-end,子項高度變 content-based 導致柱子百分比高度解析為 0(改 items-stretch + 子項 justify-end)
- 發現並記錄兩個 @olab Select 既有問題(不在此 workaround):① 預設 trigger 在 Ark 的 <button> 內再渲染 olab Button,巢狀 button 觸發 hydration error,既有 /live/select 同樣有 ② createListCollection 的 itemToString 回傳 value 而非 label,trigger 顯示原始值。本頁兩顆 Select 因設計本為固定標籤(Switch asset / Layers),改以 placeholder 走原生行為規避,未覆寫組件
- 整頁預覽改用真 viewport:AppShell 新增 /standalone/ 逃生口(該前綴下不套 sidebar / navbar / max-w-1280),配 StandalonePageShell 提供 production 量測到的頁面幾何(max-w-[1320px] + px-5 → 內容 1280,見 docs/page-layout-spec/README.md);/live/ 的兩個整頁 preview 改嵌 ViewportFrame——用 iframe 而非縮放 div,因為只有 iframe 會給頁面真正的 viewport,media query 與 min-h-dvh 才會對測試寬度解析(實測 iframe.contentWindow.innerWidth = 1440)。可切 1440/1280/1024/390 與 Fit / 1:1,並可外開 standalone
- 斷點修正:Bitcoin markets 卡格與 consensus/volume-profile 並排由 2xl(1536) 改 xl(1280)——原設定會讓 1440 viewport 只有兩欄,不是設計密度。改後 1440 下四欄各 308px(4×308 + 3×16 = 1280),與線上卡片寬度吻合
src/content/nav-sections.ts/CLAUDE.md:註冊四個新 entry 與 Allowed Product Components 規格
Data Visualization 中性語意層(--color-data-*)+ Asset Prediction handoff 首批四組件
Asset Prediction 列表頁 + BTC Dashboard 兩頁對 production(OpinionLabs/frontend@main)做全量組件與 token 盤點後,補上 Live DS 缺的一整層:圖表裡「中性、無極性、只表達資料本身」的填色與線條。既有 Tier 2 只覆蓋 UI 表面(bg/border/text/card/header-bg)與極性色(brand/outcome/team/trend/warning|success|error),中性資料填色兩邊都不屬於,所以原型只能寫 raw neutral 階與 α overlay 並自行以 --gap- 前綴標記。機器盤點出 7 個 --gap- token + 3 個連 primitive 都沒有的寫死 hex,收斂成 7 個 --color-data-* role(fill / fill-strong / fill-accent / grid / reference / band-edge / point),全部指向既有 neutral ramp 具名階,零新 Tier 1、零新色階。α → solid 的處理原則沿用 2026-07-18 Proposal B(α 合成值隨底色漂移,同一個「資料填色」在 page/card/raised 三種底上會得到三個顏色)。附帶補 --color-footer-bg(production 有 header-bg 無對稱 footer 版)。4 項原型值在 ramp 上沒有對應,2026-07-28 user 裁決「一律取目前 ramp 上最接近的階」(不新增插值階、不為系統簡潔併角色):#2b333b 格線→neutral-700 ΔE 4.19、#d7dde3 modal 標記→neutral-200 ΔE 5.38、#6e7681 軸標籤→neutral-500 ΔE 7.32、α.28 合成 #535559 區間帶邊界→neutral-600 ΔE 8.72。後者取最近階後與 data-reference 不同值,因此從初稿的「併進 data-reference」拆成獨立 role data-band-edge(family 6→7)——這個拆分反而更對:兩者都是虛線但一個標「某個位置」一個標「某塊區域的邊」,同圖會並存,差一階讓讀者分得開。初稿備案「新增 neutral-650 插值階」已否決。footer-bg 的 light 值仍為推導非實測。同輪交付兩頁盤點裡「不卡任何裁決」的四個組件(N6/N9/N11/N12);N1/N5/N7/N8/N10 全部卡在本 token 組拍板,未動工。
- ⚠ 2026-07-28 二次修正(user:「所有線上有的組件都直接用線上的,不要自己發明」):四個組件全面改接 @olab 真組件——AnalysisCard 外殼改 Card(連 p-5 一併照收,className 蓋不掉:cn() 純串接同權重)、標題改 H3(production Typography 元素與字級綁死,16px 只能是 h3)、版面改 Stack/Inline、文字改 SmallText/MediumText;HowToRead 的 pill trigger 改 Button skin=outline size=sm 經 Tooltip asChild(白邊 + 14px 是 outline skin 原樣,不覆寫);preview 的時間切換從手刻 pill 改 Button skin=secondary size=sm active(= production DurationSwitch 模式);how-to-read 的 Static Matrix 移除手繪 pill 假圖改真組件(Button 完整 state 矩陣已在 /live/button,不重畫)。仍手寫的三處皆為 production 無對應:timeline 中性圓點、KV tile 外框與 auto-fit grid(Layout 只有 flex 無 grid)、24px 數值(Typography 最大 16px 且此處是數字非標題)。副作用:改接 @olab 後三個組件與兩個 preview 頁需加 "use client"(dist 用 createContext)。已知現況:真 Button 的 selected 底色渲染成舊 α 疊色 bg-brand-2 而非 Proposal B 的 brand-tint-2,因 dist 過期,Ethan 側 rebuild 後自動對上。
packages/configs/style/style.css:@theme(dark)與 light block 各新增 6 個 --color-data-* role + --color-footer-bg;@source inline 安全清單補 7 個 role × bg-/text-/border-/fill-/stroke- 五種變體(這組有一部分走 SVG inline var(),Tailwind 靜態掃描看不到,無清單會整組被 prune)src/components/product/event-timeline/:新增 EventTimeline(N9)——三行事件條,中性圓點 + 中性連接線。刻意不沿用 production VerticalTimeline:那個是兩行 + brand 圓點 + brand 連接線的結算步驟軸,brand 色帶進度語意,事件流沒有進度src/components/product/metric-readout/:新增 MetricReadout(N6)——圖表旁的 label/value tile 列,極性 opt-in(只有 delta 給 trend,區間/價格/次數一律中性)src/components/product/how-to-read/:新增 HowToRead(N11)——icon / pill 兩種 trigger,泡泡直接用 @olab Tooltip 未改(原型手刻 ::after 是 standalone HTML 沒組件庫的權宜,不是要沿用的設計決策)src/components/product/analysis-card/:新增 AnalysisCard(N12)——分析模組共用外殼(標題 / howTo / controls / headline / 分隔線 / 卡身)src/app/live/{event-timeline,metric-readout,how-to-read,analysis-card}/page.tsx:四個 preview 頁,皆含 Interactive Demo + Static State Matrix + Provenance & Gaps(逐頁列出字級與 radius 的收斂偏離)src/content/nav-sections.ts:Product Components 群組註冊四個新頁prototypes/assets-forecasting/asset-prediction-handoff-component-map.md:新增——兩頁全量組件/token 對照(機器驗證:token 存在性、寫死 hex 反查、組件存在性逐檔讀 production repo)prototypes/assets-forecasting/asset-prediction-token-proposal-dataviz.md:新增——本 token 組提案書與 4 項待拍板值
opinion-build plugin:確認稿 → 真組件翻譯 skill(產品線 B)
把「確認稿查表翻譯真組件」流程(團隊 skills 藍圖產品線 B)打包成可分享的 Claude Code plugin(opinion-build-plugin/,與既有 opinion-prototype / opinion-ds-extraction 同打包規格)。方法 = 查表翻譯而非重新設計:頁面骨架查 layout-lookup.md(production 7 頁 runtime 量測蒸餾 + 2026-07-22 user C1–C8 逐條裁決:版型 5 家族幾何、間距三層語意 格16/列32/區塊40、section 分隔三手法、H1=text-h1+font-bold、gap30→32、shell 分界不進 skill),逐顆組件查 button.md / textlink.md(skin/variant/state 精確 hex 矩陣);查表外的一切標 gap 不即興翻譯。docs/component-lookup/ 兩份查表 git mv 進 plugin references/(PR #78 預告的最終歸屬),原位留搬家指標;骨架 evidence 層 docs/page-layout-spec/ 留在 repo(PR #80),plugin 只帶蒸餾後查表。
opinion-build-plugin/:新增(.claude-plugin/{plugin,marketplace}.json + skills/opinion-build/SKILL.md 五步工作流 + README 安裝說明)opinion-build-plugin/skills/opinion-build/references/layout-lookup.md:新增——頁面骨架查表(全站常數 / 版型家族 / 間距語意 / 分隔手法 / 標題規則 / 寫法契約)docs/component-lookup/{button,textlink}.md→opinion-build-plugin/skills/opinion-build/references/:git mv 搬家,原資料夾留 README 指標- 覆蓋現況:Layout + Button + TextLink 已蒸餾;其餘組件查表逐批擴大,加進 references/ 即生效
Proposal B:Button α overlay 全數改指 mode-aware solid ramp 階(零透明度)
user 拍板 Proposal B:Button 用到的 α overlay token(brand-1~4 = orange α10~40%、white-16 = 白/灰 α16%)不再用透明度合成,改為 mode-aware Tier 2 semantic token 分別指向既有 solid ramp 階(與 outcome-yes-tint 的 dark/light 雙 block 結構同構)。對應:brand-tint-1~4 → dark brand-950/900/800/700、light brand-50/100/200/300;outline-pressed / disabled-bg → dark neutral-700、light neutral-100。light 端 ramp 本為白底 tint 生成、與 α 合成值幾乎等值;dark 端刻意拉開階距(950→900→800→700)而非取最近合成值——α0.1~0.3 的最近階全擠在 950 附近會讓 state 進程分不開,代價是比 α 版更暖更飽和(user 看過視覺對照後拍板接受);white-16 dark 合成 #363940 ≈ neutral-700 #36393e 為精準命中(該階當初即為 disabled/overlay→solid 工作預留,見 style.css @source 註解)。Button src 遷移後零 α:secondary/outline/sportNo 的 selected 底 bg-brand-2→bg-brand-tint-2、outline pressed 與 disabled 的 white-16→outline-pressed/disabled-bg。範圍僅 Button:其他元件(featured-icon、avatar、Input、market-rules、market-hero-carousel 等)的 brand-1~4/white-16 消費者尚未遷移,flat α token 仍留在 style.css 供其消費,之後逐元件遷移、grep 零消費者後再退休。dist 未 rebuild(本 worktree 缺 vite),/live 真按鈕仍渲染舊 α,dark 端新舊等值故視覺無差。拍板依據的視覺對照(雙 canvas α vs solid 並排)保留於 /live/button 頁 Proposal B 區塊。
packages/configs/style/style.css:light/dark 兩 block 各新增 QUICK DRAFT · Proposal B 區(brand-tint-1~4、outline-pressed、disabled-bg,共 6 個 mode-aware Tier 2 token,全指既有 ramp 具名階,零新 Tier 1);@source inline 安全清單補 bg-brand-tint-{1,2,3,4} 與 bg-{outline-pressed,disabled-bg}packages/components/src/components/Button/index.tsx:secondary/outline/sportNo 的 data-[active=true]:bg-brand-2 → bg-brand-tint-2;outline active:bg-white-16 → bg-outline-pressed;disabled bg-white-16! → bg-disabled-bg!;Button class 自此零 α overlaysrc/app/live/button/page.tsx:SKIN_TOKENS/TIER1/redesign 列/demo/比較按鈕全數改用新 token;secondary selected 因 dist 過期改 sim 呈現;Proposal B 區塊改標已拍板對照紀錄;New Tier 2 Tokens 表新增 6 列- 未動:其他元件的 brand-1~4/white-16 消費者(獨立遷移長尾);flat α token 本體(仍有消費者,禁止改指);dist(無法在本 worktree rebuild,歸 mirror sync audit)
SportsGameCard(production transplant)取代手刻 MatchCard
從 OpinionLabs/frontend(Ethan production repo)把 /sports 頁賽事卡渲染層原樣 transplant 進來,成為 product component SportsGameCard(code export 名維持 production 原名 GameCard;純視覺用途,不接 store / orderbook / API)。依 user 裁決(D2)淘汰同語意的手刻 MatchCard——兩者不並存,探索層手刻版讓位給 production 真組件。新組件有獨立 preview 頁 /sports-game-card(Overview / Interactive Demo / Card Variant Matrix / Button State Matrix / Edge Cases),PAGES > Sports(/sports)改為它的整頁情境消費者(production 桌面結構 + mock data 的 color token 測試頁;原本地 mock 版 Sports 頁同步封存)。
src/components/product/sports-game-card/:新增(GameCard + BaseView / InteractiveView / SportsLineMarketOutcomeButton / PointReward / Star / 型別與 formatter,各檔頭註記出處與化簡範圍)src/app/sports-game-card/page.tsx:新增 preview 頁(含 variant 四維度矩陣與按鈕 state 矩陣)src/components/product/match-card/、src/app/match-card/:封存至_archived-claude-ds-components/product/、pages/(不刪除)src/app/sports/:改為 production transplant 頁(前身 mock 版封存至_archived-claude-ds-components/pages/sports/)CLAUDE.mdAllowed Product Components List:移除 MatchCard、登記 SportsGameCard;nav-sections.tsProduct Components 群組同步替換scripts/verify-preview-coverage.mjs:sports-game-card 的 COMPOSITION_ONLY 豁免移除(已有獨立 preview 頁)
CLAUDE.md 瘦身:抽出 UUI 上游參考、修正過期敘述(文檔稽核第 3 批)
untitledui-nextjs-starter-kit/CLAUDE.md 從 1372 行瘦身到約 900 行,聚焦 Opinion 治理規則。兩類改動:① 通用 Untitled UI 參考內容(8 個元件的 props/範例表 + text/border/fg/bg 四張 semantic token 用途表,約 470 行)原封搬到 docs/uui-component-reference.md,CLAUDE.md 留指標——這些是查表用的 library 說明,不是治理規則,卻每個 session 都佔 context。② 修正稽核發現的過期敘述(逐項對照現行程式碼驗證):Outcome/Team 雙色系統段還在教 bg-green-1/bg-blue/sport-team-a 等 legacy class,但 outcome-action.tsx 已於 07-05 遷移到 Tier 2 token(bg-outcome-yes-solid/bg-team-home-solid 等),整段照現況重寫並宣告根目錄 CLAUDE.md 為語意架構唯一權威;「Trend Colors 不存在」整段過期——trend-up/trend-down 已於 07-04/05 落地(Tier 1 = grass/brick)且 className 遷移已完成,改寫為已落地狀態;「--color-brand-500 = theme.css 失效殘留」例子過期——brand 完整 ramp 已於 07-05 落地 style.css,該例移除;三處「⚠️ 修正」段落收斂成現況敘述(舊錯誤版本的完整轉述交給 git 歷史);Tokens Still TODO 的過期清單(如宣稱 neutral 未自訂)改為「以 style.css 現有定義為準」;封存路徑補「repo 根目錄的」限定;Interaction State 表補 focus-visible pilot 已撤除的註記。所有治理規則段(Work Phases、Product Components、Pattern Mapping、Migration Workflow、Interaction/Layout/Preview 規則)逐字保留未動。
untitledui-nextjs-starter-kit/CLAUDE.md:1372 → 約 900 行(見 summary)untitledui-nextjs-starter-kit/docs/uui-component-reference.md:新增,承接原 CLAUDE.md 的 Most Used Components Reference + COLORS 四表(內容原封,附搬移說明)- Outcome/Team 段重寫依據:
src/components/base/outcome-action/outcome-action.tsx現行 class(Tier 2 token)+packages/configs/style/style.csslegacy alias 鏈實測 - 未動:根目錄 CLAUDE.md(語意架構權威);所有治理規則章節逐字保留
新增 Color Token Alignment 頁,移除兩個空白的 migration 舊頁
為與 Ethan 對齊 color token 兩層架構,新增 Migration Plan 群組下的 Color Token Alignment 頁(dated 快照文件,2026-07-08):25 個 Tier 2 role 的舊名→Tier 1→Tier 2 完整對照表、9 個 Tier 1 家族全色階(含 in production 實際引用階標註)、兩處可感知色值變更(gold light 借用階 dE 4.1、text-secondary light 併入 neutral-900)、app.opinion.trade/trending runtime 實測對照(無漂移;tint 系 10 個微調與 8 個 anchor stub 同名不同值為兩個新發現)、六階段實作建議(Claude 提案)。同時移除「Color Alignment」「Color Migration Plan」兩頁——兩者 runtime 讀取已被移除的 CSS 變數、長期渲染空白,color-target 頁 2026-07-05 起已註明由手動維護版頂上,本次正式除役。
src/app/live/foundations/color-token-alignment/page.tsx:新增(靜態快照文件,非 runtime 鏡射;頁首已註明定位與日期)src/app/live/foundations/color-alignment/page.tsx、color-migration/page.tsx:刪除(讀取已移除變數的空白頁,直接刪除非封存——頁面本身已無可渲染內容,歷史可由 git 取回)src/content/nav-sections.ts:Migration Plan 群組改為 Color Token Alignment + State Pilot Reportsrc/app/live/foundations/color-target/page.tsx:底部姊妹頁說明段改指向新頁
Button skin 名稱對齊底層 token family 命名(改名不改色)
user 發現 Button 的 skin prop 值(yes/no/sportTeamA/sportTeamB/sportDraw)跟它們實際指向的 Tier 2 token family 名稱(outcome-yes/outcome-no/team-home/team-away/team-draw)不一致,要求 audit 一次哪些需要改。查證:brand/secondary 本來就跟 token family 同名,outline 沒有對應 family 不適用;其餘 5 個 skin 全部改名對齊(yes→outcome-yes、no→outcome-no、sportTeamA→team-home、sportTeamB→team-away、sportDraw→team-draw)。改名前先確認全 repo 沒有任何 src/components/product、base、application 底下的真組件用 <Button skin="sportTeamA"> 這種寫法消費——它們都各自手刻 class 直接指 token,不經過 Button 的 skin prop,故改名影響範圍僅限 Button 自身與其 demo/preview,不影響其他組件。純改名,class 值/token 對應完全不變。
packages/components/src/components/Button/index.tsx:Skintype、skinClasses、loadingClasses的 5 個 key 改名(值不變)packages/components/dist/index.d.ts、dist/index.js、dist/index.umd.cjs:同步改名(tracked build 產物,Next app 實際解析的版本)Button.stories.tsx:AllSkins story 的skin=值改名src/app/live/button/page.tsx:SKINS、SKIN_TOKENS的skin:值、Interactive Demo/Loading Toggle 區塊的<Button skin="yes"/"no">改名- 未動:
skinClasses/loadingClasses裡的sportNo(不在Skintype 內、外部叫不到的死碼殘留,跟本次無關,屬另一個獨立清理項);src/app/pressed-proposal/page.tsx(暫時文件,只是把字串當手寫標籤,未消費 Button 的 Skin type)
移除未驗證的 focus-visible pilot(Button/Tabs)+ 全 repo 手刻 focus-visible 樣式一併撤除
user 質疑 Button preview 矩陣裡的橘色 focus-visible outline 圈「線上是沒有用的吧」——查證後確認:這是 2026-06-01 commit 0c2424c("interaction state pilot")加的**未驗證提案**,commit message 自己就寫「Phase A.5 受控 pilot、未定版 governance、待 Ethan 對齊」,從未對照線上實際樣式;而且 Live DS token 來源 style.css 本身有全域 :focus-visible { outline: none; } reset,代表線上原本就沒有這個 focus ring。user 決定不保留,且範圍擴大到全 repo:連探索層(base/application)與 product 組件裡標準 Tailwind focus-visible: 寫法(範本本來就有的鍵盤可及性樣式,非本次 pilot 加的)也一併撤除。淨效果:目前整個 design system 沒有任何組件有 focus-visible 樣式,鍵盤 focus 落回全域 reset(= 無視覺提示)——這是一次 user 主導的可及性回退決策,非缺陷,記錄於此供追溯。
packages/components/src/components/Button/index.tsx、Tabs/index.tsx:移除[&:focus-visible]:[outline:2px_solid_var(--color-brand)]PILOT 繞過寫法packages/components/dist/index.js、dist/index.umd.cjs:同步移除(tracked build 產物,Next app 實際解析的版本)src/components/base/:avatar.tsx(連同已無消費者的focusableprop一併移除)、buttons/button.tsx、buttons/button-utility.tsx、outcome-action/outcome-action.tsxsrc/components/application/:pagination/pagination.tsx、app-navigation/sidebar-navigation/sidebar-nav-tree.tsxsrc/components/product/:live-games-module、market-cds-row、market-list-item、market-rules、outcome-selector、promo-banner、watchlist-button、match-card、order-book、market-header、trade-panel(共 11 個檔案)src/app/:_shared.tsx、workflow/page.tsx、workflow/decisions/page.tsx、workflow/entities/page.tsx、everything-cds/page.tsx、market/[slug]/page.tsx、market-cd/[slug]/page.tsx、live/button/page.tsx(矩陣移除 focus-visible 排)- 連帶移除只服務 focus ring 的孤兒 class(
outline-brand/outline-brand-main) docs/live-ds-spec/button.md、tabs.md、_design-rules.md:標注 F-A 移除,反映現況;不改寫歷史分析段落- 未動:
packages/configs/style/style.css的全域:focus-visible { outline: none; }reset(本次移除的對象,非移除範圍);_archived-claude-ds-components/已封存內容;live-ds-state-architecture.html等 pilot 歷史文件(保留原始決策脈絡)
Button 移除未使用的 lightBrand / lightBrand2 skin
全 repo 排查(packages/components/src 其他真組件、src/components/product 產品組件、src/app 下所有頁面)確認 lightBrand/lightBrand2 這兩個 skin 從未被任何真實場景消費,只存在於 Button 自身的 skin 定義、Storybook demo 與 /live/button preview 頁——經 user 確認後直接刪除,非封存(屬單一元件內未使用變體的小範圍清理,非系統級大宗清理,不落入 feedback_archive_dont_delete 的 bulk-archival 情境)。
Button/index.tsx(src):Skintype、skinClasses、loadingClasses移除lightBrand/lightBrand2兩個 keypackages/components/dist/index.d.ts、dist/index.js、dist/index.umd.cjs:同步移除(此 dist 為 Next app 實際解析的 tracked build 產物,只改 src 不會反映在/live/*頁面)Button.stories.tsx:AllSkins story 移除skin="lightBrand"範例src/app/live/button/page.tsx:SKINS、SKIN_TOKENS移除對應兩筆
team-home light mode 文字色 WCAG 不合格修復(新增 team-home-950 token,merge 自 PR #58)
此 entry 記錄的是另一個並行 session 已 merge 進 main 的修復(PR #58),本分支開 PR 時撞見 merge conflict 才發現,回收合併進來,並把 sportTeamA 改成本分支同時進行的新名 team-home。原始問題:Button.tsx 的 sportTeamA skin 沿用跟其他 outcome/team skin 共用的 text-black,但 --color-black/--color-white 在 light mode block 互換(inversion bridge),導致 text-black 在 light mode 實際渲染成白字,配亮黃色 team-home 背景對比僅 1.94,WCAG 完全不合格(連 hover 階段就已經壞掉,pressed 候選再深也救不回來,最多到 3.20)。修法:新增 Tier 2 semantic token --color-team-home-950(mode-independent 單一宣告,指向 --color-gold-950,因為 gold 這個 Tier 1 primitive 本身不分 light/dark),取代 team-home 的 hover/active/data-active 文字色。命名對齊 CLAUDE.md Team Identity 表格第四欄「text-on-solid = team-home-950」的既有契約——這個 token 名字之前只存在於文件描述,從未真的在 style.css 定義過。WCAG 對比實測:light solid 7.37、light pressed 4.48、dark solid 9.74、dark pressed 7.37,四種背景組合全數合格。outcome-yes 的 light mode 也有類似(但未在 CLAUDE.md 明文立約的)文字對比問題(2.59),本次不在範圍內,留待後續獨立確認是否要類推同一套修法。
packages/configs/style/style.css:新增--color-team-home-950: var(--color-gold-950),light/dark 各一行(值相同,mode-independent)(來自 PR #58,merge 時自動整合)Button/index.tsx:team-home skin(PR #58 原用sportTeamA舊名)的hover:text-black/active:text-black/data-[active=true]:text-black全數換成text-team-home-950src/app/live/button/page.tsx:SKIN_TOKENS的team-homeentry 的 hover/pressedNative/selectedProp text 同步換成text-team-home-950- 已知但未處理(獨立追蹤):outcome-yes 在 light mode 同樣因 text-black 反轉問題對比不合格(2.59),CLAUDE.md 未明文立約 text-on-outcome-yes-solid,非本次範圍
Outcome/Team pressed 色階補齊:Button 6 skin + Tabs 4 variant 缺口修復
接續 Button/Tabs interaction state pilot(commit 0c2424c,只覆蓋 brand/secondary/default variant)的缺口盤點,補齊 outcome/team 五個 family(jungle/cherry/gold/sea/ash)的 pressed 色階。新增 5 組 Tier 2 semantic token(outcome-yes/no-pressed、team-home/away/draw-pressed),值為各 family solid 錨點在 Tier 1 scale 上深一階,套用既有「pressed 恆比 hover 再深一階」規則、不因對比好壞跳選更深候選以維持全站深淺邏輯一致。全部候選值以 WCAG 對比公式實測(vs 實際渲染文字色,非字面 token 名——light mode text-black 因 --color-black/white inversion bridge 實際渲染成白字):outcome-yes/no、team-away 五組皆過或接近 AA(4.5);team-draw 的 dark 值(ash-500,對比 4.17)低於 AA 但高於 WCAG large/bold text 的 3:1 門檻,經確認後拍板接受。同時發現一個獨立既有 bug:team-home 的 light mode 因 text-black 反轉為白字,配亮黃背景對比僅 1.94(既有 hover 階段就不合格,非本次新增),需另案處理 text-on-team-home 語意,未混入本次改動範圍。Button 的 outline/lightBrand/lightBrand2/sportNo 與 Tabs 的 underline/button/pill/newPill 五個 skin/variant 沿用既有 brand-pressed/secondary-pressed/neutral 階,未新增 token。以 Storybook(Vite 直接編譯 src,非吃過期 dist 的 Next app)驗證:All Skins / All Variants story 逐一確認 class 正確編譯、5 個新 token 於 light/dark 兩模式解析出的 hex 值與規劃一致。完整分析過程與候選色比對記錄於 repo 根目錄 live-ds-state-architecture.html §7。
packages/configs/style/style.css:新增--color-outcome-yes-pressed/--color-outcome-no-pressed/--color-team-home-pressed/--color-team-away-pressed/--color-team-draw-pressed共 5 組 × light/dark = 10 行,值取自各 family Tier 1 scale 深一階錨點Button/index.tsx:yes/no/sportTeamA/sportTeamB/sportDraw五個 skin 新增active:bg-{family}-pressed active:text-black;outline/lightBrand/lightBrand2/sportNo補上沿用既有 token 的active:classTabs/index.tsx:togglevariant 新增first:data-[selected]:active:bg-outcome-yes-pressed;underline/button/pill/newPill補上沿用既有 token 的active:class- 已知但未處理(獨立追蹤):team-home light mode 文字色因
text-blackinversion bridge 對比不合格(1.94),需改接team-home-950專屬深色文字,非本次 pressed 色階範圍 - 驗證:Storybook(
packages/components,port 6006)Button All Skins / Tabs All Variants story,逐一 grep className 確認active:全數編譯、5 個新 token 於 light/darkdata-opinion-theme切換下解析出的 hex 與規劃值一致(無 console error 為本次改動新增)
Outcome/Team 市場色 Layer 3 className 收尾遷移 + featured-icon 兩個失效殘留 token 修正
接續 PR #40(Button/Tabs 已遷移)的缺口盤點,全 repo 重新核對後發現 outcome-action.tsx(缺口最大,五種狀態全部裸色)、outcome-selector.tsx(篩選按鈕)、market-cds-hero-card.tsx、market-cds-row.tsx、order-book.tsx、market-header.tsx、live-games-module.tsx 共 7 處組件仍直接消費 Tier 1 裸色名(green-1/blue/sport-team-a/b/draw),未走 Tier 2 語意 token(outcome-yes/no-solid/tint、team-home/away/draw-solid/bg)。逐處判斷語意(排除 avatar-online-indicator/verified-tick/badges 三處非市場色的巧合綠色使用,維持不動)後全部改名,改動前後用瀏覽器 getComputedStyle 逐一核對顏色值 byte-identical。outcome-selector.tsx 的篩選按鈕用到 ring- 這個 utility 變體之前全 repo零消費者,會被 Tailwind v4 靜默 prune,於 style.css 補一行 @source inline("ring-{outcome-yes,outcome-no}-solid") 防呆。順帶查證:原始任務指示引用的舊 system-log 對照表(bg-primary→bg-page-bg 等 text/surface/border 系映射)其實已經過時——bg-primary/text-primary/text-secondary/text-tertiary/text-quaternary/border-primary/border-secondary 現在本身就是 style.css 裡完整定義、有 dark/light 兩態且進安全清單的現役 Tier 2 token,不是待遷移的舊名;真正需要處理的只剩 featured-icon.tsx 兩處指向 style.css 完全沒有定義過的失效殘留字(shadow-xs-skeuomorphic、text-fg-brand-primary,後者疑為已封存 Claude DS 探索層留下的殘骸),一併修正為 shadow-sm、text-brand-secondary。
outcome-action.tsx:unselectedBase/unselectedHover/unselectedPressed/selectedBase/resolved 共 5 組狀態,green-1/blue/sport-team-a/b/draw 全數換成 outcome-yes/no-solid/tint、team-home/away/draw-solid/bgoutcome-selector.tsx:篩選按鈕 selectedYes/selectedNo 的 text-/ring-/hover:bg-/pressed:bg- 全數換名;ring-outcome-yes/no-solid因是新消費者,style.css 新增@source inline防 prunemarket-cds-hero-card.tsx/market-cds-row.tsx:Payout/APY 標籤與數字顏色 text-green-1/text-blue → text-outcome-yes/no-solidorder-book.tsx:買賣盤 tint 底色 bg-blue-06/bg-green-1-06 → bg-outcome-no/yes-tint(含同段落註解同步更新)market-header.tsx:headline outcome 標籤與 eyebrow outcome tag 兩處 text-green-1/text-blue → text-outcome-yes/no-solidlive-games-module.tsx:隊伍名稱 hover/focus-within 顏色 text-sport-team-a/b → text-team-home/away-solidfeatured-icon.tsx:dark/modern 主題shadow-xs-skeuomorphic(style.css 未定義,全無效果)→shadow-sm;modern/outline 主題 brand 色text-fg-brand-primary(同樣未定義,等同無色)→text-brand-secondary(outline 主題的 border 部分因無對應 border token 暫維持原本無視覺效果的狀態,非本次範圍)- 確認不動:avatar-online-indicator.tsx(在線狀態語意)、verified-tick.tsx(認證勾勾暫代藍色,有註解說明)、featured-icon.tsx/badges.tsx 的 success 變體(系統成功狀態,非市場 YES)三處 green-1 使用皆與市場色語意無關
- 驗證:Next app(worktree 專用 port 3099)getComputedStyle 逐一核對全部改動點,改動前後顏色值 byte-identical;瀏覽器截圖確認 MarketCdsHeroCard/OrderBook/OutcomeSelector/LiveGamesModule 各頁視覺無變化
trend/error 家族 Layer 3 className 遷移完成:舊 flat 名 green-2/red 全數退休刪除,red-live 收編進 brick 家族
接續同日稍早的 trend 家族 token 補齊工作,執行 Layer 3 className 遷移。分批進行:① order-book.tsx/market-header.tsx 漲跌價格顏色從 text-green-2/text-red 換成 text-trend-up/text-trend-down;② trade-panel.tsx 的 tone 驅動損益顏色(realizedPnl >= 0 ? up : down)同樣換成 text-trend-up/text-trend-down,但同檔案「To Win」固定綠色(非 tone 驅動,語意是金額而非漲跌)經 user 判斷改用 text-success-solid;③ user 指出 error 語意(badges/featured-icon/input 驗證錯誤/avatar-count 通知紅點)跟 trend-down 一樣都是用 brick,一併換成 bg-error-solid/text-error-solid,範圍也擴及 /system-log、/workflow/* 這幾個內部治理頁面;④ user 追加要求把 --color-red-live 這個遊離裸 hex(#eb1d14,直播警示紅)收編進 brick 家族——因為它跟 brick-500 的 HSL 明暗度幾乎相同(都是 0.5,差別在飽和度),套用 jungle-550/cherry-550/sea-565 那套「明暗插值」命名法算出來的位置會落在 500 本身、不成立,改用不跟明暗序列掛勾的 brick-live,宣告順序放最前面。全部消費處遷移完成後,green-2(含 -1/-2/-4/-8 rgba tint)與 red(含 -1/-2/-5 rgba tint)確認零消費者,alias 本身刪除;color-target 頁三處手寫的「alias 保留中」敘述文字同步更新,避免又一次敘述文字與實際狀態脫鉤。
order-book.tsx:買賣盤價格顏色text-red/text-green-2→text-trend-down/text-trend-upmarket-header.tsx:兩處 trendUp/trendDown 條件渲染同步換名trade-panel.tsx:CDS mode 的 row.tone/hero.tone up/down(損益試算)換成 text-trend-up/text-trend-down;「To Win」固定金額换成 text-success-solid(非漲跌語意,經 user 判斷)badges.tsx/featured-icon.tsx/label.tsx/hint-text.tsx/avatar-count.tsx:error 語意全數換成 bg-error-solid/text-error-solid/ring-error-solid/system-log、/workflow、/workflow/entities、/workflow/authority四個內部治理頁面:同樣的 bg-red/text-red/border-red 換成 error-solid 版本packages/configs/style/style.css:新增--color-brick-live: #eb1d14(brick ramp 宣告區塊最前面,跟明暗序列脫鉤的獨立步階)+@source inline("bg-brick-live")防 prune;--color-red-live改指向var(--color-brick-live),值不變packages/configs/style/style.css:刪除--color-green-2/-1/-2/-4/-8、--color-red-1/-2/-5共 8 個變數(零消費者確認後);--color-red本體保留(error 語意直接消費者已全部換成 error-solid 後,同樣確認零消費者,一併刪除)/live/foundations/color-target:Tier 2 success-solid/error-solid 兩列 note、grass/brick 兩個 SINGLE_RAMP_FAMILIES note、頁尾遷移狀態說明,三處全部同步更新為「alias 已刪除」,不再顯示已過時的「order-book/market-header 仍直接消費」敘述- 驗證:Next app(worktree 專用 port 3099)getComputedStyle 逐一核對 order-book/market-header/trade-panel/everything-cds(Badge error 變體)/workflow/authority/system-log 各頁,text-trend-up/text-trend-down/text-error-solid/text-red-live/bg-brick-live 全部與改動前 byte-identical;刪除 alias 後重新整理各頁確認無 console error
trend 家族補齊三層:Tier 2 trend-up/trend-down 落地、Tier 1 去語意化命名 grass/brick、grass/brick/brand 三條完整 ramp 收進 style.css(方案 A),color-target 頁廢除寫死假設稿
接續 warning/success/error 三個系統狀態 token 的工作,user 指出 trend-up/trend-down 也還卡在最原始的 green-2/red 裸色名階段——Tier 2 該有的獨立命名沒有、Tier 1 去語意化也沒做。分三步完成:① 新增 Tier 2 role token --color-trend-up/--color-trend-down(漲/跌語意),並把 success-solid/error-solid 改指向它們收斂重複真相(原本兩組 token 各自宣告同一份色);② Tier 1 primitive 命名由 user 拍板:trend-up 底下叫 grass、trend-down 底下叫 brick(延續 jungle/cherry/gold/sea/ash 的單字自然物命名風格);③ user 檢視 color-target 頁時發現「其他 Primitive scales」區的 brand/trend-up/trend-down 三張完整 50-950 色階卡是寫死在頁面裡的生成值假設稿——style.css 裡根本不存在這些變數,違反該頁「檢驗 style.css 的驗收儀表板」定位。user 拍板方案 A:把三條生成 ramp 真實落地 style.css(grass/brick 新家族 + brand 補齊),頁面全部改 runtime 直讀,本頁不再包含任何寫死假設稿。錨點全部維持 Live 在用真實值(grass-500=#00d40e、brick-500=#d73028、brand-600=#ed6432 指回 --color-orange),其餘階沿用當初的 hue-lock + saturation-taper 生成值、非 live 逐階驗證。過程教訓:改 token 時漏了同步 color-target 頁的手寫敘述文字兩次被 user 抓到——該頁色票是 runtime 讀值但旁邊的指向鏈文字是手抄的,屬結構性風險。
packages/configs/style/style.css:新增 Tier 2 role token--color-trend-up/--color-trend-down(dark/light 兩區塊各自宣告,指向 grass-500/brick-500);--color-success-solid/--color-error-solid改指向 trend-up/trend-down(原本直接指 green-2/red,同一份色兩條平行宣告),值完全不變packages/configs/style/style.css(@theme):新增--color-grass-{50..950}、--color-brick-{50..950}兩條完整 ramp(錨點 500 = Live 在用值,其餘為生成值);新增--color-brand-{50..950}(600 = var(--color-orange) 指回 Live 主橘單一真相;500/700 = interaction state pilot 已驗證的 hover/pressed 值);新增三條@source inline防 prune 安全清單- 舊 flat 名
--color-green-2/--color-red保留、改指向 grass-500/brick-500 成為 alias(order-book.tsx/market-header.tsx 仍直接消費 text-green-2/text-red,不能刪;className 遷移是後續獨立工作);PILOT 區的單值--color-brand-500併入 ramp 本體、--color-brand-pressed改指 var(--color-brand-700),值皆不變 /live/foundations/color-target:刪除 FAMILIES 寫死陣列與 FamilyBlock/Swatch 元件(brand/trend-up/trend-down 三張生成色階假設稿),grass/brick/brand 併入 SINGLE_RAMP_FAMILIES runtime 直讀(現在 9 個家族全部 runtime 讀變數);頁首落地狀態、Tier 2 success/error 列的 t1 欄與 note、頁尾說明全部同步更新- Storybook
Foundations/Token Library:同步刪除同一份 FAMILIES 快照複製品,grass/brick/brand 併入 SINGLE_RAMP_FAMILIES;Tier 2 story 的 trend-up-500 列(原標 ⚠ 未接線)改為 trend-up/trend-down 兩列 wired,success/error 列 t1 更新 - 驗證:Next app(port 3001 worktree 專用)getComputedStyle 逐一核對 grass/brick/brand 各階與 trend-up/trend-down/success-solid/error-solid/green-2/red/brand-500/brand-pressed 解析值——全部與改動前 byte-identical(純架構收斂,零視覺變化);Storybook(Vite 直編 src)Tier1_Primitives story 實測 9 家族齊全、grass-500/brick-500/brand-600 色票 runtime 解析正確
- 追加(同日):Tier 2 表格的 D/L hex 欄也從手抄快照改為 runtime 算值——新增 collectThemeVarMaps(CSSOM 掃 dark=:root/:host 與 light=:root[data-opinion-theme="light"] 兩區塊)+ resolveVarChain(遞迴解析 var 鏈),兩處(color-target 頁 22 列、Storybook Tier2_Semantic story)同套做法;刪除全部手寫 d/l(hex/lhex)欄位。附帶效益:bg-brand-solid-hover 原本手抄「α(237,100,50,.8)」現在顯示真實解析值;實測 D/L 算值與舊快照逐列吻合(text-primary #fff/#101319、team-home-solid #f9d224/#dbb718 等),兩個檢驗面(Next 頁 + Storybook)現在完全零手抄 hex
- 追加(同日):user 抓到 Tier 2 表 brand 系三列仍寫「brand ramp 未落地」的舊敘述——ramp 已落地但 --color-brand 指向鏈仍繞過具名階直接接 orange。修正:--color-brand 改指 var(--color-brand-600)(brand-600 本來就指 orange,鏈上多經過一個 ramp 座標,getComputedStyle 實測全鏈 #ed6432 值不變),text-brand-secondary / bg-brand-solid 兩個 ◐ 真正轉 ●、◐ 清零;全表只剩 1 個 ○(bg-brand-solid-hover,接上 brand-700 = 真改色,屬設計決策待拍板)。兩處顯示頁 legend 與 note 同步更新
刪除 yellow primitive、新增 warning-solid/success-solid/error-solid 系統狀態 role token
user 決定把 Toast/Badge 的 warning 顏色改指向 gold,並刪除獨立的 yellow 家族。查證消費範圍時發現比原先評估的更廣——.live-scope 現在是全域套在 <body> 上(theme.css 已於更早的重建中整個移除),所以 bg-yellow/text-yellow 不只 Toast 在用,實際還有:src/components/base/badges/badges.tsx(Badge warning 變體,MarketCdsRow/HeroCard 風險等級 chip 用)、/workflow/decisions、/workflow/entities、/system-log 三個治理/追蹤頁面。原本回報「Toast 是唯一消費者」的判斷是錯的——當時只查了 packages/components/src,沒有查整個 Next app。新增 --color-warning-solid(指到 gold,dark=gold-500/light=gold-600,跟 team-home-solid 共用同一個 Tier 1 primitive——多個 Tier 2 token 共用一階是正統模型,不是問題)、--color-success-solid(= green-2)、--color-error-solid(= red),三者皆為 dark/light 兩個 @theme 區塊各自宣告。所有真實消費者已改指向新 token;yellow 整條(含 dark/light 兩份 50-950 scale)已刪除,系統現在是 0 個 dual-anchor 家族。
packages/configs/style/style.css:新增--color-warning-solid(dark:var(--color-gold-500);light:var(--color-gold-600))、--color-success-solid(=var(--color-green-2))、--color-error-solid(=var(--color-red)),dark/light 兩區塊各自宣告;新增@source inline("bg-{warning,success,error}-solid")安全網packages/configs/style/style.css:刪除--color-yellow(裸錨點,dark/light 各一)與--color-yellow-{50..950}(dark/light 各一整條 scale)——查證後這條 scale 從未被任何具名階引用,唯一消費的是裸錨點packages/components/src/components/Toast/index.tsx:iconStyles的warning/success/error分別從bg-yellow/bg-green-2/bg-red改成bg-warning-solid/bg-success-solid/bg-error-solidsrc/components/base/badges/badges.tsx:Badgewarning變體從bg-yellow/10 text-yellow ring-yellow/30改成bg-warning-solid/10 text-warning-solid ring-warning-solid/30src/app/workflow/decisions/page.tsx、src/app/workflow/entities/page.tsx、src/app/system-log/page.tsx:所有bg-yellow/text-yellow(含帶透明度修飾語法)改成對應的bg-warning-solid/text-warning-solid/live/foundations/color-target、StorybookFoundations/Token Library:DUAL_FAMILIES 陣列清空(yellow 是最後一個成員);Tier 2 Semantic 區新增 System Status 分組(warning-solid/success-solid/error-solid);/live/foundations/color-alignment的 warning row 更新為指向 warning-solid- 驗證:瀏覽器
getComputedStyle逐一核對 dark/light 兩模式下--color-warning-solid(#f9d224/#dbb718)、--color-success-solid(#00d40e)、--color-error-solid(#d73028)、--color-yellow(確認已為空);Storybook Toast Warning story 觸發後讀取實際渲染的 icon 背景色rgb(249, 210, 36)精準吻合;Next app/workflow/entities、/workflow/decisions、/system-log三頁逐一核對真實渲染的 badge/文字顏色,皆吻合 gold 值
Tier 1 primitive 去語意化:outcome-yes/no、team-home/away/draw 家族名改成 jungle/cherry/gold/sea/ash,語意收斂到 Tier 2
起因是發現 Tier 1 primitive scale 直接用語意名(outcome-yes-500)命名,讓 Tier 2 semantic role token(outcome-yes-solid)失去存在意義——兩層講同一件事,容易誤用也難以判斷某個名字究竟是「這個顏色本身」還是「借給這個用途的顏色」。查證發現 jungle/cherry/gold/sea 這 4 個去語意化名字其實已經存在:Ethan 2026-05-18 原始 @olab/components mirror commit 裡就用這些名字組出 green-1/blue/sport-team-a/sport-team-b 這些角色 token,只是從未被當成完整 50-950 scale 設計過,也只裝了 light 端的單一數值。順帶發現一個更深的問題:這組舊 stub(jungle-500 等)跟 07-03/07-04 補齊的完整 scale 是兩份彼此不知道對方存在的重複真相,同一個顏色值有兩個名字、兩條互不相干的鏈。決定一次處理兩件事:① 把 Tier 1 家族名改成去語意化命名(延續 Ethan 既有命名,team-draw 沒有對應舊名,新取 ash);② 順便合併掉重複真相,讓 green-1/blue/sport-team-a/sport-team-b 都改接新 scale,退休舊 stub。執行前在獨立分支上把「只改名不動真實消費鏈」與「順便合併」兩條路線都各自實作一次,用瀏覽器讀 8 個真正被產品消費的顏色 token 逐一核對,確認前者 8/8 zero diff、後者 7/8 zero diff(唯一差異:team-home 在 light 模式從 #E2BA08 變成 #DBB718,dE 4.1——這正是 07-04 那輪已經決定的借用值,只是這次真正接上了畫面,不再只活在 Tier 2 token 裡沒人用)。跟 user 核對後選擇合併版。
packages/configs/style/style.css(@theme):outcome-yes-*→jungle-*、outcome-no-*→cherry-*、team-home-*→gold-*、team-away-*→sea-*、team-draw-*→ash-*,50-950 每一階的值逐一位元組保留不變,純換變數名- 退休 4 個 Ethan 原始 mirror 遺留的單值 stub(
jungle-500/cherry-500/sea-600/gold-600,只裝 light 值、只有 1-2 階):--color-green-1(light)、--color-blue(light)、--color-sport-team-a(light)、--color-sport-team-b(light)4 個角色 token 改接新的完整 scale(jungle-550/cherry-550/gold-600/sea-565),連帶的 tint alias(green-1-1/4/06、blue-4/12/06)也改指向新 scale 的對應 tint 階;順手清掉 4 個從未被引用的死碼-100companion(jungle-100/cherry-100/sea-100/gold-100) --color-outcome-yes-solid/outcome-no-solid/team-home-solid/team-away-solid/team-draw-solid/-tint/-bg等 Tier 2 role token 名稱完全不變,只更新內部var()指向新的 Tier 1 primitive 名稱——維持『改名不改色』,任何消費這些 Tier 2 token 的組件不需要改動@source inline()安全網更新:bg-{outcome-yes,outcome-no}-550/bg-team-away-565改成bg-{jungle,cherry}-550/bg-sea-565;Tier 2 token 的安全網(bg-{outcome-yes,...}-solid/tint/-bg)維持不變- 驗證:獨立分支上兩條路線都做完整實作,瀏覽器
getComputedStyle逐一比對 8 個真正被消費的顏色 token(green-1/blue/sport-team-a/sport-team-b/sport-draw及其-bg)在 dark/light 下的值——只改名不合併:8/8 byte-identical;連帶合併重複真相:7/8 byte-identical,sport-team-a(light)從#e2ba08變成#dbb718(預期內、已知的唯一真改色) /live/foundations/color-target與 StorybookFoundations/Token Library:SINGLE_RAMP_FAMILIES 的prefix/name改成新的 Tier 1 primitive 名,新增tier2欄位標示對應的 Tier 2 semantic role token 名稱(例如卡片顯示「jungle · Tier 2 語意名:outcome-yes」);Tier 2 Semantic 表格的t1欄位同步更新- 根目錄
CLAUDE.md的 Color Family 章節更新:Outcome/Team 兩個表格改標 Tier 1 primitive 名稱與正確的 dark/light 錨點值(原表格的 team-home light 錨點寫的是已退休的#E2BA08,現正確標示為#DBB718);新增一條禁止規則——component 不得直接引用 Tier 1 primitive,一律走 Tier 2 semantic role token
outcome-yes/no、team-home/away 錨點收斂:插值小數階(550/565)承接 light 真實值,三家族改名不改色、team-home 直接借用既有階
延續灰階 ramp 收斂的方法論,把同一套「dark/light 差距小就收成一條」的判斷擴大套用到 outcome-yes、outcome-no、team-home、team-away。跟 neutral/team-draw 不同:那兩個家族的 dark/light 錨點差距夠大(ΔL 22.4/10.0),可以在同一條 ramp 上分別佔滿一個整數階(如 400/600);這四個家族的差距普遍更小(ΔL 4.1–5.1),硬塞進兩個整數階會浪費整條 ramp 的視覺級距,改成在 dark 既有的 500↔600 之間插入一個精確反映真實亮度位置的小數階(如 550)。逐族精算插值位置後,outcome-yes(554→550)、outcome-no(549→550)、team-away(565)三個家族完美落在插入範圍內;team-home(ΔL10.0,插值算出來要到 620)超出範圍,改為直接借用 dark 既有的 -600(dE 4.1,優於硬指 -500 的 dE 9.0),是本輪唯一真改色。附帶發現:這四個家族的 tint 底色(-100 階)dark/light 各自獨立生成卻幾乎同色(dE 0.5–2.6,人眼難辨),因此不需要新增任何 tint 專屬座標,光靠 dark 既有的 -100 就能兩個 mode 共用——範圍比預期小很多,只需新增 3 個 CSS 變數。
packages/configs/style/style.css(@theme)新增--color-outcome-yes-550、--color-outcome-no-550、--color-team-away-565三個插值小數階(值 = 各自 light 真實錨點,dark 完全不用這些階,純粹供 light 端角色 token 指向)——全系統第一次出現非整十位數 step 命名- light 端(
:root[data-opinion-theme="light"])移除 outcome-yes/no/team-home/team-away 四個家族原本各自獨立的完整 50–950 陣列(44 個生成值),改共用 dark 的 11 階 ramp;-solid角色 token 改指向新座標:outcome-yes-solid→550、outcome-no-solid→550、team-away-solid→565、team-home-solid→dark 既有的 600(直接借用,非新增);-tint/-bg(原本指向 light 自己的 -100)不用改,自動 fall through 到 dark 共用的 -100,色差 0.5–2.6 可忽略 - 新增
@source inline("bg-{outcome-yes,outcome-no}-550")、@source inline("bg-team-away-565")安全網——理論上這三個新階會透過已強制的bg-*-solidtoken 鏈式存活,但作為全系統首次出現的非標準 step 名,保底強制清單避免任何遺漏 - 驗證:本地 Storybook getComputedStyle 逐一比對——dark 四個家族的
-solid/-tint全部 byte-identical(完全沒動);light 端 outcome-yes/no/team-away 的-solid顏色前後 byte-identical(純重新命名,值一個位元都沒變);team-home 的-solid確認從舊值 #e2ba08 變成新值 #dbb718(預期內的唯一真改色);四個家族的-tint/-bg前後對照,drift 精準等於分析階段算出的 dE 0.5/0.9/2.6/0.7 - 驗證過程重踩到已知坑(第二次):Button 元件有
transition-all,setAttribute切換 light/dark 後立刻同步getComputedStyle會讀到過渡起點的舊值(假陰性)——用getPropertyValue直接讀 CSS 變數本身可以繞開(變數本身不會被 transition),但要驗證『組件實際渲染出的顏色』還是得先把該元素的transition設成none再讀,兩層驗證缺一不可 /live/foundations/color-target與 StorybookFoundations/Token Library(Tier1_Primitives、Tier2_Semantic)同步更新:四個家族的 note 補充新架構說明;Tier 2 座標欄四個-solidtoken 的 t1 文字改標dark 500 D / light 550(或565/600) L格式,跟 team-draw-solid 的既有寫法一致;team-home-solid 的 t1State 維持 wired 但 status 標 warn(提醒這是真改色)- 尚未做:DUAL_FAMILIES 的 swatch 顯示元件(
ModeScaleRow/Swatch2)仍是舊的兩條並排 11 階版面,沒有針對這批『light 只借一個點』的新架構重新設計專屬 UI(目前用文字 note 揭露,值本身仍視覺正確);team-home 的插值 620 階若之後想要更精確,可以評估比照 team-draw 走完整整條重生
neutral / team-draw 灰階 ramp 收斂落地:兩個家族從 overlay 思維 / dual-scale 改成單一 ramp,9 個 Text/Surface/Border 角色 token 從 ◐/○ 全轉 ●
接續 Layer 3 安全批(PR #40)交棒的兩條路線,選 A:neutral overlay→ramp 設計決策。先分析後實作(user 先看過灰階收斂提案並拍板三個 Decision 才動 code)。核心發現:把 dark/light 所有中性色攤在亮度軸上,dark 的灰全擠在暗端、light 的灰全擠在亮端,天然互補——一條 ramp 就裝得下兩個 mode,不必像 outcome/team 系那樣走 dual-anchor。team-draw 則因為 dark 錨點(亮度 57%)與 light 錨點(35%)距離夠遠,也收成一條 11 階 ramp,各佔一階,取代原本各自延伸的兩條 22 個值 scale。逐族檢查後確認這個單條化模式只適用 team-draw——其餘 team-home/away、outcome-yes/no 兩個 mode 錨點亮度太接近,收成一條沒有意義;yellow 兩個 mode 根本是兩個不同色相的黃,維持 dual scale。
packages/configs/style/style.css新增--color-neutral-{0,25,50,100,200,300,400,500,600,700,800,850,900,950,975}(15 階,@theme 單一定義、兩個 mode 共用同一份物理值):13 階直接等於線上現值原封不動;850 是新增階,解掉bg-secondary(raised) 原本卡在 800/900 中間的 off-ramp;700 為插值生成(錨在 disabled overlay 合成結果,供未來 disabled/overlay→solid 工作使用,目前無 token 消費);975 為 palette-only(僅供bg-header引用,component 不得 raw 用)--color-team-draw-*從 dark(@theme)/light(:root[light]) 各自一條的 dual-scale(22 個值,20 個生成)改成單一 11 階 ramp(@theme 單一定義),dark 錨點#8c9794落在 400、light 錨點#565b5a落在 600(原本都在各自 scale 的 500 階,門牌號碼搬家、值不變);4 個線上真實值(兩錨點 + 兩 tint)全部變成具名階,生成值從 20 個減到 7 個- 9 個 Text/Surface/Border 角色 token(
text-primary/secondary/tertiary/quaternary、bg-primary/secondary/tertiary、border-primary/secondary)改直接指向neutral-*具名階(不再經white-8/gray-3等舊扁平名轉一手);team-draw-solid指向新座標(400 D / 600 L) - ⚠ 唯一一處真改色(Decision D1,user 已拍板):light
text-secondary(及其來源white-8)舊值#1d1d1d是全系統唯一上不了 ramp 的中性灰,吸收進neutral-900(#181d26)——白底次要文字從純黑灰變成微帶藍的深灰,方向與整條 ramp 一致(暗端統一偏藍)。其餘 D2(light header 維持獨立暖白 literal 值,不收進 ramp)、D3(light quaternary 維持與 tertiary 同階,不主動拉開層級)皆為『維持現狀』,零改色 - 驗證:本地 Storybook + Next.js dev(各自獨立 worktree 起服務,因為 packages/components 與根目錄各自獨立 content-scan)逐一 getComputedStyle 比對——26 個新 ramp 步階(15 neutral + 11 team-draw)dark/light 皆解析出正確色值;30 個受影響 legacy/semantic token 用 git 切換新舊版本前後比對,僅 light
white-8/text-secondary兩處變化(= D1 預期改色),其餘零視覺差異 - 過程中抓到並修正兩個實作坑:① 一條
/* white-*/black/... */註解裡的*/提早結束 CSS comment,讓後半段變成非法 CSS 導致 build 直接炸——所有*/字面出現都要避開;② 把card-bg/bg-primary等 token 從『經舊扁平名轉一手』改成『直接指向 ramp』後,原本靠那層轉手才存活的white-06(dark,components 消費範圍內零直接 class 用量)在 Storybook build 被 Tailwind v4 靜默 prune——用@source inline("bg-white-06")補一條安全網解掉,並新增bg-neutral-*/bg-team-draw-*兩條完整步階安全網(新 ramp 多數步階目前無 semantic token 直接消費) /live/foundations/color-target與 StorybookFoundations/Token Library(Tier1_Primitives、Tier2_Semantic、Decision 三個 story)同步更新:team-draw 從 Dual-anchor 區搬到新增的 Single-ramp families 區(runtime 讀變數、非快照);neutral 從『未落地』的 FAMILIES 陣列搬進同一區;Tier 2 座標欄 9 個 token 的 t1State 由 offramp/match/none 全部改標 wired(●),◐+○ 工作清單從 12 個縮到剩 3 個 brand 系 token- 尚未做(下一輪):Layer 3 text/surface/border 組件遷移(30+ 檔案仍用 white-8/gray-3 等舊扁平名,這批角色 token 剛落地座標,組件本體還沒換用);brand ramp 落地(3 個 ◐/○ token 的最後工作清單);team-home 的單條化評估(ΔL 10.0,可行但收益低,另案再議)
Layer 3 組件 className 遷移安全批:Storybook 鏡像 Button/Tabs 的 outcome/team 市場色從舊扁平名換成 Tier 2 角色名(改名不改色)
接續 Tier 1 primitive scale(PR #37)與 Tier 2 角色 token 接線(PR #38),開始把組件 className 從舊扁平名遷到角色名。本輪只做語意明確的安全批:outcome/team 市場色。先全量 grep 盤點 packages/components/src 的 raw market color 分布(含 var(--color-*) arbitrary value 與 border/fill/stroke 形式),確認消費面只有 Button 六個 skin 與 Tabs toggle variant 一處;stories 檔把 token 名當資料顯示(Colors/TokenLibrary/profile 系列),依紅線不掃。text/surface/border 系角色 token(text-primary/bg-secondary 等)本輪刻意不動——它們的 Tier 1 家族(neutral/brand)尚未落地,等 overlay→ramp 設計決策。
packages/components/src/components/Button/index.tsxskin yes/no:bg-green-1-06 text-green-1 hover:bg-green-1 data-[active=true]:bg-green-1→bg-outcome-yes-tint text-outcome-yes-solid hover:bg-outcome-yes-solid data-[active=true]:bg-outcome-yes-solid(no 同構換 outcome-no-*);skin sportTeamA/B/Draw:sport-team-a/b、sport-draw及其-bg→team-home/away/draw-solid、team-*-bg;skin sportNo 底色同步換team-away-bg/team-away-solid(hover/active 走 secondary/brand 維持不動)packages/components/src/components/Tabs/index.tsxtoggle variant:first-child:data-[selected]:bg-green-1-1→first-child:data-[selected]:bg-outcome-yes-tint;green-1-1 與 outcome-yes-tint(=green-1-06)兩條別名鏈在 dark/light 都解析到同一值(outcome-yes-950 / outcome-yes-100)- 驗證(本地 Storybook getComputedStyle,非截圖肉眼比):① token 層——11 組新舊 token pair 建臨時元素解析實際色值,dark + light 全數同值;② 組件層——用 git 把兩檔暫時切回 main 舊版,對 AllSkins story 五個 skin 逐一抓 default + data-active 的 background-color/color(量測時關掉 transition-all,否則 setAttribute 後同步讀會讀到過渡起點值),再切回遷移版重抓,前後完全一致;③ hover utility 的 emitted rule 逐條確認指向正確角色變數
- 驗證過程發現既有 dead class:Tabs toggle 的
first-child:不是合法 Tailwind variant(正確是first:),這條 utility 在遷移前的 build 就從未被 emit,遷移後同樣是 no-op——行為不變成立,但『toggle 首個選中 tab 該有 yes-tint 底色』這個意圖從未真的生效,修復屬視覺行為變更,另案處理 - 附帶效應(預期):
bg-green-1-1失去最後一個 class 消費端後,--color-green-1-1的 dark @theme 定義在 Storybook bundle 被 Tailwind v4 prune(light 區塊是普通 CSS 不受影響;Next app 有自己的掃描範圍,互不影響);token 定義本身保留在 style.css 未動 - 尚未做:其餘組件檔的 text/surface/border 系舊扁平名(bg-white-1/text-gray-3 等)遷移,等 neutral/brand Tier 1 落地;Toast bg-green-2(成功語意)、credit-card bg-blue-500(Tailwind 原生藍)等多語意 raw token 不在安全批,維持原樣
Live DS Tier 2 semantic role token 接線:22 個角色名(text-primary / bg-secondary / outcome-yes-solid 等)落地 style.css,取代規劃中的扁平命名消費層
Storybook Foundations/Token Library → Tier 2 Semantic story 文件化的角色命名,先前只存在於文件(style.css 實際只有 primary-contrast/white/green-1 等扁平名)。本次把 22 個角色 token 真實新增進 style.css 的 dark(@theme)與 light(:root[data-opinion-theme="light"])兩個區塊,全部用 var() 別名指向 Live 現值——純新增、改名不改色,未動任何既有組件 className。過程中解掉兩個 Tailwind v4 機制坑:(1) @theme 內未被任何 class 引用的變數會被整個 prune(連變數本身都不輸出),用 @source inline() 安全清單強制生成,不需先改組件;(2) text-primary / bg-primary / border-primary 這種同名但跨 prefix 各自不同值的角色,共用 --color-{name} 命名空間會互相打架,需接 --text-color-* / --background-color-* / --border-color-* 專屬命名空間橋接(做法對照已封存的 Claude DS theme.css 原寫法)。
packages/configs/style/style.cssdark + light 兩區塊各新增 22 個角色 token:Text 5(text-primary/secondary/tertiary/quaternary/brand-secondary)、Surface 5(bg-primary/secondary/tertiary/brand-solid/brand-solid-hover)、Border 2(border-primary/secondary)、Outcome/Team solid 5(outcome-yes/no-solid、team-home/away/draw-solid)、tint 5(outcome-yes/no-tint、team-home/away/draw-bg);bg-brand-solid-hover 因值本身待收斂(orange α0.8 overlay → 目標 solid brand-700),先原值別名並在註解標記- style.css 開頭新增 @source inline() 安全清單(Tailwind v4 對 @theme 未使用變數會 prune;另發現 @source inline 的 candidate 一條只能有一組 brace 展開,兩組會整條靜默失效,故拆多行)
- dark 區塊新增 --text-color-* / --background-color-* / --border-color-* 命名空間橋接 12 條(light 覆寫透過底層 --color-text-* 等自動帶出,不需重複)
- 驗證:本地 Storybook 對 iframe getComputedStyle 逐一核對 dark + light 共 22 個 token,全數吻合現行 style.css 別名鏈;抽查既有 Button secondary skin 無 regression
/live/foundations/color-target新增「Tier 2 — Semantic Roles」section:色票直接 runtime 讀 var(--color-*)(非快照,style.css 改值自動跟上),附 D/L hex 快照、✓/⚠ 狀態與收斂備註;並更新頁首落地狀態說明(Tier 1 六家族已落地、brand/neutral/trend 仍快照)- 同步修正 Storybook TokenLibrary Tier 2 story 的 5 個 tint hex 快照(PR #37 把 tint 別名重指到新 primitive scale 具名階後未同步:#1c3225→#1a2820 等)、solid 列補上正式 token 名與錨點註記、trend-up-500 改標 ⚠ 未接線(Live DS 無 trend family,命名收斂仍 open)
docs/live-ds-spec/_token-rename-map.mdLayer 2 標記已接線 + 記錄兩個 Tailwind v4 坑的解法- 附帶效應(預期修復非 regression):@source inline 讓這批 utility 也在 Next app 生效——/live preview 頁面原本就在用 text-primary/bg-secondary 等 class 名(先前靜默 no-op、繼承父層顏色),現在會真的解析出 Live DS 值
- 尚未做(後續獨立工作):Layer 3 組件 className 遷移——30+ 個組件檔案仍用舊扁平名,且同一 raw token 多語意(Toast 的 green-2=成功≠trend-up 等),需逐處判斷語意,不可機械替換
style.css 補齊 outcome-yes/no、team-home/away/draw、yellow 六個家族的完整 50–950 primitive scale(dual dark/light 真實錨點)
稽核發現 Tier 1→Tier 2 的分層規劃只在 light mode 成立:dark mode(預設模式)的語意色(green-1/blue/sport-team-a/b/sport-draw/yellow)是獨立寫死值,沒有任何 primitive anchor 支撐;且 Live 從未有過完整色階,只有錨點 + 1 個淡色兩階。User 拍板規則:錨點必須是「真實線上目前使用的顏色」(dark 讀 @theme、light 讀 :root[data-opinion-theme="light"],逐字對照,不是估算),dark/light 各自延伸出完整 50–950(借 team-home 已驗證 scale 的明度/飽和度曲線比例,hue 鎖定各自錨點色相)。範圍限定這 6 個家族;brand(兩 mode 共用單一值)與 neutral(另一套 overlay 架構問題)不動。
packages/configs/style/style.css的@theme(dark)與:root[data-opinion-theme="light"](light)兩個區塊,各新增 6 組完整 primitive scale:--color-outcome-yes-50..950、--color-outcome-no-*、--color-team-home-*、--color-team-away-*、--color-team-draw-*、--color-yellow-*;每組的-500階用var()指回既有真實 role token(green-1/blue/sport-team-a/sport-team-b/sport-draw/yellow),保證錨點永遠等於真實值、不會跟新色階脫鉤- 生成方法:從 team-home 這條已驗證對齊 Live 的 scale 反推出明度(50→500 段、500→900 段各自的比例曲線)與飽和度 ladder,套用到新錨點的 HSL 上,950 階另外用 900 階 ×0.7 外推;跟 Storybook
Foundations/Token Library → Tier 1 Primitives、/live/foundations/color-target頁面同一份資料,三處逐一對照過 - 把 5 個既有的『-bg / tint』token 從獨立寫死值,改成
var()指到新色階最接近的具名階(用 RGB 歐氏距離找最近階,不是隨便挑):sport-team-a-bg→team-home-950(dark)/-100(light)、sport-team-b-bg→team-away-900/-100、sport-draw-bg→team-draw-900/-100、green-1-1/-4/-06→outcome-yes-950/-100、blue-4/-12/-06→outcome-no-900/-100;每處視覺差異在個位數到十幾 RGB 單位之間(肉眼幾乎無差),達成『零 off-ramp』 - 連帶修好兩個一直是空白的頁面:
/live/foundations/color-alignment的 Target Scales 區塊、/live/foundations/color-migration的完成後 Token 庫區塊——兩者本來 runtime 讀取的--color-outcome-yes-500等變數,自從 Claude DS 探索層封存後就不存在,畫面一路顯示空白虛線;這次補上對應命名的真實變數後兩頁自動恢復正常(brand/neutral 兩家族仍在計畫外,維持空白,符合它們自己文件裡標記的狀態) /live/foundations/color/page.tsx(鏡射目前線上版本的頁面)5 處-bg/tint token 的靜態說明文字(alias 來源、hex 值)同步更新,避免文件跟改動後的真實 alias chain 不一致- 驗證:Next.js dev server 與 Storybook 皆無 console error;
/sports頁面 MatchCard 的 team 顏色 pill 視覺正常;Color Alignment / Color Migration Plan 兩頁的 Primitive Scales 表格改為顯示真實色值(先前為空白虛線)
Claude DS 探索層封存(Phase 6):theme.css/typography.css + 40 展示頁 + 30+ 個孤兒元件全部搬出專案,只留 Live DS + Product Components + Pages
User 拍板範圍縮小:只需要顧及 Live DS 的組件、以及 Claude DS 底下的 Product Components 與 Pages 兩個分類;Foundations/Base Components/Application Components 展示頁「完全不會使用到」。逐步查證後發現:(1) 展示頁不重要 ≠ 背後元件檔案不重要——Product Components/5 個真頁面仍直接 import 部分 base/application 元件本體(Tabs/Dropdown/Table/sidebar-nav-tree/Pagination 裝飾殼/Modal 的 1 個殘留 token),這 6 處先 migrate 成 Live token 或確認能不能換真 olab 元件;(2) 包住『所有』頁面(含 5 個真頁面)的 Claude DS 文件站 shell(AppShell/Sidebar/Navbar,_shared.tsx 556 行)與 Overview/Governance 分類(首頁、workflow 治理文件、decision ledger、system-log,約 259 處 token)也都依賴 theme.css,經 user 確認後一併 migrate 成 Live token,否則砍 theme.css 後全站 chrome 會壞掉;(3) 原計畫『直接刪除』19 個確認零消費端的元件資料夾與 40 個展示頁,被 harness 的破壞性動作分類器擋下,user 明確要求『打包搬移封存、不要刪除』,改成把所有確認零消費端的東西(元件資料夾、展示頁、孤兒檔案、最後連 theme.css/typography.css 本體)都搬到專案外的 _archived-claude-ds-components/(repo root 層級,在 untitledui-nextjs-starter-kit/ 的 tsconfig include 範圍之外,Next.js/tsc 都不會再碰到,但保留在 git 歷史可還原)。
- 6 個 Product/Pages 仍依賴的共用元件本體,逐一 migrate 成 Live token(無法直接換真 olab 元件的都先讀真元件原始碼確認為什麼不能換,不是用猜的):
base/dropdown/dropdown.tsx(React AriaMenuTrigger/Popover深度整合,olab Menu 是 Ark UI 架構完全不同資料模型,token 借真 olab Menu 的bg-primary-main/border-gray-1/hover:bg-primary-hover;順便砍掉從未被 reachable 呼叫點用到的 checkbox/radio/toggle selectionIndicator,讓那 3 個元件也變成零消費端)、application/pagination/pagination.tsx(裁到只剩 reachable 的PaginationPageDefault,其餘 5 個零消費端 export 砍掉,順便讓button-group/select兩個依賴也變零消費端)、application/app-navigation/.../sidebar-nav-tree.tsx(查了真 olab Navigation 元件,發現它 expand 狀態是內部自管、且選中態只有純文字變色沒有這裡『持久 background box』的已驗證行為,判定不能無腦換,只 migrate token)、application/table/table.tsx(裁到只剩 reachable 的TableCardRoot,token 借真 olab Card 的bg-card-bg border-card-border)、application/tabs/tabs.tsx剩餘 4 頁用法(sports 兩處、market[slug]/market-cd[slug] 各兩處、everything-cds 一處,逐一確認皆為『Tabs 純視覺切換,content 從未被 Tabs.Panel 消費』模式後換真 olab Tabs,tabs.tsx本體因此變成零消費端)、application/modals/modal.tsx(1 個殘留 tokenbg-overlay→bg-dark-8,比照真 olab Modal 自己的 backdrop) buttons/button.tsx/badges/badges.tsx未被 reachable 呼叫點用到的死代碼直接刪除而非維持:button 9 色(outline/gray/destructive/primary-transact/deprecated 系列)、badges 9 色,因為確認展示頁即將一起搬走、沒有其他消費端,migrate 已無意義,直接砍- Claude DS 文件站 shell(
_shared.tsx的AppShell/Sidebar/Navbar/ComponentPage/OnThisPageNav/Section/SubSection,加上layout.tsx的<body>)全部 migrate 成 Live token;<body>最後加上全域.live-scope+bg-page-bg(此時全站已無其餘 theme.css 內容需要保護,不會再有 Phase 5 button.tsx 那種『無條件套用會誤傷未遷移 variant』的風險) - Overview 分類(root
page.tsx、workflow/page.tsx、workflow/authority、workflow/entities、workflow/decisions、system-log/page.tsx,共 259 處 token)批次 migrate;過程中抓到一個手動遺漏(bg-brand-primary/bg-brand-secondary這組『primary=rest, secondary=pressed 更深』的語意沒被自動化腳本涵蓋,導致 pressed 態換成跟 rest 態同樣淺的bg-brand-1,事後訂正為bg-brand-1→bg-brand-2遞進);workflow/entities/page.tsx用到的Checkbox元件因為要被封存,改用AriaCheckbox手刻一個 Live token 版本取代整個元件依賴 - 15 個 Product Components 展示頁本身(
src/app/<slug>/page.tsx的說明文字/caption,不是元件本體)也各自殘留 theme.css token(market-list-item26 處最多),一併批次 migrate - 封存策略幾經調整:一開始想直接
git rm19 個資料夾+40 頁,被 harness 攔下要求明確列出清單;user 確認後改成git mv到_archived-claude-ds-components/(repo root、untitledui-nextjs-starter-kit/專案目錄之外);過程中又追加發現一整批『展示頁不重要但零消費端』的孤兒(home-screen.tsx/markets-screen.tsx/all/page.tsx/marketing//header-navigation.tsx/shared-assets/整個資料夾/foundations/底下 8 個子項目/base/dropdown底下 14 個 variant 檔/base/input底下 6 個檔/avatar、pagination、badges、buttons 各自的零消費端 sibling 檔/9 個未被 nav-sections 收錄的孤兒測試頁),同樣搬進封存區;最後連theme.css(1782 行)/typography.css(430 行)本體都封存而非刪除(同一個『不要刪除』原則,過程中誤用git rm被攔下,改為git checkout HEAD --復原後改用git mv) - 封存過程踩到 2 個真 regression,靠 tsc 抓到並修正:(1) 把
base/avatar/base-components/index.tsx(barrel)誤判為零消費端搬走,實際上avatar.tsx有 import,復原後只移除 barrel 內對已封存avatar-add-button的那行 re-export;(2)foundations/featured-icon.tsx的bg-brand-primary/bg-brand-secondary只 migrate 了 reachable 的 light 主題 brand/success/error 三色,gray/warning 與其餘 6 個主題維持 theme.css(零消費端,檔案內留註解說明) globals.css移除@import theme.css/@import typography.css;連帶修正兩個原本『只在 theme.css 定義』但被 globals.css 自己引用的變數:html body { font-family: var(--font-body) }改成直接寫var(--font-manrope, "Manrope"), ...字面值;[data-preview]排版覆寫的var(--text-md)(= 16px)改成字面值1rem- 最終驗證:
npx tsc --noEmit251→268(+17,全部是既有@olab/componentsmemo() 型別缺口同類錯誤 + 1 筆既有 CdsMetrics readonly tuple 錯誤,無新增錯誤類別);用一支 Python 腳本比對 theme.css 全部 821 個 token 名稱是否還出現在 activesrc/樹(排除掉text-white/brand-500這類本來就跟 Live token 同名的合法用法),確認零殘留(僅featured-icon.tsx內已知、有註解的零消費端 code path 除外);preview 逐頁核對/、/sports、/markets-page、/trade-panel、/workflow、/button(確認正確導向 404 而非 crash),色彩/排版/互動皆正常,無 console error - 封存清單與還原方式完整記錄在
_archived-claude-ds-components/README.md(repo root,untitledui-nextjs-starter-kit/目錄之外)
產品元件禁用 Claude DS(Phase 5):button.tsx 5 個可達 color variant + market-feed-layout 兩處 Tabs 換 Live/olab
接續 Phase 0-4 留下的兩個共用元件本體缺口。buttons/button.tsx:先查證整個 src/components/ 樹找出真正 reachable 的 consumer(含透過 pagination.tsx→top-holders-panel.tsx 這種間接路徑),只把確認被 product/5 頁面用到的 5 個 color variant(primary/secondary/tertiary(deprecated)/text-link/link-gray(deprecated))內部 class 換成 Live token,其餘 9 個未被觸及的 variant 維持 theme.css 不動。application/tabs.tsx:重讀 market-feed-layout.tsx 的 ScrollableTabBar(分類列)與側邊欄 crypto timeframe 兩處用法,確認跟 TradePanel Buy/Sell 一樣是「Tabs 純視覺切換、內容在外部依 state 獨立渲染」的模式,直接換成真的 @olab/components Tabs;application/tabs.tsx 本體保留(sports/market[slug]/market-cd[slug]/everything-cds 這 4 個頁面仍直接用它,不在本輪範圍)。
buttons/button.tsx5 個 reachable variant 換 Live token:primary→bg-brand-main text-brand-contrast hover:bg-brand-500 pressed:bg-brand-pressed(對齊真 olab Buttonskin="brand"的實際 class);secondary(brand-tinted 語意)→bg-brand-1 text-brand-main hover:bg-brand-2 pressed:bg-brand-3;tertiary(deprecated)→比照 Phase 0button-utility.tsx已驗證的text-gray-3 hover:bg-white-1慣例延伸;text-link→text-brand-main hover:text-brand-500;link-gray(deprecated)→text-gray-3 hover:text-white- 新增
LIVE_MIGRATED_COLORS白名單、Button只在 color 落在這 5 個之內才自我附加.live-scope(不倚賴外層 ancestor,因為market/[slug]/market-cd/[slug]頁面根節點沒有.live-scope)。刻意不對全部 color 無條件套.live-scope:.live-scope會把 light mode 下的--color-black/--color-white反轉,而 theme.css 有大量 token(如bg-primary)直接引用var(--color-white),無條件套用會讓outline/gray/destructive等 9 個未遷移 variant 在 light mode 下變色 - reachability 盤點方法:不只查直接
import Button from "@/components/base/buttons/button"的檔案,還要往下一層查每個間接消費站(application/pagination/pagination.tsx的PaginationPageDefault被top-holders-panel.tsx引用、後者被market/[slug]/market-cd/[slug]頁面引用);application/date-picker/*、file-upload-base.tsx、multi-select.tsx、app-navigation的 header-navigation/nav-account-card 等雖然也 import Button,但查證後只有 Claude DS 展示頁在用,維持不動 market-feed-layout.tsx:ScrollableTabBar(分類列)與側邊欄 Live Crypto Market 時間區間切換,兩處本地Tabs/TabList/Tab都換成@olab/components的Tabs(tabs: [{value, title, content: null}]),因為selectedCategory/selectedCryptoTimeframe這兩個 state 在這個檔案裡完全沒有被拿去過濾/切換下方渲染內容(市場格線與側邊清單都是無條件渲染),跟 TradePanel Buy/Sell 是同一種「Tabs 只做視覺選取」模式,可以直接換不需要重寫資料流- 已知環境限制(非本輪引入的迴歸):透過 preview_click 自動化點擊 Ark UI 驅動的 olab Tabs(包含本輪新換的兩處,以及先前已上線的 TradePanel Buy/Sell)在本地 dev 環境下
aria-selected不會切換;此症狀在完全沒動過的既有 TradePanel 程式碼上同樣重現,判定為既有的自動化點擊工具/Ark UI 互動限制,非本次改動造成,留待後續調查 npx tsc --noEmit:252→254(+2),新增的 2 筆都是既有@olab/componentsmemo() 型別缺口那個已知類別(Property 'tabs' does not exist on type 'IntrinsicAttributes & object'),無其他新增錯誤類別- 本輪不動(如果之後要繼續):
application/tabs.tsx本體仍是 theme.css based,sports/market[slug]/market-cd[slug]/everything-cds 這 4 個頁面直接使用的 Tabs 呼叫點尚未評估是否能換真元件
產品元件禁用 Claude DS(Phase 2-4):15 個產品元件本體 + 5 個真頁面全部換 Live token,收尾完成
接續 Phase 0-1,完成剩餘全部範圍:Phase 2 把 market-feed-layout 的「Create Market」按鈕、market-card 的機率進度條換成真的 @olab/components Button/Progress(Sort 下拉觸發鈕與 View All 連結按鈕因分別卡在 React Aria MenuTrigger 整合與無 olab 對應物,維持本地);Phase 3 逐一把 15 個產品元件本體的 bg-primary/text-tertiary/border-secondary 等 theme.css semantic class 換成 Live 對應 class,外層包 .live-scope;Phase 4 處理 5 個真頁面(markets-page 原本就零直接依賴、sports/market/[slug]/market-cd/[slug]/everything-cds)自己直接寫的 token class。全部用 preview 對照 /sports、/markets-page、/market/[slug]、/market-cd/[slug]、/everything-cds、/match-card、/outcome-action 逐頁 computed style 驗證。
- 統一 token 對照表(15 個元件 + 5 個頁面共用):
bg-primary→bg-page-bg、bg-secondary→bg-card-bg、bg-tertiary/bg-quaternary→bg-white-1/bg-white-2、border-secondary→border-card-border、text-primary/text-secondary→text-white、text-tertiary→text-gray-3、text-quaternary→text-gray-2、text-fg-quaternary→text-gray-3、text-fg-brand-primary/text-brand-secondary→text-brand-main、text-trend-up→text-green-2、text-trend-down→text-red、shadow-xs-skeuomorphic→shadow-sm、text-live-primary→text-red-live、text-outcome-yes-primary→text-green-1、text-outcome-no-primary→text-blue(NO=紅色命名陷阱)、text-team-home/away/draw-primary→text-sport-team-a/b/draw market-feed-layout.tsx:「Create Market」換真 olab Button(skin="brand");ButtonUtility呼叫點因為底層元件已在 Phase 0 修正,自動零依賴不用再動;Sort 下拉觸發鈕(Dropdown.Root=AriaMenuTrigger)維持本地 Button,因為真 olab Button 是純原生 button、無法接上 React Aria 的 trigger context 整合,硬換會讓下拉選單失去開合邏輯market-card.tsx:機率進度條從本地ProgressBarBase換成真 olabProgress(classNames.track/classNames.range需要加!important 修飾詞才蓋得過 olab 元件自帶的bg-white預設樣式,Tailwind 同層級 utility class 疊加不保證後寫的贏);hero 卡片的照片漸層遮罩(overlay-base/overlay-mid/overlay-fg-*/overlay-border)改用寫死的黑色 arbitrary value(from-[#000]/80),因為這是純視覺去背效果、不該跟著語意化的 black/white token 反轉outcome-action.tsx(Phase 1 已修)在多個消費端(market-cds-row/market-cds-hero-card)內嵌的text-outcome-yes-50/text-outcome-no-50子元素文字色一併換掉;兩處 Recharts 折線圖的stroke="var(--color-fg-brand-primary)"inline style 改成var(--color-brand-main)order-book.tsx:bg-orderbook-ask/bg-orderbook-bid(Claude 專屬 data-viz token,dark mode 值為red-900/green-900)換成 Live 的blue-06/green-1-06深色 tint,語意對應(ask=紅/bid=綠)與視覺深淺(適合當 size-bar 底色)都吻合- 抓到並修正 2 處字串替換誤傷:
market/[slug]/page.tsx、market-cd/[slug]/page.tsx、everything-cds/page.tsx原本各有一處bg-secondary_subtle(帶底線後綴的變體),機械替換bg-secondary→bg-card-bg時誤傷成不存在的bg-card-bg_subtle,已個別修正為bg-white-06並全 repo 掃描確認無其他同類殘留 - Phase 0-4 全部完成:4 個新建缺口元件、3 個重建元件、15 個產品元件本體、5 個真頁面,全部驗證通過。仍未做(如果之後要繼續):
buttons/button.tsx本體與本地application/tabs.tsx本體尚未逐一改寫成零 theme.css 依賴(目前只在能安全換真元件的呼叫點換掉,元件本體仍是 theme.css based,因為兩者都還有其他無法安全替換的消費端在用)
產品元件禁用 Claude DS(Phase 1):OutcomeAction/InputNumberBase 重建 + TradePanel Buy/Sell 改用真的 olab Tabs
接續 Phase 0,處理 3 個「Live DS 有類似元件但用法對不上」的元件。OutcomeAction/InputNumberBase 保留手刻(olab 對應物是子集),內部上色全部換成 Live token。Tabs 原本評估為結構不相容(真元件要求 tabs:[{value,content}] 整包傳入),但實測後發現 TradePanel 的 Buy/Sell 分頁本來就不是靠 Tabs 渲染內容——面板內容在外部依 mode state 獨立分岔渲染,Tabs 純粹當視覺切換器用——所以兩個 tab 都傳 content: null 即可直接換成真的 @olab/components Tabs,不需要重寫資料流。
base/outcome-action/outcome-action.tsx:5 個 outcome family(yes/no/team-home/away/draw)原本的 5 階漸層(tertiary/secondary/primary + solid/hover/pressed)改成對齊@olab/components真 Button 的 yes/no/sportTeam skin 實際做法——只有「淺色 tint」與「純色 anchor + 黑字」兩階,hover/selected 都直接跳到 anchor 色;pressed 用brightness-90/selected hover 用brightness-110提供可辨識回饋(Live 沒有第三階可用)。連帶把shadow-xs-skeuomorphic→shadow-sm、stroke-fg-primary→stroke-white,tile-md/lg尺寸與 resolved 打勾態的手刻邏輯不變base/input/input-number.tsx:拿掉對本地Button的依賴(水平排列的加減按鈕原本靠本地Button color="tertiary",但真 olab Button 不支援 React Aria 的slot機制,改成跟垂直排列一樣直接用AriaButton),欄位外框從outline-primary/outline-error換成 Live 的outline-gray-1/outline-red(對齊@olab/components真 Input 自己的border-gray-1/border-red做法)trade-panel.tsx:Buy/Sell 切換從本地application/tabs換成真的@olab/componentsTabs(variant="underline"),兩個 tab 皆傳content: null因為面板內容本來就在外部依modestate 分岔渲染,不經過 Tabs 的 content slot。本地application/tabs.tsx本身仍被market-feed-layout.tsx使用,不在本輪範圍,留給 Phase 3- 已用 preview 驗證:
/sports、/everything-cds、/trade-panel、/outcome-action四頁,YES/NO 與五個 team family 的 selected/unselected computed style 逐位元吻合 style.css 十六進位值;InputNumberBase 欄框色rgb(33,41,49)=gray-1;Tabs 點擊 Sell 後 CTA 文字正確從 Buy 變 Sell,無 console error,tsc --noEmit無新增型別錯誤(僅既有@olab/componentsmemo() 型別缺口那個既知類別) - 本輪不動(留給 Phase 2 以後):15 個產品元件本體逐一換 token class、
buttons/button.tsx/progress-indicators.tsx消費站能否直接換真元件、5 個真頁面收尾
產品元件禁用 Claude DS(Phase 0):Avatar/ButtonUtility/LiveIndicator/Badges 重建為零 theme.css 依賴
User 拍板:產品元件(src/components/product/* 及其依賴的 base/application 元件)不得依賴任何 Claude DS 的東西(theme.css token、本地手刻邏輯)——有 @olab/components 對應真元件就用真元件,沒有對應物就重建成只依賴 Live DS token(packages/configs/style/style.css)的版本。範圍確認:只限產品元件這條線,30+ 個 Claude DS 純展示頁與 theme.css 本體不在這次範圍內。三個並行 agent 查完 style.css 完整 token 清單、15 個產品元件逐一的 Claude DS 依賴、@olab/components 完整能力盤點(含 4 個真缺口:Avatar/icon-only 按鈕/live 狀態點/通用 Badge)後,這次先做 Phase 0——重建這 4 個真缺口對應的本地元件,全部改用 Live token,preview 驗證 computed style 逐位元吻合。
base/avatar/avatar.tsx+ 4 個 base-components 子檔案(online-indicator/verified-tick/count/company-icon):text-quaternary/bg-tertiary/ring-secondary_alt等 theme.css token 全部換成 Live 對應角色(text-gray-3/bg-white-1/ring-white-2/bg-green-1/bg-red等),根節點包.live-scope。avatar-add-button.tsx因為拉進整條 Tooltip 依賴鏈且無任何產品元件會用到,本輪不動base/buttons/button-utility.tsx:改用 Live token(bg-white-1/text-gray-3/outline-brand-main),因為tooltipprop 會拉出base/tooltip/tooltip.tsx,一併把 tooltip 氣泡的bg-primary-solid/text-tooltip-supporting-text換成 Live 的bg-dark/text-gray-3base/status/live-indicator.tsx:text-live-primary/bg-live換成 style.css 裡專門為此存在的--color-red-live(text-red-live/bg-red-live)。連帶把--animate-live-ripple這個 keyframe 動畫從theme.css搬到live-bridge.css(Live DS 整合檔),因為它只被這個重建後的元件使用,留在 theme.css 裡等於元件仍間接依賴該檔案base/badges/badges.tsx:只修正Badge元件type="color"會用到的 4 色(gray/success/warning/error,MarketCdsRow/HeroCard 風險等級 chip 用這幾色)——換成 Live 的white-1/green-1/yellow/red(配 Tailwind v4 的/10、/30透明度修飾語法),根節點包.live-scope。其餘 9 色與BadgeWithDot/BadgeWithIcon等變體只有 Claude DS 展示頁在用,不在本輪範圍,維持原樣- 已用 preview 對照
/sports、/everything-cds、/trade-panel三頁的 computed style 逐位元驗證:LIVE 文字rgb(235,29,20)=#EB1D14、風險等級 badge 四色(A+/B/C/F)分別吻合green-1/gray-3/yellow/red、Avatar 預留底色rgb(27,34,41)=white-1、ButtonUtility 文字色rgb(139,148,158)=gray-3,皆與 style.css 定義的十六進位值逐位元一致,無 console error - 本輪不動(留給 Phase 1 以後):
outcome-action/input-number/application/tabs三個資料模型不相容的元件重建、15 個產品元件本體逐一換 token class、5 個真頁面收尾
全 repo 稽核 Claude DS 指向問題:補上根目錄 README + CLAUDE.md 剩餘兩處,跟另一個並行 session 的修正合併
User 稽核發現「文檔一直指向 Claude DS,但 Claude DS 根本沒在用、也不可以用」,要求全 repo 稽核。查證時發現有另一個並行 session 已經在同一時間處理了幾乎同一件事(見上一筆「修正子資料夾 CLAUDE.md」log,已先行 merge):子資料夾 CLAUDE.md 的 Source of Truth/Figma/Product Components 組合規則、以及 trade-panel.tsx 的 quick-amount/CTA/兩個 toggle 換真 @olab/components Button/Switch,都已經在那筆變更完成並 preview 驗證過。這次 rebase 合併時保留那筆的 trade-panel.tsx 版本(含 CTA 一併換成真元件,覆蓋了本 session 原本『CTA 尺寸/色系無對應、不換』的保守判斷),本 session 的獨有貢獻收斂為:根目錄 README.md(先前唯一還沒人修的文件)+ 子資料夾 CLAUDE.md 的 Page Building Rules 步驟 1、Pattern Mapping 表逐列補「先查 @olab/components」欄位(兩處都是另一 session 沒動到的段落)。另外發現一筆 project memory 曾記載「trade-panel.tsx 已換成真 olab 元件」但當時從未真的 commit,是本次稽核起點之一,該記憶已於 memory 系統修正。
- 根目錄
README.md:repo 結構註解與核心規則段落,把「theme.css = 設計 token 的 single source of truth」改成跟根CLAUDE.md一致的雙系統描述(Live DS 現行事實來源 / theme.css 為 Claude DS 探索層) untitledui-nextjs-starter-kit/CLAUDE.md:Page Building Rules 步驟 1 補「先查 @olab/components」;Pattern Mapping 表逐列新增「先查 @olab/components」欄位,標明哪些 pattern 已有真元件對應、哪些目前仍只能手刻trade-panel.tsx:與另一 session 的變更 rebase 合併,最終保留該筆版本(quick-amount/Max/CTA 按鈕與兩個 toggle 皆為真@olab/componentsButton/Switch,整個面板包一層.live-scope);移除本 session 原本額外加在個別元素上的多餘巢狀.live-scope(父層已經包了,巢狀是 no-op 雜訊)- 本輪不動:全面 token migration 執行(5 個 Claude DS Pages + 其餘 14 個 Product Components 轉正、
:root反轉為 Live 架構預設)、market-cds-row/everything-cds等檔名 cds 命名清理、nav-sections.ts分類重整——需要更大工程量,留給下一輪
MatchCard team 色系未選中狀態 + Game View 品牌橘字:對照線上實測修正
User 質疑 MatchCard 顏色是否跟線上一致,開真的 app.opinion.trade/sports 逐一 getComputedStyle 實測後,確認 2 處落差並修正:team-home/away/draw 未選中狀態的文字先前用了變淡的色階、線上其實維持錨點原色;team-home/away/draw 未選中背景的暗色調(-950 階)用 HSL ladder 公式生成、跟線上實際色值有感知得到的差距;Game View 品牌橘字踩到 light/dark mode 語意 token 內部不一致(light 早就對到 -600,dark 誤指到 -500)。三處都用線上量到的精確 RGB 值修正,逐一 preview 覆核,非新設計決策。
theme.css深色模式--color-text-team-{home,away,draw}-secondary:從 anchor 的 -300(變淡階)改指回 -500(錨點本色),對齊線上『未選中文字=跟選中同一個顏色,只有背景變暗』的實際做法theme.css--color-team-{home,away,draw}-950:從 HSL ladder 公式生成值改為線上實測值(home rgb(48,49,34)、away rgb(29,49,65)、draw rgb(52,62,59)),僅動這一階,未動 -975(無線上量測數據,保留原樣);有連帶引用此階的text-on-team-{home,draw}-solid(選中態上的深色字)與 deprecatedoutcome-a-950別名,變動幅度極小(同樣是很深的顏色)、已 preview 確認選中態對比度沒有劣化theme.css深色模式--color-fg-brand-primary:從--color-brand-500改為--color-brand-600。根因不是 anchor 錯——light mode 本來就對到 -600(跟線上 rgb(237,100,50) 精準吻合),只有 dark mode 這行寫錯指到 -500,是 light/dark 內部不一致的既有 bug,非新的視覺對齊決策;影響範圍含其他 14 個消費此 token 的元件(featured-icon、sidebar-nav-tree、progress-indicators、dropdown、pin-input 等),已用 preview 抽查/sports、/colors兩頁確認無 console error、無視覺劣化- 三處修正皆用 app.opinion.trade 實際頁面 getComputedStyle 量測值驗證吻合(非估計),已用本地 preview 覆核 selected/unselected 兩種狀態的 computed style 逐位元核對
修正子資料夾 CLAUDE.md「theme.css = 唯一真相」的舊規範,改為 Live DS 優先
User 質疑近三天新做的 sports/match-card/button 為何蓋在 Claude DS 而非 Live DS。查證後發現根因不是選錯,而是 untitledui-nextjs-starter-kit/CLAUDE.md 本身明文寫著『this codebase 的 component library 是唯一真相』『組合規則=由 base/application components 組成』,完全沒提 @olab/components——只要這份文件不改,任何 session 蓋新頁面都會被引回 Claude DS。已修正文件本身,並在能力範圍內把三天內更新的元件換成真組件。
CLAUDE.mdSource of Truth 節:改為『正式來源=packages/configs/style/style.css+@olab/components;theme.css/base/application components=探索層,用於 olab 尚無對應元件時的暫時骨架』,並訂出組件選用優先序(olab 有對應原子元件→優先用,包.live-scope;沒有→才 fallback 手刻)CLAUDE.mdFigma as Reference Only 第 3 點、Product Components 組合規則:同步改為『有 olab 真組件時 Live DS 優先』,不再要求一律 base/application 組成trade-panel.tsx:quick-amount 按鈕(10/50/100/Max)與 CTA 按鈕改用真@olab/componentsButton(skin="secondary"/"brand")取代本地手刻Button;Set Expiration / Slippage Tolerance 兩個開關改用真Switch(checked/onCheckedChange)取代本地Toggle;根節點加live-scopeclass 讓 Ethan 的 role-based token 正確解析。已用 preview 實測:quick-amount 按鈕 computed style 確認吃到--color-secondary-main,Switch 切換後正確顯示bg-brand實色,無 console error- 查證後明確判斷『無法/不宜換真組件』的部分,予以保留,未強行替換:
OutcomeAction(yes/no/team 圓角按鈕)——real Button 的 yes/no/sportTeam skin 只覆蓋 sm/lg 這一種用法,換掉會讓 MarketCdsRow/MarketCdsHeroCard 用到的 tile-md/tile-lg 尺寸與 resolved(已結算打勾)模式失去對應,屬於跨元件回歸風險,未動;Tabs(Buy/Sell 切換)——真 Tabs 要求整包tabs:[{value,content}]陣列一次傳入,TradePanel 現在是『同一份表單依 mode 分岔』而非『每個 tab 各自內容』,換了等於重寫資料流,未動;InputNumberBase(Amount/Limit Price 輸入)——真Input是通用 Field,沒有內建加減號 stepper,也不是數值 callback API,換了會讓現有 UX 消失,未動;ButtonUtility(設定齒輪 icon-only 按鈕)、Avatar、LiveIndicator、PromoBanner/SidebarNavTree內的手刻可點擊元素——olab 目前完全沒有對應元件可換
3-way 賽事的下單面板其實是獨立 Yes/No 子市場,不是共用隊伍色 selector——重新架構 TradePanel 資料流
User 目視點出 4 個問題,逐一查證後修正 3 個小 bug,並發現 1 個真正的架構落差:線上 3-way 賽事(有 Draw)點擊任一 pill 後,Bet Slip 顯示的其實是該選項自己的獨立 Yes/No 二元市場(如「Portugal wins」Yes/No,Outcome Semantic 綠紅色),只有副標題用隊伍色標示是哪個選項;2-way 賽事(無 Draw)則維持隊伍色按鈕直接下單,因為兩邊本身就是互斥的完整市場。這跟先前『3-way 用同一個隊伍色 3 選 1 selector』的假設不同,已重新設計資料流對齊。
sidebar-nav-tree.tsx:修正『All』列——先前誤把它的紅字跟『選中』邏輯綁在一起,這次確認它的顏色其實純粹來自 live-badge 語意(text-live-primary,綁isLiveprop)跟選中與否無關,『All』永遠不會有選中底色塊。移除誤判的rowSelected/labelSelected綁定outcome-action.tsx:fixedWidth模式(目前唯一消費者是 MatchCard 隊伍按鈕)額外套用text-sm! font-bold!(14px/700),對齊線上實測值;未加fixedWidth的既有呼叫點(MarketCard 等)字級字重完全不變- 新增
TradePanel的subtitle?: ReactNodeprop,渲染在 title 下方一行,顏色由呼叫端決定(TradePanel 本身不需要認識 Team Identity 色系) page.tsx重新設計資料流:新增SubmarketKey(home/draw/away)與activeSubmarketstate;buildTradeOutcomes()改為依match.draw是否存在分岔——無 draw 維持原本的隊伍色 2 選 1;有 draw 則回傳outcome: "yes"/"no"的二元市場,YES 價格取該選項自己的probability;新增buildTradeSubtitle()依 submarket 回傳對應隊伍色的副標題(「{home} wins」/「Draw」/「{away} wins」);selectOutcomeFor/activateMatch依match.draw分岔設定activeSubmarket+selectedOutcomeId="yes"(3-way)或直接設selectedOutcomeId=隊伍id(2-way);新增rowSelectedId()讓 MatchCard 列上的 pill 高亮繼續正確反映activeSubmarket(即使 TradePanel 內部的selectedOutcomeId現在對 3-way 賽事來說是 "yes"/"no" 而非隊伍 id)- 已用 preview 實測驗證:3-way 三顆 pill(home/draw/away)各自點擊後 TradePanel 正確顯示對應顏色副標題 + YES/NO 綠紅按鈕;2-way 賽事點擊後維持原本隊伍色按鈕、無副標題;兩種情境切換互不影響
- 本輪明確不動:team-away unselected 按鈕顏色跟線上不同(anchor 已對齊、只有 tint 深淺不同)——依 user 指示維持現狀,不做 palette 重算,因為改 unselected tint 而不重算整條 scale 屬於 CLAUDE.md 明文禁止的 shortcut
live-fidelity-audit skill 首次實跑:4 個平行 agent 抓到 outcome 按鈕寬度未鎖定等根本問題,逐項修正
依新建的 live-fidelity-audit skill 對 /sports 跑第一次正式四維度稽核(顏色/字體/組件/spacing+sizing)。核心發現:outcome 按鈕(隊伍縮寫/Draw)寬度未鎖定,隨文字內容伸縮,線上是固定 120×32px——這正是這個 skill 為了避免再犯而建立的那類 bug,第一次跑就抓到且已復現確認。另外抓到 2 個組件邏輯 bug、9 個字體絕對值落差、sidebar 選中態視覺語意(先前判斷有誤,這次用實際點擊測試定案:選中=橘字+底色塊,不是純文字變色)。
- 新增
OutcomeAction/OutcomeSelector的fixedWidthopt-in prop(outcome-action.types.ts、outcome-action.tsx、outcome-selector.tsx)——sm size 加h-8 w-[120px],預設false完全不影響既有呼叫點(/trending、MarketCard的 YES/NO content-sized 按鈕維持原樣,已用 preview 逐頁核對無回歸)。match-card.tsx傳入fixedWidth修正隊伍按鈕,實測跨 2-way/3-way 多組不同字數的按鈕皆為 120×32px sidebar-nav-tree.tsx:修正選中態視覺——先前(2026-07-01)誤判「選中=純文字變橘、無底色」,這次用 click-through 直接測試(點 Basketball 觀察前後)確認正確行為是「選中=橘字 +bg-secondary底色塊」,恢復rowSelectedclass;同時把 sport 名稱字級從text-sm font-semibold(14px/600) 改text-bodyL font-bold(16px/700)、count 改text-bodyS(12px),對齊線上實測絕對值match-card.tsx:Avatar 國旗從size="sm"(32px) 改size="xs"(24px,最接近線上實測 26px 的既有 token 階);Game View chip 字級text-sm(14px) 改text-bodyS(12px)trade-panel.tsx:標題字重font-semibold(600) 改font-bold(700,與上一輪 MatchCard 隊名字重同一類錯誤,這次在 TradePanel title 復發並修正);Est. Fee 數值字級 12px 改text-bodyM(14px),並修正金額為 0 時的顯示邏輯(原本印字面值0.00 $,現改用既有DASH常數顯示—,對齊線上空狀態格式)page.tsx修正 2 個邏輯 bug:(1) 新增activateMatch()統一處理「切換作用中賽事」,Game View 點擊時一併重設selectedOutcomeId為新賽事的 home 方,修正先前選取狀態跨賽事殘留的問題 (2) 新增DEFAULT_MATCHfallback chain(Live Now 為空時退回第一個日期分組的賽事),activeMatch型別改為| undefined、TradePanel 改條件渲染,修正LIVE_MATCHES為空陣列時會直接崩潰的問題;SectionHeader的isLive改用LIVE_MATCHES.length > 0動態綁定(原本寫死true)- 本輪明確保留、未動工的決策點(詳見對話紀錄,需 user 裁示):team-home/away/draw unselected 色階與線上不同(selected 態 anchor 完全命中,需整條 scale 重算,非單點可改);team-away selected 文字對比度實測未達 AA(與 CLAUDE.md 文件宣稱的比值不符,需重新核算);Tab bar/MatchCard outcome 按鈕字級/TradePanel Total-ToWin 數值層級等多處字體落差,根因是 base utility token 尚未遷移到 product semantic token,屬於範圍較大的 token 遷移工作,未在本輪順手做掉;TradePanel 內邊距 p-4 vs 線上 p-5——沿用 2026-07-01 已做的決定,維持 CLAUDE.md Card Pattern 的 p-4,不追線上數值
Sports 頁三個目視發現的缺陷 + 根因:驗證方法論漏洞、預設主題未鎖定 dark
User 目視發現 3 個先前 sub-agent 驗證漏抓的缺陷。逐一查根因後修正:全站未鎖定 dark 為預設主題(導致依瀏覽器系統色彩偏好隨機顯示淺色,讀起來像「顏色全錯」)、MatchCard 隊伍名稱字重量錯(驗證時只查相對層級沒查絕對數值)、MatchCard outcome 按鈕排列方向寫錯且早有反證數據卻沒回頭修(two-way 誤寫成直排,線上不論二/三選項永遠橫排)。
src/providers/theme.tsx:ThemeProvider加defaultTheme="dark"+enableSystem={false}。Root cause:Opinion Trade 是 dark-canonical 產品,但本專案 ThemeProvider 先前只給enableSystem,會跟隨訪客瀏覽器/OS 的色彩偏好決定初次渲染主題;訪客系統若是淺色偏好,頁面會整頁套用淺色 token 開啟,跟記憶中永遠深色的正式站放在一起看『顏色明顯不一樣』,但其實每個 token 本身沒錯,只是被錯的主題模式渲染。此為全站設定變更,非只影響 /sportsmatch-card.tsxTeamRow 隊伍名稱字重:font-medium(500) 改font-bold(700)。Root cause:字體驗證 agent 當時只確認『比聯盟文字大、比聯盟文字深』的相對層級關係就判 PASS,沒有對線上做同元素的絕對數值量測(線上實測Anna Blinkova為font-weight:700)match-card.tsxOutcomeSelector 的layout從isThreeWay ? "pair-horizontal" : "pair-vertical"改為固定"pair-horizontal"。Root cause 有兩層:(1) 建構初期只憑一張截圖粗略判斷『兩隊時直排』,未經嚴謹量測 (2) 後續在同一 session 內已經量到反證(SNIG/JEAN兩顆按鈕flex-direction:row、同一 y 座標、寬度加總對得上父層),卻沒有把這筆自己蒐集到的證據拿回去對照並修正 component——這是沒有把已知證據跟既有實作做交叉檢查- 四維度驗證 sub-agent 的方法論記錄成教訓(供未來驗證任務參考,非本次程式碼變更):顏色/字體驗證需固定並明確記錄所在的 color-scheme(light/dark)再下結論,不能隱含假設單一模式;字體驗證『數值型』欄位(如 font-weight)必須直接對線上同元素量測絕對值,不能只驗相對大小關係就判 PASS;組件結構驗證除了『元素是否存在』還必須驗證『排列方向/flex-direction 是否一致』,欄位存在不代表排法對
Sports 分類頁落地:新增 MatchCard / PromoBanner Product Component,OutcomeAction 擴充 Team 色系
依 category-page.md 版型 B(賽程文法)重建 /sports 頁,對照線上版逐項核實。新增兩個 Product Component(MatchCard、PromoBanner)、擴充 application/app-navigation 一個新 variant(SidebarNavTree),並擴充共用 base 元件 OutcomeAction/OutcomeSelector 支援 team-home/away/draw 色系(原本只認 outcome-yes/no)。
- 新增
src/components/product/match-card/match-card.tsx— 賽程列 Product Component,two-way(含比分)/ three-way(含 Draw + %)由draw是否存在驅動,非獨立 variant prop;顏色一律 Team Identity,不用 outcome-yes/no - 新增
src/components/product/promo-banner/promo-banner.tsx— 滿版寬活動 CTA 橫幅,fluid width + 固定 aspect-ratio;標題燒進 imageUrl 圖檔本身(不疊加 DOM 文字,避免窄欄位時二行標題被裁切) - 新增
src/components/application/app-navigation/sidebar-navigation/sidebar-nav-tree.tsx— 可展開的分類 filter tree variant(sport → 子聯盟 + 數量),與既有 sidebar-simple / sidebar-dual-tier 並存,不共用同一份邏輯(NavList 的 collapsible 只能 toggle、不能同時導頁) - 擴充
src/components/base/outcome-action/outcome-action.tsx+outcome-action.types.ts—OutcomeType由"yes" | "no"擴為含"team-home" | "team-away" | "team-draw",六層 state(unselected/selected × base/hover/pressed + loading/resolved)全數補齊 team 對應 token;OutcomeSelectorItem.outcome型別同步放寬。此變更為 additive,既有 yes/no 呼叫點行為不變 - 新增
src/app/sports/page.tsx— 組出完整頁面:category tab bar(Sports 固定選中)、SidebarNavTree、H1 + Games/Props pill、PromoBanner、Live Now + 日期分組 MatchCard 列表、TradePanel 常駐右欄(selectedOutcomeId 與 MatchCard 共享同一份 selection state) - 新增
src/app/match-card/page.tsx、src/app/promo-banner/page.tsx兩個 Product Component preview 頁(Interactive Demo + State Matrix),並登記進src/content/nav-sections.ts(Product Components 與 Pages 兩個 group) - CLAUDE.md Allowed Product Components List 補上 MatchCard / PromoBanner 完整規格列
- Scope 決策記錄:頂部 Global Nav 沿用既有 repo 慣例(/trending、/esports 皆不在 page.tsx 內重複渲染 HeaderNavigation,交給 docs AppShell),本頁跟進不額外渲染
- 【線上逐項核實修正】對照 app.opinion.trade/sports 實測後修正:(1) MatchCard 比分只在 status=live 顯示(原本誤綁 two-way/three-way,改綁 live/upcoming)(2) Game View 從 link-gray 按鈕改為 bg-fg-brand-primary/15 + text-fg-brand-primary 的藥丸 chip (3) 國旗 Avatar 從 rounded=false/xs(24px) 改為 rounded/sm(32px) (4) SidebarNavTree 選中態從 bg-secondary box 改為純文字變色(text-fg-brand-primary),hover 才有 box,並在 All 列下方加 divider (5) /sports 頁預設選中每場的 home 方(不再是 null),對齊線上『bet slip 永遠有一方預選』的行為
- 【TradePanel 補齊 limit 分支】實測發現 orderType='limit' 時線上多兩個欄位:Limit Price(¢ 單位,獨立於 Amount)與 Set Expiration toggle(取代 Slippage Tolerance,market 才顯示 Slippage)。已在 trade-panel.tsx 新增
isLimitOrder分支補齊,market order 既有行為(YES/NO CDS demo、Slippage Tolerance)完全不變,純 additive,已用 /trade-panel preview 頁驗證兩種 orderType 切換正常 - 【四維度 sub-agent 驗收(顏色/字體/組件/spacing)後再修正】(1) MatchCard 從無背景分隔列表列改成真正的卡片(
rounded-xl border border-secondary bg-secondary p-4,對齊 CLAUDE.md Card Pattern 的 p-4 規則,且卡片間 gap-4,與線上量測的 16px 卡片間距/內邊距一致)(2) 三欄版面 gap 從 24px 改為 40px(gap-6→gap-10)、左側 filter tree 寬度 224px 改 200px、TradePanel 欄寬 360px 改 308px,皆為線上實測數值 (3) 分類 tab bar 內 gap 12px 改 16px;H1 列到 PromoBanner 的間距 16px 改 32px(gap-4→gap-8);PromoBanner 到分組標題間距 16px 改 24px(gap-4→gap-6);分組標題到第一張卡片原本 0px,補上 16px (4) H1 從 Tailwind 內建text-2xl改成本專案 product semantic tokentext-h1(渲染值相同 24px/700,但走對 token 路徑),並加shrink-0 whitespace-nowrap避免與旁邊 Games/Props pill 搶版位時斷行 (5)TradePanel新增 optionaltitleprop,/sports頁傳入"{home} vs {away}",補齊線上『下單面板上方永遠顯示對戰雙方名稱』的缺漏 (6)MatchCard的 Game View chip 補上onGameView實際 callback(原本/sports頁組裝時完全沒接,是個靜默死按鈕),行為暫定為把該場切換為 TradePanel 的作用中賽事 - 【本輪明確不修的項目(design decision 或需要 Phase A,非本次擅自判定)】Games/Props 分類 tab 的 Props 分頁目前無對應內容(正式站 Props 是 market-card 格線,跟 Games 的賽程列表是完全不同 UI pattern,需要另開 Phase A component mapping,不能直接生 code);分類 tab bar 選中態的顏色強度(本地
bg-brand-primary_alt中性色 vs 線上純橘實色)是既有 dark-mode token 的刻意選擇,是否改回 solid 橘屬 design decision;選中賽事列展開的 Order book/Spread/Liquidity Rewards 資訊列,超出原始 Phase A 範圍,先記錄不動工 - 【修正後再驗一輪,補兩項密度落差】三欄佈局/主欄垂直節奏/H1/TradePanel title 等前一輪修正皆已確認對齊線上。新發現並修正:(1)
sidebar-nav-tree.tsx的rowBase從py-2.5(撐出 40px 不均一列高、列間無 gap)改為固定h-8(32px)+px-2,並在 nav 容器與子清單加gap-1(4px),"All" 列的分隔從疊加border-b+mb-1+pb-3改成獨立<hr className="my-1 h-px w-full border-none bg-border-secondary" />——對齊線上『均一 32px 密集列表 + 4px 列距』的節奏,而非之前鬆散不均一的 40/43px (2)match-card.tsx兩隊之間垂直間距gap-1.5(6px)改gap-2(8px),對齊線上量測值。兩項皆已用 preview 實測確認(row height=32px、row gap=4px) - 【明確不跟進的一項】MatchCard/TradePanel 的卡片內邊距,此輪驗證量到線上是
p-5(20px) 而本地是p-4(16px)。**刻意維持p-4**——CLAUDE.md 的 Layout & Spacing Rules 明文規定 Card Pattern 固定用p-4,這是 repo 既有治理規則而非本次隨意判斷;若要改需 user 明確裁示要不要動這條規則,不由 Claude 逕自對齊線上數值
Worktree preview QA 一律使用 explicit worktree launch entry
在 worktree 內跑 preview 驗證時,如果 launch config 指到 main checkout 而不是 worktree,server 會默默跑起來、回 200 OK、log 看起來正常,但實際吃的是 main 的 source — 你改的東西完全沒被 verify。本次踩坑:foundation preview 新增後持續 404,debug 半天才發現 worktree 層級的 .claude/launch.json 裡 'Next.js Dev (Turbopack)' entry 的 runtimeArgs 寫死 cd 到 main checkout 路徑。Rule:未來在 worktree 做 preview QA,一律 launch worktree-explicit entry,不要假設 default 跑的是當前 worktree code。
- Symptom:worktree 改完 source、起 preview、所有新路由 404;/live/button 也 500(module not found)。Restart server、清 .next cache、touch 檔案、加 index page 全沒用。表面上看像 Turbopack 沒掃到深層目錄或 @ark-ui 模組缺漏的雙重問題
- Root cause:worktree 層級的 .claude/launch.json(git ignored 不會進 commit)的 'Next.js Dev (Turbopack)' entry 寫死
cd <main checkout>/untitledui-nextjs-starter-kit && npm run dev— 那是 main checkout 路徑,不是當前 worktree。server 啟動成功但讀的是 main code - 驗證盲點:preview tool 回的 200 / 404 / 500 都是針對 server 實際讀的 code,與 worktree 改動無關。Visual screenshot 看起來是 'design system' 沒錯,因為 main 也是同一份 design system,只是缺新加的頁
- Rule:worktree preview QA 一律新增或使用 worktree-explicit launch entry(runtimeArgs 寫死 cd 到當前 worktree 的 starter-kit 路徑)。不可信任 default 'Next.js Dev (Turbopack)',因為它可能繼承自 main checkout 的 template
- 本次處理:worktree 層 .claude/launch.json 新增 'Next.js Dev (Worktree)' entry 並驗證指向正確路徑。原 entry 保留不動。worktree-level launch.json 在 .gitignore 名單(.claude/launch.json)不會進 commit
- 額外踩到:debug 過程中曾誤判 Turbopack 沒掃到 src/app/live/foundations/ 深層目錄,加了 foundations/page.tsx 當 index 強制觸發 — 換 launch entry 後確認 Turbopack 沒問題、該 debug 檔已在 commit 前清掉。同期間誤加進 package 內 .claude/launch.json 的 'Next.js Dev (Webpack)' entry 也 git restore 還原
- Generalization:worktree 內任何吃 cwd 的工具(preview server、test runner、build script)都要先確認 cwd = worktree 而非 main。否則 validation 結果不可信
- Template 隱憂(記錄,不在此次處理):worktree-level .claude/launch.json 是 untracked,每個新 worktree 從 template 複製。若 template 仍寫死 main 路徑,每次新 worktree 都會中招。未來可考慮把 template 內的硬編碼路徑改為 worktree-relative 或加 worktree-explicit entry 進 template
Live Mirror 擴展:補齊 @olab/components 剩餘 20 個元件的 /live preview 頁
把 Live Mirror baseline(2026-05-15 commit 46ec931)只有 Button / Card / Typography 三個 preview 的狀態,一次補齊到 23 個。20 個新頁全部走同一份範本:使用 ComponentPage / Section / SubSection 做頁面 chrome(Claude DS semantic token),展示區直接 import 自 @olab/components 並在 .live-scope 內 render(Ethan flat token 自動 resolve)。每頁同時含 Interactive Demo + Static State Matrix,符合 Preview Page Completeness Rule。不動 packages/ 任何檔案、不動 theme.css / live-bridge.css、不動 Claude DS 既有元件與 preview,純粹「補頁」工作。
- 新增 20 個 preview 頁(src/app/live/<slug>/page.tsx):checkbox、switch、input、select、pin-input、date-picker、filter、spinner、skeleton、progress、toast、modal、popover、tooltip、menu、tabs、navigation、pagination、layout、table。slug 一律 kebab-case,與既有 button / card / typography 同層級
- 頁面 chrome 規則:使用 ../../_shared 的 ComponentPage / Section / SubSection,配合 text-primary / text-tertiary / text-secondary / bg-secondary / border-secondary 等 Claude DS semantic token。展示區內部 Ethan 元件用自身 className(text-bodyM、text-gray-3 等)是允許的,因為被 .live-scope 包住會 resolve 到 Ethan token
- Import 路徑:全部走
@olab/components(tsconfig path alias → packages/components/dist/index.js)。禁止直接 import packages/...,維持 mirror 邊界 - 每頁結構:Interactive Demo(含 useState、行為驗證)+ 多個 Static State Matrix(變體 / 尺寸 / 狀態 snapshot)。Toast 頁額外 render <Toast /> portal;Modal 頁用 useState 控制三組 open/close 變體;Table 頁包含 sortable、selectable、loading、empty 四種狀態 demo
- Skipped 6 個元件(沿用 baseline 決策):Icon / BusinessIcon / AssetIcon / CryptoIcon / ChainIcon / ClearableSelect — 它們依賴 Vite svgr 的 ReactComponent 語法,Next.js 需先設 @svgr/webpack loader,屬於另一個 task。/live 入口頁標 Icon / Image 為 todo(其餘 6 個 SVG 元件不個別列出,避免 noise)
- src/content/nav-sections.ts:在 'Live Mirror' / '@olab/components' group 的 items array 加 20 筆,每筆含 slug / label / importPath / onThisPage,nav 側欄即時可用
- src/app/live/page.tsx:把 20 個元件從 status: "todo" 改成 "live"。/live 入口卡片網格現在 23 live + 2 todo(Icon / Image),todo 卡片保留 opacity-60 視覺降階
- Constraint 遵守:完全沒動 packages/ 內任何檔案、沒動 src/styles/theme.css / typography.css / live-bridge.css、沒動 src/components/base/ 或 application/ 或 product/ 任何元件、沒動 Claude DS 既有 preview 頁。Live Mirror 是 production source of truth,這次工作只是把它的可瀏覽 surface 補完
- Verify:npm run dev 起 dev server,curl http://localhost:3000/live/<each-slug> 全部 200;spot-check Claude DS /button 與 /colors 維持 200、HTML 不含 .live-scope 與 Ethan flat token(如 bg-brand-main / text-bodyM 等),證明 token 隔離無洩漏
- Out of scope:6 個 SVG-blocked 元件 preview(需先設 @svgr/webpack);Storybook 完整重現;packages/ 任何修改;新增 Claude DS token 或 component;Ethan API 改寫為 Claude DS 慣例(kebab-case file、prop 改名等,Ethan 是 source of truth 不能動)
Live Mirror 整合:Ethan @olab/components 進駐,與 Claude DS 雙軌並存
把 Ethan 同事提供的線上 production 元件套件(@olab/components)以 read-only mirror 方式整合進 worktree,作為當前 production / dev contract 的 source of truth。Claude DS 的 token 系統、preview 頁、component 庫完全不動,繼續扮演 future direction 角色。兩邊透過 .live-scope CSS 隔離 + token bridge 共存。新增 Live Mirror 分類於 sidebar,首批包含 Button / Card / Typography 三個元件 preview,其餘 22 個元件標記為 todo 待後續展開。
- 新增 untitledui-nextjs-starter-kit/packages/ 目錄,為 Ethan packages.zip 的整包鏡像(不含 node_modules)。放在 starter kit 內部而非 worktree root,是為了讓 style.css 的
@import "tailwindcss"能透過 Node 解析鏈走到 starter kit 的 node_modules。components/ 含 26 個 base + 4 個 business icons 的 source + dist;configs/style/ 含線上實際使用的 style.css。原則:mirror read-only,未來 Ethan 更新只需整包覆蓋這個資料夾,starter kit bridge 不變 - Mirror 的唯一 patch:packages/components/.gitignore 拿掉
dist那一行。原因:Ethan dev repo 把 dist 排除是因為他們 CI 會重 build;我們是 vendoring 場景,dist 是 import 目標,必須進 git。.gitignore 是 dev 設定不是元件 API 一部分,這條 patch 不影響任何 runtime 行為。未來 Ethan 更新 packages/ 時要記得重複這個一行刪除 - token collision 稽核:兩邊只有 5 個 key 撞名(--color-black、--color-white、--shadow-sm/md/xl)。額外發現 Ethan style.css 用
--color: initial清掉 Tailwind 預設色,會影響 Claude DS 5 處使用預設色的地方(rating-stars / credit-card decorative blobs)。透過 live-bridge.css 處理兩件事 - 新增 src/styles/live-bridge.css:import Ethan style.css → 在 @theme 補回 5 個 Tailwind 預設色 token(blue-500、green-500、orange-500、pink-500、yellow-400)→ 在 :root 用 Claude 值還原 5 個撞名 token → 在 .live-scope 用 Ethan 值重新覆蓋。CSS 變數 cascade 確保 Ethan 元件只在 /live 路由下看到自己的 token 值
- src/styles/globals.css:在 theme.css / typography.css 之後 @import live-bridge.css;加 @source 指令讓 Tailwind 掃描 packages/components/dist 取得 utility class
- src/providers/theme.tsx:新增 OpinionThemeAttributeBridge — 把 next-themes 的 resolvedTheme 同步寫到 <html> 的 data-opinion-theme attribute,讓 Ethan 元件能跟著 light/dark toggle。Claude DS 的 .light-mode / .dark-mode class 機制不動
- tsconfig.json:新增 path alias
@olab/components→./packages/components/dist/index.js。原因:Ethan 的 packages/components/package.json 寫的 main/module 路徑(index.umd.js / index.mjs)跟實際 dist 檔名(index.umd.cjs / index.js)對不上,直接走 package exports 會 resolve fail。用 path alias 繞過、保持 mirror 不動 - package.json:新增 runtime deps @ark-ui/react ^5.18.2、@internationalized/date ^3.8.2、classnames ^2.5.1(Ethan 元件依賴)。motion / tailwindcss 已存在
- 新增 /live 路由:src/app/live/layout.tsx 套 .live-scope 包;src/app/live/page.tsx 為入口列出 26 個元件;先實作 button / card / typography 三個元件頁(每頁含 Interactive Demo + Static State Matrix,符合 Preview Page Completeness Rule);其餘 22 個顯示為 todo
- src/content/nav-sections.ts:擴展 NavCategory union 加 'Live Mirror',加進 NAV_CATEGORY_ORDER,新增兩個 nav section(About + @olab/components 群組)
- Out of scope(不在這次整合):Icon / BusinessIcon / CryptoIcon / ChainIcon / AssetIcon(依賴 Vite svgr 的 ReactComponent 語法,Next.js 需另設 @svgr/webpack loader,後續再說);其他 22 個元件 preview 頁;Storybook;packages/ 的 React 18 peer warning
- long-term:Ethan version = current production contract / source of truth;Claude DS = future direction / governance / token evolution。兩者並存,不互相覆蓋,未來合流方向待後續討論
Base layer typography 治理:base / foundations contract surface 不導入 product semantic token (h*/body*)
Base components typography audit 結論 — base layer 維持既有 text-xs / sm / md / lg / xl + text-display-* scale,**不**導入同日新增的 product semantic token (text-h1 / h2 / h3 + text-bodyXl / L / M / S)。理由為三:(1) base = 全站共用契約 surface,覆蓋 marketing / admin / settings 等非 product 場景;(2) base 元件 typography 與 size variant 強綁(sm button = text-sm 是 size 系統契約一部分,不是視覺裝飾);(3) body* tokens 帶 .01–.02em letter-spacing 是針對 product UI screen-reading 刻意設計,套到 button / form / tooltip 等控制元件會破壞視覺密度。本 entry 為 governance 紀錄,無 code 變更,避免未來誤把 base 元件批量 migrate 至 product semantic token
- Audit scope:src/components/base/(19 個 component 完整掃描)+ src/components/foundations/ + src/components/application/(部分掃描,long-form 候選未完整深掃)
- 結論:base/ 全部 19 個 component → do-not-migrate(buttons / tags / dropdown / select / input / label / hint-text / checkbox / toggle / radio-buttons / avatar / pin-input / tooltip / progress-indicators / form / file-upload-trigger / outcome-action / slider / textarea / status)
- Category A 純 UI controls(button / tag / dropdown / select item)→ do-not-migrate:typography 是 size variant 契約一部分,sm button = text-sm 不能換 — 換 token 連帶影響 padding / hit area / icon size 對齊
- Category B form 系統(label / hint-text / input / checkbox / toggle / radio)→ do-not-migrate:form 內所有元件用同一套 size scale,導入 LS 會破壞跨 form layout 對齊與標籤 baseline 一致性
- Category C scale-locked display(avatar initials / pin-input slots)→ do-not-migrate:typography 與 box geometry 直接綁定(avatar size-6 對 text-xs、size-16 對 text-display-xs;pin-input size-16/20/24 對 text-display-lg/xl),換 token 字會跑出 box
- Category D user-content carrier(tooltip / select supportingText / dropdown account row)→ do-not-migrate(傾向):雖內容由 user props 傳入,但 row 內視覺對齊與 sibling 強耦合,必須走同 token
- Category E hint / static label(input cmd badge / dropdown shortcut / dropdown section header)→ do-not-migrate:對齊耦合 + pixel-perfect 需求(鍵盤快捷鍵符號 baseline 不可飄)
- Category F numeric display(progress-indicators tabular-nums)→ do-not-migrate:唯一 base 用 tabular-nums 處,現狀無 font-number 符合「base 不引入 product semantic」原則
- Application layer 觀察:long-form 候選為 EmptyState description / Modal body description / Toast description / FileUpload helper text — 若未來考慮 application 局部 migration,需獨立 micro-audit 並逐 component 評估 audience(product vs marketing vs admin),不可批次 sweep
- Foundations layer:FeaturedIcon icon-only 無 typography → SKIP
- Marketing layer:未掃描,但通常用 text-display-* 為 hero — 若做 marketing 對齊 Live 為獨立任務,不在本 audit 結論範圍
- 禁止事項(governance enforcement):(1) base 元件禁止使用 text-h1 / h2 / h3 / text-bodyXl / L / M / S;(2) base 元件禁止引入 font-number;(3) base 元件禁止為對齊 product 視覺加 letter-spacing override(tracking-wide 等);(4) 若有 base 元件被 product 場景使用而視覺需調整,應走 product wrapper / variant 路線,不改 base
- Token 保留:Phase B 新增的 7 個 product semantic token (h1/h2/h3 + bodyXl/L/M/S) 仍保留在 theme.css,等待 product layer 元件按需 wire — 不撤回、不 deprecation。/typography preview T-7 section 同樣保留
- Phase C product migration 狀態:暫緩。已驗證 top-holders-panel migration(4 處 className)在 working tree,本 commit 同步 revert 回 text-sm / text-xs 維持與 audit 結論一致
- 未來重啟條件:當 product layer 元件實際需要對齊 Live typography 視覺時,逐 component 走 Phase A audit → Phase B migrate → preview verify 三段流程,禁止 batch sweep;order-book 因高密度 panel 風險仍維持 single decision point
Typography token 對齊 live:新增 product semantic 階(h1/h2/h3 + bodyXl/L/M/S),與 base utility 階並存
對齊 live Opinion.trade typography 系統 — 新增 7 個 product semantic token:text-h1 / text-h2 / text-h3(含 h3 letter-spacing 0.01em)+ text-bodyXl / text-bodyL / text-bodyM / text-bodyS(含 .01em–.02em letter-spacing)。採分層策略:新 token 為 product UI 層(market card、portfolio panel、product page heading);既有 text-xs/sm/md/lg/xl 維持為 base utility 層(Untitled UI button / badge / avatar / radio / toggle / tooltip 沿用)。--font-number 註解補一行對齊註記,標明等於 live --font-secondary(Space Mono fallback Manrope),但保留 number 命名以強調 product-semantic intent。Typography 為 mode-agnostic,無 dark mode override。本輪僅補 token + preview,product component migration 為 follow-up Phase C
- theme.css:@theme block 新增 7 個 product semantic typography token — --text-h1 (24/32) / --text-h2 (20/28) / --text-h3 (16/20, ls 0.01em) + --text-bodyXl (20/24, ls 0.01em) / --text-bodyL (16/20, ls 0.01em) / --text-bodyM (14/20, ls 0.02em) / --text-bodyS (12/16, ls 0.02em)。size 與 line-height 沿用既有 calc(var(--spacing) * N) 模式保持一致性;letter-spacing 沿用 live em 表達式直接帶入
- theme.css:新增區塊上方 banner 註解明確分層 — base utility 階(text-xs/sm/md/lg/xl)給 Untitled UI base components 用、product semantic 階(h1/h2/h3 + body*)給 product UI 用。並列存在不替代,避免動 base components 視覺
- theme.css:--font-number comment 補一段 ALIGNMENT 註記 — 標明等於 live --font-secondary(Space Mono fallback Manrope),但保留 number 命名以強調 product-semantic intent(intent = numeric data)而非位置命名(secondary = second font)
- Live 對齊差異點:live 將 letter-spacing 放在 body* 層而非 base size 層。我們沿用此分離 — base text-* 維持無 LS 不影響 base components,product body* 帶 LS 提供 live 視覺對齊
- Live gap 不補:live --text-2xl (24/32) / --text-3xl (30/36) 無對應新增。display-xs (24/32) 已等值覆蓋 text-2xl;display-sm (30/38) 接近 text-3xl 差 2px line-height,視為可接受範圍不另起 token
- typography preview page (src/app/typography/page.tsx):新增 T-7 Product Semantic Scale section — Static State Matrix(7 個 token spec table 含 size / line-height / letter-spacing / usage 欄)+ Interactive Demo(h1→bodyS 嵌入 product card composition + bodyXl→bodyS 對齊比較版面)。同步更新 onThisPage 加 T-7 anchor。符合 Preview Page Completeness Rule
- Mode neutrality:typography 為 mode-agnostic — 不為 dark mode 加任何 size / line-height / letter-spacing override。Color 仍由 typography.css 的 --text-color-* 處理
- 規則遵循:DS Token 先補齊再 migration(per feedback_ds_token_before_migration memory)。本輪只動 token + preview,不碰任何 product component。Product component migration(把 product/* 的 raw text-sm 換成 text-bodyM 等)為 follow-up Phase C 另案
- Verify:/typography page 渲染新 T-7 section、7 個新 token 透過 Tailwind class(text-h1 / text-bodyM 等)正確解析、letter-spacing 在 sample 文字可見效果、無 build / console error
- Out of scope(不在此 PR):product component migration(market-header / market-cds-hero-card / trade-panel / live-games-module 等的 text-sm/md/lg → bodyM/L 替換)、.prose long-form content 樣式調整、live 的 text-2xl/3xl 對應評估
Team palette anchor 對齊 live:home/away/draw -500 重新定錨 + 整條 scale 重算
三家族 team palette anchor (-500) 全部對齊 live site:team-home #E2BA08→#F9D224、team-away #2383D8→#3391E2、team-draw #565B5A→#8C9794。每家族整條 50–975 共 12 階圍繞新 anchor 透過 HSL ladder 重算。Sports / esports market 視覺整體亮化、活潑化,特別是 draw 從深灰變中灰偏綠。連帶 text-on-team-draw-solid 從 white flip 為 team-draw-950(深字)以維持 WCAG AA Normal 對比
- theme.css palette:team-home / team-away / team-draw 三家族 × 12 階 = 36 個 hex 值替換。anchor (-500) 各自對齊 live
--color-sport-team-a(#F9D224) /--color-sport-team-b(#3391E2) /--color-sport-draw(#8C9794)。其餘階透過 HSL ladder(H 保持、S 兩端漸降、L 標準階梯)平滑生成 - team-home 新 hue H=49°(黃)— 比舊家族(H 不變但 anchor 更亮)整體偏向亮黃;team-away 新 hue H=208°(藍)— 比舊家族略亮、略冷;team-draw 新 hue H=164°(中灰偏綠青),anchor 從 L=35% 跳到 L=57%,整條 scale 視覺往中色帶移動
- theme.css text-on-solid:text-on-team-draw-solid 從 var(--color-white) flip 為 var(--color-team-draw-950),light + dark mode 都改。原因:新 anchor #8C9794 + white 對比 ~3.46:1,跌破 WCAG AA Normal (4.5:1),flip 為深字後對比 >12:1 完全安全
- 解釋註解同步更新:theme.css 該區塊 banner 註解列出三 family 的 light vs dark anchor 規則,加上 2026-04-29 flip 紀錄
- 規則遵循:走 palette anchor 路線(per feedback_palette_anchor_alignment memory)— 沒改 semantic mapping(bg-team-*-solid 仍指 -500),純 palette 重算 + text-on flip。bg-team-*-solid_hover/_pressed 等所有 hover/pressed 階自動跟新 -600/-700 走,無需個別調整
- CLAUDE.md:Brand Customizations Applied 段 Team identity hex 更新;Outcome / Team Dual Color System 段表格三家族 hue 欄改為新 anchor;text-on-{X}-solid mapping 表格的 team-draw 行從 'white / neutral-500 dark enough' 改為 'team-draw-950 (dark) / anchor 500 是 light mid-gray, white 失 AA Normal'
- Component code 完全不動 — palette swap 透過 semantic token 自動傳播至所有 sports market UI(market-card 的 team variant、live-games-module、resolved card、team chip 等)
- Verify:/colors page 三 scale 重新 derive 並渲染、bg-team-*-solid 解析新 anchor、text-on-team-draw-solid 在 light/dark 都解析為 team-draw-950 dark 文字、無 build / console error
- Risk 預警(已 verify 過):sports market UI 視覺整體亮化、team-draw button 文字反轉(白→深)、任何視覺 QA 對舊色記憶要重新校準
- Out of scope(不在此 PR):team brand alpha 變體(live
--color-orange-1style)、team-*-bg 直接對應 live 的 #303122/#1D3141/#343E3B(我們用 -950/-975 表達同概念,hex 略不同但語意對齊)
LiveIndicator 動畫從 opacity blink 改為 outward ripple
原 --animate-blinking(亮/暗 opacity 閃爍)視覺語意接近 on/off 報警,與「持續進行中」事件性 LIVE 的語意不符。改為 ripple 模型:中心 dot 穩定、外層同色 ring 從中心向外 scale + 同步淡出。週期 1.4s = 300ms 顯現 + 300ms 淡出 + 800ms 休息(heartbeat 節奏),easing ease-out。文字 (LIVE) 仍不做任何動畫
- theme.css:移除 --animate-blinking 與 @keyframes blinking。新增 --animate-live-ripple: live-ripple 1400ms ease-out infinite + @keyframes live-ripple(0% scale(1) opacity:0 → 21.43% scale(2) opacity:0.4 → 42.86% scale(3) opacity:0 → 100% scale(3) opacity:0 為 800ms 休息段)。peak ripple 直徑 = dot × 3(sm 6→18px、md 8→24px)
- live-indicator.tsx:dot wrapper 由單 span 改為 relative 容器內含兩個 span — 一個 absolute inset-0 ring(套 bg-live + animate-live-ripple,僅 isAnimated=true 時 render;初始 opacity 由 keyframe 0% 控制為 0,不再用 opacity-40 class)+ 一個 relative size-full 穩定 dot。穩定 dot 永遠不做動畫。isAnimated=false 仍只 render 穩定 dot(無 ripple)
- live-indicator preview 頁 Interactive Demo 描述文字同步更新為 1.4s 三段式 ripple 模型敘述
- Timing 設計理由:原 2.5s ease-in-out 連續 fade 太慢、無 rest 段;改為 300ms 顯現 + 300ms 淡出 + 800ms 休息的 heartbeat pattern,符合 figma interaction spec(After delay 800ms / Smart animate 300ms / Ease out)
- Token 不變:仍使用 --color-live (#EB1D14)。LiveIndicator API 不變:size / label / isAnimated / className 簽名與行為相同
- Layout 影響:dot 視覺 footprint 在 ripple 顯現期會超出 dot 自身 box(peak 達 dot × 3),但 ripple 達 peak 時 opacity 已歸零;rest 段內整個 ripple 不可見,對 LIVE 文字 / clock 文字的閱讀影響極低(已 verify)
- 語意層紀錄:blink = on/off 報警;ripple heartbeat = 持續事件 + 節律感。LIVE 應為持續事件(市場進行中、比賽進行中、串流播送中),故 ripple + rest 為正確視覺模型
LIVE indicator:採用上游 --color-live family + 新增 LiveIndicator base component
對齊 live site 的 LIVE 紅點指示系統 — 採用上游 commit 1d22478 引入的 --color-live (#EB1D14) family(rebase 整合避免雙 token)、新增 --animate-blinking 動畫(後續 fix 改 ripple)、LiveIndicator base component(含 size / label / isAnimated props)。為 live games / live markets / 串流類即時性提示提供共用 primitive。為對齊 live site 的第四步(前三步:brand anchor、neutral cool gray、header surface hierarchy)
- Token:採用上游 1d22478 已建立的 --color-live (#EB1D14) + --color-text-live-primary + --color-fg-live-primary family。本 PR 不另起重複 token;rebase 整合時把 LiveIndicator 對齊到 live family,移除原先的 --color-status-live 草案
- theme.css:@theme block 新增 --animate-blinking: blinking 2.5s ease-in-out infinite + @keyframes blinking(0%,100% opacity:1 → 50% opacity:0.3)。週期與 live site --animate-blinking 對齊;opacity 0.3 floor 避免完全 fade 看起來像壞掉
- theme.css:property mirror 區新增 --background-color-live,讓 Tailwind bg-live utility 正確 resolve(上游 family 只暴露 --text-color-live-primary mirror)
- 新 component:src/components/base/status/live-indicator.tsx。API 為 size (sm | md,預設 md) / label (ReactNode | null,預設 'LIVE') / isAnimated (boolean,預設 true) / className。dot 用 bg-live + animate-blinking class,文字用 text-live-primary + uppercase + tracking-wide。aria:有 label 時依賴文字;label=null 時 wrapper 自動帶 aria-label='Live',dot 永遠 aria-hidden
- isAnimated=false 用於 Static State Matrix snapshot(凍結動畫便於 QA 視覺對比),不應出現在 production 行為層 — 動畫關閉只是視覺工具
- 新 preview page:src/app/live-indicator/page.tsx 含 Interactive Demo + Static State Matrix(SIZE × ANIMATED 4-cell 矩陣 + LABEL VARIANTS 三種 + In Context 嵌入 card / inline text),符合 Preview Page Completeness Rule
- nav-sections.ts:Base group 在 dot-icon 之後新增 live-indicator slug(label 'Live Indicator',含 onThisPage anchor)
- 規則紀錄:動畫只套在 dot 不套在 label 文字,避免分心;token 命名走 semantic(live)而非色相系(red-live),與 outcome-yes/team-home 命名邏輯一致
- Wiring follow-ups(不在此 PR):live-games-module 接入 LiveIndicator、market-card 的 LIVE state 接入、everything-cds 的純文字 'Live' span 升級為 LiveIndicator
Header surface layer:新增 bg-header semantic token 與 neutral-975 palette 階
建立 layer hierarchy(header > page > card)— 新增 --color-bg-header semantic token,dark mode 對齊 live --color-header-bg #07090E;neutral palette 補上 -975 階作為 anchor,但限定為 palette-only,禁止 component 直接使用 raw step。為對齊 live brand 與 surface 系列補完的第三步(前兩步:brand anchor 對齊 #ED6432、neutral palette 換 Opinion Cool Gray)
- theme.css:palette 新增 --color-neutral-975: rgb(7 9 14)(= #07090E,live --color-header-bg dark mode exact)。註解明確標示為 palette-only anchor — UI / component 層禁止 bg-neutral-975 raw 使用,未來深色 surface 需求一律走新 semantic token,不擴散 raw step 使用
- theme.css @theme block:新增 --color-bg-header semantic token。light mode = var(--color-white)(live #fffbf9 暖 tint 留 follow-up,本輪不做);dark mode 在 .dark-mode block override 為 var(--color-neutral-975)
- theme.css:light / dark 雙態都加上 --background-color-header property mirror,讓 Tailwind bg-header utility 正確 resolve
- header-navigation.tsx 兩處 bg-primary → bg-header:desktop 主 header section(h-16 outer)+ secondary nav 條(tabs/pills 容器)
- mobile-header.tsx:mobile header outer container bg-primary → bg-header
- Rule 確立(同步 feedback memory):對齊外部視覺差異(live / Figma)只能改 palette anchor 並重算 scale,禁用 semantic mapping override 當 shortcut;同源原則 — 深色 surface 要層次時新增 semantic(如 bg-header),不直接擴散 raw palette step 使用
- Follow-ups(不在此 PR):light mode header warm tint #fffbf9、LIVE indicator red #EB1D14 + blinking 動畫、team-home/team-away/team-draw -500/-400 anchor mismatch 對齊 live(走 palette anchor 路線)、orange glow filter、skeleton loading colors
新增 live token family + LiveGamesModule v2 重構
全局新增 live identity color(#EB1D14,light/dark 同色、不依賴 scale),取代 LiveGamesModule 內 text-error-primary 的語意誤用;同時將 LiveGamesModule 改為固定標題「Games」+ 4 場動態 budget + per-team probability + home/away hover 色,schema 加 status "final"、shortName、probability、volumeRank
- theme.css:新增 --color-live: #EB1D14(單一 stop,無 scale),加 LIVE IDENTITY COLOR 註解 block 釐清 vs error / outcome / trend 的語意分界;light = dark 同色不另外 override
- theme.css:新增 semantic indirection --color-text-live-primary 與 --color-fg-live-primary,對應 Tailwind utility text-live-primary 與 text-fg-live-primary(後者透過 --color-fg-* 註冊讓所有 color utility 都能解析)
- theme.css:property mirror --text-color-live-primary 加在 outcome-on-solid mirror 之後,確保 Tailwind v4 utility 解析到位。dark-mode 區塊不重複定義(underlying var 不變,inherit 自 :root 即可)
- live-games-module.tsx:移除 title prop,標題硬編為「Games」;status union 加 "final";LiveGameTeam 加 shortName?: string 與 probability?: number;LiveGame 加 volumeRank: number(純排序用,display 仍走 volume string)
- live-games-module.tsx:新增 allocateBudget(sections, 4) — 把全部 sub-section games 攤平 → 依 status (live/upcoming/final) → volumeRank desc 排序 → 取前 4 → 依 sectionIndex 分回原 category。任一 sub-section budget=0 → 該 section header + View All 整塊隱藏;全部 0 → 整 module return null(讓 sidebar siblings 上補位)
- live-games-module.tsx:MetaRow live 狀態 clock 由 text-error-primary 改 text-live-primary;module 標題的 Signal01 icon 由 text-error-primary 改 text-fg-live-primary。final 狀態 clock 與 upcoming 同樣走 text-tertiary
- live-games-module.tsx:TeamRow 加 colorRole: "home" | "away" prop。teams[0] 永遠用 home 色、teams[1] 永遠用 away 色(不對應真實主客場,token 設計初衷即「陣營識別」,esports 也照用)。隊名 + probability 共用 group-hover:text-team-home-primary / group-hover:text-team-away-primary,hover 時兩列同步切色
- live-games-module.tsx:新增 probability 欄位(w-8 / text-md / font-semibold / tabular-nums / text-right);只有兩隊都帶 probability 才整欄渲染,任一邊缺值整欄隱藏。長隊名 fallback 用 native title attribute 顯示全名(避免與 AriaLink 巢狀 react-aria Tooltip)
- live-games-module.tsx:MOCK_LIVEGAMES_SECTIONS 改為精準 4 場(2 sports:live + final、2 esports:live + upcoming),剛好對應 budget=4 確保 sidebar canonical 情境完整渲染;舊版 5 場會把 final 排到 budget 外無法展示
- /live-games-module preview page:API table 全面對齊新 schema(移除 title prop、新增 status / volumeRank / shortName / probability 行);States Matrix 涵蓋 default / sports-only / esports-only / all-upcoming / no-view-all;Edge Cases 新增 tied (both isLeading=true)、long names with shortName fallback、probability missing、budget overflow (6→4)、mixed empty、all empty 六個 demo
- nav-sections.ts:live-games-module entry 的 onThisPage anchor sections-matrix → states-matrix(label 與 page id 對齊)
- Out of scope:pulse dot 動畫(v2)、即時 polling、個人化 ranking、3+ 隊 / 單人賽事支援、Game hub page 本身(市場 detail)。docs/prd/games-module.md 為本次決策來源
/colors page:deprecated token 從 Primitive Scales 抽離為獨立 section
outcome-a / outcome-b 是 dual color system 之前的舊命名,雖然在 theme.css 仍以 deprecated alias 形式存在(指向 team-a / team-b 以保 back-compat),但不應被當成新色彩系統的 primitive 來呈現。Primitive Scales section 加入過濾、新增 Deprecated Tokens section 以 muted 視覺單獨展示
- /colors page:新增 DEPRECATED_FAMILIES Set(outcome-a、outcome-b)與 deriveDeprecatedFamilies helper;deriveProductFamilies 新增前置 filter,把 deprecated alias families 從 Primitive Scales 的 live-derive 結果中排除
- /colors page:新增 DEPRECATED_ALIAS_TARGET map(outcome-a → team-a、outcome-b → team-b)作為 UI 顯示用 label 來源
- /colors page:在 Primitive Scales 之後、Rules & Don'ts 之前插入新 <Section id="deprecated" title="Deprecated Tokens">。內容含 ⚠ amber warning banner(「不要新增使用」+ migration 來歷說明)+ 每個 deprecated family 的 row:DEPRECATED badge / → alias of {target} / 沿用 PrimitiveScale 元件渲染色階(opacity-60 整體 muted)
- /colors page Primitive Scales 段落 description 加上跨段連結 → 點 Deprecated Tokens 可跳該 section
- nav-sections.ts /colors 的 onThisPage 加入 { id: "primitives" } 與 { id: "deprecated" } 兩個 anchor,讓 sidebar TOC 能直接跳到新 section
- Token 層不動(per 用戶規則):theme.css 的 --color-outcome-a-* / --color-outcome-b-* 仍是 var(--color-team-a-*) / var(--color-team-b-*) 的 deprecated alias,本 PR 純文檔/preview 結構修正
- Verify:/colors Primitive Scales 段落內 outcome-a / outcome-b 0 命中(filter 生效);新 deprecated section 存在且包含 outcome-a、outcome-b、team-a、team-b 四個關鍵 token 名稱;視覺上 banner 採 warning(amber)配色、swatch 整體 opacity-60、上方 chip 為 DEPRECATED 標示。Section 排序:Primitive Scales → Deprecated Tokens → Rules & Don'ts
- 已知未處理(這次 scope 外):nav-sections /colors onThisPage 仍含一批未實作的 anchor(outcome-yes、outcome-no、outcome-semantic、outcome-bg、outcome-solid)— 屬先存 nav-stale 議題,與 deprecated 結構獨立
Outcome 語意 migration — pages / content / incidentals(PR 3c / 3 部曲收尾)
/outcome-action snapshot static matrix、/colors page、/trade-panel page 的 outcome-a/b 殘留全清;nav-sections.ts anchor 名稱對齊;content workflow 歷史 known-issue 加「2026-04-26 已修正」標記;incidental A 的 audit 框架修正(不是違規而是 Don't 教學)。Codebase 在非 deprecated 區域達成 zero outcome-a/b 殘留
- /outcome-action page:snapshotClass 物件 outer key a/b → yes/no,全部 className 字串 outcome-a/b-* → outcome-yes/no-*;selected 系列 text-white 順手改 text-on-outcome-yes/no-solid(mirror PR 3a 真 OutcomeAction 修法)
- /outcome-action page:Snapshot 元件 outcome prop type "a" | "b" → "yes" | "no";其下兩處 <Snapshot outcome="a"|"b"> 呼叫跟著改
- /outcome-action page L180 explanation copy:原「禁止使用 Button outcome-a / outcome-b variants」更新為「Button 不再提供 outcome 色 variants(2026-04-26 已移除)」反映 PR 3a 已刪除事實
- /outcome-action page L458 known-issue B1:「outcome-b-solid dark mode contrast = 4.40:1 (AA fail)」加標 ✓ 2026-04-26 已修正,附 migration 後 outcome-no + text-on-outcome-no-solid 的新對比數據(3.8:1 AA Large、hover→4.8:1 AA)
- /colors page:14 處 outcome-a/b 引用全清 — Compare ✓ Do 卡 ring/text、tokenIntent 資料表(mc-outcome、tp-outcome-selector)、Color Roles outcome 區 OutcomeAction + token name mono、Outcome ≠ Trend section narrative 重寫為「決策語意 vs 價格 movement」、Compare 兩組(trend-on-yes Don't / outcome-on-pl Don't)的 outcome 引用 rename outcome-yes/no(per α 決議:rename 保留 Don't 教學、不換 trend 破壞對比)、disabled outcome reduced-opacity Don't 卡同樣 rename
- /colors page:YES/NO pill demo 與 disabled Don't 卡 text-outcome-a-950 / text-white 改 text-on-outcome-yes-solid / text-on-outcome-no-solid,跟 token system per-family 對比規則對齊
- /trade-panel page L8-9 OUTCOMES 字面量 outcome "a"|"b" → "yes"|"no"(PR 3b deferred 項收尾)
- nav-sections.ts /colors slug 的 onThisPage anchor IDs:outcome-a / outcome-b 改為 outcome-yes / outcome-no,labels「Outcome A / B」改「Outcome YES / NO」。注意:/colors page 從未實作這幾個 anchor(只有 outcome-vs-trend 存在),這是先存的 nav-stale 議題(不在本 PR 範圍)
- design-process.ts L204-205 + workflow.ts L343-344 歷史 known-issue 加 ✓ 2026-04-26 已修正 標記,per 用戶規則「保留並加標記」,不刪除
- incidental A audit 框架修正:原 audit 列為「P/L 用 outcome 違 outcome ≠ trend 規則」實為 <Compare> 的 Don't 半邊刻意違規教學示範,並非真 bug。經用戶確認走 (1):rename outcome-a → outcome-yes 保留 Don't pedagogy + token name 跟系統現狀一致。已知 codebase 不存在真實 outcome 用於 P/L 的違規。同樣邏輯適用 disabled-with-outcome-reduced-opacity Don't 卡
- Verify:/outcome-action(matrix YES bg = green tone, NO bg = red tone, 0 outcome-a/b)、/colors(16 outcome-yes、5 outcome-no、0 outcome-a/b)、/trade-panel(4 outcome-yes、4 outcome-no、0 outcome-a/b)
- 全 codebase grep(排除 src/styles/theme.css deprecated alias、/outcome-colors preview 的故意展示、system-log 歷史條目、@deprecated 註解、'2026-04-26 已修正' 標記):剩餘命中全部是 OutcomeAction component 名稱與檔案路徑(不是 token),非 token 殘留為零
- ⚠️ Pedagogy 意外發現(記錄、不在此 PR 修):/colors 的 Outcome ≠ Trend Compare 視覺對比崩塌 — Don't 半邊(trend-up-solid YES + outcome-yes-primary P/L)與 Right 半邊(outcome-yes-solid YES + trend-up P/L)視覺幾乎相同,因為 outcome-yes 與 trend-up 都 alias 到 Tailwind green。Pedagogy 在 namespace 層仍 intact、視覺層 broken。屬 PR 1 dual color system 延伸後果,比之前預警的 outcome-yes ≈ team-a (lime) 更嚴重。Tuning 仍待後續 PR(用戶 PR 3 規則:不動 palette / 不新 token)
- PR 3 三部曲收尾:3a base API + WCAG fix + dead code 刪除、3b product consumer rename、3c pages/content/markers cleanup。視覺 brand-level 改變(YES 由 lime → green、NO 由 blue → red)已全 codebase 落地。Out of scope:outcome-a/b deprecated alias 最終移除時機(PR 4 之後評估)、視覺對比 tuning(待後續)
Outcome 語意 migration — product layer(PR 3b / 3 部曲第 2 步)
MarketCard / MarketHeader / OutcomeSelector 三個 product component 改用 outcome-yes / outcome-no semantic;resolved-state 的 yes/no→a/b 三元映射全部移除,直接傳 "yes"|"no";連帶最小 fix /outcome-selector 與 /market/[slug] page 內 OutcomeSelectorItem 字面量,讓 product preview surface 全綠
- market-card.tsx:5 處 <OutcomeAction outcome="a"|"b"> JSX prop(GroupOutcomeRowActive、SingleOutcomeActive、MarketCardHero × 1 active 對 + 2 處 resolved 跳轉)改為 "yes"|"no"
- market-card.tsx:2 處 resolved 三元 outcome={resolved === "yes" ? "a" : "b"} 直接收掉,改為 outcome={resolved as "yes" | "no"} 將 OutcomeType 與資料層 "yes"|"no"|"tie" 的窄型直接對齊(tie 在外層 ternary 已分流到 ResolvedTieBadge,type assert 安全)。children label 的 yes/no→YES/NO 顯示文字維持原樣(不在 PR 3b 範圍)
- market-header.tsx:L153 headlineOutcomeLabel 的 text-outcome-a-primary 改為 text-outcome-yes-primary(YES 機率 hero 數字旁邊的小 label)
- outcome-selector.tsx:OutcomeSelectorItem.outcome type 由 "a" | "b" 改為 "yes" | "no"(連動 OutcomeAction 新 OutcomeType);filterStyles selectedA/selectedB key 改為 selectedYes/selectedNo,class 字串 outcome-a/b-* 全改 outcome-yes/no-*;FilterPill 內 isSelected 條件 item.outcome === "a"|"b" 跟著改 "yes"|"no"
- /outcome-selector/page.tsx + /market/[slug]/page.tsx PAIR / OUTCOMES 字面量:item.outcome "a"|"b" 改 "yes"|"no"。**這是 page 層 minimum prop rename**(PR 3b 通過 α 規則拉進來保 product preview verify surface 不破,per 用戶 2026-04-26 決議「product preview 必需的最小 prop rename」)。/trade-panel/page.tsx 同樣有 PAIR 但不在 PR 3b verify 列表,仍是 PR 3c 範圍
- Out of scope(仍留 PR 3c):static state matrix、explanation copy、token reference 文案、/colors page 的 Do 範例 + YES/NO pill + L824 P/L 違 outcome ≠ trend(incidental A)、nav-sections.ts anchor IDs、design-process / workflow.ts 歷史 known-issue 加 「2026-04-26 已修正」標記、/trade-panel page PAIR const
- Verify:/market-card(YES 套用 outcome-yes / NO 套用 outcome-no,0 處 outcome-a/b 殘留)、/market-header(3 處 text-outcome-yes-primary、0 處 outcome-a-)、/outcome-selector(decision OutcomeAction + filter pill 兩種 emphasis 都用新 class)、/market/[slug]、/markets-page(46 YES/NO 全套用,0 殘留)、/trending(46 YES/NO 全套用,0 殘留)。所有頁面渲染無 runtime error
- 視覺改變範圍實際命中:所有 product page YES = green-500、NO = red-500(unselected base 是 950 tinted bg + 300 text)。此為 dual color system PR 1 token foundation 之後第一次「肉眼可見」的 product surface 變色
Outcome 語意 migration — base layer(PR 3a / 3 部曲第 1 步)
OutcomeAction / Badge / Button base 層由 outcome-a/b 改為 outcome-yes / outcome-no 語意;同步修復先存的 WCAG 對比 bug、刪除已棄置的 Button outcome-a/b variants;Tier 2 product 與 Tier 3 pages 留待 PR 3b / 3c
- outcome-action.types.ts:OutcomeType 由 "a" | "b" 改為 "yes" | "no",反映 codebase 100% 是 YES/NO market 的語意
- outcome-action.tsx:outcomeStyles sortCx 全表 a/b key 改 yes/no、bg/text/border-outcome-a/b-* class 全改 -yes-* / -no-*;移除 L27 「outcome-b-solid dark mode = 4.40:1」 known-issue 註記(contrast 在新 yes/no 上重新計算,舊註記過期)
- outcome-action.tsx:incidental B 修復 — selectedBase 由 data-selected:text-white 改為 data-selected:text-on-outcome-yes-solid(green-950 dark text)/ text-on-outcome-no-solid(white)。lime + white = 1.6:1 fail WCAG 的長期 bug 自然解決;green-500 + green-950 ≈ 7:1 AAA、red-500 + white = 3.8:1(hover→600 升 4.8:1 AA)
- buttons/button.tsx:incidental D 修復 — 整段刪除 deprecated Button color="outcome-a" / "outcome-b" variants(OUTCOME 註解 block + styles.colors entries + DeprecatedButtonColor union 條目),共 24+ 行 dead code 清掉。preview 已長期標明「禁止使用」、grep 確認零外部 consumer
- badges/badge-types.ts + badges.tsx:BadgeColors union 與 filledColors map 由 "outcome-a" / "outcome-b" 改名為 "outcome-yes" / "outcome-no";class 字串 outcome-a-* → outcome-yes-*、outcome-b-* → outcome-no-*。零外部 consumer,rename 為純清整動作
- /outcome-action page:interactive demo 區的 <OutcomeAction outcome="a"|"b"> JSX prop 改為 "yes"|"no"(base 層 type 改後 page-level minimum fix),覆蓋 SelectionPair multi-line block + 多處 size/mode 組合行;page-local <Snapshot outcome="a"|"b"> 與 SNAPSHOT_CLASSES 鍵維持原樣(Tier 3 範圍),static state matrix className 字串也維持 outcome-a/b(透過 PR 1 deprecated alias chain 解析到 team-a/b raw 值)
- PR 3 切分原則確認:3a base / 3b product / 3c pages。3a 後 product 頁(market-card、market-header、outcome-selector)會暫時 break(OutcomeAction prop type 不接受 "a"|"b"),這是 PR 3b 的 verify surface 不是 3a 的;本 PR 不動 product
- Out of scope(PR 3b / 3c 處理):MarketCard / MarketHeader / OutcomeSelector consumer 改名(3b);/outcome-action static matrix className 字串、/colors page Do 範例 + token reference + YES/NO pill、/colors:824 P/L 違 outcome ≠ trend 規則(incidental A)、nav-sections.ts anchor IDs、design-process / workflow.ts 歷史 known-issue 加 "2026-04-26 已修正" 標記(3c)
- Verify:preview server /outcome-action 已驗 — interactive demo selected YES bg 解析到 lab(70.55%) green-500、color 解析到 lab(15.68%) green-950 dark text、data-selected variant 正確注入;Static matrix 維持 lime/blue(預期);/badge / /button 頁正常渲染、無 runtime error;hydration warnings 是 Sun-icon 既存問題、不歸本次
Outcome / Team Dual Color System(token foundation)
Market 顏色系統正式分流為兩套 semantic:YES/NO 用 outcome-yes / outcome-no(綠/紅),sports identity 用 team-a / team-b / team-draw(lime/blue/neutral);舊 outcome-a / outcome-b 標記 @deprecated 並退化為 team-a / team-b 的 back-compat alias
- theme.css:raw scale 重新分流。lime palette(原 --color-outcome-a-*)改名為 --color-team-a-*;blue palette 同理改為 --color-team-b-*;新增 --color-team-draw-* alias 到 --color-neutral-*;新增 --color-outcome-yes-* / --color-outcome-no-* alias 到 --color-green-* / --color-red-*。視覺零變動,因為 raw 值原封保留
- theme.css:--color-outcome-a-* / --color-outcome-b-* 改寫為 var(--color-team-a-*) / var(--color-team-b-*) 的 deprecated alias,標 @deprecated 註解;舊 component 透過 alias chain 解析,視覺不變
- theme.css:補齊 team-a / team-b / team-draw / outcome-yes / outcome-no 的 light + dark semantic tokens(text / fg / bg primary–tertiary / bg solid + hover + pressed / border + subtle),結構完全鏡像現有 outcome-a/b。team-draw 與 outcome-yes/no 無 -975 stop,dark mode bg-tertiary fallback 到 -950(Decision #2)
- theme.css:新增 --color-text-on-{team-a|team-b|team-draw|outcome-yes|outcome-no}-solid token;team-a / outcome-yes 用 dark text(-950)以避開 lime / green 上 white 對比不足,其餘三個用 white(Decision #4)
- theme.css:補齊 Tailwind property-color 鏡像(--text-color-* / --background-color-* / --border-color-* / --ring-color-* / --outline-color-*),確保 utility class(如 bg-team-a-solid、text-on-outcome-no-solid)可被 Tailwind 解析
- CLAUDE.md:Outcome Color System 段落整段重寫為「Outcome / Team Dual Color System」;補上 dual system 判斷表、text-on-solid mapping 表、deprecation note、known issues(team-a lime ≈ outcome-yes green 待後續解);Brand Customizations Applied 對應 bullet 同步更新
- /outcome-colors preview:新增 outcome-yes/no、team-a/b/draw 的完整 11-stop scale swatch;新增 text-on-*-solid 對比驗證(default / hover / pressed 三檔,含 light + dark mode);新增 YES/NO market vs Sports market 對照 demo;舊 outcome-a/b scale 保留並標記 deprecated 警示
- Decision Points 鎖定(PR1):outcome-yes/no 不做 -975(#2)、不做 hover/pressed text-on token(#3)、outcome-yes solid 文字用 dark(#4)、CLAUDE.md 與 token 同 PR 改(#5)、trend / outcome 維持分離 namespace(#6)、outcome-a/b 保留 alias 並 deprecated(#1)
- Stale memory 修正:2026-04-21 條「outcome-a/b raw scale 已 alias 到 green/red」從未在 code 落地,已於 memory 加註並改寫;新規則為 lime/blue = team identity、green/red = outcome semantic 兩套 namespace 並行
- Out of scope(後續 PR 處理):codebase audit、component migration(market-card / outcome-action / market-header 等)、outcome-a/b 最終移除時機、multi-team(>2 隊)token 擴張
Live-token Foundation colors + Recently Updated nav
Foundation colors page rewritten as live system view (primitives + semantics read from theme.css at runtime); NAV_SECTIONS extracted to content/; auto-generated Recently Updated nav group added
- Added live-token reader in /colors: stylesheet walk + getComputedStyle + MutationObserver on <html> class → categorised snapshot of --color-* CSS vars (primitives by family/step, semantics by role/name); all swatches and inspector samples render via var() so theme.css changes propagate without page.tsx edits
- Added runtime-derived product family list: a primitive family is considered product-facing iff its name is referenced by any semantic token (--color-(text|bg|border|fg|ring)-<family>-*); Tailwind default palettes used only via aliasing are excluded automatically
- Added canonical 11-column grid (50..950) with runtime warning when theme.css introduces non-canonical steps (e.g. brand-25, outcome-a-975, outcome-b-975)
- Added Color Architecture primer at top of /colors — 3-layer explanation (Primitive → Semantic → UI) with Do / Don't compare using real Button / OutcomeAction, for PM / dev audiences
- Added Inspector panel with runtime token-existence validation: each element → token intent mapping is checked against live snapshot; missing tokens render explicit error state
- Extracted NAV_SECTIONS from src/app/_shared.tsx into src/content/nav-sections.ts — single source of truth for design-system navigation
- Added scripts/generate-recent-updates.mjs + predev / prebuild npm hooks → produces src/content/recent-updates.generated.ts consumed by _shared.tsx
- Added 'Recently Updated' nav group pinned at top of sidebar when content present; auto-lists most recently touched preview slugs
- Foundation page rule (implicit): token data (values, step membership, family membership) must be derived from theme.css at runtime; only structural UI config (canonical grid, inspector element-intent mapping) may remain static in page.tsx
- Semantic Tokens in UI section on /colors temporarily hidden (Coming soon) — inspector interaction pattern pending rework; chip / tab / highlight scaffolding retained in source for resumption
Market Detail (Single) Phase B landing
Added 6 product components, number/money font token, market detail page route, and deferred-components tracking surface
- Added --font-number semantic typography token (theme.css) — dedicated slot for all numerical display; currently aliased to --font-body; usage rule: apply font-number utility + tabular-nums to every number-bearing element; no component-level font-family overrides
- Added 6 product components (src/components/product/): OutcomeSelector (Layer-2 grouping of OutcomeAction), MarketRules, OrderBook (market-specific visualization, NOT a Table variant), TradePanel (consumes OutcomeSelector), MarketHeader, TopHoldersPanel
- Added product component layering rule: TradePanel MUST consume OutcomeSelector; MUST NOT construct OutcomeAction pairs internally; OrderBook MUST NOT inherit Table behaviors (sort / pagination / row selection / filter)
- Added /market/[slug] route — Single Market Detail page wiring MarketHeader + Chart (recharts mock) + OrderBook + Activity Tabs + TradePanel + TopHoldersPanel + Merge/Split (Button → Modal) + Terms; sticky right sidebar
- Added Deferred Components tracking page at /deferred-components — authoritative list of Phase 1 items intentionally not built this iteration (ActivityTicker, Indicator History, Resolution by AI chip, CommentThread, Related News, Resolved state, Mobile variants, Merge/Split modal UI, Space Mono swap, Chart/OrderBook real data)
- Added pre-check discipline: before Phase B, verified OutcomeAction pressed layer, Select trigger-only feasibility (FAIL → Button+Modal fallback), inline two-state switcher pattern, and number font slot existence
- Preview registered: sidebar nav now lists all 7 product components (MarketCard + 6 new) and the Deferred page under Product Components; Market Detail appears under Product Pages
Migration to Phase A / Phase B + DS Audit
Replaced 2-agent + 3-skill pipeline with Phase A (Analysis) / Phase B (Build) dual-mode model; completed design system audit with 3 remediation steps
- Replaced Migration Agent / New Feature Agent with Phase A (Migration/Analysis) and Phase B (Builder/Implementation)
- Replaced 4-step pipeline (Analyze → Map → Build → Audit) with 6-step Migration Workflow (Understand → Section → Map → Gap → Decide → Build)
- Added Product Components Layer with Allowed List (MarketCard, MarketFeedLayout)
- Added Interaction State Naming Rule: 'active' banned; pressed (transient) vs selected (persistent)
- Added Preview Completeness Rule: Interactive Demo + Static State Matrix both required
- Added System Log Rule: system-level changes must be logged
- DS Audit Step 1: markets-screen.tsx — replaced hand-rolled tabs with DS Tabs, hand-rolled cards with MarketCard
- DS Audit Step 2: extracted MarketFeedLayout from markets-page + trending (~454 → ~20 lines each)
- DS Audit Step 3: fixed --radius-2xl token; added overlay token group; ButtonColor @deprecated; FeaturedIcon modern/modern-neue gray-only discriminated union
Pressed State Enforcement + Preview Audit Trigger
Added verification gates for interactive components: pressed state must be verified, and Preview System Curator audit must run after any interactive component change
- Interactive components MUST be verified to render a visible pressed state before completion
- Pressed implementation MUST be verified to use interaction layer (not props)
- Preview System Curator audit MUST run after adding or updating any interactive component
- Audit MUST verify Interactive Demo includes pressed state and that pressed is visually distinguishable
- No interactive component is considered complete without passing these gates
Pressed State Requirement
Pressed is required for interaction feedback but must never be modeled as a persistent state or prop
- Every interactive component MUST render a visible pressed state in both light and dark mode
- Pressed must be implemented via interaction layer only (pressed: / React Aria data-pressed)
- Pressed must NOT be stored in component state or exposed as isPressed prop
- Defined layering order: base → selection → interaction (pressed is additive intensity)
- Pressed must NOT override selection identity (selected + pressed preserves identity, adds intensity)
- Added audit criteria for components failing the rule
OutcomeAction v2 — 3-layer state model
Separated mode / interaction / selection; removed active: pseudo-class; introduced isSelected as persistent selection
- Introduced OutcomeAction 3-layer state model (mode / interaction / selection)
- Removed CSS active: pseudo-class in favor of pressed: (React Aria data-pressed)
- Introduced isSelected prop as persistent selection state, mapped to data-selected + aria-pressed
- Separated resolved mode from interaction states (resolved is a mode, not a state)
- Defined priority order: disabled > loading > selected > interaction
- Renamed preview matrices: Active — Interaction × Selection Matrix / Resolved — Variant Matrix
Refactor to 2 Agents + Skills
Replaced 4-agent pipeline with 2 task-based agents and 3 shared skills
- Introduced Migration Agent
- Introduced New Feature Agent
- Extracted design_analyzer, system_mapper, design_auditor as skills
- Converted code_generator into internal step
Added Preview System Curator
Introduced a dedicated agent to manage preview/documentation system
- Added preview_system_curator agent
- Separated preview layer from product build pipeline
- Defined preview-specific responsibilities (IA, consistency, mapping)
Preview now uses central config
Refactored preview pages to consume structured workflow config
- Created src/content/workflow config
- Removed duplicated content in preview pages
- Established single source of truth for workflow docs