Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<project>/patterns/<id>/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).
Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,36 @@ npm install
npm run dev
```

Open `http://localhost:5173/`. Your single starter design lives at `design/welcome.tsx`. Add new pages in `design/<group>/<name>.tsx` — they're auto-discovered as `/designs/<group>/<name>`.
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

```bash
# 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/<id>/. 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/<group>/<name>.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

```
Expand Down
21 changes: 14 additions & 7 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,33 @@ npm install
npm run dev
```

打开 `http://localhost:5173/`。脚手架自带 `design/welcome.tsx`。新增页面 = `design/<group>/<name>.tsx`,自动发现,路由自动是 `/designs/<group>/<name>`
打开 `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/<id>/ 下
# 创建匹配的 pattern 文件。你审核通过。

# 3. 启动设计 server(底层是 Vite)
npm run dev # http://localhost:5173/
# 3. 同一份 PRD,点 "Copy Claude prompt" → 粘到 Claude Code:
# /new-design 把选中的 pattern 模板复制到 design/<group>/<name>.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 再继续。

## 架构总览

```
Expand Down
20 changes: 10 additions & 10 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ npx @omit-design/cli init my-app

| | |
|---|---|
| `omit-design init <name>` | 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 <name>` | 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 <pattern> <path>` | Scaffold a design page from a preset-mobile pattern template. |
| `omit-design new-page <pattern> <path>` | Scaffold a design page by copying a project-local pattern template (`<project>/patterns/<pattern>/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
Expand All @@ -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

Expand All @@ -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/<id>/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
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
"",
Expand Down
33 changes: 32 additions & 1 deletion packages/cli/src/commands/lint.ts
Original file line number Diff line number Diff line change
@@ -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<string, string> = {
Expand All @@ -10,13 +11,33 @@ const SEVERITY: Record<string, string> = {

const HINT: Record<string, string> = {
"omit-design/require-pattern-header":
"add `// @pattern: <name>` on the first line (name must exist in @omit-design/preset-mobile/PATTERNS.md)",
"add `// @pattern: <name>` on the first line (name must match a directory under <project>/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<boolean> {
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;
Expand Down Expand Up @@ -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];

Expand Down
60 changes: 41 additions & 19 deletions packages/cli/src/commands/new-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <project>/patterns/.",
},
path: {
type: "positional",
Expand All @@ -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);
}

Expand Down Expand Up @@ -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")
Expand Down
20 changes: 11 additions & 9 deletions packages/cli/templates/init/CLAUDE.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ omit-design 项目 — 设计稿是真实可点击的 React 页面,不是图片

2. **业务页面禁止字面量**:颜色(`#hex` / `rgb` / 命名色)、像素长度。走 token(`var(--om-*)` / `var(--ion-*)`) 或 Om* 组件的 props。

3. **每个业务页面文件第一行**:`// @pattern: <name>`,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>`,name 必须存在为 `<project>/patterns/<name>/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 必须真用其签名组件**:每个 `<project>/patterns/<name>/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 <name>`(lint + a11y + 截图一气呵成)

Expand All @@ -62,17 +64,17 @@ npm run build # 生产构建
```

访问入口:
- `/workspace` — 项目工作台
- `/designs/main/welcome` — 默认欢迎稿(scaffold 自带)
- `/workspace/app/theme-editor` — 主题编辑器
- `/workspace` — 项目工作台(项目列表 / Library / Theme Editor 都从这里进)
- `/designs/<group>/<name>` — 业务设计稿(`design/<group>/<name>.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 数据

## 何时问用户

Expand Down
Loading
Loading