yuanlang12/openpipal/design-system-authoring

Claude Code and agent skill by yuanlang12/openpipal

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

把一套设计系统做成 ~/.openpipal/design-systems/<名>/ 下的文件夹交付物——目录蓝图、tokens、组件三件套(jsx + .d.ts + .prompt.md)、@dsCard 预览卡、UI kit、编译产物与画廊评审闭环。用户说"做一套设计系统 / UI kit / 品牌规范库 / 组件库",或要修订、增补、重新编译已有的那一套时必读。只是要给单个交付物定色板和字体,读 design-tokens 就够,不用读这份。

Install

npx vskill@latest install yuanlang12/openpipal/design-system-authoring

SKILL.md

design-system-authoring

这份技能管怎么把一套设计系统组织成一个文件夹,并让宿主把它编译成能用的东西。

  • 色值怎么推、字号刻度怎么定、token 该叫什么名字 → design-tokens,本篇不重复。
  • 单个页面的版式与观感判断不在本篇范围。

一、交付物是一个文件夹,不是 artifact

  • 落点 ~/.openpipal/design-systems/<系统名>/,kebab-case。
  • 本交付豁免"整页 HTML 走 artifact"的规则:设计系统就是一堆文件,用 write 逐个文件写。
  • `SKILL.md` 存在 = 注册完成。 宿主列举设计系统时只认一件事:目录下有一个真实的、非软链的

SKILL.md(role-manager.ts:352-367)。没有它,这套系统在新建任务界面里等于不存在; 有了它,不需要任何别的登记动作。

  • 系统名会被校验:非空、≤128 字符、首尾无空白、不以 . 开头、不含 / \ .. \0

(design-system-resource.ts:198-210);系统目录本身不能是软链(同文件 13-33)。

  • SKILL.md frontmatter 的 name / description 就是画廊页眉的标题与副标题

(role-manager.ts:449-469,逐行解析、只抓这两个键,不是 YAML 解析器)。

谁会读这个文件夹——这决定了它该怎么组织

用户在新建任务界面勾选一套系统后,它以 category="design-system" 加绝对路径进入那个会话的上下文 (RolePreflowPanel.tsx:517)。那个会话拿到的指令是固定的(agent-runtime/openpipal-prompt-core.ts:181-182): 先读 SKILL.md → 按它的指引读 README / tokens / 组件文件 → 要用哪个组件先读它的 .prompt.md → 交 dc 产物时把 token 文件的 :root { --… } 块原样拷进 helmet `<style>`。

三条推论,直接决定蓝图:

  1. SKILL.md 是唯一入口,必须写清阅读顺序,不能只写一句"看 README"就完事;
  2. 每个组件的 .prompt.md 是它的权威用法说明,不是可选装饰;
  3. 每个 token 文件要能被单独整块拷走——语义别名和它依赖的基础值放同一个文件里,

跨文件 var() 引用在拷贝后会断链,下游拿到的是一堆解析不出来的变量名。

二、目录蓝图

