baoyu-design 的內部運作,照管線順序走一遍:它怎麼載入、怎麼掃描並分類你 CSS 裡的 token、編譯與匯入各產生什麼、那份 token 白名單從哪來,以及模型最後怎麼被指導使用這些 token。所有機制均取自 baoyu-design 原始碼;下方的 token 分類器與白名單用的是編譯器裡的真正正則,可互動。
用「根目錄 marker」辨識,不看資料夾名(use-design-system.md:15-18):
| kind | root marker | note |
|---|---|---|
| design system | _ds_manifest.json | 在根;不帶 _d_meta.json |
| project | _d_meta.json | 綁定系統的 _ds_manifest.json 深一層 |
glob designs/*/_ds_manifest.json 只中 authored DS,不中被消費副本(那些在 designs/*/_ds/*/)。
編譯器對 @import closure 裡每個 CSS 檔跑一條正則抽 token,再依值分類。這是原始碼裡的真正規則(ds-core.mjs:124,146-158):
DECL_RE = /(--[A-Za-z0-9-]+)\s*:\s*([^;]+);[ \t]*(?:\/\*\s*@kind\s+([A-Za-z]+)\s*\*\/)?/g hasColor = /#[0-9a-fA-F]{3,8}\b/ || /\b(rgba?|hsla?|okl?ch|oklab|lab|lch|hwb|color)\(/ || {transparent,currentcolor,black,white,red,green,blue,gray,grey} hasLen = /(^|[\s,(])-?\d*\.?\d+(px|rem|em|vh|vw|vmin|vmax|%|pt)\b/ color+len → shadow · color → color · len → spacing · else → other 值分類只產這四種。font 等其他 kind 值分類產不出,只能靠 /* @kind font */ 明標 /* @kind X */ 覆寫上面的值分類 · 值裡的 var() 會先被解析再分類
兩個易踩點:① 分類只看「值」不看「名」——--potato: 42px → spacing、--wu-liao: #f30 → color,名字對編譯器毫無意義。② 名字只認 [A-Za-z0-9-]——含底線的宣告(--x_y)會被整條靜默丟棄、無警告(宣告 300 個含一底線名 → 只收 299)。長度單位也別漏 vmin/vmax。
compile-design-system.mjs 寫出三個生成檔(絕不手改),build-preview.mjs 另寫 preview.html:
| artifact | role |
|---|---|
| _ds_bundle.js | 所有元件打包掛到 window.<Namespace> |
| _ds_manifest.json | namespace / components / tokens(107,含kind) / cards / startingPoints / globalCssPaths / fonts / source |
| _adherence.oxlintrc.json | oxlint 設定(全 warn,勸告性) |
| preview.html | 單檔互動檢視頁 |
這份 adherence 設定實際會擋掉什麼(compile:280-338)——它是消費頁被 lint 時才套用的規則,不在編譯期強制:
no-restricted-syntax: Literal[/#[0-9a-fA-F]{3,8}/] → "Raw hex color — use a token via var()." Literal[/\b\d+px\b/] → "Raw px value — use a spacing token via var()." no-restricted-imports: 元件要從 index.js 匯入,不准碰內部 per-component: 由 .d.ts 生出 prop 名白名單 + enum 值規則 x-omelette: { components, tokens, tokenKinds, fontFamilies }
import-design-system.mjs 複製進 _ds/<slug>/:CSS @import closure、CSS 裡的 url() 目標(字型/圖)、bundle/manifest/adherence/README/SKILL、assets/;並生成(非複製)_ds_prompt.md。只寫 _ds/<slug>/ 與 _d_meta.json。
_ds_prompt.md = 每次設計前要讀的強制契約:綁定宣告 + 僅視覺 scope + bundle 接線(React UMD / stylesheet links / bundle script)+ 完整 guide 內嵌 + token 白名單。(babel 範例與 component excerpt 只在系統有 component 時才出現;本 DS 0 元件,故無。)
上一段 import 生成的 _ds_prompt.md 裡,逐字列出一份模型設計時只准用的 var(--*) 清單——這就是白名單。機制(ds-prompt.mjs:269-285 ← import:206):把你 CSS 裡宣告的所有 --* 名稱去重、排序,不依 kind 過濾——連 base ramp(--brand-*/--n-*)都收。「只用語意層 token」是 authoring guide 的文字約定,不是機制強制的。
白名單裡有幾個,等於你在 CSS 裡寫了幾個合法 --*——沒有上限、沒有註冊、baoyu 沒有一份內建清單。此 DS 目前 88 個(下方),多寫一個就 89。manifest 記的是 107 個(含 :root[data-theme=dark] 的 19 個同名重定義);白名單經 new Set() 去重後成 88。
到這裡,token 已被抽取、分類、編譯、匯入,也收進了白名單。但真正的問題還沒答——一套 DS 有數十上百個 token,模型怎麼決定 --accent 擺按鈕、--space-6 擺 padding?這裡把它講死。
生成 _ds_prompt.md 的 ds-prompt.mjs 全檔 290 行——grep 不到任何 CSS 屬性名(padding / background / color / box-shadow 一個都沒有),沒有任何 token→用途的對應表。它只是把字串拼起來。沒有演算法決定 token 擺哪。
但「指導模型忠實使用 token」的 prompt 是有的,而且是純文字、分三層注入(附確切 file:line):
關鍵的分界——上面每一行講的都是紀律(跟著系統、只用它的 token、比對視覺語彙、別發明),沒有任何一行是「--accent → 按鈕」這種逐元素對應:
「要用 DS 的 token、只准用白名單、別發明色、比對視覺語彙」——上面可引用的那些行。
--accent 到底擺哪個元素——沒有來源行。模型靠 ① token 名字(--surface-panel 一看就知是面板背景)② kind(color 擺色屬性、spacing 擺 padding)③ guide 的角色描述 ④ 一般 CSS 慣例,自己推。
這也是為什麼 authoring guide 死命要求語意命名——名字是模型判斷位置的唯一線索。取成 --x1 --x2 模型就只能亂猜。而 adherence lint 只擋「用了原始 hex/px」,不檢查你有沒有用「對」的 token——把 --text-faint 用在該用 --text-primary 的地方,它一聲不吭。
沒有任何 baoyu-design 的 skill/prompt/bash 會「以主色+截圖為輸入自動生出 design system」。自動化匯入只接受結構化來源:.fig 解碼(import-figma)、真實 CSS(import-from-html「read code not screenshots」)、GitHub 原始碼(import-from-github)。generate-images.md 是 raster 圖後端,不是 DS 產生器。
baoyu 提供的是 (a) 散文方法論(authoring guide 教你 token 怎麼分層、cards 怎麼寫)+ (b) compile/import 工具鏈。而「這主色配什麼中性色、這截圖的設計語言是什麼、OKLCH 色階算多少」——全是模型自己推的,沒有腳本。baoyu 保證格式一致與可消費,不保證設計品質。
_ds/celestial-design-system/,全部視覺僅用其 token 白名單(此 DS 目前 88 個)構成。設計語彙源自使用者提供的鋼彈00 Veda 控制台截圖;主色 #0186d1。~/.claude/skills/baoyu-design/ 原始碼。