為代理使用設計的設計系統
此技能旨在提供設計決策的參考,而非強制規定。任何建議都可能因正當的專案目標(專家表達需求、品牌需求、平台限制、既有慣例)而被捨棄。請明確指出取捨,並在有意做出相反選擇時說明理由。範例使用 React 與 TypeScript,因為這些是模型先驗知識最清晰的領域;但其推理可套用至任何技術棧。
目標
程式碼生成模型是一個取樣器。它會根據訓練中學到的知識(先驗)以及對話中的內容(上下文),發出它認為最合理的程式,然後保留所有能通過其可執行檢查的程式。「以人為本」的 API 問的是:「正確的事情能被表達出來嗎?」而「以代理為本」的 API 問的是:「在所有看起來正確的事物中,有多少是真正正確的?」因此,目標有兩個部分:讓最可能的程式是正確的,並讓那些不太可能的程式在出貨前失敗。
這其實是常見的 API 設計原則(Bloch 的「容易正確使用,難以錯誤使用」、Minsky 的「讓非法狀態無法被表達」、成功的陷阱),在兩個更嚴格的假設下進行:消費者的熟悉度是基於公開程式碼的可衡量分佈,且消費者只會驗證它能夠執行的部分。
消費者的三個屬性
以下所有內容皆源於此。當某項主張對你的決策至關重要,或你需要進行本地測量時,請參閱證據分類帳;其中包含來源、範圍說明以及測量流程。
-
先驗知識是頻率形狀、版本混合且無法修補的。 模型對某個模式的了解程度與它在訓練中出現的頻率成正比;準確率與語料庫頻率成對數線性關係。庫的所有版本會疊加成一個分佈,這就是為什麼模型在 25% 到 38% 的時間裡會發出已棄用的 API,且在版本固定的任務上得分約為 50%。庫發佈的任何內容都無法編輯這個分佈,只能相符或覆蓋它。
-
上下文會覆蓋先驗,但僅限於實際存在的上下文。 從過時代碼提取的提示導致 70% 到 90% 的棄用用法;而從當前代碼提取的提示則導致 9% 到 18%。檔案中的同層級程式碼對輸出的影響大於庫發佈的任何內容。檢索到的文件有幫助;單純的版本標籤幫助有限。代理永遠不會載入的已發佈文件則毫無作用。
-
只有可執行的回饋才算驗證。 型別檢查是確定性、低成本且始終在循環中的,且在 LLM 生成的 TypeScript 中,94% 的編譯錯誤是型別錯誤,因此它能捕捉到很多問題。渲染成本高,需要設置,且即使有螢幕截圖,視覺語言模型對佈局的判斷仍然很弱。如果型別系統沒有編碼的內容,模型一旦出錯就會直接出貨。
兩個後果。錯誤的成本是不對稱的:人類看到一個錯誤的間距能在幾秒內修復,而代理的錯誤間距會直接出貨,因此「易於修復」的價值遠低於「難以出錯」。此外,演進必須是累加的:重新命名或改變語意會讓代理付出代價,直到下一次訓練運行,且舊的形式永遠不會完全從分佈中消失。因此,當你必須做出破壞性變更時,請將遷移資訊放在型別和同位置的文件中,而不僅僅是更新日誌。
三項策略
每個設計槓桿都是其中之一。在評估設計時,請詢問它採用了哪項策略,又削弱了哪項策略。
策略 1:讓預設猜測是正確的
代理的第一個猜測是先驗加上可見上下文。有兩個因素塑造了它。
名稱是對分佈的賭注。 名稱的價值是清晰度乘以一致性:其公開用法的單義性程度,乘以該意義是否與你的相符。清晰度與語料庫大小、年限和穩定性相關,因此 HTML 和 CSS 規範詞彙優於 ARIA 模式名稱,優於跨庫詞彙(sm/md/lg、primary/danger、solid/outline/ghost),也優於任何單一庫的慣用語;在語義允許的範圍內,盡可能深入地錨定。一致性是二元的,而一個熟悉但行為不同的名稱是最糟的情況:模型會自信地套用學到的意義,且程式碼仍然能編譯。當沒有清晰的名称與你的語義一致時,請使用從已知語法組合而成的新名稱。Page/PageHeader/PageTitle 不存在於任何主流庫中,但 X/XHeader/XTitle 的組合無償地傳達了層級和子代順序,並且在自動完成和代理的計劃中保持可發現性。一個依賴於另一屬性的屬性(例如 direction 開關下的 align)相當於兩個耦合的賭注;把它們解耦成每軸一個元件或明確的名稱。好處是減少了一個自由度,而不是拯救了一個先驗;模型處理 CSS 本身的軸翻轉是沒有問題的。
可見的程式碼庫就是提示。 同層級用法、目錄和規範範例才是代理實際依賴的條件。規律性會壓縮提示:如果每個元件都用相同的詞彙組合相同的軸,那麼上下文中的兩個元件就能教會整個目錄,而每個同義詞或每個元件的例外都是一個需要被教導的事實,但通常不會被教導。目錄會劃分輸出:代理在閱讀程式碼之前會先以「頁面標題」和「工具列」等單位進行規劃,並強制將計劃套用於任何存在的內容,因此請根據這些單位來命名元件。範例展示的內容將成為工作流程,包括 className 覆蓋;請展示預期的路徑,並保持範例與代理必須接觸的程式碼版本一致且置於同一位置。
先驗的強度為遵循代理必須編寫的語法提供了正當理由。但它絕不能為暴露一個代理本不該做出的決策提供正當理由;那是策略 3 的範疇。
策略 2:讓錯誤輸出在出貨前就失敗
任何渲染或執行時期會捕捉到的東西都必須更早失敗,否則它就會出貨。
在型別能表達的地方,把它放進型別裡。 使用封閉的聯集而非沒有人設計過的布林組合。使用可區分聯集來處理依賴於模式的屬性。使用 Omit 來排除某個部分不支援的內容,而不是記錄「不要在這種情況下使用它」。對於那些行為層次留給消費者決定的意義,使用必要的屬性(對話框標題、圖示按鈕標籤、錯誤文字;Radix 和 React Aria 僅在代理永遠看不到的控制台中警告這些)。對 asChild/as 或明確的非多型變體(如 ButtonLink)的不支援情況產生編譯錯誤。測試方法每次都一樣:一個填寫屬性的代理會看到合法的詞彙,而非法程式則無法編譯。
在型別無法表達的地方,讓分歧在程式碼編寫處可見。 名稱承載了型別無法編碼的佈局預期(容器擁有其內邊距、區塊填滿其父層、間距來自上層的 gap;案例研究列出了這些)。一個破壞其名稱所創建預期的元件會編譯成功並在代理無法看到的那個維度上安靜地錯誤渲染。請在呼叫點(一個必要的屬性、一個不衝突的名稱)或代理可靠載入的上下文中(程式碼旁邊的規則)標示出這個破壞。代理不會遇到的文檔不算數。
策略 3:移除決策點
一個只有透過渲染才能看到正確性的決策應該屬於函式庫,而不是呼叫點。一個原則涵蓋四個粒度:
- 數值。 量化每個幅度,並透過語義角色來解析外觀:在呼叫點使用
gap="3"和tone="danger",而在主題中使用 px 和 hex。模型了解 flex 和 grid 的機制;它們無法在看不到的情況下選擇 12 還是 16,而且獨立選擇會在不同的檔案中漂移,形成無人能見的節奏。規範關鍵字(justify="space-between")在規模消除猜測的同時保持了語義的自由度。 - 組合。 擁有佈局,而不僅僅是小工具。Braid 的規則是最乾淨的版本:元件不攜帶外部空白,而佈局元件負責所有間距。一個停留在小工具邊界的系統會讓每個呼叫點即興發揮。
- 模式。 當組合重複出現時(如
FormField、EmptyState、PageHeader、ConfirmDialog),就把它封裝成元件。每次從原始元件重新推導都是一次偏離的機會。 - 適配。 預設處理視口、內容長度、書寫方向、動畫和對比度偏好,以及載入/空/錯誤狀態,或在型別中要求這些。呼叫點中的媒體邏輯是一種盲目的決策。
逃生艙(className、style、CSS 變數)應該存在,且其機率應低於預設路徑:可用、有排序、但永遠不是重點。如果它們是示範的工作流程,那麼策略 1 已經讓它們成為了預設猜測。
制度決定可讀性 vs. 契約
存在兩種連貫的哲學;shadcn 是第一種最清晰的表述。
- 可讀性。 將源碼發送到儲存庫中。代理可以閱讀和編輯它,且樣式在編寫處是可見的。當代理擁有元件時是正確的:它會打開檔案,而可讀的實作(同位置樣式、最小的間接性)正是它所需要的。
- 契約。 公開一個類型化的 API 並隱藏實作。當代理從應用程式程式碼中消費元件時是正確的:它看到导入、型別和少數同層級用法,且很少打開實作。可讀的樣式就是可寫的樣式,而呼叫點的樣式正是策略 3 想要移除的熵。
大多數系統兩者都需要,根據受眾劃分:可讀性給維護 components/ 的人,契約給從中導入的每個人。在應用策略之前,請先決定一個檔案屬於哪個制度。當系統基於 shadcn 或與之比較時,請閱讀 shadcn 案例研究;它會詳細說明哪些要保留、哪些要更改以及原因。
張力
有兩個,每個都有多個面向。請明確指出取捨;不要假裝它是免費的。
- 集中的機率 vs. 表達力。 每個被移除的自由度都是某人的能力損失。它表現為意圖元件 vs. 原始元件、封閉的軸 vs. 正交的屬性,以及拉伸軸 vs. 分割它(在每個值都回答同一個問題時拉伸;shadcn 的
Buttonvariant,融合了意圖、樣式和元素身份,是經典的融合軸)。有排序的逃生艙是常見的答案。 - 先驗對齊 vs. 所有權。 匹配慣例可以免費獲得正確性,但也會引入慣例的缺陷和變動。它表現為熟悉的名稱 vs. 獨特的名稱(獨特的名稱會降低可發現性;符合語法的新名稱可恢復大部分可發現性)和先驗友好 vs. 設計更好。偏離一次性的對齊成本;保留一個缺陷則會永遠賠上正確性。
透過測量代替信任
這三個屬性是針對模型層級測量的。這些策略是與這些測量結果一致的機制,而不是針對任何 UI 庫測量的;證據文件標記了哪些是測量結果。在採用規則之前,請先運行它:20 到 40 個現實的 UI 任務,每個制度(無上下文、型別加上兩個同層級用法、完整源碼)進行多次取樣;對首次嘗試編譯、被型別捕捉到的錯誤 vs. 靜默錯誤、跨同層級呼叫點的代幣差異、可訪問性 lint 以及簡短的視覺檢查進行評分;更改一個設計決策;重新運行。保留能夠改善數據的規則。
審查清單
- 策略 1:熟悉類似庫的模型是否會在第一次嘗試時猜中這個,如果名稱是借用來的,行為是否完全相同?規範範例是否展示了預期的路徑?
- 策略 2:所有現實中的錯誤使用是否都無法編譯?當名稱的視覺預期被破壞時,這個破壞在程式碼編寫處是否可見?
- 策略 3:是否有任何數值、組合、模式或適配留給了呼叫點盲目決定?
- 制度:這個檔案是由代理擁有還是消費,可讀性/契約的選擇是否經過深思熟慮?
- 演進:這個變更是否會破壞根據去年先驗編寫的程式碼,且遷移資訊是否在型別和同位置文件中可見?