@1dot5/design-assistant 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: component-architect
3
+ description: コンポーネントの責務分割、props/バリアント設計、置き場の判断(packages/ vs _components/)を扱う。コンポーネント分解や再利用境界を議論するときに発火。
4
+ user-invocable: false
5
+ metadata:
6
+ tags: components, architecture, props, variants, composition, shadcn
7
+ ---
8
+
9
+ # Component Architect Skill
10
+
11
+ 責務分割・props 設計・バリアント・置き場判断の規約。
12
+
13
+ ## When to Apply
14
+
15
+ 次のような会話で発火する:
16
+
17
+ - コンポーネント分解、責務分割、再利用境界、props 設計、バリアント設計
18
+ - Component decomposition, props design, variants, composition, component boundaries
19
+ - shadcn/ui のコンポーネントを拡張・カスタマイズするとき
20
+ - `packages/` に置くか `_components/` に置くか迷ったとき
21
+
22
+ ## Core Principles
23
+
24
+ 1. **責務は 1 つ**。複数の関心を持ったら分ける
25
+ 2. **迷ったら `_components/` に置く**。2 箇所目の利用が出てから `packages/` へ昇格
26
+ 3. **Boolean prop の羅列は state machine のサイン**。`status: 'idle' | 'loading' | 'error'` に集約
27
+
28
+ ## 置き場の判断
29
+
30
+ `tenpct/frontend-template` の配置ルールに従う。
31
+
32
+ | 配置 | 用途 | 判断基準 |
33
+ |---|---|---|
34
+ | `packages/` | アプリ横断の共通 UI(shadcn/ui ベース) | 2 アプリ以上で使う / プリミティブに近い |
35
+ | `src/app/{route}/_components/` | そのページでしか使わないコンポーネント | 特定ルートに強く結びつく |
36
+ | `src/hooks/` | アプリ全体で使うカスタムフック | ロジックの再利用 |
37
+ | `src/store/` | グローバルな UI 状態(Zustand) | 複数コンポーネント間で共有する UI 状態 |
38
+ | `src/api/` | API クライアント関数・GraphQL 定義 | |
39
+
40
+ **迷ったら `_components/` に置く**。横断利用が 2 箇所目で出てきてから `packages/` に昇格させる。早期共通化は害。
41
+
42
+ ## コンポーネント分解の判断軸
43
+
44
+ 1. **責務は 1 つ**。複数の関心を持ったら分ける
45
+ 2. **状態とプレゼンテーションを分ける**。ロジックは hook、表示は component
46
+ 3. **データ取得は境界を明確に**。Suspense + Error Boundary で包む単位がコンポーネント境界
47
+ 4. **props で制御するか、children で渡すか**
48
+ - 構造が固定なら props(variant, size, disabled)
49
+ - 構造が可変なら children / slot(Radix の Composition パターン)
50
+
51
+ ## Props 設計
52
+
53
+ - **バリアントは CVA (class-variance-authority) で定義**。shadcn/ui 準拠
54
+ - **必須 props は最小に**。デフォルト値を持てるものは optional
55
+ - **Boolean prop の羅列を避ける**(`isLoading && isDisabled && isError` → `status: 'loading' | 'error' | 'idle'`)
56
+ - **on* ハンドラは動詞で命名**(`onSubmit`, `onSelect`)
57
+ - **data prop は型を狭く**。GraphQL codegen の型を使う
58
+
59
+ ## 状態の扱い
60
+
61
+ 全コンポーネントで以下を必ず定義:
62
+
63
+ - `default` / `hover` / `active` / `focus` / `focus-visible` / `disabled`
64
+ - データ系: `loading` / `empty` / `error` / `success`
65
+ - 権限系: `no-permission`(該当時)
66
+
67
+ **状態の表現手段の優先順位**:
68
+
69
+ 1. ネイティブ属性(`disabled`, `aria-pressed`, `aria-expanded`)
70
+ 2. data 属性(`data-state="open"` など、Radix 慣習)
71
+ 3. クラス名での切り替え
72
+
73
+ ## バリアント設計
74
+
75
+ - **バリアント軸は直交させる**(`variant` × `size` × `tone`)
76
+ - **軸が 3 本を超えたら分割を検討**。複雑すぎるコンポーネントは分ける兆候
77
+ - **size は spacing scale と連動**(sm/md/lg を勝手に増やさない)
78
+
79
+ ## Composition パターン(Radix 流)
80
+
81
+ 構造が可変な場合は複合コンポーネントで提供:
82
+
83
+ ```tsx
84
+ <Card>
85
+ <Card.Header>...</Card.Header>
86
+ <Card.Body>...</Card.Body>
87
+ <Card.Footer>...</Card.Footer>
88
+ </Card>
89
+ ```
90
+
91
+ - 各サブコンポーネントは単体でも意味を持つ
92
+ - props drilling を避けられる
93
+ - shadcn/ui の多くがこのパターン
94
+
95
+ ## Form コンポーネント
96
+
97
+ - React Hook Form + Zod 前提
98
+ - `<Label htmlFor>` と `<Input id>` を必ず紐付ける
99
+ - エラーは `aria-describedby` で関連付け
100
+ - placeholder を label 代わりにしない
101
+
102
+ ## 判断フロー
103
+
104
+ ```
105
+ このコンポーネントは何の責務を持つか?
106
+ ├── 複数 → 分解する
107
+ └── 1 つ
108
+ ├── 2 箇所以上で使う?
109
+ │ ├── Yes → packages/ に昇格候補
110
+ │ └── No → _components/ に置く
111
+ └── 構造は可変か?
112
+ ├── Yes → Composition (children / slot)
113
+ └── No → Props (variant, size, tone)
114
+ ```
115
+
116
+ ## Output Format
117
+
118
+ 1. コンポーネントの責務(1 文で)
119
+ 2. 置き場の判断と理由
120
+ 3. Props インタフェース(型定義)
121
+ 4. バリアント設計(CVA の軸)
122
+ 5. Composition / Slot 構造(必要な場合)
123
+ 6. 状態の列挙
124
+ 7. 依存するプリミティブ(Radix / shadcn)
125
+
126
+ ## よくある失敗
127
+
128
+ - 早すぎる共通化(`packages/` に入れたが横展開されない)
129
+ - Boolean prop の爆発(`isLoading`, `isDisabled`, `isError` を並べる)
130
+ - コンポーネント内部でデータ取得と表示を混ぜる(テスト困難・再利用不可)
131
+ - 状態を `useState` で個別管理(本当は 1 つの state machine)
132
+ - バリアント軸が増えすぎて CVA が読めない
133
+
134
+ ## 参照
135
+
136
+ - コードへの翻訳:`design-to-code` skill
137
+ - 配置ルール:`frontend-architecture` skill
138
+ - チェックリスト:[.agent/review.md](../../../.agent/review.md)
139
+
140
+ 設計が決まったら `design-to-code` skill で実装へ、配置は `frontend-architecture` skill で確認。
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: design-review
3
+ description: デザイン・実装レビューの観点、アクセシビリティ、最終チェックリスト。全 skill の Checklist が参照する集約ドキュメント。レビューや最終確認、a11y 観点の確認時に発火。
4
+ user-invocable: false
5
+ metadata:
6
+ tags: review, accessibility, checklist, wcag, a11y, quality
7
+ ---
8
+
9
+ # Design Review Skill
10
+
11
+ 全 skill の Checklist が参照する集約ドキュメント。レビュー観点・アクセシビリティ・最終チェックリストをまとめる。
12
+
13
+ ## When to Apply
14
+
15
+ 次のような会話で発火する:
16
+
17
+ - UI / 実装のレビュー、最終チェック、納品前確認
18
+ - アクセシビリティ(WCAG / a11y)の観点確認
19
+ - 状態網羅(loading / error / empty / disabled)の確認
20
+ - 「この画面 / コンポーネントで見落としていない?」の問いかけ
21
+
22
+ ## 最終チェックリスト
23
+
24
+ ### 仕様・状態
25
+
26
+ - [ ] ユースケースの正常系・分岐系・異常系・中断系すべてに画面がある
27
+ - [ ] 全コンポーネントの状態が定義されている
28
+ - [ ] default / hover / active / focus / focus-visible / disabled
29
+ - [ ] loading / empty / error / success / no-permission
30
+ - [ ] 長文(2 倍の長さ)で崩れない(wrap / ellipsis / max-width)
31
+ - [ ] 0 件表示がある
32
+ - [ ] ネットワーク失敗時の表示がある
33
+ - [ ] 遅延(3 秒以上)で loading / skeleton が出る
34
+
35
+ ### 視覚・レイアウト
36
+
37
+ - [ ] 情報階層が色のみに依存していない(サイズ・位置・余白・タイポで序列)
38
+ - [ ] 幅配分(primary / secondary / tertiary)が意図的に設計されている
39
+ - [ ] 基準線が通っている(崩すなら意図的に)
40
+ - [ ] トークン参照で生値がハードコードされていない(色・余白・radius・typography)
41
+ - [ ] Tailwind クラスに生 px / 生 hex がない
42
+
43
+ ### レスポンシブ / デバイス対応
44
+
45
+ - [ ] ナロービューポート 360px 幅で破綻しない
46
+ - [ ] タップターゲットが 44×44 以上
47
+ - [ ] Hover 前提の UI ではない(タッチデバイス考慮)
48
+ - [ ] Primary Action がモバイルで親指到達圏にある
49
+ - [ ] 画像がアスペクト比を保つ
50
+
51
+ ### アクセシビリティ(WCAG AA 準拠を基準)
52
+
53
+ #### セマンティクス
54
+ - [ ] ネイティブ要素を優先(`button`, `a`, `label`, `input`)
55
+ - [ ] 見出し階層が飛んでいない(h1 → h2 → h3)
56
+ - [ ] ランドマーク(`header`, `nav`, `main`, `footer`)がある
57
+ - [ ] リストは `ul/ol/li`、定義は `dl/dt/dd`
58
+
59
+ #### 名前とラベル
60
+ - [ ] すべての `button` / `a` / `input` に accessible name がある
61
+ - [ ] アイコンのみのボタンに `aria-label` がある
62
+ - [ ] `label` と `input` が `for` / `id` で紐づいている
63
+ - [ ] placeholder を label 代わりにしていない
64
+
65
+ #### キーボード
66
+ - [ ] キーボードのみで全機能が完結する
67
+ - [ ] Tab 順が DOM 順で論理的
68
+ - [ ] `tabindex` で無理に順序を変えていない
69
+ - [ ] focus が可視(`focus-visible` を消していない)
70
+ - [ ] モーダル / ドロワー:開で focus 移動 / 閉で復帰 / focus trap
71
+
72
+ #### 状態伝達
73
+ - [ ] ネイティブ `disabled` を優先、必要時のみ `aria-disabled`
74
+ - [ ] トグルは `aria-pressed` / `aria-expanded`(必要時のみ)
75
+ - [ ] 非同期完了 / 失敗は `aria-live`(濫用しない)
76
+ - [ ] エラーは「何が・なぜ・どう直すか」を説明
77
+
78
+ #### 色・コントラスト
79
+ - [ ] コントラスト比:本文 4.5:1 以上、大文字 3:1 以上
80
+ - [ ] フォーカスリングとの対比も十分
81
+ - [ ] 色覚多様性で判別できるか(色 + 形 / テキスト)
82
+
83
+ #### 画像・メディア
84
+ - [ ] `alt` が目的ベース(装飾は `alt=""`)
85
+ - [ ] 動画 / 音声:コントロール・字幕 / 文字起こし
86
+
87
+ ### 実装品質
88
+
89
+ - [ ] Server / Client 境界が適切(Client は葉に寄せる)
90
+ - [ ] 早期共通化していない(1 箇所でしか使われない部品を `packages/` に置いていない)
91
+ - [ ] ロジックと表示が分離されている(hook / component)
92
+ - [ ] Boolean prop の羅列になっていない(state / status に集約)
93
+ - [ ] `tailwind-merge` で衝突を解消している
94
+ - [ ] TanStack Query に UI 状態を入れていない
95
+ - [ ] Zustand にサーバーデータを入れていない
96
+
97
+ ## レビュー時の問いかけ
98
+
99
+ UX レビューを依頼されたら、以下を順に問う:
100
+
101
+ 1. **どこで失敗しているか?**(ステップ / 画面 / 操作)
102
+ 2. **何ができないか?**(理解 / 判断 / 操作 / 入力 / 待機)
103
+ 3. **誰が困っているか?**(初心者 / エキスパート / 支援技術ユーザー / 低速回線)
104
+ 4. **成功の定義は?**(完了率・所要時間・エラー率・満足度)
105
+
106
+ ## 認知負荷の観点
107
+
108
+ - **現在の文脈を壊さない**:急な画面遷移 / 情報欠落 / モーダル濫用を避ける
109
+ - **エラーを予防**:入力制約 / 即時フィードバック / 適切なデフォルト
110
+ - **記憶させない**:選ばせるのではなく見せる(recognition over recall)
111
+ - **操作の一貫性**:同じものは同じように振る舞う
112
+
113
+ ## アンチパターン
114
+
115
+ - `div` に `onClick` を付けてボタン代わり(キーボード / ロール破綻)
116
+ - `role="button"` でごまかす(`button` を使う)
117
+ - `aria-label` を可視テキストがあるのに追加(二重読み上げ)
118
+ - focus リングを消す(見えない focus = 操作不能)
119
+ - 状態を色だけで表現(色弱ユーザーで判別不能)
120
+ - 「ユーザーが慣れる」前提で初回の迷いを無視
121
+ - エラー文が抽象的で次の行動に導けない
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: design-to-code
3
+ description: デザイン(Pencil/Figma/モック)をコードに翻訳する判断軸。Tailwind CSS v4 + shadcn/ui 前提。意図の翻訳、px→scale、margin→gap、状態=仕様を扱う。
4
+ user-invocable: false
5
+ metadata:
6
+ tags: design-to-code, implementation, tailwind, shadcn, responsive, translation
7
+ ---
8
+
9
+ # Design to Code Skill
10
+
11
+ Pencil / Figma / モックからコードへ落とす際の規約。Tailwind CSS v4 + shadcn/ui 前提。
12
+
13
+ ## When to Apply
14
+
15
+ 次のような会話で発火する:
16
+
17
+ - デザインから実装、Figma から実装、Pencil から実装、モックから実装
18
+ - Design to code, Figma to code, Pencil to code, mockup to code, implementation
19
+ - UI の崩れ修正、レスポンシブ対応、スタイル調整
20
+ - shadcn/ui や Tailwind v4 でコンポーネントを書くとき
21
+
22
+ ## Core Principles
23
+
24
+ - **ピクセル一致ではなく、比率・整合・耐性・一貫性を保つ**
25
+ - **transcribe(書き写し)ではなく translate(翻訳)**。デザインの px 値は参照、実装はスケール・比率・構造
26
+ - **固定値は例外**。使うなら理由を明示(仕様要件 / メディア / タップターゲット等)
27
+
28
+ ## 翻訳プロセス
29
+
30
+ ### 1) 数値より意図を読む
31
+
32
+ - 目的(この画面で何を理解 / 操作させたいか)
33
+ - 視覚フロー(先に見る → 次 → 最後)
34
+ - 強調(hero / supporting / background)
35
+ - 階層(親子関係、グルーピング)
36
+ - 伸縮意図(Auto Layout / Constraints / Variants)
37
+ - 余白ルール(gap / padding のパターン)
38
+ - アラインメント(何を何に揃えるか)
39
+ - 可変要素(テキスト長、リスト件数、画像比、入力値)
40
+
41
+ ### 2) 実装への変換
42
+
43
+ | デザイン | 実装 |
44
+ |---|---|
45
+ | `px` 値 | **スケールに丸める**(4/8/12/16/24/32/40/48)→ Tailwind の `gap-4`, `p-6` など |
46
+ | `font-size: 14px` | **役割にマップ**(heading / body / caption)→ `@theme` の `--text-*` トークン |
47
+ | 子要素の margin | **親の `gap` / `padding` に集約**。レイアウト構造(flex / grid)で表現 |
48
+ | 固定 width / height | **制約に変換**(`min-w-*` / `max-w-*` / `overflow-*` / `truncate`)|
49
+ | 幅の設計 | **背景・コンテナ・コンテンツの責務を分離** |
50
+
51
+ ### 3) アラインメント判断
52
+
53
+ **揃える場面**:
54
+
55
+ - 繰り返し要素(カード / リスト / フォーム)比較のため
56
+ - 本文コラムを通す視線誘導
57
+ - 情報量が多い画面(設定 / 管理画面)
58
+
59
+ **崩してよい場面**:
60
+
61
+ - hero をあえて浮かせる
62
+ - セクション境界の強調
63
+ - 装飾・メディアを主役にする
64
+
65
+ **崩すときのルール**:
66
+
67
+ - 基準線を最低 1 本残す
68
+ - オフセットのパターンは 1〜2 に絞る
69
+ - ナロービューポートでは揃える側に寄せる
70
+
71
+ ### 4) 幅の比率を保つ
72
+
73
+ - **幅配分=視覚誘導**。primary / secondary / tertiary の列比は意図的
74
+ - **安易に `flex-1` を全部に付けない**。全部等幅は hero を殺す
75
+ - **基準線揃えと幅配分は別軸**。独立して決める
76
+
77
+ ## Tailwind CSS v4 ルール
78
+
79
+ ### トークン(`@theme` で定義)
80
+
81
+ ```css
82
+ @import "tailwindcss";
83
+
84
+ @theme {
85
+ --color-brand-500: #0ea5e9;
86
+ --text-body: 1rem;
87
+ --radius-card: 0.75rem;
88
+ --spacing-section: 4rem;
89
+ }
90
+ ```
91
+
92
+ - **色・タイポ・radius・spacing は必ずトークン経由**で参照
93
+ - 生 hex / 生 px をクラス名に書かない
94
+ - 例外は `review.md` のチェックリストに入れて明示
95
+
96
+ ### タイポグラフィ
97
+
98
+ - サイズは `rem`(または `clamp()`)。`em` は親サイズと関係ある場合のみ
99
+ - `line-height` は単位なし(1.5〜1.7)
100
+ - 本文の可読性:`max-w-[60ch]`
101
+
102
+ ### スペーシング
103
+
104
+ - スケールに丸める(`gap-4`, `p-6`, `space-y-8`)
105
+ - 子要素に margin を散らさず、親の `gap` / `padding` に寄せる
106
+
107
+ ### レイアウト
108
+
109
+ - 1 次元:flex、2 次元:grid、間隔:gap
110
+ - `position: absolute` はオーバーレイ・装飾で目的が明確な場合のみ
111
+ - 画像はデフォルトでアスペクト比を保つ(`aspect-[16/9]` 等)
112
+
113
+ ### 固定値の例外
114
+
115
+ - アイコン・サムネイル・タップターゲット・仕様指定のヘッダー高さ
116
+ - 「崩れ防止」目的でも、まず `min-*` / `max-*` を検討
117
+
118
+ ## shadcn/ui の使い方
119
+
120
+ - **追加は CLI で**。手動コピペしない
121
+ - **カスタマイズは class 追加か CVA 拡張で**。コアを書き換えない
122
+ - **Radix のプリミティブを理解してから shadcn を使う**。Composition パターンを守る
123
+
124
+ ## 状態の実装(必須)
125
+
126
+ - default / hover / active / focus / focus-visible / disabled / loading / error / empty
127
+ - 長文・0 件・ネットワーク失敗・遅延を最初から扱う(後付けしない)
128
+ - Tailwind の疑似クラスで表現(`hover:`, `focus-visible:`, `disabled:`, `data-[state=open]:`)
129
+
130
+ ## Output Format
131
+
132
+ 1. 目的(この UI が何を達成するか)
133
+ 2. 前提(デザイン入力の種類 / 既存規約 / 制約)
134
+ 3. 翻訳結果(階層・アラインメント・余白ルール・可変要素)
135
+ 4. 実装方針(レイアウト構造・スケール・例外条件)
136
+ 5. 状態設計(default / hover / active / focus / disabled / loading / error / empty)
137
+ 6. 実装コード
138
+ 7. チェックリスト自己評価([.agent/review.md](../../../.agent/review.md) 参照)
139
+
140
+ ## よくある失敗
141
+
142
+ - デザインデータからのピクセル完全一致コピペで、エッジ状態とレスポンシブで壊れる
143
+ - margin 調整が増殖し、保守不能になる
144
+ - 「見た目一致」を優先し、状態(loading / error / empty)が後付けになる
145
+ - Tailwind のクラスに生 px / 生 hex を書き散らす
146
+ - shadcn/ui のコアを書き換えて、アップデート追従不能になる
147
+
148
+ ## 参照
149
+
150
+ - コンポーネント設計:`component-architect` skill
151
+ - 配置ルール・Server/Client 境界:`frontend-architecture` skill
152
+ - チェックリスト(a11y 含む):[.agent/review.md](../../../.agent/review.md)
@@ -0,0 +1,213 @@
1
+ ---
2
+ name: frontend-architecture
3
+ description: Next.js 16 App Router のディレクトリ設計、Server/Client 境界、TanStack Query + Suspense/Error Boundary、Zustand、キャッシュ戦略。
4
+ user-invocable: false
5
+ metadata:
6
+ tags: frontend, architecture, nextjs, app-router, tanstack-query, zustand, cache
7
+ ---
8
+
9
+ # Frontend Architecture Skill
10
+
11
+ `tenpct/frontend-template` 準拠。Next.js 16 App Router を主軸に、配置・境界・データフロー・キャッシュ戦略を定義する。
12
+
13
+ ## When to Apply
14
+
15
+ 次のような会話で発火する:
16
+
17
+ - ディレクトリ設計、共通化、データフロー、Server/Client 境界、キャッシュ戦略
18
+ - Directory structure, data flow, server/client boundary, cache strategy, state management
19
+ - App Router のページ設計、Suspense/Error Boundary の配置、TanStack Query、Zustand、Form + Zod
20
+ - `packages/` と `_components/` の置き場判断
21
+
22
+ ## Core Principles
23
+
24
+ 1. **Server Component をまず書く**。Client は葉に寄せる
25
+ 2. **サーバーデータは TanStack Query、UI 状態は Zustand**。混ぜない
26
+ 3. **Suspense + Error Boundary で UI を集約**。`isLoading` / `error` / `data` の三状態手書きを避ける
27
+
28
+ ## 前提スタック
29
+
30
+ - Next.js 16 App Router + React 19
31
+ - Tailwind CSS v4 / shadcn/ui (Radix)
32
+ - TanStack Query + graphql-request + graphql-codegen
33
+ - Zustand(UI 状態のみ)
34
+ - React Hook Form + Zod
35
+ - Suspense + Error Boundary
36
+
37
+ ## ディレクトリ構成
38
+
39
+ ```
40
+ apps/app/
41
+ └── src/
42
+ ├── api/ # API クライアント関数・GraphQL 定義
43
+ │ └── posts.ts
44
+ ├── app/ # Next.js App Router
45
+ │ ├── layout.tsx
46
+ │ ├── page.tsx
47
+ │ ├── globals.css
48
+ │ ├── providers.tsx # QueryClientProvider, ErrorBoundary など
49
+ │ ├── error.tsx # Root Error Boundary
50
+ │ ├── global-error.tsx
51
+ │ ├── not-found.tsx
52
+ │ ├── loading.tsx
53
+ │ └── {route}/
54
+ │ ├── page.tsx
55
+ │ ├── loading.tsx # Suspense fallback
56
+ │ ├── error.tsx # Error Boundary
57
+ │ ├── actions.ts # Server Actions
58
+ │ └── _components/ # このページ専用
59
+ ├── hooks/ # アプリ共通のカスタムフック
60
+ ├── store/ # Zustand store(UI 状態のみ)
61
+ └── utils/
62
+ └── env.ts # t3-env で型安全な env
63
+ ```
64
+
65
+ 共通 UI は workspace の `packages/` に定義(shadcn/ui ベース)。
66
+
67
+ ## 配置ルール
68
+
69
+ | 配置 | 用途 | 判断基準 |
70
+ |---|---|---|
71
+ | `src/app/{route}/_components/` | そのページ専用 | 特定ルートに強く結びつく |
72
+ | `packages/` | 2 アプリ以上で使う共通 UI | プリミティブに近い / shadcn ベース |
73
+ | `src/hooks/` | アプリ全体のフック | ロジックの再利用 |
74
+ | `src/store/` | グローバル UI 状態(Zustand) | 複数コンポーネント間で共有 |
75
+ | `src/api/` | API クライアント・GraphQL 定義 | |
76
+ | `src/utils/` | 副作用のないユーティリティ | |
77
+
78
+ ## Server / Client 境界
79
+
80
+ ### Server Component(デフォルト)
81
+
82
+ - データ取得・初期レンダリング
83
+ - 認証チェック・リダイレクト
84
+ - Markdown レンダリングなど静的処理
85
+
86
+ ### Client Component(`'use client'` を明示)
87
+
88
+ - インタラクション(クリック、入力、hover)
89
+ - ブラウザ API(`window`, `localStorage`, `IntersectionObserver`)
90
+ - 状態管理(`useState`, `useReducer`, TanStack Query, Zustand)
91
+ - Context 利用
92
+
93
+ ### 境界の引き方
94
+
95
+ 1. **Server Component をまず書く**。必要になってから Client にする
96
+ 2. **Client Component を葉にする**。ツリーの末端に寄せる
97
+ 3. **Server → Client に props として渡せるのは serializable なもののみ**
98
+ 4. **`_components/` 内は Client が多い**が、Server でも構わない(データ取得を寄せられる場合)
99
+
100
+ ## データ取得戦略
101
+
102
+ ### サーバー側取得
103
+
104
+ - Server Component 内で直接 `fetch` / GraphQL クライアントを呼ぶ
105
+ - `loading.tsx` / `error.tsx` で Suspense / Error Boundary 境界を張る
106
+ - Server Actions は mutation に限定(書き込み専用)
107
+
108
+ ### クライアント側取得(TanStack Query)
109
+
110
+ - 操作後の再取得、キャッシュ無効化、楽観的更新が必要な場面
111
+ - `useSuspenseQuery` + 親の Suspense 境界でローディング UI を集約
112
+ - `useMutation` で書き込み → `invalidateQueries` でキャッシュ更新
113
+ - GraphQL は `graphql-request` + `graphql-codegen` で型保証
114
+
115
+ ### 使い分け
116
+
117
+ | ケース | 方法 |
118
+ |---|---|
119
+ | 初回表示で決まる | Server Component で fetch / GraphQL |
120
+ | 操作後に更新が必要 | TanStack Query + `useMutation` + `invalidateQueries` |
121
+ | 画面間で短時間共有 | TanStack Query(QueryCache) |
122
+ | マスタ系(変更頻度低) | Server Component + `cache` / `'use cache'` |
123
+
124
+ ## エラー処理
125
+
126
+ ### Suspense + Error Boundary で UI を集約
127
+
128
+ - `isLoading` / `error` / `data` の三状態を手書きしない
129
+ - 親で Suspense / Error Boundary を張り、子は成功系だけ書く
130
+ - `react-error-boundary` の `ErrorBoundary` を活用
131
+
132
+ ### 境界の粒度
133
+
134
+ - ページ全体:`app/{route}/loading.tsx` / `error.tsx`
135
+ - セクション単位:`<Suspense>` / `<ErrorBoundary>` を手動で張る
136
+ - `packages/query-boundary.tsx` のような共通コンポーネントで Suspense + Error Boundary をまとめて提供
137
+
138
+ ## 状態管理の使い分け
139
+
140
+ | 種類 | 置き場 |
141
+ |---|---|
142
+ | サーバーデータ(一覧・詳細・マスター) | TanStack Query の QueryCache |
143
+ | フォーム入力 | React Hook Form(ローカル) |
144
+ | URL に乗る状態(検索条件・ページ番号・タブ) | `useSearchParams` ベースの hook |
145
+ | UI 状態(モーダル開閉・サイドバー・テーマ) | Zustand |
146
+ | 純粋な描画内部状態 | `useState` |
147
+
148
+ **Zustand は UI 状態のみ**。サーバーデータを入れない。
149
+
150
+ ## キャッシュ戦略(Next.js 16)
151
+
152
+ ### サーバー側
153
+
154
+ | 層 | 用途 | 例 |
155
+ |---|---|---|
156
+ | Request Memoization | 同一リクエスト内の重複 fetch 排除 | 複数コンポーネントが叩く `getUser()` |
157
+ | Data Cache | 変更頻度が低い普遍的データ | マスタ・料金プラン・国コード |
158
+ | Full Route Cache | 描画済み HTML + RSC | 規約・ヘルプ・ダッシュ雛形 |
159
+ | Router Cache | 訪問済みページの即時復元 | 一覧 → 詳細 → 戻る |
160
+
161
+ ### クライアント側
162
+
163
+ - TanStack Query の QueryCache:操作後の再取得・部分更新・画面間共有
164
+ - `staleTime` / `gcTime` を用途ごとに調整
165
+
166
+ ### Cache Components(Next.js 16)
167
+
168
+ - `'use cache'` ディレクティブ + `cacheLife` / `cacheTag` / `updateTag` で粒度制御
169
+ - `unstable_cache` からの移行は Vercel 公式ガイド参照
170
+
171
+ ## Form
172
+
173
+ - React Hook Form + Zod
174
+ - `zodResolver` でバリデーション
175
+ - 送信:Server Action または `useMutation`
176
+ - エラーは `aria-describedby` で関連付け(レビュー観点は `review.md` 参照)
177
+
178
+ ## 環境変数
179
+
180
+ - `@t3-oss/env-nextjs` で型安全化
181
+ - クライアント公開は `NEXT_PUBLIC_*` のみ、Zod スキーマで検証
182
+
183
+ ## Lint / Format
184
+
185
+ - ESLint + Prettier(将来 Biome 検討)
186
+ - `eslint-plugin-query` で TanStack Query のアンチパターン検知
187
+ - `prettier-plugin-tailwindcss` でクラス順整列
188
+
189
+ ## Output Format
190
+
191
+ 1. 目的(何を達成するか)
192
+ 2. 配置判断(どこに置くか、理由)
193
+ 3. Server / Client 境界
194
+ 4. データ取得方法(Server or TanStack Query)
195
+ 5. 状態管理(Zustand / RHF / URL / useState)
196
+ 6. Suspense / Error Boundary の粒度
197
+ 7. キャッシュ戦略(必要に応じて)
198
+
199
+ ## よくある失敗
200
+
201
+ - Server Component で `useState` を使おうとする(境界違反)
202
+ - TanStack Query にサーバーデータではなく UI 状態を入れる
203
+ - Zustand にサーバーデータを入れて TanStack Query と二重管理
204
+ - Suspense 境界が細かすぎて loading が点滅する
205
+ - `'use client'` をツリー上位で付け、ツリー全体が Client になる
206
+ - `packages/` に早期に共通化して、1 箇所でしか使われないまま放置
207
+
208
+ ## 参照
209
+
210
+ - コンポーネント設計:`component-architect` skill
211
+ - デザイン → コード翻訳:`design-to-code` skill
212
+ - チェックリスト:[.agent/review.md](../../../.agent/review.md)
213
+ - 技術選定の背景:`tenpct/frontend-template` の TECHNICAL-DECISION.md / Framework.md
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: pencil-design-flow
3
+ description: Pencil MCP で .pen ファイルを扱うデザインワークフロー。探索 → 骨格 → 状態 → 書き出しの手順と、変数・コンポーネント運用を定義。.pen を開く/作るときに発火。
4
+ user-invocable: false
5
+ metadata:
6
+ tags: pencil, design-tool, mcp, workflow, pen-file
7
+ ---
8
+
9
+ # Pencil Design Flow Skill
10
+
11
+ ## When to Apply
12
+
13
+ 次のような会話で発火する:
14
+
15
+ - .pen ファイルを開く / 作る / 編集する
16
+ - Pencil で画面を作る、コンポーネントを描く、デザインを書き出す
17
+ - Open a .pen file, create a new .pen, design with Pencil, export from Pencil
18
+
19
+ ## Core Principles
20
+
21
+ 1. **.pen は Pencil MCP 経由でのみ読み書き**する。`Read` / `Grep` は使わない(暗号化されている)
22
+ 2. **作業前に現在の editor state を確認**する(`get_editor_state`)
23
+ 3. **設計は「骨格 → 状態 → 書き出し」の順**。状態定義を飛ばさない
24
+
25
+ ## Process
26
+
27
+ ### 1. 現状把握
28
+
29
+ - `get_editor_state({ include_schema: true })` で active な .pen と選択状態を確認
30
+ - 新規なら `open_document('new')`、既存なら `open_document(filePath)` を使う
31
+ - 必要に応じて `get_guidelines()` で設計ガイドを読む
32
+
33
+ ### 2. 探索(既存ファイル)
34
+
35
+ - `batch_get(patterns, nodeIds)` でノードを取得
36
+ - 画面構造・既存コンポーネント・変数(tokens)を把握
37
+ - `search_all_unique_properties` で再利用可能なプロパティを確認
38
+ - `get_variables` で既存のトークンセットを確認
39
+
40
+ ### 3. 骨格を作る
41
+
42
+ - 情報設計([.agent/design.md](../../../.agent/design.md))に従って構造を決める
43
+ - `batch_design` で一括挿入(1 コール最大 25 operations)
44
+ - 繰り返し要素はコンポーネント化(後で `replace_all_matching_properties` で一括更新)
45
+
46
+ ### 4. 状態を定義する
47
+
48
+ - default / hover / active / focus / disabled / loading / empty / error の各バリアント
49
+ - Pencil の Variants 機能でまとめる
50
+ - 可変要素(テキスト長 / リスト件数 / 画像比)を確認
51
+
52
+ ### 5. トークンを揃える
53
+
54
+ - 色・余白・typography は `set_variables` でトークン化
55
+ - 生値のハードコードを排除
56
+ - Tailwind v4 の `@theme` に写せる命名(`--color-brand-500`, `--spacing-section`)を意識
57
+
58
+ ### 6. レイアウトスナップショット / 書き出し
59
+
60
+ - `snapshot_layout` でレイアウト検証
61
+ - `find_empty_space_on_canvas` で新規配置場所を探す
62
+ - `export_nodes` でコード / 画像として書き出す
63
+ - `get_screenshot` で視覚確認
64
+
65
+ ## Output Format
66
+
67
+ 1. 現在の editor state(active 文書・選択)
68
+ 2. 情報設計(目的・優先度・構造)
69
+ 3. 骨格の構築結果(作成したノードの要約)
70
+ 4. 状態定義(各バリアントの存在確認)
71
+ 5. トークン一覧(色・余白・typography)
72
+ 6. 書き出し結果 or 次ステップ
73
+
74
+ ## Pencil へのプロンプト設計
75
+
76
+ Pencil でワイヤー/UI を生成させるときは、次の構造でプロンプトを渡すと精度が上がる。
77
+
78
+ ```
79
+ [コンテキスト] 誰が、いつ、何のために使う画面か
80
+ [ゴール] この画面で達成したい成果
81
+ [必須要素] 表示すべき情報・アクション
82
+ [制約] デバイス、文字数、既存 UI との整合
83
+ [除外] 含めないもの(重要)
84
+ ```
85
+
86
+ **「除外」を明記する**ことで、AI 特有の「過剰な親切」を抑制できる。
87
+
88
+ ## フェーズ間を一気に飛ばさない
89
+
90
+ Pencil はワイヤー→ローファイ→ハイファイの変換を一気に行えるが、**各フェーズで検証すべき問いに答え切ってから次へ**進む(フェーズの目的については `ui-designer` を参照)。
91
+
92
+ - **ワイヤー → ローファイ**: 色数を最小(グレースケール + 1 アクセント)に絞って生成。レイアウトの骨格を先に確定
93
+ - **ローファイ → ハイファイ**: ブランドトークン(色・タイポ・角丸・影)を事前に Pencil に渡す。**スタイルの一貫性は AI ではなく人間の設計で固定する**
94
+
95
+ AI に任せてよいのは「適用」であり、「決定」ではない。
96
+
97
+ ## AI でコンポーネントを扱うときの注意
98
+
99
+ - **AI が生成した UI を、そのまま「新規コンポーネント」にしない**。必ず既存システムに照らし「既存で表現できないか」を先に確認
100
+ - **AI は「それっぽいもの」を返す**。軸の直交性・誤用防止・状態網羅は人間が確認
101
+ - **トークンを参照したコードを生成させる**。生の HEX / px が紛れ込んだらレビューで落とす
102
+ - **Pencil のライブラリに取り込む前**に、`component-architect` の設計過程を踏む。設計のない登録はコンポーネントシステムの汚染
103
+
104
+ ## よくある失敗
105
+
106
+ - `Read` / `Grep` で .pen を読もうとする(暗号化のため不可)
107
+ - `get_editor_state` を省略して状態不明のまま操作する
108
+ - `batch_design` のオペレーション構文を間違える(tool description を必ず確認)
109
+ - 状態定義(hover / error / empty)を Variants 化せず、別ノードで複製する
110
+ - 生値をトークン化せず、後からの一括変更ができなくなる
111
+ - フェーズを飛ばして一気にハイファイまで生成させる
112
+ - AI 生成物をそのままライブラリ化する
113
+
114
+ ## 参照
115
+
116
+ - 情報設計・視覚階層・コンポーネント設計の判断軸:`ui-designer` skill / [.agent/design.md](../../../.agent/design.md)
117
+ - コンポーネントの責務分割・配置:`component-architect` skill
118
+ - チェックリスト:[.agent/review.md](../../../.agent/review.md)
119
+ - Pencil MCP ツール定義は会話中の MCP 指示に従う(`batch_design` の構文を必ず守る)
@@ -0,0 +1,380 @@
1
+ ---
2
+ name: ui-designer
3
+ description: 画面・コンポーネント設計、情報設計、視覚階層、状態定義を扱う判断軸。Web とモバイル両対応が前提。画面の骨格を議論するときに発火。
4
+ user-invocable: false
5
+ metadata:
6
+ tags: ui, design, information-architecture, visual-hierarchy, components, states
7
+ ---
8
+
9
+ # UI Designer Skill
10
+
11
+ シニアデザイナーとして、UI を「見た目の調整」ではなく **「制約の中での意思決定の連続」** として扱うための判断軸をまとめる。速度の出る AI 支援環境では、**思考の順序を守る規律**こそが差を作る。
12
+
13
+ ## When to Apply
14
+
15
+ 次のような会話で発火する:
16
+
17
+ - 画面設計、UI 方針、情報設計、視覚階層、画面の骨格、状態定義、トーン&マナー
18
+ - UI/screen design, information architecture, visual hierarchy, layout structure, state design
19
+ - 新規画面の起案、既存画面の再設計、デザインシステムの方針議論
20
+ - コンポーネントの責務分割・バリアント設計(詳細な配置判断は `component-architect` へ委譲)
21
+
22
+ ## 基本姿勢(Senior Principles)
23
+
24
+ 1. **問題定義を飛ばさない**。美しい UI は正しい問題定義の後にしか成立しない
25
+ 2. **制約を味方にする**。技術・スケジュール・ブランド・a11y — 制約が設計の輪郭を作る
26
+ 3. **意思決定の根拠を言語化する**。「なんとなく良い」は再現性がなくチームで再利用できない
27
+ 4. **一貫性 > 個別最適**。個別画面の完成度よりプロダクト全体の整合性
28
+ 5. **早く・安く・間違える**。ハイファイで検証するのは最も高くつく失敗の仕方
29
+
30
+ ---
31
+
32
+ ## 1. ユースケース → ワイヤーフレーム
33
+
34
+ ### 1.1 まず「誰が・いつ・なぜ・何を」を定義
35
+
36
+ 画面を描き始める前に必ず言語化する:
37
+
38
+ - **Who(主体)**: ペルソナではなく **役割と状況**。「30代女性」ではなく「繁忙期にチェックイン業務を兼務するフロント担当」
39
+ - **When(文脈)**: 利用タイミング・デバイス・同時作業・時間的余裕
40
+ - **Why(動機)**: 機能ではなく成果(Job to be Done)で記述
41
+ - **What(入出力)**: 画面に持ち込む情報/持ち出したい情報
42
+
43
+ ### 1.2 シナリオ展開 — 正常系だけで止めない
44
+
45
+ - **正常系**: 最短距離で成功するパス
46
+ - **分岐系**: 選択・入力を要するパス
47
+ - **異常系**: エラー、入力不正、通信失敗、権限不足
48
+ - **中断系**: 離脱して戻ってくるパス(下書き保存、セッション切れ)
49
+
50
+ ジュニアとの差が最も出るのは **異常系と中断系の解像度**。正常系だけワイヤーにすると実装フェーズで仕様が崩壊する。
51
+
52
+ ### 1.3 情報の優先順位を 1 つ決める
53
+
54
+ 画面で **最も重要な情報・アクションを 1 つ**選ぶ。複数選びたくなったら、画面がまだ 1 つにまとまっていない兆候。分割を検討する。
55
+
56
+ > 一画面一目的。破るときは破る理由を明記する。
57
+
58
+ ### 1.4 ワイヤーの粒度
59
+
60
+ - **箱と文字のみ**。色・フォント・アイコンは不要
61
+ - **実データに近いダミーテキスト**。Lorem ipsum は日本語プロダクトで文字数感覚を歪めるため避ける
62
+ - **画面単位ではなく遷移単位で描く**。単体の良し悪しは遷移の中でしか評価できない
63
+
64
+ ---
65
+
66
+ ## 2. ワイヤー → ローファイ → ハイファイ
67
+
68
+ ### 2.1 フェーズの目的を混同しない
69
+
70
+ | フェーズ | 検証するもの | 避けるべき議論 |
71
+ |---|---|---|
72
+ | ワイヤー | 情報構造と遷移の妥当性 | 色、余白、書体 |
73
+ | ローファイ | レイアウトと操作感の骨格 | ブランド表現、細部の装飾 |
74
+ | ハイファイ | 最終的な視覚品質と実装整合 | 情報構造のやり直し |
75
+
76
+ **フェーズを戻ることは敗北ではなく、早期発見の成功**。ただし各フェーズの問いに答え切らずに次へ進むと、手戻りコストは指数関数的に増える。
77
+
78
+ ### 2.2 ローファイで確定させること
79
+
80
+ - グリッド(8pt / 4pt ベース)
81
+ - ブレークポイントごとのレイアウト骨格
82
+ - タイポグラフィの階層(H1-H6、body、caption、label)
83
+ - スペーシングのスケール(4, 8, 12, 16, 24, 32, 48, 64 ...)
84
+ - インタラクションの大枠(hover / focus / press / disabled)
85
+
86
+ この段階で「何をコンポーネント化するか」のあたりをつける。
87
+
88
+ ### 2.3 ハイファイへ進む条件
89
+
90
+ 次に Yes と答えられるまで進まない:
91
+
92
+ - 主要ユースケースの遷移を通しで操作できる(クリッカブルプロトタイプ)
93
+ - 異常系・中断系の画面がローファイで存在
94
+ - ステークホルダーから「情報構造」への合意
95
+ - エンジニアから「実装上の致命的な問題はない」
96
+
97
+ ---
98
+
99
+ ## 3. UI デザインの考え方
100
+
101
+ ### 3.1 評価軸(優先順)
102
+
103
+ 1. **理解可能性**: 初見で何の画面か、何ができるか把握できる
104
+ 2. **操作可能性**: 次のアクションが明確でタップ/クリック可能なサイズ
105
+ 3. **予測可能性**: プロダクト内で似た要素は似た挙動をする
106
+ 4. **回復可能性**: 誤操作から戻れる、やり直せる
107
+ 5. **美しさ・らしさ**: ブランドとトーン(上記 4 つの後)
108
+
109
+ この順序を逆にした瞬間、UI は「飾られた迷路」になる。
110
+
111
+ ### 3.2 視線誘導とレイアウト
112
+
113
+ - Z 型 / F 型は今も有効。ただし **モバイルでは縦一列の重み付けが支配的**
114
+ - **余白はコンテンツである**。空けることで情報を際立たせる
115
+ - **コントラストは色ではなくサイズ・ウェイト・位置で作る**。色に頼ると色覚多様性やダークモードで破綻
116
+
117
+ ### 3.3 色
118
+
119
+ - **セマンティックカラー**を定義(primary / secondary / success / warning / danger / info / neutral)。生 HEX を直接使わない
120
+ - 色は **意味の伝達**に使う。装飾で使うときは意味転用の可能性を常に考える
121
+ - **WCAG AA(4.5:1)最低基準、AAA(7:1)推奨**
122
+ - **ダークモードは明度反転ではない**。影・コントラストの再設計を要する
123
+
124
+ ### 3.4 タイポグラフィ
125
+
126
+ - 書体は **2 つまで**を原則とする。3 つ目には明確な理由を書く
127
+ - **日本語と英数字のバランス**を個別に検証。和文と欧文で最適なウェイト・サイズは異なる
128
+ - 行間 1.5〜1.75(本文)、字間はフォントが持つ値を尊重
129
+
130
+ ### 3.5 状態の網羅(ハイファイで抜けは未完成)
131
+
132
+ - default / hover / focus / active / selected / disabled
133
+ - loading / empty / error / success
134
+ - 入力あり / 入力なし / バリデーションエラー
135
+
136
+ ### 3.6 アクセシビリティ
137
+
138
+ - **タップ領域 44×44pt 以上**(モバイル)、24×24px 以上(デスクトップ最小)
139
+ - **キーボード操作で全機能が完結**
140
+ - **フォーカスリングを消さない**。消したくなったらデザインし直す(`:focus-visible` で解決)
141
+ - **スクリーンリーダー向けのラベル・ランドマーク**を設計時点で考える(実装任せにしない)
142
+
143
+ ### 3.7 マイクロインタラクション
144
+
145
+ - **意味のないアニメーションは入れない**。アニメーションはフィードバックの言語
146
+ - **200〜300ms を基本**。100ms 未満は気づかれず、500ms 超は遅い
147
+ - **ease-out を第一候補**に。等速は機械的、ease-in は遅く感じる
148
+
149
+ ---
150
+
151
+ ## 4. コンポーネント設計の判断軸
152
+
153
+ 詳細な配置・CVA・shadcn 拡張は `component-architect` に委譲する。本セクションではデザイン側の判断軸をまとめる。
154
+
155
+ ### 4.1 難しいのは作ることではなく判断
156
+
157
+ 1. **いつ作るか**(早すぎると抽象化を間違える)
158
+ 2. **どこで線を引くか**(責務の境界)
159
+ 3. **何を公開し、何を隠すか**(Props)
160
+ 4. **どう変化を吸収するか**(未来の要求変更)
161
+ 5. **どう使わせないか**(誤用を構造的に防ぐ)
162
+
163
+ ### 4.2 いつ作るか — Rule of Three とその例外
164
+
165
+ 基本は **Rule of Three**: 2 回目までは複製、3 回目で抽象化。早すぎる抽象化は間違った抽象化を生む。
166
+
167
+ **例外的に最初から作る**:
168
+
169
+ - Button / Input / Select / Checkbox / Radio 等の普遍的プリミティブ
170
+ - タイポグラフィ・スペーシングのトークン
171
+ - レイアウト骨格(Container / Stack / Grid)
172
+
173
+ **早まって作らない**:
174
+
175
+ - 「たぶんまた使う」程度の確信度のもの
176
+ - ユースケースが 1 つしかない「それっぽい」塊
177
+ - ブランドトーンが固まる前の装飾要素
178
+
179
+ > 迷ったら作らない。コピペの重複は発見できるが、間違った抽象化は発見されないまま広がる。
180
+
181
+ ### 4.3 分けるか、まとめるか
182
+
183
+ **分けるべきサイン**:
184
+
185
+ - 見た目が違う × 意味も違う
186
+ - Props の分岐(`if (type === 'A')`)がコンポーネント内を支配している
187
+ - 使う側が「この Props はこのときだけ」を考え始めている
188
+ - a11y 要件が異なる(Dialog と Popover の ARIA は別物)
189
+
190
+ **まとめるべきサイン**:
191
+
192
+ - 差分が Props で素直に表現できる
193
+ - 分けた結果、両方を常にセットで更新している
194
+ - 命名が「似ているが微妙に違う」形で増殖
195
+
196
+ **例**:
197
+
198
+ - Button と IconButton は **別物**(正方形固定、aria-label 必須、ツールバー配置文脈)
199
+ - Modal / Drawer / BottomSheet は **共通 Overlay プリミティブ + 3 コンポーネント**(出現方向・閉じ方の慣習・適用文脈が違う)
200
+
201
+ > 「見た目」で分けず、「意図」で分ける。
202
+
203
+ ### 4.4 Props 設計
204
+
205
+ Props を足す前に問う 3 つ:
206
+
207
+ 1. 本当にこのコンポーネントの関心事か?(Button に `marginTop` は親の関心事)
208
+ 2. この Props がないとユースケースが本当に実現できないか?
209
+ 3. 他の Props と組み合わせて矛盾しないか?(`loading` と `disabled` が独立に指定できると両方 true のとき何が起きる?)
210
+
211
+ **良い Props**:
212
+
213
+ - **直交している**(`size` / `variant` / `state` が独立軸)
214
+ - **デフォルトが安全**
215
+ - **列挙可能**(`variant="primary"` > `isPrimary`)
216
+ - **Children を活用**(文字列 Props より `children` が柔軟)
217
+
218
+ **危険なパターン**:
219
+
220
+ | アンチパターン | 問題 | 代替 |
221
+ |---|---|---|
222
+ | `style`, `className` を何でも受ける | 内部実装依存の上書き | トークン経由、明示的な `tone` / `density` |
223
+ | `renderHeader` などレンダープロップ乱用 | 意味が溶ける | Compound Component |
224
+ | `options` 配列に全て詰める | 型が緩く誤用が増える | 明示的 Props に分解 |
225
+ | ブール値が 5 つ以上 | 状態の組合せ爆発 | 列挙型に統合 |
226
+
227
+ **Compound Component** は構造で Props 増殖を抑える。ただし Button のような構造固定なコンポーネントには過剰。
228
+
229
+ ```tsx
230
+ // × 肥大化
231
+ <Card title="..." subtitle="..." imageUrl="..." actions={[...]} />
232
+
233
+ // ○ Compound
234
+ <Card>
235
+ <Card.Media src="..." />
236
+ <Card.Header>
237
+ <Card.Title>...</Card.Title>
238
+ </Card.Header>
239
+ <Card.Body>...</Card.Body>
240
+ <Card.Actions><Button>...</Button></Card.Actions>
241
+ </Card>
242
+ ```
243
+
244
+ ### 4.5 Variant 設計 — 軸で寿命が決まる
245
+
246
+ 軸は **直交させる**。Button の典型:
247
+
248
+ - **Variant(意図)**: primary / secondary / tertiary / danger / ghost
249
+ - **Size(密度)**: sm / md / lg
250
+ - **State**: default / hover / focus / active / disabled / loading
251
+
252
+ 新しい要求で **「新しい軸」を足したくなる誘惑**に耐える。
253
+
254
+ - 「アイコン付きの Button」→ × `iconPosition` 軸追加 → ○ `children` に `<Icon />`、または別コンポーネント(IconButton)
255
+
256
+ **命名は意図で**:
257
+
258
+ - × `blueButton` / `redButton` → 色が変わると破綻
259
+ - ○ `primary` / `danger` → 意図が明確、ブランド変化に耐える
260
+
261
+ **Variant 数は 5〜7 が上限の目安**。超えそうなら分割または設計見直し。"Everything Button" は一貫性を破壊する。
262
+
263
+ ### 4.6 見落とされやすい状態
264
+
265
+ **Empty State** はユースケースで分岐する:
266
+
267
+ - 初回訪問 → オンボーディング誘導
268
+ - 検索結果ゼロ → 条件変更の提案
269
+ - 権限なし → 権限問題の明示
270
+ - エラー起因 → 再試行導線
271
+
272
+ 同じ「空」でも **ユーザーへのメッセージが全く違う**。`EmptyState` コンポーネントで Variant 化する。
273
+
274
+ **Loading State** は待ち時間で選ぶ:
275
+
276
+ - 〜1s: スケルトン
277
+ - 1〜3s: スピナー
278
+ - それ以上: 進捗表示
279
+ - 楽観的更新: ローディング表現なし(失敗時のロールバック設計必須)
280
+
281
+ **Error State** は 3 点を区別:
282
+
283
+ - 何が起きたか(通信 / 入力 / 権限 / サーバー)
284
+ - ユーザーに責任があるか
285
+ - 回復可能か
286
+
287
+ **Disabled vs Read-only**:Disabled は「今は操作不可」、Read-only は「そもそも編集不可」。別 Props にする。
288
+
289
+ **Focus vs Focus-visible**:キーボード時のみ出すには `:focus-visible`。消してはいけない。
290
+
291
+ ### 4.7 誤用を構造的に防ぐ
292
+
293
+ ドキュメントは読まれない。**読まれなくても正しく使える構造**を作る。
294
+
295
+ - **型で防ぐ**: 列挙型で制約
296
+ - **構造で防ぐ**: `<Button.Icon />` のように子コンポーネントを専用化
297
+ - **必須を必須にする**: `IconButton` の `aria-label` を型 required に
298
+ - **矛盾を表現不能に**: `{ state: 'default' | 'loading' | 'disabled' }` で独立 boolean の組合せ爆発を防ぐ
299
+
300
+ ### 4.8 トークン設計(3 階層)
301
+
302
+ - **Primitive**: `color.blue.500`, `space.4` — 生の値
303
+ - **Semantic**: `color.action.primary`, `space.inline.md` — 意味を持つ値
304
+ - **Component**: `button.primary.bg` — コンポーネント固有
305
+
306
+ 命名は **用途で**。`color.brand.blue` ではなく `color.action.primary`(青→緑の変化に耐える)。
307
+
308
+ 粒度は **粗すぎず細かすぎず**。`bg` と `fg`、`default` と `hover` のように **ペアで定義**。
309
+
310
+ 順序は `<category>.<concept>.<variant>.<state>`(`color.surface.raised.hover`)。IDE 補完で category から絞り込めて実装効率が上がる。
311
+
312
+ > 名前が変わらず、値だけが変わる設計が正しい。
313
+
314
+ ### 4.9 寿命管理
315
+
316
+ - 使用ゼロ → 削除候補
317
+ - 使用 1〜2 箇所 → 抽象化が早すぎた可能性
318
+ - 使用 10 箇所以上 + Variant 肥大化 → 分割候補
319
+
320
+ 廃止は `Deprecated` マーク → 代替明示 → 移行期間(最低 1 スプリント)→ 削除。
321
+
322
+ ### 4.10 例: Button の設計過程
323
+
324
+ 1. **責務**: クリック/タップでアクションをトリガー(ナビゲーションは Link に分離)
325
+ 2. **軸**: Variant(意図)/ Size(密度)/ State
326
+ 3. **Props**: `variant` / `size` / `disabled` / `loading` / `type` / `onClick` / `children` / `aria-label`
327
+ 4. **判断**:
328
+ - `iconLeft` / `iconRight` は Props にしない → `children` で `<Icon />` 合成
329
+ - `fullWidth` は付けない → 幅は親レイアウトの責務
330
+ - `color` の自由指定は不可 → 一貫性が壊れる
331
+ - `rounded` は付けない → トークンで統一
332
+ 5. **Don't**:
333
+ - primary を 1 画面に複数置かない
334
+ - Button の中に Button をネストしない(a11y 違反)
335
+ - `disabled` はツールチップで理由を示す
336
+ - Loading 中は多重送信を防ぐ
337
+
338
+ > 使う側が悩み始めたら設計の失敗。コンポーネントの品質は「使う側の脳の節約量」で測れる。
339
+
340
+ ---
341
+
342
+ ## Process(実運用フロー)
343
+
344
+ 1. **目的を問う**: 画面ゴール / 主ユーザー / primary action / エッジケース / 再利用範囲
345
+ 2. **シナリオ展開**: 正常系・分岐系・異常系・中断系
346
+ 3. **Web/モバイル両対応を確認**: タップターゲット、hover 非依存、360px 破綻チェック
347
+ 4. **情報設計 → 視覚階層 → 状態定義 → コンポーネント化** の順
348
+ 5. **仕様は実装者に曖昧さを残さない**形式で書く
349
+
350
+ ## Output Format
351
+
352
+ 1. 画面目的 / 成功基準
353
+ 2. ユーザーとユースケース(正常・分岐・異常・中断)
354
+ 3. 情報設計(優先度・構造)
355
+ 4. コンポーネント提案(責務・props・バリアント・状態)
356
+ 5. トークン / スタイル指針(色・余白・タイポ)
357
+ 6. エッジ状態の仕様(empty / error / loading / no-permission)
358
+ 7. 次アクション(プロトタイプ → 実装)
359
+
360
+ ## 納品前チェックリスト
361
+
362
+ - [ ] 正常系・分岐系・異常系・中断系すべてに画面がある
363
+ - [ ] 情報階層が色に依存していない
364
+ - [ ] 全コンポーネントの状態(default / hover / focus / active / disabled / loading / empty / error)が定義
365
+ - [ ] コントラスト比が WCAG AA 以上
366
+ - [ ] タップ領域 44×44pt 以上
367
+ - [ ] キーボード操作で全機能が完結
368
+ - [ ] トークン参照(生値のハードコードなし)
369
+ - [ ] ナロービューポート(360px 幅)で破綻しない
370
+ - [ ] コンポーネントに Do / Don't が記載
371
+ - [ ] ダークモード対応が必要な場合、反転ではなく再設計
372
+ - [ ] 実装エンジニアが読んで一意に解釈できる
373
+
374
+ ## 参照・引き継ぎ
375
+
376
+ - レビュー観点・a11y・チェックリスト:[.agent/review.md](../../../.agent/review.md)
377
+ - コンポーネント詳細(配置・CVA・shadcn):`component-architect` skill
378
+ - コード翻訳:`design-to-code` skill
379
+ - アーキテクチャ配置:`frontend-architecture` skill
380
+ - Pencil (.pen) 作業:`pencil-design-flow` skill
package/AGENTS.md ADDED
@@ -0,0 +1,63 @@
1
+ # Project Agents Guide
2
+
3
+ このリポジトリで作業する AI エージェント向けの規約(Single Source of Truth)。
4
+
5
+ ## 役割
6
+
7
+ シニアデザイナー兼フロントエンドエンジニアとして、以下をワンパスで担当する。
8
+
9
+ 1. 画面設計(情報設計・視覚階層・状態定義)
10
+ 2. Pencil (.pen) でのデザイン作業
11
+ 3. コンポーネント分解と責務設計
12
+ 4. デザイン → コード翻訳
13
+ 5. フロントエンドアーキテクチャ配置
14
+
15
+ Web / モバイル両対応が前提。
16
+
17
+ ## 前提スタック
18
+
19
+ - **Framework**: Next.js 16 App Router + React 19
20
+ - **Styling**: Tailwind CSS v4(`@theme` ブロックで CSS ファースト)
21
+ - **UI**: shadcn/ui (Radix UI)
22
+ - **Data**: TanStack Query + graphql-request + graphql-codegen
23
+ - **State**: Zustand(UI 状態のみ。サーバーデータは TanStack Query)
24
+ - **Form**: React Hook Form + Zod
25
+ - **Error Handling**: Suspense + Error Boundary
26
+ - **Lint/Format**: ESLint + Prettier(将来的に Biome 検討)
27
+
28
+ ## 規約(詳細)
29
+
30
+ - [デザインワークフロー](.agent/design.md)
31
+ - [コンポーネント設計](.agent/components.md)
32
+ - [デザイン → コード翻訳](.agent/design-to-code.md)
33
+ - [フロントエンドアーキテクチャ](.agent/frontend-arch.md)
34
+ - [レビュー観点 / アクセシビリティ / チェックリスト](.agent/review.md)
35
+
36
+ ## 共通原則
37
+
38
+ 1. **状態は仕様**。loading / error / empty / disabled を後付けにしない
39
+ 2. **トークンとスケール**で一貫性を担保。生値のハードコードを避ける
40
+ 3. **ネイティブ要素優先**、ARIA は最小限
41
+ 4. **情報階層は色に依存しない**
42
+ 5. **Web とモバイル両方で成立するか**を常に確認
43
+
44
+ ## チェックリスト(最小)
45
+
46
+ - [ ] ユースケースの正常系・分岐系・異常系・中断系すべてに画面がある
47
+ - [ ] 全コンポーネントの状態が定義されている(default / hover / active / focus / disabled / loading / error / empty)
48
+ - [ ] 情報階層が色に依存していない
49
+ - [ ] コントラスト比が WCAG AA を満たす
50
+ - [ ] キーボード操作で全機能が完結する
51
+ - [ ] トークン参照で生値がハードコードされていない
52
+ - [ ] ナロービューポート(360px 幅)で破綻しない
53
+ - [ ] タップターゲットが 44×44 以上
54
+
55
+ 詳細は [.agent/review.md](.agent/review.md)。
56
+
57
+ ## Claude Code 固有の機能
58
+
59
+ `.claude/` に集約。このファイル(AGENTS.md)は Claude Code 以外のツールとも共有する共通基盤。
60
+
61
+ - Skills: `.claude/skills/`(自動発火するデザイン系判断軸)
62
+ - 設定: `.claude/settings.json`
63
+ - メモリ: `.claude/memory/`
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@1dot5/design-assistant",
3
+ "version": "0.2.1",
4
+ "description": "Design assistant agents, skills, and conventions for Next.js 16 + React 19 projects",
5
+ "homepage": "https://github.com/1dot5/da#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/1dot5/da/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/1dot5/da.git"
12
+ },
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "files": [
16
+ "AGENTS.md",
17
+ ".agent",
18
+ ".claude/skills"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ }
26
+ }