<系统名>/
├── SKILL.md # 入口:品牌一句话 + 阅读顺序 + 硬规则。存在即注册
├── README.md # 设计圣经,画廊底部整篇 markdown 渲染
├── styles.css # 只放 @import 行,自己不写任何声明(见 §8)
├── tokens/*.css # :root 里的 --x: y,一个关注点一个文件
├── components/<组>/ # <Name>.jsx + <Name>.d.ts + <Name>.prompt.md + <组>.card.html
├── ui_kits/<产品面>/ # index.html(+ README + jsx)
├── guidelines/*.html # specimen 预览卡
├── assets/ # logo / 图标 / 插画(拷进来的,不是画的)
├── fonts/ # .woff2 / .woff / .ttf / .otf
└── _* # 宿主生成,别手写(见 §4)

以 _ 或 . 开头的目录不进任何扫描;ui_kits assets fonts node_modules screenshots shared 不进卡片扫描(role-manager.ts:446、ds-compile.ts:171)。品牌方有自己的目录惯例时从其惯例, 但 SKILL.md / styles.css / components/ / ui_kits/ 这四个位置是硬约定,改了扫描就找不到。

三、骨架:能直接抄的三个最小件

预览卡(一张一个 .html)
html
<!-- @dsCard group="Colors" viewport="700x180" name="Brand Ramp" subtitle="品牌色 50→900" -->
<!DOCTYPE html>
<html lang="zh"><head><meta charset="utf-8"><link rel="stylesheet" href="../styles.css"></head>
<body style="margin:0;padding:16px;background:var(--paper);font-family:var(--font-body)">
<div style="display:flex;gap:8px">
<div style="flex:1;height:64px;border-radius:var(--radius-md);background:var(--brand-500)"></div>
<div style="flex:1;height:64px;border-radius:var(--radius-md);background:var(--brand-700)"></div>
</div>
</body></html>

首行必须是 @dsCard(见 §5)。styles.css 走相对路径,深度按卡片所在目录算: guidelines/ 用 ../,components/core/ 用 ../../。卡内不写标题不加外框——卡名由画廊渲染在卡外。

组件三件套
jsx
// components/core/Button.jsx
export function Button({ variant = 'primary', size = 'md', disabled, children, ...rest }) {
const [pressed, setPressed] = React.useState(false) // 注意:React.useState,不是裸 useState
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onPointerDown={() => setPressed(true)}
onPointerUp={() => setPressed(false)}
style={{ transform: pressed ? 'translateY(0.5px)' : 'none' }}
{...rest}
>{children}</button>
)
}

这个例子走 className,那就意味着 .btn / .btn-primary 等必须在被 styles.css @import 的 CSS 里 真实定义(§10);不想维护类名就全用内联 style + var(--token),两条路都行,混着走最容易漏。

ts
// components/core/Button.d.ts —— LOAD-BEARING:组件约束规则的唯一输入
export interface ButtonProps {
/** 视觉变体 @default 'primary' */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
/** 尺寸 @default 'md' */
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
children?: React.ReactNode
}
md
<!-- components/core/Button.prompt.md -->
主操作按钮。一屏最多一个 primary,其余用 secondary / ghost。
<Button variant="primary" size="md">保存</Button>
variant: primary(实底品牌色)/ secondary(描边)/ ghost(透明)/ danger(实底红)
size: sm 28px / md 36px / lg 44px 行高
组件卡(消费编译产物)
html
<!-- @dsCard group="Components" viewport="700x260" name="Buttons" subtitle="四种变体 × 三种尺寸 × 禁用态" -->
<!DOCTYPE html>
<html lang="zh"><head><meta charset="utf-8"><link rel="stylesheet" href="../../styles.css"></head>
<body style="margin:0;padding:16px;background:var(--paper)">
<div id="root"></div>
<script src="../../_vendor/react.production.min.js"></script>
<script src="../../_vendor/react-dom.production.min.js"></script>
<script src="../../_ds_bundle.js"></script>
<script>
var DS = window.AcmeUi_1f3c9d; // namespace 见 _ds_manifest.json
var h = React.createElement;
ReactDOM.createRoot(document.getElementById('root')).render(
h('div', { style: { display: 'flex', gap: '12px' } },
h(DS.Button, { variant: 'primary' }, '主按钮'),
h(DS.Button, { variant: 'secondary' }, '次按钮'),
h(DS.Button, { variant: 'ghost' }, '幽灵'),
h(DS.Button, { variant: 'danger', disabled: true }, '禁用')))
</script>
</body></html>

普通 <script> + React.createElement,不用 text/babel、不用任何远程地址(见 §6)。 组件或 token 改了,这张卡重编译后会跟着变——这是它比手写 CSS 复刻更保真的原因。

四、编译:一次工具调用,落四件产物

create_artifact({ type: 'design-system', title: '<显示名>', content: '{"name":"<文件夹名>"}' }) 既是"把画廊交给用户"的手势,也是编译触发器(openpipal-product-tools.ts:1155-1187)。它落四件:

产物是什么出处
_vendor/react*.production.min.jsReact 18.3.1 UMD 两件,每次编译都刷新ds-compile.ts:108-115、741-745
_ds_manifest.json12 键机器索引:namespace components cards tokens globalCssPaths fonts brandFonts hasThumbnailHtml source,另有恒为空的 startingPoints templates themesds-compile.ts:789-809
_ds_bundle.jsformat 4 预编译包,挂到 window.<namespace>ds-compile.ts:811-886
_adherence.oxlintrc.json从 .d.ts 生成的 oxlint 规则ds-compile.ts:888-895
  • namespace = PascalCase(文件夹名)_sha1(文件夹名)前6位(ds-compile.ts:121-132)。确定性、同名可复现;

首字符是数字时前缀 Ds。别自己编,编完了从 _ds_manifest.json 里读。

  • best-effort:单件失败不阻断其余,错误随工具返回值回来(前 6 条),比如某个 jsx 的 esbuild 语法错。

