diff --git a/.changeset/product-design-md.md b/.changeset/product-design-md.md new file mode 100644 index 000000000..bf6b514c4 --- /dev/null +++ b/.changeset/product-design-md.md @@ -0,0 +1,5 @@ +--- +'8341-ui': patch +--- + +レジストリに `@8341/design-md` を追加しました。`npx shadcn@latest add @8341/design-md` で、リポジトリ直下にプロダクト用の DESIGN.md、`docs/design-principles/` にデザイン原則の全文が置かれます。DESIGN.md には、コードから読み取れない設計の考え方(プロダクトの前提、画面領域ごとの UI の方向性とサービス種別、大事にすること、外した必須)と、原則の必須の要約だけを書きます。 diff --git a/.claude/skills/ds-build-ui/SKILL.md b/.claude/skills/ds-build-ui/SKILL.md index c17e30b07..cc3364b3b 100644 --- a/.claude/skills/ds-build-ui/SKILL.md +++ b/.claude/skills/ds-build-ui/SKILL.md @@ -9,8 +9,8 @@ description: このデザインシステムを使って画面やフォームを 文書は大きいので、**全文ではなく必要な節だけ** を読む(見出しで探して、その節を読む)。 -0. プロダクトの DESIGN.md。無ければ `templates/PRODUCT-DESIGN.md` から作り、UI の方向性とサービス種別を決める。`design-principles` スキルの手順(ヒアリング → 方向性 → 特化ルール → 原則 → WCAG)に従う -1. `DESIGN.md` は次の節だけ: 「9. Agent Prompt Guide」のクイックリファレンス、「7. Do's and Don'ts」、「4. Component Stylings」の一覧と、使うコンポーネントの節。色や余白を決めるときだけ「2. Color Palette & Roles」「5. Layout Principles」 +0. プロダクトの DESIGN.md の「プロダクトの前提」〜「採用したルールと外したもの」。無ければ `npx shadcn@latest add @8341/design-md` で置き(このリポジトリの中なら雛形は `templates/PRODUCT-DESIGN.md`)、( )のままの欄を `docs/design-principles/hearing.md` の項目で聞いて埋める +1. 8341-ui の `DESIGN.md`(このリポジトリ直下)は次の節だけ: 「9. Agent Prompt Guide」のクイックリファレンス、「7. Do's and Don'ts」、「4. Component Stylings」の一覧と、使うコンポーネントの節。色や余白を決めるときだけ「2. Color Palette & Roles」「5. Layout Principles」 2. `docs/rulebook/02-accessibility.md` の「必須ルール」の表(A1〜A16) 3. フォームやボタンの文言を書くときだけ `docs/rulebook/05-writing.md` 4. 使うコンポーネントのストーリー(`packages/ui/src/components/ui//index.stories.tsx`) diff --git a/DESIGN.md b/DESIGN.md index 8b9a81f0a..6d40c4fcf 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -19,7 +19,7 @@ - **基盤のデザインシステム**: 特定のプロダクトに属さず、複数のプロダクトで使い回す - **画面設計のルール**: [design-principles](./docs/design-principles/README.md) を採用する。適用順は UI の方向性 → サービス別の特化ルール → 原則 → WCAG 2.2 -- **UI の方向性とサービス種別**: 基盤では決めない。各プロダクトの DESIGN.md で決める(雛形: `templates/PRODUCT-DESIGN.md`) +- **UI の方向性とサービス種別**: 基盤では決めない。各プロダクトの DESIGN.md で決める(`npx shadcn@latest add @8341/design-md` で置く。雛形は `templates/PRODUCT-DESIGN.md`) - **原則とトークン・コンポーネントの対応**: `docs/rulebook/01-principles.md` - **必須ルールを外した項目**: なし。タップ領域 44px、文字のコントラストを一律 4.5:1、リンクの下線必須、二重のフォーカス線は、design-principles の必須より厳しくしている diff --git a/Makefile b/Makefile index 442b1d014..5119986de 100644 --- a/Makefile +++ b/Makefile @@ -74,8 +74,9 @@ tokens: ## トークンの CSS・Tailwind テーマ・Figma JSON を生成する palette: ## palette.config.json から primitive カラーを生成し直す pnpm --filter $(TOKENS) palette -design-md: ## DESIGN.md の表をトークンから更新する +design-md: ## DESIGN.md の表をトークンから、プロダクト用の雛形の原則を docs/design-principles から更新する pnpm --filter $(TOKENS) design-md + node scripts/sync-product-design.ts tw-merge: ## クラス統合の設定(tw-merge-config.ts)を更新する pnpm --filter $(TOKENS) tw-merge diff --git a/README.md b/README.md index 16f7cf19c..b380f9d8d 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,14 @@ export function SignUp() { } ``` +**4. DESIGN.md を置く** — 画面を作る人とエージェントが、コードから読み取れない設計の考え方(プロダクトの前提と、[デザイン原則](./docs/design-principles/README.md)の必須)を共有するためのファイルです。 + +```bash +npx shadcn@latest add @8341/design-md # リポジトリ直下に DESIGN.md、docs/design-principles/ に原則の全文 +``` + +DESIGN.md の「プロダクトの前提」「画面領域ごとの方向性」を埋めてから画面を作ります(エージェントに頼むと、足りない項目を聞きながら埋めます)。トークンや部品の一覧は書きません。それはコードが正です。 +
npm パッケージとして使う(公開した場合) diff --git a/docs/decisions/0019-product-design-md.md b/docs/decisions/0019-product-design-md.md new file mode 100644 index 000000000..29006f636 --- /dev/null +++ b/docs/decisions/0019-product-design-md.md @@ -0,0 +1,24 @@ +# 0019 8341-ui を使うプロダクトに DESIGN.md と design-principles をレジストリで配る + +- 日付: 2026-10-23 +- 状態: 採用 + +## 背景 +UI の方向性とサービス種別は各プロダクトの DESIGN.md で決める(ADR-0007)。しかし 8341-ui の部品を shadcn で取り込んだプロダクトには、原則もプロダクト用 DESIGN.md も届かなかった。雛形 `templates/PRODUCT-DESIGN.md` を手でコピーし、原則は GitHub のリンクで読む前提で、プロダクト側のエージェントは原則を参照できなかった。ファクトリーで作ったデザインシステムには原則が入るのに、部品を使うだけのプロダクトには入らない差があった。 + +## 決定 +- shadcn のレジストリに `@8341/design-md`(`registry:file`)を足す。`npx shadcn@latest add @8341/design-md` で、リポジトリ直下に `DESIGN.md`、`docs/design-principles/` に原則の全文が置かれる(`target` の `~` はプロジェクトのルート) +- プロダクト用 DESIGN.md には **コードから読み取れない設計の考え方だけ** を書く。プロダクトの前提、画面領域ごとの UI の方向性とサービス種別、このプロダクトで大事にすること、外した必須とその理由、8341-ui に足したものの理由。トークンの値や部品の一覧は書かない(コードが正) +- DESIGN.md の「デザイン原則」は `` にし、`scripts/sync-product-design.ts` が `docs/design-principles/` から判断の順序・ルールの強さ・UI の方向性と各原則の「必須」を生成する。`make check` で鮮度を検査する +- 配るファイルは `packages/ui/scripts/build-registry-assets.ts`(旧 `build-registry-css.ts`)が `registry-assets/` に作る。原則の文書からは `` 〜 `` を取り除く(利用側はファクトリーではない) +- 配る原則のファイル一覧は `generate-registry.ts` が `docs/design-principles/` から作り、`registry.json` の `--check` で追従させる + +## 理由 +- 部品の取り込みと同じ shadcn の仕組みに乗せれば、プロダクト側の手順が 1 行増えるだけで済み、版を固定したレジストリ(`r/v<版>/`)で原則の版もそろう +- トークンや部品の仕様を DESIGN.md に写すと、コードと二重管理になり古くなる。コードから読み取れない判断の軸だけを置けば、DESIGN.md は短く保て、エージェントが毎回読んでも負担が小さい(ADR-0018) +- 原則の必須の要約を生成にすれば、原則を直したときにプロダクト用の雛形が古くならない + +## 影響・トレードオフ +- 原則の更新を取り込むと DESIGN.md が上書きになる。手順(作業用のブランチで取り込み、`auto` の範囲と `docs/design-principles/` だけを採用する)を DESIGN.md の末尾に書いた +- npm パッケージで使う場合は、この仕組みは使えない(shadcn のレジストリだけ)。必要になったら別の配り方を検討する +- 原則の必須は要約で、推奨・レビュー項目・理由は `docs/design-principles/` の全文を読む diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 75ba14978..f9744a6f2 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -22,6 +22,7 @@ | [0016](./0016-component-structure.md) | コンポーネントのディレクトリ構造を PascalCase とコロケーションにする | 採用 | | [0017](./0017-slides.md) | スライドを HTML のデッキとしてトークンから組む(ds-slides) | 採用 | | [0018](./0018-progressive-disclosure.md) | エージェント向けの文書を必要なときだけ読み込む構成にする | 採用 | +| [0019](./0019-product-design-md.md) | 8341-ui を使うプロダクトに DESIGN.md と design-principles をレジストリで配る | 採用 | ## テンプレート diff --git a/docs/design-principles/README.md b/docs/design-principles/README.md index cf38a4e33..90e64ea5f 100644 --- a/docs/design-principles/README.md +++ b/docs/design-principles/README.md @@ -97,7 +97,7 @@ WCAG 2.2 の Level A と AA の達成基準のうち、配色、文字、レイ #### スキルとして使う(Claude Code・Cursor・Codex) -このディレクトリのルールは、このリポジトリのスキルとして使える。スキルの置き場所とエージェントごとの読み込み方は [`docs/ai-agents.md`](../ai-agents.md) に書いている。 +8341-ui のリポジトリでは、このディレクトリのルールをスキルとして使える。スキルの置き場所とエージェントごとの読み込み方は [8341-ui の docs/ai-agents.md](https://github.com/tkc310/8341-ui/blob/main/docs/ai-agents.md) に書いている。 **スキル**: `design-principles`(`.claude/skills/design-principles/SKILL.md`) - デザイン作業時に読み込まれる手順とルーター @@ -114,7 +114,11 @@ WCAG 2.2 の Level A と AA の達成基準のうち、配色、文字、レイ **新しいデザインシステムを丸ごと作る**: `ds-factory` スキル(デザインシステムファクトリー)が、このルールとヒアリングの結果から、8341-ui をひな形に別リポジトリのデザインシステムを生成する([`docs/factory.md`](../factory.md))。 -**ルールへのフィードバック**: 対話中にルールの改善点(ギャップ、曖昧さ、適合しないルール、繰り返される例外)が見つかった場合、エージェントがユーザーに伝え、このリポジトリの `docs/design-principles/` への編集を提案する。プロダクト固有の例外は、そのプロダクトの DESIGN.md に記録する。 +#### 8341-ui を使うプロダクトで使う + +`npx shadcn@latest add @8341/design-md` を実行すると、このディレクトリがプロダクトの `docs/design-principles/` に、各ルールの必須をまとめた DESIGN.md がリポジトリ直下に置かれる。プロダクトの前提(UI の方向性、サービス種別、外した必須)は、その DESIGN.md に書く。配られたこのディレクトリは直接編集しない。 + +**ルールへのフィードバック**: 対話中にルールの改善点(ギャップ、曖昧さ、適合しないルール、繰り返される例外)が見つかった場合、エージェントがユーザーに伝え、[8341-ui](https://github.com/tkc310/8341-ui) の `docs/design-principles/` への編集を提案する。プロダクト固有の例外は、そのプロダクトの DESIGN.md に記録する。 ## 注意事項 diff --git a/docs/improvement/log.md b/docs/improvement/log.md index 99fcdb3bb..0c42788dc 100644 --- a/docs/improvement/log.md +++ b/docs/improvement/log.md @@ -4,6 +4,7 @@ | 日付 | 種別 | 内容 | 再発防止 | | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| 2026-10-23 | 機能 | 8341-ui を使うプロダクトに、DESIGN.md と design-principles を配るレジストリ item `@8341/design-md` を追加。DESIGN.md はコードから読み取れない考え方(プロダクトの前提・画面領域ごとの方向性・大事にすること・外した必須)と原則の必須の要約だけにし、トークンや部品の一覧は載せない | ADR-0019。原則の要約は `scripts/sync-product-design.ts` が docs/design-principles から生成し、`make check` で鮮度を検査。配布する原則のファイル一覧も registry.json の --check で追従 | | 2026-10-23 | 文書 | design-principles の原則「削る」に、部品を組み合わせたページで不要なセカンダリラベルや説明を避け、関係をレイアウトで伝えるルールを追加(必須 3・推奨 1・レビュー 3)。WCAG 3.3.2・1.3.1 を緩めないよう、見えるラベルと構造は残す条件を明記。ルールブック 01・05 と DESIGN.md の Don't に反映 | 原則のレビュー項目に追加(design-review で確認される)。05 文言で `supportText` をラベルの言い換えに使わないと明記 | | 2026-10-21 | 開発体験 | スキルの一覧 `docs/skills.md` を追加し、README・AGENTS.md・CONTRIBUTING・ai-agents.md からリンク(重複していた表は一覧へのリンクに置き換え)。文書を必要なときだけ読み込むよう、大きいスキル(design-principles・ds-factory・ds-slides・ds-storybook-preview)を本体と段階ごとの文書に分け、AGENTS.md に読み込みの方針を追加 | ADR-0018。`scripts/check-skills.ts`(name・description・一覧の網羅・本体 2,000 字以内・リンク切れ・参照されない文書)を `make check` に追加 | | 2026-10-20 | 機能 | 発表用のスライドを作るスキル `ds-slides` を追加。HTML のデッキを semantic トークンだけで組み(`scripts/slides/`)、design-principles の原則をスライドに落としたルールを 07 章にまとめた | ADR-0017。`make slides-check`(ソースの S1〜S20 の検査と deck.css の監査)を `make check` に組み込み、見本 `scripts/slides/examples/sample.html` を毎回検査 | diff --git a/docs/rulebook/01-principles.md b/docs/rulebook/01-principles.md index bb94d4282..64b73a504 100644 --- a/docs/rulebook/01-principles.md +++ b/docs/rulebook/01-principles.md @@ -14,7 +14,7 @@ design-principles のルールは、次の順で適用する。後のルール 4. **WCAG 2.2 の達成基準**(必須はどの段階からも緩めない) 8341-ui は複数のプロダクトで共有する基盤なので、1 と 2 は決めない。 -UI の方向性とサービス種別は、8341-ui を使う各プロダクトの DESIGN.md で決める(雛形: [`templates/PRODUCT-DESIGN.md`](../../templates/PRODUCT-DESIGN.md))。 +UI の方向性とサービス種別は、8341-ui を使う各プロダクトの DESIGN.md で決める。プロダクトには `npx shadcn@latest add @8341/design-md` で、DESIGN.md(雛形: [`templates/PRODUCT-DESIGN.md`](../../templates/PRODUCT-DESIGN.md)。コードから読み取れない考え方と原則の必須)と `docs/design-principles/` を配る(ADR-0019)。 8341-ui が受け持つのは 3 と 4 であり、どのサービス種別でも組み立てられる部品とトークンを用意する。 ## ウェブデザインの原則との対応 diff --git a/package.json b/package.json index 35f38a891..65f6c26a9 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build": "pnpm build:tokens && pnpm --filter 8341-ui build", "build:storybook": "pnpm build:tokens && pnpm --filter 8341-ui-storybook build", "storybook:preview": "pnpm build:storybook && pnpm --filter 8341-ui-storybook preview:publish", - "check": "pnpm build:tokens && vp check && vp run -r check && depcruise packages apps && node scripts/slides/check.ts && node scripts/check-skills.ts", + "check": "pnpm build:tokens && vp check && vp run -r check && depcruise packages apps && node scripts/slides/check.ts && node scripts/check-skills.ts && node scripts/sync-product-design.ts --check", "test": "pnpm build:tokens && vp run -r test", "test:vrt": "pnpm build:tokens && pnpm --filter 8341-ui test:vrt", "test:e2e": "pnpm build:tokens && pnpm --filter 8341-ui-storybook test:e2e", diff --git a/packages/ui/package.json b/packages/ui/package.json index aa42c73a1..31c820868 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -52,7 +52,7 @@ "test:vrt": "vp test --project vrt", "figma:publish": "figma connect publish", "registry": "node scripts/generate-registry.ts", - "registry:build": "node scripts/build-registry-css.ts && shadcn build ./registry.json -o ../../apps/storybook/public/r" + "registry:build": "node scripts/build-registry-assets.ts && shadcn build ./registry.json -o ../../apps/storybook/public/r" }, "dependencies": { "8341-ui-tokens": "workspace:*", diff --git a/packages/ui/registry.json b/packages/ui/registry.json index f892a27d6..b38134cdb 100644 --- a/packages/ui/registry.json +++ b/packages/ui/registry.json @@ -61,6 +61,94 @@ } ] }, + { + "name": "design-md", + "type": "registry:file", + "title": "DESIGN.md", + "description": "プロダクト用の DESIGN.md(コードから読み取れない設計の考え方)と design-principles。リポジトリ直下に DESIGN.md、docs/design-principles/ に原則の全文を置く", + "files": [ + { + "path": "registry-assets/DESIGN.md", + "type": "registry:file", + "target": "~/DESIGN.md" + }, + { + "path": "registry-assets/design-principles/README.md", + "type": "registry:file", + "target": "~/docs/design-principles/README.md" + }, + { + "path": "registry-assets/design-principles/hearing.md", + "type": "registry:file", + "target": "~/docs/design-principles/hearing.md" + }, + { + "path": "registry-assets/design-principles/principles.md", + "type": "registry:file", + "target": "~/docs/design-principles/principles.md" + }, + { + "path": "registry-assets/design-principles/services/README.md", + "type": "registry:file", + "target": "~/docs/design-principles/services/README.md" + }, + { + "path": "registry-assets/design-principles/services/admin.md", + "type": "registry:file", + "target": "~/docs/design-principles/services/admin.md" + }, + { + "path": "registry-assets/design-principles/services/consumer.md", + "type": "registry:file", + "target": "~/docs/design-principles/services/consumer.md" + }, + { + "path": "registry-assets/design-principles/services/internal.md", + "type": "registry:file", + "target": "~/docs/design-principles/services/internal.md" + }, + { + "path": "registry-assets/design-principles/services/saas.md", + "type": "registry:file", + "target": "~/docs/design-principles/services/saas.md" + }, + { + "path": "registry-assets/design-principles/sources/README.md", + "type": "registry:file", + "target": "~/docs/design-principles/sources/README.md" + }, + { + "path": "registry-assets/design-principles/sources/digital-agency-service-design.md", + "type": "registry:file", + "target": "~/docs/design-principles/sources/digital-agency-service-design.md" + }, + { + "path": "registry-assets/design-principles/sources/jakobs-law.md", + "type": "registry:file", + "target": "~/docs/design-principles/sources/jakobs-law.md" + }, + { + "path": "registry-assets/design-principles/sources/nngroup-f-shaped-pattern.md", + "type": "registry:file", + "target": "~/docs/design-principles/sources/nngroup-f-shaped-pattern.md" + }, + { + "path": "registry-assets/design-principles/sources/nngroup-ten-usability-heuristics.md", + "type": "registry:file", + "target": "~/docs/design-principles/sources/nngroup-ten-usability-heuristics.md" + }, + { + "path": "registry-assets/design-principles/ui-orientation.md", + "type": "registry:file", + "target": "~/docs/design-principles/ui-orientation.md" + }, + { + "path": "registry-assets/design-principles/wcag-for-designers.md", + "type": "registry:file", + "target": "~/docs/design-principles/wcag-for-designers.md" + } + ] + }, { "name": "accordion", "type": "registry:ui", diff --git a/packages/ui/scripts/build-registry-css.ts b/packages/ui/scripts/build-registry-assets.ts similarity index 57% rename from packages/ui/scripts/build-registry-css.ts rename to packages/ui/scripts/build-registry-assets.ts index 657d8ea10..90ddd9d52 100644 --- a/packages/ui/scripts/build-registry-css.ts +++ b/packages/ui/scripts/build-registry-assets.ts @@ -1,13 +1,19 @@ /** - * shadcn のレジストリで配るテーマ CSS(registry-assets/8341-ui.css)を作る。 + * shadcn のレジストリで配るファイルを registry-assets/ に作る。 + * + * 1. テーマ CSS(8341-ui.css) * * src/styles/theme.css は npm パッケージ 8341-ui-tokens の CSS を @import している。 * レジストリだけで導入した利用者はそのパッケージを持たないので、@import をビルド済みのトークン CSS の中身に置き換え、 * 1 ファイルで完結させる(ADR-0012)。 * + * 2. プロダクト用の DESIGN.md と design-principles(@8341/design-md。ADR-0019) + * templates/PRODUCT-DESIGN.md を DESIGN.md として、docs/design-principles/ を置く。 + * 利用側はファクトリーではないので、 〜 は取り除く。 + * * 実行: registry:build の中で自動で呼ばれる。先に 8341-ui-tokens のビルドが要る */ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; const ROOT = resolve(import.meta.dirname, '..'); @@ -42,4 +48,16 @@ const header = [ mkdirSync(OUT_DIR, { recursive: true }); writeFileSync(resolve(OUT_DIR, '8341-ui.css'), header + css.replace(/^\/\*[\s\S]*?\*\/\n/, '')); -console.log('✔ registry-assets/8341-ui.css'); + +const REPO = resolve(ROOT, '../..'); +cpSync(resolve(REPO, 'templates/PRODUCT-DESIGN.md'), resolve(OUT_DIR, 'DESIGN.md')); +const principlesDir = resolve(OUT_DIR, 'design-principles'); +rmSync(principlesDir, { recursive: true, force: true }); +cpSync(resolve(REPO, 'docs/design-principles'), principlesDir, { recursive: true }); +for (const d of readdirSync(principlesDir, { recursive: true, withFileTypes: true })) { + if (!d.isFile() || !d.name.endsWith('.md')) continue; + const file = resolve(d.parentPath, d.name); + const md = readFileSync(file, 'utf8'); + writeFileSync(file, md.replace(/[\s\S]*?\n?/g, '')); +} +console.log('✔ registry-assets/(8341-ui.css・DESIGN.md・design-principles/)'); diff --git a/packages/ui/scripts/generate-registry.ts b/packages/ui/scripts/generate-registry.ts index 1a3a2b259..370c73dfa 100644 --- a/packages/ui/scripts/generate-registry.ts +++ b/packages/ui/scripts/generate-registry.ts @@ -4,7 +4,8 @@ * * - registry:base 8341-base … Base UI を固定した土台。利用側の `shadcn init` で読み込む * - registry:lib tv … tv / cn / フォーカス表示のクラス - * - registry:file theme … Tailwind のテーマ。トークンの CSS を同梱した 1 ファイル(scripts/build-registry-css.ts が作る) + * - registry:file theme … Tailwind のテーマ。トークンの CSS を同梱した 1 ファイル(scripts/build-registry-assets.ts が作る) + * - registry:file design-md … プロダクト用の DESIGN.md と docs/design-principles/(同上。ADR-0019) * - registry:ui 各コンポーネント * * 実行: make registry(--check で差分検出)。公開は shadcn build(registry:build) @@ -34,6 +35,13 @@ interface Item { [k: string]: unknown; } +/** docs/design-principles/ のファイル(design-md で利用側の docs/design-principles/ に置く) */ +const PRINCIPLES = resolve(ROOT, '../../docs/design-principles'); +const principleFiles = readdirSync(PRINCIPLES, { recursive: true, withFileTypes: true }) + .filter((d) => d.isFile() && d.name.endsWith('.md')) + .map((d) => relative(PRINCIPLES, resolve(d.parentPath, d.name))) + .sort(); + /** コンポーネントのディレクトリ(PascalCase。src/components/ui/Button など) */ const components = readdirSync(resolve(ROOT, 'src/components/ui'), { withFileTypes: true }) .filter((d) => d.isDirectory()) @@ -126,6 +134,22 @@ const registry = { // npm の 8341-ui-tokens に依存させず、レジストリだけで完結させる(ADR-0012) files: [{ path: 'registry-assets/8341-ui.css', type: 'registry:file', target: 'styles/8341-ui.css' }], }, + { + name: 'design-md', + type: 'registry:file', + title: 'DESIGN.md', + description: + 'プロダクト用の DESIGN.md(コードから読み取れない設計の考え方)と design-principles。リポジトリ直下に DESIGN.md、docs/design-principles/ に原則の全文を置く', + // ~ は利用側のプロジェクトのルート + files: [ + { path: 'registry-assets/DESIGN.md', type: 'registry:file', target: '~/DESIGN.md' }, + ...principleFiles.map((f) => ({ + path: `registry-assets/design-principles/${f}`, + type: 'registry:file', + target: `~/docs/design-principles/${f}`, + })), + ], + }, ...uiItems, ], }; diff --git a/scripts/sync-product-design.ts b/scripts/sync-product-design.ts new file mode 100644 index 000000000..585ae20dd --- /dev/null +++ b/scripts/sync-product-design.ts @@ -0,0 +1,113 @@ +/** + * プロダクト用 DESIGN.md の雛形(templates/PRODUCT-DESIGN.md)の を、 + * docs/design-principles/ から生成する。載せるのは判断の順序・ルールの強さ・各ルールの「必須」だけ(コードから読み取れない考え方)。 + * 雛形は shadcn のレジストリ(@8341/design-md)で、利用側のリポジトリ直下に DESIGN.md として置かれる。 + * + * 実行: make design-md(--check で差分検出。make check から呼ぶ) + */ +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join, normalize, resolve } from 'node:path'; + +const ROOT = resolve(import.meta.dirname, '..'); +const DP = 'docs/design-principles'; +const TEMPLATE = resolve(ROOT, 'templates/PRODUCT-DESIGN.md'); +const CHECK = process.argv.includes('--check'); + +const read = (rel: string) => readFileSync(resolve(ROOT, DP, rel), 'utf8'); + +/** 見出し(# の数と文言が一致)から、同じか上の段の次の見出しまで */ +function section(md: string, level: number, title: string): string { + const lines = md.split('\n'); + const start = lines.findIndex((l) => l === `${'#'.repeat(level)} ${title}`); + if (start < 0) throw new Error(`見出し「${title}」が見つかりません`); + const end = lines.findIndex((l, i) => i > start && new RegExp(`^#{1,${level}} `).test(l)); + return lines.slice(start + 1, end < 0 ? undefined : end).join('\n'); +} + +/** 「**必須**」の直後の箇条書き(空行を挟まずに続くもの) */ +function required(text: string): string[] { + const lines = text.split('\n'); + const start = lines.indexOf('**必須**'); + if (start < 0) return []; + const out: string[] = []; + for (const line of lines.slice(start + 1)) { + if (line.startsWith('- ')) out.push(line); + else if (line.trim() || out.length) break; + } + return out; +} + +/** 最初の表(見出し行を含む) */ +function table(text: string): string { + return text + .split('\n') + .filter((l) => l.startsWith('|')) + .join('\n'); +} + +/** 相対リンクを、利用側のリポジトリでのパス(docs/design-principles/…)の表記にする */ +function relink(text: string, from: string): string { + return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (all, label: string, href: string) => { + if (/^[a-z]+:/i.test(href) || href.startsWith('#')) return all; + const path = normalize(join(DP, dirname(from), href.split('#')[0] ?? '')); + return `${label}(\`${path}\`)`; + }); +} + +function build(): string { + const readme = read('README.md'); + const orientation = read('ui-orientation.md'); + const services = read('services/README.md'); + const principles = read('principles.md'); + + const rules = table(section(principles, 2, '適用ルール')) + .split('\n') + .slice(2) + .map((row) => row.split('|').map((c) => c.trim())) + .map(([, name = '', decides = '']) => ({ name, decides })); + + const out = [ + `8341-ui の design-principles から生成した要約(手で直さない)。各ルールの「必須」だけを載せる。推奨・レビュー項目・理由は \`${DP}/\` の該当する節を読む。`, + '', + '### 判断の順序とルールの強さ', + '', + `1. UI の方向性(\`${DP}/ui-orientation.md\`)`, + `2. サービス別の特化ルール(\`${DP}/services/\`)。原則と食い違う判断は特化ルールを採る`, + `3. ウェブデザインの原則(\`${DP}/principles.md\`)。1 と 2 が書いていない判断に使う`, + `4. WCAG 2.2 の達成基準(\`${DP}/wcag-for-designers.md\`)。必須は 1〜3 のどれからも緩めない`, + '', + table(section(readme, 2, 'ルールの強さ')), + '', + '### UI の方向性', + '', + ...required(section(orientation, 3, 'ドメインとユーザーによる判断')).map((l) => relink(l, 'ui-orientation.md')), + '', + '### サービス別の特化ルール', + '', + relink(table(section(services, 2, 'どの文書を使うか')), 'services/README.md'), + '', + '### ウェブデザインの原則(必須)', + ]; + for (const { name, decides } of rules) { + const bullets = required(section(principles, 3, name)); + if (!bullets.length) throw new Error(`原則「${name}」に必須がありません`); + out.push('', `#### ${name}`, '', `${decides}。`, '', ...bullets.map((l) => relink(l, 'principles.md'))); + } + return out.join('\n'); +} + +const current = readFileSync(TEMPLATE, 'utf8'); +const re = /()[\s\S]*?()/; +if (!re.test(current)) throw new Error('templates/PRODUCT-DESIGN.md に がありません'); +const next = current.replace(re, `$1\n${build()}\n$2`); + +if (CHECK) { + if (next !== current) { + console.error('✖ templates/PRODUCT-DESIGN.md の原則が古くなっています。make design-md を実行してください'); + process.exit(1); + } + console.log('✔ templates/PRODUCT-DESIGN.md の原則は docs/design-principles と一致しています'); +} else { + writeFileSync(TEMPLATE, next); + console.log('✔ templates/PRODUCT-DESIGN.md(auto:principles)'); +} diff --git a/templates/PRODUCT-DESIGN.md b/templates/PRODUCT-DESIGN.md index 713f677f4..5d8617d46 100644 --- a/templates/PRODUCT-DESIGN.md +++ b/templates/PRODUCT-DESIGN.md @@ -1,52 +1,221 @@ # DESIGN.md — (プロダクト名) -> 8341-ui を使うプロダクトの DESIGN.md の雛形。プロダクトのリポジトリ直下にコピーして埋める。 -> 8341-ui 側の仕様(トークン・コンポーネント・Do / Don't)は 8341-ui リポジトリの DESIGN.md を参照し、ここには写さない。 -> 部品は shadcn 方式(`npx shadcn@latest add @8341/{name}`)か npm の `8341-ui` で取り込む。 -> 判断の順序は [design-principles](https://github.com/tkc310/8341-ui/tree/main/docs/design-principles) に従う: UI の方向性 → サービス別の特化ルール → 原則 → WCAG。 +> 8341-ui を使うプロダクトの DESIGN.md。**コードから読み取れない設計の考え方だけ** を書く。 +> トークンの値、コンポーネントの一覧と使い方は、コード(8341-ui の部品とテーマ)が正なので、ここには写さない。 +> 置き方: `npx shadcn@latest add @8341/design-md`(このファイルと `docs/design-principles/` が入る)。 +> `` の範囲は 8341-ui から配られる部分。手で直さない(下の「更新のしかた」)。 -## プロダクト概要 +## エージェントへ -- 対象ユーザー: (職種、習熟度、利用頻度) +- 画面を作る・直す前に、「1. プロダクトの前提」〜「4. 採用したルールと外したもの」を読む。( )のままの欄があれば、`docs/design-principles/hearing.md` の項目で利用者に聞いて埋めてから作る +- 判断の順序は、画面の方向性 → サービス別の特化ルール → 原則 → WCAG 2.2。迷ったら「5. デザイン原則」の該当ルールを見て、詳しくは `docs/design-principles/` の該当する節だけを読む(全文を読まない) +- 原則に足りない点を見つけたら、`docs/design-principles/` を直さずに、この DESIGN.md の「4.」に例外として書き、8341-ui に改善を提案する + +## 1. プロダクトの前提 + +- 誰が使うか: (職種、習熟度、利用頻度) +- 何をするためのものか: (利用者がこのプロダクトで終えたい主な作業) - ドメイン: (業務領域。専門的か日常的か) +- 使う場面と機器: (デスクトップ / タブレット / スマートフォン。主と補助。静かな事務所か、移動中か) - アクセシビリティ目標: WCAG 2.2 Level AA(8341-ui の基準。これより緩めない) -## 対象デバイス +## 2. 画面領域ごとの方向性とサービス種別 + +一つのプロダクトに複数の種類の画面があるときは、領域ごとに書く。選び方は `docs/design-principles/ui-orientation.md` と `docs/design-principles/services/README.md`。 + +| 画面領域 | UI の方向性 | 理由 | サービス種別 | +| --- | --- | --- | --- | +| (例: 請求書の管理) | オブジェクト指向 | 経理担当者が対象を熟知している | 管理画面 | +| (例: 初回セットアップ) | タスク指向 | 初回のみで手順を知らない | SaaS | + +## 3. このプロダクトで大事にすること + +部品やトークンでは決まらない、このプロダクト固有の判断の軸を書く。画面ごとに迷ったときに立ち返る基準になる。 + +- 体験の性格: (例: 速さより確かさ。取り消せない操作の前には必ず確認する) +- 言葉づかい: (例: 利用者の業務用語を使う。「案件」と呼び、「プロジェクト」と書かない) +- 画面の構成: (グローバルナビゲーションに対象名を並べるか作業名を並べるか、ヘルプと検索の位置、空状態の文面) +- 避けること: (例: 1 画面に主操作を 2 つ置かない) + +## 4. 採用したルールと外したもの + +- 特化ルール: (`docs/design-principles/services/` のどれを、どの画面領域に採るか) +- 必須を外した項目と理由(なければ「なし」): + +| ルール | 外した理由 | +| --- | --- | +| (なし) | | + +- 8341-ui に無いものを足した理由(部品やトークンそのものはコードにある。ここには「なぜ」だけを書く。ほかのプロダクトでも使えそうなら 8341-ui に提案する): + +| 足したもの | 理由 | +| --- | --- | +| (なし) | | + +## 5. デザイン原則 + + +8341-ui の design-principles から生成した要約(手で直さない)。各ルールの「必須」だけを載せる。推奨・レビュー項目・理由は `docs/design-principles/` の該当する節を読む。 + +### 判断の順序とルールの強さ + +1. UI の方向性(`docs/design-principles/ui-orientation.md`) +2. サービス別の特化ルール(`docs/design-principles/services/`)。原則と食い違う判断は特化ルールを採る +3. ウェブデザインの原則(`docs/design-principles/principles.md`)。1 と 2 が書いていない判断に使う +4. WCAG 2.2 の達成基準(`docs/design-principles/wcag-for-designers.md`)。必須は 1〜3 のどれからも緩めない + +| ルール | DESIGN.md での扱い | +| --- | --- | +| WCAG の必須 | 外せない。緩めることもできない | +| 特化ルールと原則の必須 | 原則として採る。外すときは、外す理由を DESIGN.md に書く | +| 推奨 | プロダクトごとに採るかどうかを決める | + +### UI の方向性 + +- ドメインが専門的であり、対象ユーザーがそのドメインに習熟している場合は、オブジェクト指向 UI を採用する。専門家は対象と操作の関係をすでに知っているため、対象から操作を選ぶほうが速い。 +- ドメインが日常的である場合は、オブジェクト指向 UI を採用する。利用者はドメインの対象を日常の知識から理解できる。 +- ドメインが専門的であり、対象ユーザーがそのドメインに習熟していない場合は、タスク指向 UI を採用する。専門的な対象を前提知識なしに操作させるには、典型的な手順をシステムが示す必要がある。 + +### サービス別の特化ルール + +| 画面 | 文書 | +| --- | --- | +| レコードを繰り返し検索・更新・監査する | 管理画面(`docs/design-principles/services/admin.md`) | +| 自社の社員が、自分の業務手続きを完了する。顧客には開かない | 社内向けツール(`docs/design-principles/services/internal.md`) | +| 契約した組織が、継続して日常業務に使う | SaaS(`docs/design-principles/services/saas.md`) | +| 個人が、自分のために使う | toCサービス(`docs/design-principles/services/consumer.md`) | + +### ウェブデザインの原則(必須) + +#### 読み順と優先度 + +どれを先に気づかせ、どの順で読ませるか。 + +- その画面で最初に伝える要素を、サイズ、位置、色のいずれかで他より先に気づく状態にする。 +- 見出しの段(大見出し、中見出し、小見出し)ごとに、見た目の差を付ける。同じ段には同じ見た目を使う。 +- 一つだけ強調する要素は、周囲と違う見た目にする。強調は画面につき少数にとどめる。 + +#### まとまり + +同一画面の中で、どれとどれを一つのグループに見せるか。 + +- 関係する要素(ラベルと入力欄、見出しと本文)の間隔を、関係しない要素の間隔より狭くする。 +- グループの境は、余白、枠、背景のいずれかか、その組み合わせで分かるようにする。 +- 同じグループに属する要素は、色、形、サイズを揃える。違うグループは、そのいずれかで区別する。 +- 手順や時系列は、線や並びに沿って一つの連なりに見えるようにする。 + +#### 文字と図の判別 + +文字と、情報を伝える図が判別できるか。 + +- 通常サイズの文字と背景のコントラスト比は 4.5:1 以上、大きい文字は 3:1 以上にする。プレースホルダーも文字である。 +- 情報を伝えるアイコン、グラフ、入力欄の境界と、隣接する色とのコントラスト比は 3:1 以上にする。 +- 色だけで状態や系列を区別しない。形、ラベル、パターンのいずれかを併用する。 +- 本文の文字サイズと行間は、デザインシステムに一つの初期値として定め、画面ごとに縮めない。 +- コンテナの内側には余白を置き、文字が枠に接触しないようにする。 + +#### 配置の秩序 + +位置が基準線に乗り、画面の視覚的な重さが極端に偏らないか。 + +- 関係する要素群を、左、中央、右のいずれかの基準線で揃える。 +- 列の幅と列間は、画面内で同じグリッドに乗せる。 +- 主内容と副内容を並べるとき、幅の比を画面内で一定にする。 + +#### 操作の幾何 + +押せるものの大きさと、隣との距離。 + +- ポインタで操作する対象は、一辺 24 CSS ピクセル以上にする。満たさない対象は、各対象のバウンディングボックスの中心に置いた直径 24 CSS ピクセルの円が、他の対象やその円と交差しない位置に置く。 +- 同じ画面で続けて押す操作は、直前に操作する要素(入力欄、選択した行など)の近くに置く。 + +#### 一度に出す数 + +同時に選ばせる個数。 + +- 一つの決定に、画面いっぱいの並列選択肢を出さない。分類、段階的な開示、既定値のいずれかで個数を減らす。 +- 長い一覧は、見出し、ページ分割、絞り込みのいずれかでまとまりに分ける。 + +#### 一貫性 + +画面をまたいで同じ機能を同じものに見せるか。ウェブで広く使われている配置を崩さないか。 + +- 保存、取消、削除のように同じ機能のボタンは、全画面で同じ見た目とラベルにする。 +- 色の意味(リンク、エラー、成功)を画面ごとに変えない。 +- ナビゲーションの位置と項目の順を、全ページで変えない。 +- ロゴの位置、戻る操作、主要なメニューの置き方は、他のウェブサイトで広く使われている配置から外れる理由があるときだけ変える。利用者は他サイトで過ごす時間のほうが長く、そちらの配置を前提に操作する、という観察がジャコブの法則と呼ばれる。 + +#### 状態と応答 + +今何が起きているか。待ちが長いか。 + +- 操作を受け付けたこと、処理中であること、完了または失敗したことを、それぞれ画面に表示する。 +- 応答がすぐ終わらない処理には、待っていることが分かる表示を出す。 +- ナビゲーションでは、現在のページがどこかを表示する。 +- 自動で動くカルーセルや通知は、利用者が止められるようにする。条件は wcag-for-designers.md(`docs/design-principles/wcag-for-designers.md`) の 2.2.2 に従う。 + +#### 誤りと撤回 + +失敗を防ぐか。失敗の内容と戻り方を示すか。操作を中断できるか。 + +- 削除や送信など取り消せない操作の前に、内容を確認する段階を置く。 +- 入力の形式と制約(必須か、文字数、使える文字)を、入力する前に示す。 +- エラーが起きた項目と、利用者が自分で直せる方法を示す。番号だけのエラーにしない。 +- モーダルと複数段の入力には、閉じる、中断する、下書きを残すのいずれかを置く。 +- 時間制限を置く場合は、解除、調整、延長のいずれかを利用者ができるようにする。条件と例外は wcag-for-designers.md(`docs/design-principles/wcag-for-designers.md`) の 2.2.1 に従う。 + +#### 再認 + +思い出させず、見せて選ばせるか。 + +- 入力値の候補があるときは、自由入力だけにせず候補を出す。 +- 直前までに入力した値を、後の段で再入力させない。セキュリティ上必要な再入力と、無効になった値は除く。 +- アイコンだけの操作には、ラベルかツールチップで名前を添える。 + +#### 文言 + +ラベルと文が、操作の結果と入力の意味を言うか。 + +- ボタンとリンクのラベルに、移動先か動作が入るようにする。「こちら」「詳細」「もっと見る」だけにしない。 +- 入力欄には、何を入れるかのラベルを付ける。形式の指定(日付の並びなど)は、エラーのあとではなく前に書く。 +- エラー文には、何が違うかと、どう直すかを書く。 +- 利用者の作業用語を使い、画面の実装用語をラベルに出さない。 + +#### 削る -- (デスクトップ / タブレット / スマートフォン。主と補助) +その画面の主タスクに不要な要素を置かないか。レイアウトで伝わることを文字で繰り返さないか。 -## 画面領域ごとの方向性とサービス種別 +- その画面の主タスク(利用者がその画面で終えたい作業)に使わない装飾、説明、動く要素を置かない。 +- 見出し、配置、まとまりで既に伝わることを、セカンダリラベル(小見出し、見出しの上の補助ラベル、項目名の前置き)や説明文で繰り返さない。例: 入力欄のラベルを言い換えただけの補足文、ページの見出しを言い直すだけの導入文、カードの中の「詳細」「情報」だけの小見出し、表の列見出しと同じ語を各セルに付けた前置き。 +- 項目どうしの関係(どれが同じグループか、どれが主でどれが従か)は、レイアウト(近さ、並び、枠や背景、大きさの差)で示す。関係を説明する文を足して補わない。 +- 削ってよいのは重複している表示だけである。入力欄の見えるラベル(WCAG 3.3.2)、見出しや表の見出しセルなど構造を表す要素(1.3.1)、アイコンだけの操作の名前は残す。見た目で示したグループには、読み上げでも取り出せる名前(`fieldset` と `legend`、区画の見出しなど)を付ける。 +- 主タスクを遮るポップアップは、利用者の判断が要る場合(未保存の変更の破棄など)に限る。それ以外の通知は、主タスクを遮らない位置に出す。 +- 自動で再生・移動する要素は、主タスクの妨げになる場合は初期状態で動かさない。 -一つのプロダクトに複数の種類の画面があるときは、領域ごとに書く。 +#### 助けの場所 -| 画面領域 | UI の方向性 | 理由 | サービス種別 | -| ------------------------ | ---------------- | ------------------------------ | ------------ | -| (例: 請求書の管理) | オブジェクト指向 | 経理担当者が対象を熟知している | 管理画面 | -| (例: 初回セットアップ) | タスク指向 | 初回のみで手順を知らない | SaaS | +ヘルプと検索を、サイト内の同じ場所に置くか。 -## 採用したルール +- ヘルプ、検索、問い合わせを置くときは、全ページで同じ位置に置く。WCAG 3.2.6 が求めるのは、ほかの要素に対する相対的な順序を変えないことである。 +- ヘルプの項目は、操作の名前で探せるようにする。 +- 目的のページへ到達する手段を、ナビゲーション以外にもう一つ置く。検索、サイトマップ、一覧のいずれでもよい。手続きの途中の段や、手続きの結果として表示するページは除く。手段の組み合わせは wcag-for-designers.md(`docs/design-principles/wcag-for-designers.md`) の 2.4.5 に従う。 -1. UI の方向性: 上の表のとおり -2. 特化ルール: (services/ のどれを採るか) -3. 原則: design-principles の適用ルール(8341-ui の対応表: `docs/rulebook/01-principles.md`) -4. WCAG 2.2 AA: 8341-ui のトークンとコンポーネントで担保。画面ごとの確認項目は `docs/rulebook/02-accessibility.md` +#### 短い経路 -## 必須ルールを外した項目 +繰り返す操作に、より短い別経路があるか。 -| ルール | 外した理由 | -| -------------------- | ---------- | -| (なければ「なし」) | | +- 基本の経路(ボタンを順に押す)を残したうえで、繰り返す操作にショートカット、一括操作、検索のいずれかがある。 +- キーボードだけで基本の経路を完了できるようにする。詳細は wcag-for-designers.md(`docs/design-principles/wcag-for-designers.md`) の 2.1.1 に従う。 -## 8341-ui からの拡張 +#### 自律的な処理の範囲 -プロダクト固有に足したトークンやコンポーネント。他のプロダクトでも使えそうなら 8341-ui への追加を Issue で提案する。 +人の操作を待たずに進む処理の境界が分かるか。 -| 名前 | 種類 | 理由 | -| ---- | ---- | ---- | -| | | | +- 自動で進んだ処理について、何を根拠に何をしたかを画面上で示す。 +- 利用者が止められる地点と、結果を取り消せる地点を置く。 +- 人とシステムのどちらが最終決定を持つかを、その画面に書く。 + -## ナビゲーションと画面構成 +## 更新のしかた -- グローバルナビゲーション: (対象名を並べるか、作業名を並べるか) -- ヘルプと検索の位置: (全ページで同じ位置) -- 空状態の文面: (「対象がまだありません」か「作業を始めましょう」か) +8341-ui の新しい版の原則を取り込むときは、`npx shadcn@latest add @8341/design-md` を作業用のブランチで実行する。上書きの確認が出たら受け入れ、`docs/design-principles/` と、この DESIGN.md の `` の範囲だけを残して、1.〜4. は元に戻す(git の差分で選ぶ)。