diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e8df2..3cae7fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,34 @@ All notable changes to omit-design are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [cli 0.4.1] - 2026-05-11 — Quick-start fixes + +### Fixed +- **Scaffold no longer ships a `design/welcome.tsx`** that referenced the + removed `welcome-view` pattern. Fresh projects start with truly empty + `design/` and `mock/` (only `.gitkeep`). `npm run lint` passes + immediately after `init`. +- **`omit-design lint`** on a project with no `design/**/*.tsx` files now + exits 0 with `✓ no design/*.tsx files yet — nothing to lint` instead of + the noisy `No files matching the pattern "design/**/*.tsx" were found` + error from ESLint. +- **`omit-design new-page`** now reads `/patterns//template.tmpl.tsx` + (project-local) instead of the removed `@omit-design/preset-mobile/templates/` + path. If `patterns/` is empty or the requested pattern is missing, the + command exits with a hint pointing at `/distill-patterns-from-prd` or + `/add-pattern`. +- **Scaffold CLAUDE.md / README** no longer reference removed concepts + (`PATTERNS.md` in preset-mobile, `patterns.config.json`, the 8 starter + list). Default `app/App.tsx` redirect falls back to `/workspace` when + `design/` is empty. +- **Lint `require-pattern-header` HINT** no longer points at the removed + `@omit-design/preset-mobile/PATTERNS.md`; now references project-local + `patterns/` directory. + +### Changed +- **README Quick start** (root EN + zh-CN) rewritten to reflect the + PRD → distill → new-design flow. + ## [0.4.0] - 2026-05-11 — Patterns 平台化 Engine 0.4.0 · CLI 0.4.0 · dev-server 0.2.0 (preset-mobile / eslint-plugin / figma-plugin unchanged). diff --git a/README.md b/README.md index 1465194..4321620 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ npm install npm run dev ``` -Open `http://localhost:5173/`. Your single starter design lives at `design/welcome.tsx`. Add new pages in `design//.tsx` — they're auto-discovered as `/designs//`. +Open `http://localhost:5173/` — the workspace lands on `/workspace` with your project. `design/` and `patterns/` start empty; you create both in Claude Code (see below). ## Five-minute tour @@ -41,20 +41,28 @@ Open `http://localhost:5173/`. Your single starter design lives at `design/welco # 1. New project npx @omit-design/cli init cafe-pos cd cafe-pos && npm install +npm run dev # http://localhost:5173/ -# 2. Scaffold a page from a pattern template -npx omit-design new-page list-view design/orders/list +# 2. In the workspace (Library → PRDs → + New), write a PRD. +# Then click "Distill patterns from this PRD" and paste into Claude Code: +# /distill-patterns-from-prd reviews the PRD, creates matching pattern +# files under patterns//. You approve them. -# 3. Run the design server (Vite under the hood) -npm run dev # http://localhost:5173/ +# 3. From the same PRD, click "Copy Claude prompt" and paste into Claude Code: +# /new-design copies the chosen pattern's template into design//.tsx +# and fills the placeholders. -# 4. Run the four hard rules (also runs on every git commit via husky) -npm run lint # blocks design literals, non-whitelist imports, missing @pattern headers +# 4. The four hard rules run on every git commit via husky: +npm run lint # blocks design literals, non-whitelist imports, missing @pattern + # headers, files that declare a pattern but don't import any of + # its signature components -# 5. When new omit-design versions ship, one-shot upgrade +# 5. When new omit-design versions ship: npx omit-design upgrade # bumps deps + scans your project for removed-class refs ``` +No PRD yet? Just ask Claude to make a page — `/new-design` calls `/add-pattern` in conversational mode (5 short questions) and produces a minimal pattern first. + ## Architecture ``` diff --git a/README.zh-CN.md b/README.zh-CN.md index 340cfe3..3d87582 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -33,26 +33,33 @@ npm install npm run dev ``` -打开 `http://localhost:5173/`。脚手架自带 `design/welcome.tsx`。新增页面 = `design//.tsx`,自动发现,路由自动是 `/designs//`。 +打开 `http://localhost:5173/`,工作台自动落在 `/workspace`。`design/` 和 `patterns/` 起步都是空的,两者都在 Claude Code 里按需生成(见下)。 ```bash # 1. 新建项目 npx @omit-design/cli init cafe-pos cd cafe-pos && npm install +npm run dev # http://localhost:5173/ -# 2. 用 pattern 模板生成新页面 -npx omit-design new-page list-view design/orders/list +# 2. 在工作台 Library → PRDs → + New 写一段 PRD +# 点 "Distill patterns from this PRD" → 复制 prompt → 粘到 Claude Code: +# /distill-patterns-from-prd 会读这份 PRD,按需在 patterns// 下 +# 创建匹配的 pattern 文件。你审核通过。 -# 3. 启动设计 server(底层是 Vite) -npm run dev # http://localhost:5173/ +# 3. 同一份 PRD,点 "Copy Claude prompt" → 粘到 Claude Code: +# /new-design 把选中的 pattern 模板复制到 design//.tsx, +# 填好占位符。 -# 4. 跑四条硬规则(git commit 时也会自动跑,挡掉违规提交) -npm run lint # 拦字面量 / 非白名单 import / 缺 @pattern 头 / pattern 没用对应签名组件 +# 4. 四条硬规则在 git commit 时由 husky 自动跑: +npm run lint # 拦字面量 / 非白名单 import / 缺 @pattern 头 / pattern 没用其 + # 签名组件 # 5. omit-design 发布新版后,一键升级 npx omit-design upgrade # 升 deps + 扫项目里残留的旧类名 ``` +没 PRD?直接让 Claude 做页面 —— `/new-design` 会调起 `/add-pattern` 对话模式(5 个问题)先生成最小 pattern 再继续。 + ## 架构总览 ``` diff --git a/packages/cli/README.md b/packages/cli/README.md index e142b8c..b6143df 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -14,11 +14,11 @@ npx @omit-design/cli init my-app | | | |---|---| -| `omit-design init ` | Scaffold a new project (Vite + React + preset-mobile + 4 ESLint hard rules + `.claude/skills/` + `.claude/agents/` + `.claude/settings.json` + husky pre-commit hook). Auto-runs `git init` (gated by `--no-git`). | +| `omit-design init ` | Scaffold a new project (Vite + React + preset-mobile + 4 ESLint hard rules + `.claude/skills/` + `.claude/agents/` + `.claude/settings.json` + husky pre-commit hook). `design/` and `patterns/` ship empty — patterns are produced on demand via `/distill-patterns-from-prd` or `/add-pattern`. Auto-runs `git init` (gated by `--no-git`). | | `omit-design dev` | Start the local design server (Vite). | | `omit-design lint [files...]` | Run the four hard rules (`no-design-literal` / `whitelist-ds-import` / `require-pattern-header` / `require-pattern-components`). With no args, scans `design/**/*.tsx`. With explicit positional file paths (used by lint-staged), scans only those — non-`design/*.tsx` paths are silently skipped. | | `omit-design skills update` | Sync the cli's built-in `.claude/skills/` into the current project's `.claude/skills/`. | -| `omit-design new-page ` | Scaffold a design page from a preset-mobile pattern template. | +| `omit-design new-page ` | Scaffold a design page by copying a project-local pattern template (`/patterns//template.tmpl.tsx`). If `patterns/` is empty or the pattern is missing, exits with a hint pointing at `/distill-patterns-from-prd` or `/add-pattern`. | | `omit-design upgrade` | Bump all `@omit-design/*` deps to npm latest, install, scan project for removed-class references, and link the CHANGELOG. | ## Quick start @@ -30,7 +30,7 @@ npm install npm run dev ``` -Then open `http://localhost:5173/`. The scaffold ships a single demo design at `design/main/welcome.tsx`. +Then open `http://localhost:5173/`. The workspace lands on `/workspace` with your project. `design/` and `patterns/` start empty — see the [main README](../../README.md) for the PRD → distill → new-design flow. ## Examples @@ -57,11 +57,11 @@ omit-design skills update omit-design skills update --dry-run # preview only omit-design skills update --target other/dir -# new-page (scaffold a page from a preset-mobile pattern template) -omit-design new-page list-view design/main/products -omit-design new-page detail-view design/main/order --force -# patterns: dashboard / detail-view / dialog-view / form-view / -# list-view / sheet-action / tab-view / welcome-view +# new-page (copy a project-local pattern template into design/) +# Requires patterns//template.tmpl.tsx to already exist — +# create patterns via /distill-patterns-from-prd or /add-pattern in Claude Code. +omit-design new-page list-view design/orders/list +omit-design new-page detail-view design/orders/detail --force # upgrade (bump all @omit-design/* deps + scan project for legacy refs) omit-design upgrade @@ -82,9 +82,9 @@ Each scaffolded project is self-defending without further wiring: - **`.husky/pre-commit`** — runs `omit-design lint` on every staged `design/**/*.tsx` via lint-staged. Installed by husky's `prepare` script on `npm install`. - **`.claude/settings.json`** — denies AI edits to `app/`, `eslint.config.js`, `vite.config.ts`, `tsconfig.json`, `.husky/`, `package.json`. The deny list is intentional friction; AI working on design files won't trip it. -- **`.claude/skills/`** — 7 Claude Code skills organized as entry / make / deliver: +- **`.claude/skills/`** — 9 Claude Code skills organized as entry / make / deliver: - **Entry**: `start` (state diagnosis), `omit-design-cli` - - **Make**: `new-design`, `add-pattern` + - **Make**: `distill-patterns-from-prd` (PRD → patterns), `add-pattern` (conversational or manual), `new-design` (page from pattern), `bootstrap-from-figma` (visual theme from Figma URL) - **Deliver**: `audit-design`, `ship-design` - Plus `omit-design` (philosophy + 4-layer constraint reference) - **`.claude/agents/`** — 2 sub-agents that take heavy work out of the main conversation: diff --git a/packages/cli/package.json b/packages/cli/package.json index 5c34db6..a453c80 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@omit-design/cli", - "version": "0.4.0", + "version": "0.4.1", "type": "module", "description": "omit-design CLI: init / dev / lint / new-page / skills / upgrade. Init scaffolds husky pre-commit + .claude/settings.json domain bounds.", "bin": { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index b0e03cd..0d0a1bd 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -4,7 +4,7 @@ import { defineCommand, runMain } from "citty"; const main = defineCommand({ meta: { name: "omit-design", - version: "0.4.0", + version: "0.4.1", description: [ "AI-collaborative design composition framework — write TSX, lint with hard rules, preview locally.", "", diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 1651daa..6ab2fab 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -1,5 +1,6 @@ import { defineCommand } from "citty"; import { spawnSync } from "node:child_process"; +import fs from "node:fs/promises"; import path from "node:path"; const SEVERITY: Record = { @@ -10,13 +11,33 @@ const SEVERITY: Record = { const HINT: Record = { "omit-design/require-pattern-header": - "add `// @pattern: ` on the first line (name must exist in @omit-design/preset-mobile/PATTERNS.md)", + "add `// @pattern: ` on the first line (name must match a directory under /patterns/)", "omit-design/whitelist-ds-import": "use @omit-design/preset-mobile / whitelisted Ionic containers (IonList / IonBackButton / IonIcon)", "omit-design/no-design-literal": "use tokens: var(--om-*) / var(--ion-*), or pass Om* component props", }; +async function hasDesignFiles(cwd: string): Promise { + const dir = path.join(cwd, "design"); + try { + const stack: string[] = [dir]; + while (stack.length > 0) { + const cur = stack.pop()!; + const entries = await fs.readdir(cur, { withFileTypes: true }); + for (const e of entries) { + if (e.name.startsWith(".")) continue; + const full = path.join(cur, e.name); + if (e.isDirectory()) stack.push(full); + else if (e.isFile() && e.name.endsWith(".tsx")) return true; + } + } + } catch { + // no design/ at all + } + return false; +} + interface ESLintMessage { ruleId: string | null; severity: number; @@ -72,6 +93,16 @@ export default defineCommand({ return; } + // Fresh project (no design/*.tsx yet) → silent pass instead of failing + // with "No files matching ...". Lint has nothing to enforce here. + if (!usedExplicit && !(await hasDesignFiles(cwd))) { + process.stdout.write( + `✓ no design/*.tsx files yet — nothing to lint\n` + ); + process.exit(0); + return; + } + const targets = explicitFiles.length > 0 ? explicitFiles : [args.glob]; diff --git a/packages/cli/src/commands/new-page.ts b/packages/cli/src/commands/new-page.ts index aea8b1b..a6759a2 100644 --- a/packages/cli/src/commands/new-page.ts +++ b/packages/cli/src/commands/new-page.ts @@ -5,13 +5,13 @@ import fs from "fs-extra"; export default defineCommand({ meta: { name: "new-page", - description: "Scaffold a design page from a preset-mobile pattern template.", + description: "Scaffold a design page by copying a project-local pattern template.", }, args: { pattern: { type: "positional", required: true, - description: "Pattern name (e.g. list-view, detail-view, form-view).", + description: "Pattern name — must match a directory under /patterns/.", }, path: { type: "positional", @@ -26,31 +26,53 @@ export default defineCommand({ }, async run({ args }) { const cwd = process.cwd(); - const templatesDir = path.join( - cwd, - "node_modules", - "@omit-design", - "preset-mobile", - "templates" - ); + const patternsDir = path.join(cwd, "patterns"); - if (!(await fs.pathExists(templatesDir))) { + if (!(await fs.pathExists(patternsDir))) { process.stderr.write( - `✗ ${path.relative(cwd, templatesDir)}/ not found. run npm install first, or run from an omit-design project root.\n` + `✗ patterns/ not found. run this from an omit-design project root.\n` ); process.exit(1); } - const tmplFile = path.join(templatesDir, `${args.pattern}.tmpl.tsx`); + const patternDir = path.join(patternsDir, args.pattern); + const tmplFile = path.join(patternDir, "template.tmpl.tsx"); + if (!(await fs.pathExists(tmplFile))) { - const available = (await fs.readdir(templatesDir)) - .filter((f) => f.endsWith(".tmpl.tsx")) - .map((f) => ` · ${f.replace(/\.tmpl\.tsx$/, "")}`) + const available = (await fs.readdir(patternsDir, { withFileTypes: true })) + .filter((d) => d.isDirectory() && !d.name.startsWith(".")) + .map((d) => ` · ${d.name}`) .sort() .join("\n"); - process.stderr.write( - `✗ unknown pattern: ${args.pattern}\n\navailable patterns:\n${available}\n` - ); + + if (available.length === 0) { + process.stderr.write( + [ + `✗ no patterns yet in ${path.relative(cwd, patternsDir)}/.`, + ``, + `Patterns are produced on demand in Claude Code:`, + ` - With a PRD: workspace Library → PRDs → "Distill patterns from this PRD"`, + ` then paste into Claude Code (/distill-patterns-from-prd).`, + ` - Without a PRD: ask Claude /add-pattern; it asks 3-5 questions`, + ` and produces a minimal pattern.`, + ``, + `Then re-run: omit-design new-page ${args.pattern} ${args.path}`, + ``, + ].join("\n") + ); + } else { + process.stderr.write( + [ + `✗ unknown pattern: ${args.pattern}`, + ``, + `Available patterns in ${path.relative(cwd, patternsDir)}/:`, + available, + ``, + `(or run /add-pattern in Claude Code to create a new one)`, + ``, + ].join("\n") + ); + } process.exit(1); } @@ -79,7 +101,7 @@ export default defineCommand({ ``, `Next steps:`, ` 1. update meta.name / description`, - ` 2. replace TODO placeholders (wire up data / refine copy / fix IonBackButton.defaultHref)`, + ` 2. replace TODO placeholders (wire up data / refine copy)`, ` 3. start dev server: npm run dev`, ``, ].join("\n") diff --git a/packages/cli/templates/init/CLAUDE.md.tmpl b/packages/cli/templates/init/CLAUDE.md.tmpl index 2fb526f..8d20c35 100644 --- a/packages/cli/templates/init/CLAUDE.md.tmpl +++ b/packages/cli/templates/init/CLAUDE.md.tmpl @@ -34,14 +34,16 @@ omit-design 项目 — 设计稿是真实可点击的 React 页面,不是图片 2. **业务页面禁止字面量**:颜色(`#hex` / `rgb` / 命名色)、像素长度。走 token(`var(--om-*)` / `var(--ion-*)`) 或 Om* 组件的 props。 -3. **每个业务页面文件第一行**:`// @pattern: `,name 必须存在于 `node_modules/@omit-design/preset-mobile/PATTERNS.md`(8 个 pattern:list-view / detail-view / form-view / sheet-action / dialog-view / welcome-view / dashboard / tab-view)。 +3. **每个业务页面文件第一行**:`// @pattern: `,name 必须存在为 `/patterns//pattern.json` 的目录。Pattern 是项目本地资产 —— 新项目 `patterns/` 起步为空,通过下面的 skill 按需生成。 -4. **声明的 pattern 必须真用其签名组件**:`@pattern: list-view` 必须 import `OmListRow` / `OmCouponCard` / `OmSettingRow` / `OmProductCard` / `OmMenuCard` / `OmEmptyState` 中至少一个,其它 pattern 同理。映射见 `node_modules/@omit-design/preset-mobile/patterns.config.json`。 +4. **声明的 pattern 必须真用其签名组件**:每个 `/patterns//pattern.json` 的 `whitelist` 字段列出该 pattern 必须 import 至少一个的 `Om*` 组件。lint 由 `require-pattern-components` 强制。 5. **新增页面 / 模式**:优先走对应 skill: - 不知道做什么 → `/start`(状态诊断) - - 新页面 → `.claude/skills/new-design` - - 新模式 → `.claude/skills/add-pattern` + - 有 PRD,想抽 pattern → `.claude/skills/distill-patterns-from-prd` + - 无 PRD,想要新 pattern → `.claude/skills/add-pattern`(对话模式) + - 新页面 → `.claude/skills/new-design`(空 `patterns/` 时会自动调上面两个之一) + - 有 Figma 链接,想抽视觉主题 → `.claude/skills/bootstrap-from-figma` - 审查 → `.claude/skills/audit-design` - 准备发布 → `/ship-design `(lint + a11y + 截图一气呵成) @@ -62,17 +64,17 @@ npm run build # 生产构建 ``` 访问入口: -- `/workspace` — 项目工作台 -- `/designs/main/welcome` — 默认欢迎稿(scaffold 自带) -- `/workspace/app/theme-editor` — 主题编辑器 +- `/workspace` — 项目工作台(项目列表 / Library / Theme Editor 都从这里进) +- `/designs//` — 业务设计稿(`design//.tsx` 自动注册) +- `/workspace/:projectId/theme-editor` — 主题编辑器 ## 不要做的事 - ❌ 在业务页面写 `#FF6B00` 这种字面量 - ❌ 从 `@ionic/react` 直接 import `IonButton` 等视觉组件(走 Om* 封装) - ❌ 接真实 API -- ❌ 不读 PATTERNS.md 就"自创"模式 -- ❌ 在 `design/` 外写 mock 数据 +- ❌ 在 `patterns/` 为空时直接写 `design/` 文件(必须先 distill-patterns-from-prd 或 add-pattern) +- ❌ 在 `mock/` 外写 mock 数据 ## 何时问用户 diff --git a/packages/cli/templates/init/README.md.tmpl b/packages/cli/templates/init/README.md.tmpl index c6c4223..257a3a7 100644 --- a/packages/cli/templates/init/README.md.tmpl +++ b/packages/cli/templates/init/README.md.tmpl @@ -9,18 +9,28 @@ npm install npm run dev # http://localhost:5173 ``` -打开 `/designs/main/welcome` 看 demo;`/workspace` 看项目目录。 +打开 `/workspace` 看项目工作台。`design/` 起步为空,第一张页面通过下面的方式生成。 ## 三件事 ### 加新页 +推荐流程(在 Claude Code 里): + +1. 工作台 **Library → PRDs → + New**,写一段需求描述 +2. 点 **Distill patterns from this PRD** → 复制 prompt → 粘到 Claude Code → 跑 `/distill-patterns-from-prd`,它会在 `patterns/` 下生成 1-N 个匹配的 pattern +3. 跑 `/new-design `,按 pattern 模板复制出 `design//.tsx` + +或不写 PRD,直接对 Claude 说"做一张订单详情页",`/new-design` 会自动调起 `/add-pattern` 对话模式问 3-5 个问题再产页面。 + +也可手工: + ``` -design/orders/detail.tsx # 第一行: // @pattern: detail-view +design/orders/detail.tsx # 第一行: // @pattern: # 自动注册路由 /designs/orders/detail ``` -或跟 Claude 说"加一张订单详情设计稿",会自动从 [PATTERNS.md](./node_modules/@omit-design/preset-mobile/PATTERNS.md) 选 pattern + 复制 template。 +(前提:`patterns//pattern.json` 存在且 `whitelist` 命中此页面 import。) ### 加 mock @@ -42,11 +52,12 @@ design/orders/detail.tsx: npm run lint ``` -AI 友好结构化输出(rule / file:line / sample / hint)。三条硬规则: +AI 友好结构化输出(rule / file:line / sample / hint)。四条硬规则: 1. 文件头 `// @pattern: ` -2. import 来源白名单 -3. 禁止颜色 / px 字面量,走 token +2. 声明的 pattern 必须真用其签名组件(由 `patterns//pattern.json` 的 `whitelist` 强制) +3. import 来源白名单(`@omit-design/preset-mobile` + 少量布局类 Ionic 组件) +4. 禁止颜色 / px 字面量,走 token ## 设计哲学 diff --git a/packages/cli/templates/init/app/App.tsx b/packages/cli/templates/init/app/App.tsx index 39f047c..a694a59 100644 --- a/packages/cli/templates/init/app/App.tsx +++ b/packages/cli/templates/init/app/App.tsx @@ -26,6 +26,8 @@ setupIonicReact({ mode: "ios" }); function DesignRoutes() { const projects = useProjects(); const allEntries = projects.flatMap((p) => p.entries); + // 第一张 design/ 下的页面作为 /designs/ 的默认入口;空项目时把用户送回 /workspace + const fallback = allEntries[0]?.href.replace(/^\/designs\//, "") ?? "/workspace"; return ( {allEntries.map((entry) => { @@ -33,7 +35,7 @@ function DesignRoutes() { const relPath = entry.href.replace(/^\/designs\//, ""); return } />; })} - } /> + } /> ); } diff --git a/packages/cli/templates/init/design/.gitkeep b/packages/cli/templates/init/design/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/cli/templates/init/design/welcome.tsx b/packages/cli/templates/init/design/welcome.tsx deleted file mode 100644 index 92a5eff..0000000 --- a/packages/cli/templates/init/design/welcome.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// @pattern: welcome-view -export const meta = { - name: "欢迎", - pattern: "welcome-view", - description: "默认欢迎页", - source: "scaffold", -} as const; - -import { useNavigate } from "react-router-dom"; -import { OmButton, OmPage } from "@omit-design/preset-mobile"; -import { brand } from "../mock/onboarding"; - -export function WelcomePage() { - const navigate = useNavigate(); - return ( - -
-