看到 ⚠️ 设计系统编译告警 就修,那是编译器在指你的语法错。

  • 时序:写完组件三件套 → 先调一次 create_artifact(type='design-system') 让 _vendor/ 和

_ds_bundle.js 落盘 → 再写并自检消费真组件的卡 → 收尾用同一个 id 再调一次刷新画廊。 第一次编译之前,任何引用 _ds_bundle.js 的卡都是白板。

  • 同一套系统反复发布:带历史里那个画廊 artifact 的原 id。不带 id 且标题相近会被拦

(只和同 type 比对,artifact-store.ts:544-556),拒绝消息里给的 id 就是本系统的画廊,带上它重发。 force_new: true 只在用户明确要"另做一份留着对比"时用。

  • 没有 jsx 组件的系统照样能编(components 为空壳),画廊走 @dsCard 派生,不受影响。
_ds_bundle.js 怎么编出来的——这决定了 jsx 怎么写

transformComponentSource(ds-compile.ts:493-531)在交给 esbuild 之前剥掉所有 import 行:

  • import React from 'react' → 剥。React 由 _vendor/ 提供的全局承接。
  • `import { useState } from 'react'` 也被剥,且不做任何补偿。 组件里写裸 useState 就是渲染期

ReferenceError。一律 `React.useState` / `React.useEffect` / `React.useMemo`。

  • import { Icon } from './Icon.jsx' → 剥,并把这个文件里所有独立出现的 Icon 改写成

__ds_scope.Icon(ds-compile.ts:843-845)。求值时解析,文件先后顺序无关。

  • 任何 npm 依赖 → 静默剥掉。组件只能靠 React + CSS 变量,不能有第三方包。

暴露规则:只有 components/** 的具名导出挂上 window.<namespace>;ui_kits/** 的 jsx 照样编译进包, 但只进 unexposedExports,卡片里取不到(ds-compile.ts:854-861)。要被 kit 复用的东西必须住在 `components/`。 每个模块单独 try/catch,出错记进 window.<namespace>.__errors (ds-compile.ts:849-853)——卡片渲染不出来时先打这个数组。

_adherence.oxlintrc.json 怎么来的

buildAdherence(ds-compile.ts:602-707)用 TypeScript 解析 components/**/*.d.ts:

  • 只认 `interface` 声明,type X = {…} 不算。XProps → 组件 X;不带 Props 后缀的接口名会被

原样当成组件名,所以数据形状接口别塞进组件的 .d.ts。

  • 每个 interface 的属性名 → 该组件的 prop 白名单,额外放行 key ref className style children

(ds-compile.ts:623)。

  • 纯字符串字面量联合('primary' | 'ghost')→ 枚举校验;掺进任何非字面量类型就整条不算枚举。
  • 三条全局禁令,全 warn 级(ds-compile.ts:627-641):裸 hex 色值 / 裸 px 数值 /

不在 brandFonts 里的 font-family。第三条只在 `brandFonts` 非空时才生成。

这份文件没有宿主在跑。 全仓只有 ds-compile.ts 提到 oxlint,应用里没有任何地方执行它—— 落盘只为互操作,给外部工具和人读。所以那三条禁令没有机制兜底,是纪律不是门闩,靠你自己守。

五、@dsCard:画廊的驱动元数据

每张预览卡首行一条注释,属性顺序任意、单双引号都容(ds-compile.ts:188-210、role-manager.ts:551-571):

html
<!-- @dsCard group="Type" viewport="700x220" name="Display Scale" subtitle="展示字级 1.333 比例" -->
  • viewport="WxH" → 画廊 iframe 的实际渲染尺寸(DesignSystemGallery.tsx:345-352)。

缺省 700×400;高度被静默夹到 640 上限(role-manager.ts:569),写 900 会得到 640。

  • group → 归入哪个分组。缺省用卡片所在的顶层目录名,根目录下的卡兜底成 general。

组名用 Title Case,全套一致(Colors / Type / Spacing / Motion / Components / Brand)。

  • name 缺省由文件名标题化——先去 .html 再去 .card,-_. 转空格后首字母大写

(buttons.card.html → Buttons,type-scale.html → Type Scale);subtitle 缺省空。

  • 分组顺序按首次出现,组内按文件名排序。

缺 `@dsCard` 不报错:卡照样进画廊,只是掉进兜底分组、尺寸也是兜底值。缺标签视为这张卡没做完。

