設計系統 (Design System)
同一個專業知識,應用於三個階段。協調器會明確傳遞 mode(模式)。
| 模式 (Mode) | 時間點 | 問題 |
|---|---|---|
| PLAN (規劃) | 功能開發階段 2 | 此功能應使用哪些模式/抽象化? |
| BUILD (建置) | 功能開發階段 4 | 在實作時套用 PLAN 的決策。 |
| REVIEW (審查) | 功能開發階段 5 | 實作結果是否確實遵循了那些決策? |
當 mode 未指定但存在分支差異時,預設為 REVIEW。 當 mode 未指定且尚無差異時,預設為 PLAN。
PLAN 模式 (規劃模式)
在程式碼尚未存在時使用。輸入:需求精簡摘要、Figma 節點或截圖、範圍內的路由。
- 搜尋 pattern-library(
src/index.ts、src/theme.ts、src/core/)和 app-pro-ui 中相似的功能。 - 對設計稿中的每個主要 UI 元素執行抽象化測試(如下)。
- 針對每個元素:標明應「保留為局部(leave local)」、「使用現有 PL API(existing PL API)」、「使用 token/變體(token/variant)」,或「需要 PL PR(PL PR needed)」,並附上引用路徑。
- 標記 PL 的排程順序(PL PR 在 app-pro-ui 之前或與其平行進行)以及缺少的文案/素材。
如果沒有 UI 表面:直接回傳一行「不適用(無 UI)」。
輸出: # Design system (PLAN): [feature] — 包含重用清單、預期變更、排程順序。不需要分支差異。
BUILD 模式 (建置模式)
在實作期間使用。輸入:設計系統 PLAN 輸出、正在編輯的檔案。
- 套用 PLAN 的決策:使用具名 PL 元件/標記;尊重「保留為局部」的建議。
- 遵循下面的推理層 — 不要在功能程式碼中重新建立可重複使用的外觀。
- 如果在編寫程式碼的過程中發現 PLAN 有誤,請在建置摘要中記錄此偏差;不要默默偏離。
輸出: 除非有偏差,否則不需要輸出 — 如有偏差則在建置摘要中說明。
REVIEW 模式 (審查模式)
在實作完成後使用。掃描此分支(app-pro-ui 或 pattern-library)中,那些應該歸屬於設計系統的 UI 決策。不要只說「刪除這個樣式」或「使用一個共享元件」。優先選擇能讓任何功能都可以做同樣事情的、最小且為「全域性」的模式庫變更 — 或者,如果無法證明抽象化的正當性,就建議保留局部實作。
這並不是說「所有樣式都應屬於 pattern-library」。重複出現的產品/UX 決策應逐漸被編碼到系統中,這樣個別的建置者就不需要重新發明它們。功能程式碼仍然可以在局部進行組合。
架構規則:可重複使用的視覺決策應歸屬於 pattern-library。功能程式碼可以使用 sx 來處理真正局部的佈局/組合,但不應在功能中重新建立可重複使用的元件樣式、標記、狀態或互動模式。當存在已建立的 PL/MUI API 時,應避免使用 style、自訂 CSS 和舊式的 MUI 系統 props。
此技能是設計系統(DS)層。對於 app-pro-ui 的 hook/util/grid 結構和一般程式碼衛生,請使用 refactoring 技能。
推理層 (Reasoning layer)
使用以下規則來判斷每個發現。這些規則來自多年來 pattern-library 和 app-pro-ui 的審查意見。優先選擇現有的 API,而不是創造新的 API。 將程式碼移到 pattern-library 並不自動等於是一種改進。
抽象化測試 (Abstraction test)
對每個候選配方、標記或新 API 執行此測試。這是主要的判斷依據 — 檢測僅用於產生候選項目。
- 這是局部組合嗎?
- 同樣的配方是否出現在其他地方?
- 這個元件本來就應該擁有這個嗎?
- 將它移到 pattern-library 是否會減少未來有意義的決策?
- 新增一個 API 是否反而會讓 pattern-library 變得更糟?
僅憑相似性並不能證明抽象化的正當性。兩個看起來相似的程式碼片段可能代表不同的產品概念。當同一個產品/UX 決策應該一起演進時,才優先選擇共享的抽象化。
sx 不是問題。問題在於設計決策存在於何處。使用主題標記的 sx 比使用硬編碼的 hex 顏色好。一個重複出現的配方可能應該屬於 pattern-library。一個一次性的 <Stack sx={{ mt: 2 }}> 通常不需要。如果測試認為局部解決方案是正確的,就將其從發現項目中省略 — 不要將其列為「低優先級」。
1. 使用設計系統 — 不要在功能中重新設計可重複使用的決策
- 對於可重複使用的產品模式,優先使用已建立的 pattern-library 元件(例如
CardWithAction,即使沒有 action 按鈕;CardCTAFeatured作為複合卡片;DetailPanelToggle)。不要從原始的Card重新建構這些元件。 - 當佈局是局部的時,優先使用 MUI 組合原語,如
Stack/Grid,而不是自訂的佈局 CSS。 - 在發明局部幾何形狀之前,先使用已建立的佈局原語和預設值。
- 將可重複使用的顏色、陰影以及行銷/AI 相關的視覺效果,作為以調色板為基礎的標記(如
primary.main、ai.gradient.start)放入pattern-library/src/theme.ts。當一個值是一個產品決策時,優先使用標記或theme.shadows,而不是硬編碼的 hex 顏色、填充或陰影。 - 使用
sx,而不是已棄用的 MUI 樣式-as-props。不要在共享元件上添加樣式命名的 props 來解決這個問題。當sx是用於組合而非可重複使用的外觀時,局部的sx是沒問題的。 - 如果一個功能需要一個新的、其他表面也會共享的外觀(如特色媒體、展開覆蓋層、反向/行銷表面),請在 pattern-library 中添加一個模式或 prop。
2. 保持共享元件的通用性
- 不要在共享的
Card、PageSection或公開的index.ts匯出上,提交功能特定的內距(padding)、側邊欄或圖表變更。範圍限定於原型;回復不相關的檔案。當只有項目本身有問題時,只修改項目本身,而不是修改外層的「chrome」。 - Props 的名稱應考慮到重用性(例如
footerDescription,而不是一個功能的標籤)。額外的行為應是可選的(例如文字區域的行號、逗號)。 - 優先使用主題變體和 Storybook
args,而不是分叉的 JSX(例如variant === "marketing"的檢查)。 - 如果應用程式沒有使用模式庫內部的圖表或其他元件,就不要將其從 pattern-library 中匯出。
- 長期來看,應將一個表面包裹在一個主題中(例如行銷主題),而不是將一次性的視覺 props 貫穿到每個頁面。
3. 邊緣情況的視覺 QA(Visual QA on the edges)
- 檢查圓角、等高卡片、孤立換行文字、行動裝置 FAB 折疊、z-index 與抽屜/模態框的關係,以及控制項在白色背景下是否仍然正常運作。
- 詢問是否需要特殊的間距,或者預設樣式是否已經與其他模態框/頁面相符。不要為了與一個未對齊的相鄰元素對齊而去填補一個共享標題的內距 — 應該修復那個相鄰元素。
4. 質疑設計需求 (Question the design requirement)
在為了支援一個不尋常的設計而增加 DS 功能之前,先確定該設計本身是否應該遵循一個已建立的系統行為。不要僅僅為了編碼一個有問題的一次性需求而擴展設計系統。
5. 抵制不必要的 API 表面 (Resist unnecessary API surface)
每個提議的 DS 抽象化都必須證明它增加的 API 表面是合理的。將程式碼移到 pattern-library 並不自動等於是一種改進。不能改善體驗的配置就是維護成本。
只保持 DS 架構的衛生:不必要的會干擾組合的包裝器、重複的樣式、元件的 API 複雜性,以及在共享原語中的功能特定邏輯。當巢狀的 Box 妨礙 Stack/sx 組合時,將其扁平化。對於簡單的標記,使用主題的簡寫(例如 background.main),而不是使用 useTheme。
6. 與相關表面一同發布 (Ship with the related surface)
- 應用程式必須使用的 pattern-library 變更,需要一個匹配的 app-pro-ui 或 moz-ui 的 PR,以及一個驗證它的方法(amplify/domain 或等同的方法)。
- 在建議如何提升版本之前,從使用此函式庫的倉庫(
package.json、lockfile或安裝文件)中確認當前的套件/版本慣例。不要假設一個特定的 pin 風格。 - 在消費端應用程式的 PR 準備好之前,不要合併一個共享的原語。
當被呼叫時(REVIEW 模式)
複製並追蹤進度:
審查進度:
- [ ] 對比此分支與其合併基礎 (merge-base) 的差異
- [ ] 收集候選項目(非結論)
- [ ] 根據意圖進行分組(相同的決策,非相同的程式碼)
- [ ] 執行抽象化測試
- [ ] 在 pattern-library 中搜尋現有的歸宿
- [ ] 建議最小變更 — 包括「保留為局部 / 不做 DS 變更」
- [ ] 報告發現;除非被要求,否則不進行實作
不一定需要找到問題。如果差異是適當的局部程式碼,直接說明並停止。
步驟 1:掃描分支 (Scan the branch)
檢查此分支與其合併基礎 (merge-base) 的差異。偵測預設分支(git symbolic-ref refs/remotes/origin/HEAD 或 origin/main,然後是 main)。從有變更的倉庫(app-pro-ui 或 pattern-library)執行。
DEFAULT=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/@@')
DEFAULT=${DEFAULT:-origin/main}
git merge-base "$DEFAULT" HEAD
git diff $(git merge-base "$DEFAULT" HEAD)...HEAD -- '*.tsx' '*.ts' '*.jsx' '*.js' '*.css' '*.scss'
git diff --name-only $(git merge-base "$DEFAULT" HEAD)...HEAD
在新增的行中搜尋候選項目:
樣式相關
style=sx=styled(css`/css(makeStyles/createStyles/withStyles- 硬編碼的
#hex、rgb(、rgba(、px(需調查,不要自動分類) - 舊式的 Box props:
display=、alignItems=、justifyContent=、flexDirection=
重新發明或偏離的 UI
- 使用原始
Card,但已有已建立的卡片模式可用(如CardWithAction、CardCTAFeatured) - 使用自訂的展開控制,而非
DetailPanelToggle - 使用自訂的佈局 CSS,但
Stack/Grid就能進行局部組合 - 發明頁面幾何形狀,但佈局原語或預設值已設定寬度
- 使用硬編碼的陰影,而非
theme.shadows,且該陰影是一個共享的外觀 - 新的公開匯出或主題編輯,但僅服務於一個功能
然後開啟每個命中結果,並閱讀周圍的 JSX 以了解其意圖。同時也要捕捉在 JSX 之前賦值的 sx 物件、<style> 標籤和匯入的樣式表。
忽略 .cursor/、node_modules/、.next/。包含功能程式碼、共享的 components/、Storybook 和 src/theme.ts。
步驟 2:先檢測,再判斷 (Detect, then judge)
檢測是一種啟發式方法。分類是在執行抽象化測試之後。
| 檢測器 | 視為 | 範例 |
|---|---|---|
style= / styled( / css / 新的 .css | 候選洩漏 (Candidate leak) | <Card style={{ flexBasis: "50%" }}> |
| 硬編碼的視覺數值,看起來像是在編碼設計系統決策,特別是顏色、間距、字體排印、圓角半徑、陰影和重複的尺寸 | 候選標記 (Candidate token) | color: "#002F50";在視為洩漏前,先調查 px |
使用主題鍵的 sx={{ ... }} | 候選配方 (Candidate recipe) | sx={{ mt: 3, width: "100%" }} |
| 舊式的 Box 系統 props | 候選舊式寫法 (Candidate legacy) | <Box display="flex" alignItems="center"> |
| 未使用現有 PL 元件 | 候選現有系統解決方案 (Candidate existing system solution) | 表格未包裹在 CardWithAction 中 |
| 在共享元件上的功能特定編輯 | 候選範圍洩漏 (Candidate scope leak) | 為了匹配一個故事而在 CardHeader 上添加內距 |
| 單一功能的 prop 或匯出 | 候選非通用 API (Candidate non-generic API) | 功能命名的 footer prop;匯出未使用的圖表 |
在將硬編碼數值分類前先進行調查。合法的局部情況包括:計算出的尺寸、圖表幾何形狀、圖示/畫布內部邏輯、邊框、虛擬化佈局和元件的內在約束。
最後手段的例外情況(需記錄原因;不要為此發明原語):
- 無法使用 pattern-library props 的第三方 / 畫布 / 圖表內部邏輯
- 列印/PDF 佈局,且沒有現有的報表原語,且確實是一次性的
- 僅原型檔案,且未從 pattern-library 中匯出
通過抽象化測試、被判斷為局部組合的候選項目,不屬於發現項目。僅在它們可能會被誤認為是遺漏(gap)的情況下,才將它們放在不要提升 (Do not promote) 類別中。否則,將它們省略。
步驟 3:根據意圖進行分組 (Cluster by intent)
將表達相同決策的命中結果進行分組,而不是相同的 CSS 或看起來相似的標記。
典型的意圖:顏色/強調、間距/排列配方、字體排印、元件外框、互動狀態、功能模式(banner、頁面標題、卡片 CTA、狀態)、通用 vs 單一功能 API、原型範圍、有問題的設計需求。
一個群組 → 一個建議。優先選擇一個強而有力的建議,而不是同一個發現的多個微小變體。
步驟 4:先搜尋 pattern-library (Search pattern-library first)
在提出任何新專案之前,先搜尋同級的 pattern-library 倉庫。引用路徑。
- 公開匯出 —
pattern-library/src/index.ts - 標記和全域預設值 —
pattern-library/src/theme.ts pattern-library/src/core/下的元件 props / 變體- app-pro-ui 中已有的、已經避免此洩漏的相同元件的使用方式
將 UI 吸收到 DS 中的先例(引用這些;不要將內部邏輯複製到 app-pro-ui):
- 語意 prop:
Typography的lineClamp—pattern-library/src/core/Typography/Typography.tsx - 語意 prop:
Button的aiGlow—pattern-library/src/core/Button/Button.tsx