Welcome to {brand.tagline}

-

{brand.version}

- navigate("/workspace")}> - 进入工作台 - -
-
- ); -} -export default WelcomePage; diff --git a/packages/cli/templates/init/mock/.gitkeep b/packages/cli/templates/init/mock/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/cli/templates/init/mock/onboarding.ts b/packages/cli/templates/init/mock/onboarding.ts deleted file mode 100644 index 27e42eb..0000000 --- a/packages/cli/templates/init/mock/onboarding.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 项目级 mock 数据。业务页面通过相对路径 import,例如: - * - * import { brand } from "../mock/onboarding"; - * - * 这里不受 ESLint 设计稿规则约束(只 design/ 内受约束),你可以放任何字段。 - */ -export const brand = { - tagline: "omit-design", - version: "v0.1.0", -}; diff --git a/packages/cli/templates/init/prds/example.md b/packages/cli/templates/init/prds/example.md index 69fe0c7..bbe315c 100644 --- a/packages/cli/templates/init/prds/example.md +++ b/packages/cli/templates/init/prds/example.md @@ -1,14 +1,22 @@ --- title: Example PRD -pattern: list-view -target: design/main/welcome.tsx +pattern: +target: design//.tsx status: draft --- # Example PRD -This is a placeholder PRD. Edit it from the workspace's **Library → PRDs** tab, -then click "Copy Claude prompt" and paste into Claude Code to scaffold a design. +This is a placeholder PRD. Edit it from the workspace's **Library → PRDs** tab. +Two hand-off buttons there: + +- **Distill patterns from this PRD** — runs `/distill-patterns-from-prd` to + create matching pattern definitions under `patterns/`. Run this first. +- **Copy Claude prompt** — wraps the PRD in a `/new-design` invocation to + scaffold the actual design page from an existing pattern. + +Both buttons just put a prompt on your clipboard — you paste it into Claude +Code yourself. ## Goal diff --git a/templates/init/CLAUDE.md.tmpl b/templates/init/CLAUDE.md.tmpl index 2fb526f..8d20c35 100644 --- a/templates/init/CLAUDE.md.tmpl +++ b/templates/init/CLAUDE.md.tmpl @@ -34,14 +34,16 @@ omit-design 项目 — 设计稿是真实可点击的 React 页面,不是图片 2. **业务页面禁止字面量**:颜色(`#hex` / `rgb` / 命名色)、像素长度。走 token(`var(--om-*)` / `var(--ion-*)`) 或 Om* 组件的 props。 -3. **每个业务页面文件第一行**:`// @pattern: `,name 必须存在于 `node_modules/@omit-design/preset-mobile/PATTERNS.md`(8 个 pattern:list-view / detail-view / form-view / sheet-action / dialog-view / welcome-view / dashboard / tab-view)。 +3. **每个业务页面文件第一行**:`// @pattern: `,name 必须存在为 `/patterns//pattern.json` 的目录。Pattern 是项目本地资产 —— 新项目 `patterns/` 起步为空,通过下面的 skill 按需生成。 -4. **声明的 pattern 必须真用其签名组件**:`@pattern: list-view` 必须 import `OmListRow` / `OmCouponCard` / `OmSettingRow` / `OmProductCard` / `OmMenuCard` / `OmEmptyState` 中至少一个,其它 pattern 同理。映射见 `node_modules/@omit-design/preset-mobile/patterns.config.json`。 +4. **声明的 pattern 必须真用其签名组件**:每个 `/patterns//pattern.json` 的 `whitelist` 字段列出该 pattern 必须 import 至少一个的 `Om*` 组件。lint 由 `require-pattern-components` 强制。 5. **新增页面 / 模式**:优先走对应 skill: - 不知道做什么 → `/start`(状态诊断) - - 新页面 → `.claude/skills/new-design` - - 新模式 → `.claude/skills/add-pattern` + - 有 PRD,想抽 pattern → `.claude/skills/distill-patterns-from-prd` + - 无 PRD,想要新 pattern → `.claude/skills/add-pattern`(对话模式) + - 新页面 → `.claude/skills/new-design`(空 `patterns/` 时会自动调上面两个之一) + - 有 Figma 链接,想抽视觉主题 → `.claude/skills/bootstrap-from-figma` - 审查 → `.claude/skills/audit-design` - 准备发布 → `/ship-design `(lint + a11y + 截图一气呵成) @@ -62,17 +64,17 @@ npm run build # 生产构建 ``` 访问入口: -- `/workspace` — 项目工作台 -- `/designs/main/welcome` — 默认欢迎稿(scaffold 自带) -- `/workspace/app/theme-editor` — 主题编辑器 +- `/workspace` — 项目工作台(项目列表 / Library / Theme Editor 都从这里进) +- `/designs//` — 业务设计稿(`design//.tsx` 自动注册) +- `/workspace/:projectId/theme-editor` — 主题编辑器 ## 不要做的事 - ❌ 在业务页面写 `#FF6B00` 这种字面量 - ❌ 从 `@ionic/react` 直接 import `IonButton` 等视觉组件(走 Om* 封装) - ❌ 接真实 API -- ❌ 不读 PATTERNS.md 就"自创"模式 -- ❌ 在 `design/` 外写 mock 数据 +- ❌ 在 `patterns/` 为空时直接写 `design/` 文件(必须先 distill-patterns-from-prd 或 add-pattern) +- ❌ 在 `mock/` 外写 mock 数据 ## 何时问用户 diff --git a/templates/init/README.md.tmpl b/templates/init/README.md.tmpl index c6c4223..257a3a7 100644 --- a/templates/init/README.md.tmpl +++ b/templates/init/README.md.tmpl @@ -9,18 +9,28 @@ npm install npm run dev # http://localhost:5173 ``` -打开 `/designs/main/welcome` 看 demo;`/workspace` 看项目目录。 +打开 `/workspace` 看项目工作台。`design/` 起步为空,第一张页面通过下面的方式生成。 ## 三件事 ### 加新页 +推荐流程(在 Claude Code 里): + +1. 工作台 **Library → PRDs → + New**,写一段需求描述 +2. 点 **Distill patterns from this PRD** → 复制 prompt → 粘到 Claude Code → 跑 `/distill-patterns-from-prd`,它会在 `patterns/` 下生成 1-N 个匹配的 pattern +3. 跑 `/new-design `,按 pattern 模板复制出 `design//.tsx` + +或不写 PRD,直接对 Claude 说"做一张订单详情页",`/new-design` 会自动调起 `/add-pattern` 对话模式问 3-5 个问题再产页面。 + +也可手工: + ``` -design/orders/detail.tsx # 第一行: // @pattern: detail-view +design/orders/detail.tsx # 第一行: // @pattern: # 自动注册路由 /designs/orders/detail ``` -或跟 Claude 说"加一张订单详情设计稿",会自动从 [PATTERNS.md](./node_modules/@omit-design/preset-mobile/PATTERNS.md) 选 pattern + 复制 template。 +(前提:`patterns//pattern.json` 存在且 `whitelist` 命中此页面 import。) ### 加 mock @@ -42,11 +52,12 @@ design/orders/detail.tsx: npm run lint ``` -AI 友好结构化输出(rule / file:line / sample / hint)。三条硬规则: +AI 友好结构化输出(rule / file:line / sample / hint)。四条硬规则: 1. 文件头 `// @pattern: ` -2. import 来源白名单 -3. 禁止颜色 / px 字面量,走 token +2. 声明的 pattern 必须真用其签名组件(由 `patterns//pattern.json` 的 `whitelist` 强制) +3. import 来源白名单(`@omit-design/preset-mobile` + 少量布局类 Ionic 组件) +4. 禁止颜色 / px 字面量,走 token ## 设计哲学 diff --git a/templates/init/app/App.tsx b/templates/init/app/App.tsx index 39f047c..a694a59 100644 --- a/templates/init/app/App.tsx +++ b/templates/init/app/App.tsx @@ -26,6 +26,8 @@ setupIonicReact({ mode: "ios" }); function DesignRoutes() { const projects = useProjects(); const allEntries = projects.flatMap((p) => p.entries); + // 第一张 design/ 下的页面作为 /designs/ 的默认入口;空项目时把用户送回 /workspace + const fallback = allEntries[0]?.href.replace(/^\/designs\//, "") ?? "/workspace"; return ( {allEntries.map((entry) => { @@ -33,7 +35,7 @@ function DesignRoutes() { const relPath = entry.href.replace(/^\/designs\//, ""); return } />; })} - } /> + } /> ); } diff --git a/templates/init/design/.gitkeep b/templates/init/design/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/init/design/welcome.tsx b/templates/init/design/welcome.tsx deleted file mode 100644 index 92a5eff..0000000 --- a/templates/init/design/welcome.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// @pattern: welcome-view -export const meta = { - name: "欢迎", - pattern: "welcome-view", - description: "默认欢迎页", - source: "scaffold", -} as const; - -import { useNavigate } from "react-router-dom"; -import { OmButton, OmPage } from "@omit-design/preset-mobile"; -import { brand } from "../mock/onboarding"; - -export function WelcomePage() { - const navigate = useNavigate(); - return ( - -
-