尺寸纪律:宁多张小卡,不做一张大卡。主色 / 中性色 / 语义色分开,display / body / mono 分开, 间距 token 和间距实例分开。一套系统典型 12–20 张。

UI kit 不吃 viewport:ui_kits/<目录>/index.html 在画廊里恒为全宽 × 640 高 (DesignSystemGallery.tsx:394-400),给 kit 写 @dsCard 只影响 _ds_manifest.json 的 cards 条目。

六、画廊预览是严格断网的沙箱

卡片和 UI kit 在画廊里通过本地回环 HTTP 以 iframe 加载,响应头带一条严格 CSP (design-system-resource.ts:73-102,http-server.ts:1312-1346):

default-src 'none'; script-src 'unsafe-inline' <本系统目录>; style-src 'unsafe-inline' <本系统目录>;
img-src data: blob: <本系统目录>; font-src data: <本系统目录>; connect-src 'none';
frame-src 'none'; worker-src 'none'; form-action 'none'; base-uri 'none'; sandbox allow-scripts

能加载的只有本设计系统目录内的文件,加上内存里的 data: / blob:。落到实处:

  • 字体必须是本地文件 + `@font-face`。 没有 Google Fonts、没有任何字体 CDN、没有 @import url(https://…)。

这条和 dc-authoring 的字体口径是同一条。可伺服的字体后缀只有 .woff2 .woff .ttf .otf (design-system-resource.ts:182-188),别的后缀直接 404。缺正版字体就用系统字体栈替身并打旗标(§10)。

  • 远程图标、远程图片、远程脚本一律被拒。素材拷进 assets/ 再相对引用。
  • connect-src 'none' → fetch / XMLHttpRequest / WebSocket / sendBeacon 全断。UI kit 的"数据"

只能是写死在页面里的常量。

  • 沙箱不含 allow-same-origin → 文档处于不透明源:**localStorage / sessionStorage 一碰就抛

SecurityError**;alert / confirm(无 allow-modals)、表单提交(无 allow-forms 且 form-action 'none')、嵌套 iframe(frame-src 'none')都不可用。交互状态只能放在内存里。

  • 没有 'unsafe-eval' → 别在卡片里 eval / new Function / text/babel 现场编 JSX。JSX 由

_ds_bundle.js 预编译,这是它存在的理由。

  • 单个文件超过 8MB 不伺服(design-system-resource.ts:7)。大图先压。

不能通过放宽预览的安全策略来兼容外部依赖。 把依赖拷进目录里,并在 README 记来源。

七、宿主已经替你做了什么

这些不用你写,写了也是白写:

  • 不用手写 token 清单、分类、品牌字体清单、lint 规则。 编译器静态解析你的 CSS 与 .d.ts 自动生成。
  • 不用手写 `_ds_manifest.json` / `_ds_bundle.js` / `_adherence.oxlintrc.json` / `_vendor/`。

手写就是和编译器打架——下次发布会被整个覆盖。你只写源:jsx / d.ts / prompt.md / css / card.html / README.md / SKILL.md。

  • 不用手动登记系统。 SKILL.md 落地即被列举,新建任务界面自动出现。
  • 不用维护评审台账。 _review.json 由画廊读写(role-manager.ts:375-418),下次发布时宿主自动

把"仍被踩且未解决"的项列到工具返回值里。别手写、别删。

  • 不用自己截图。 render_artifact 自检会渲染页面、回传 console 错误/警告、跑一遍文本重叠检测

(最多 5 条)、抓一段页面文本摘要,并把截图作为 image block 直接内联在工具结果里回给你 (openpipal-product-tools.ts:730-784;截图同时存到本会话的 ~/.openpipal/outputs/<会话id>/.self-check/(路径在结果里), 那份是给人看的)。你不需要、也不应该去 read 那个 png 文件。 两种降级会明说:模型不支持看图、或图太大(base64 超 600KB)时只给文本摘要。

  • 不用给画廊单独做缩略图。 根目录的 thumbnail.html 只被记进 manifest 的 hasThumbnailHtml,

画廊不消费它。

八、约束与静默失败

不知道就会踩、踩了不报错的:

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 96 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
52
Repository forks
6
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/yuanlang12/openpipal/design-system-authoring/badge)](https://verified-skill.com/skills/yuanlang12/openpipal/design-system-authoring)

Something wrong with this skill or its page? Tell us.