Welcome to {brand.tagline}

-

{brand.version}

- navigate("/workspace")}> - 进入工作台 - -
-
- ); -} -export default WelcomePage; diff --git a/templates/init/mock/.gitkeep b/templates/init/mock/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/init/mock/onboarding.ts b/templates/init/mock/onboarding.ts deleted file mode 100644 index 27e42eb..0000000 --- a/templates/init/mock/onboarding.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 项目级 mock 数据。业务页面通过相对路径 import,例如: - * - * import { brand } from "../mock/onboarding"; - * - * 这里不受 ESLint 设计稿规则约束(只 design/ 内受约束),你可以放任何字段。 - */ -export const brand = { - tagline: "omit-design", - version: "v0.1.0", -}; diff --git a/templates/init/prds/example.md b/templates/init/prds/example.md index 69fe0c7..bbe315c 100644 --- a/templates/init/prds/example.md +++ b/templates/init/prds/example.md @@ -1,14 +1,22 @@ --- title: Example PRD -pattern: list-view -target: design/main/welcome.tsx +pattern: +target: design//.tsx status: draft --- # Example PRD -This is a placeholder PRD. Edit it from the workspace's **Library → PRDs** tab, -then click "Copy Claude prompt" and paste into Claude Code to scaffold a design. +This is a placeholder PRD. Edit it from the workspace's **Library → PRDs** tab. +Two hand-off buttons there: + +- **Distill patterns from this PRD** — runs `/distill-patterns-from-prd` to + create matching pattern definitions under `patterns/`. Run this first. +- **Copy Claude prompt** — wraps the PRD in a `/new-design` invocation to + scaffold the actual design page from an existing pattern. + +Both buttons just put a prompt on your clipboard — you paste it into Claude +Code yourself. ## Goal