sparkle-design-cli 2.4.2 → 2.5.0-beta.2

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.
@@ -6,8 +6,9 @@ import { setupAssistant } from '../lib/setup.js';
6
6
  import { runStopHook } from '../lib/stop-hook.js';
7
7
  import { PLUGIN_SPEC_MARKDOWN } from '../lib/plugin-api.js';
8
8
  import { loadAntiPatternPlugins } from '../lib/load-plugins.js';
9
+ import { runRules } from '../lib/rules-report.js';
9
10
 
10
- const SUBCOMMANDS = new Set(['generate', 'check', 'setup', 'stop-hook', 'plugin-spec']);
11
+ const SUBCOMMANDS = new Set(['generate', 'check', 'rules', 'setup', 'stop-hook', 'plugin-spec']);
11
12
 
12
13
  function requireOptionValue(args, index, flags) {
13
14
  const value = args[index + 1];
@@ -134,6 +135,41 @@ function parseSetupOptions(args) {
134
135
  return options;
135
136
  }
136
137
 
138
+ /**
139
+ * `rules` のオプション解析。
140
+ *
141
+ * 値の検証を省くと、`--format jsno` やタイプミスが**黙ってテキスト出力に
142
+ * フォールバックして exit 0** になる。JSON を期待している CI や AI は、
143
+ * エラーではなく解析できない stdout を受け取ることになる。他のサブコマンドと
144
+ * 同じく、未知のフラグと不正な値はその場で失敗させる。
145
+ * en: Without validation a typo like `--format jsno` would silently emit text
146
+ * and exit 0, handing non-JSON stdout to a caller that asked for JSON.
147
+ */
148
+ function parseRulesOptions(args) {
149
+ const options = { format: 'text', help: false };
150
+ const FORMATS = new Set(['text', 'json']);
151
+
152
+ for (let i = 0; i < args.length; i += 1) {
153
+ const arg = args[i];
154
+ if (arg === '-h' || arg === '--help') {
155
+ options.help = true;
156
+ continue;
157
+ }
158
+ if (arg === '--format') {
159
+ const value = requireOptionValue(args, i, '--format');
160
+ if (!FORMATS.has(value)) {
161
+ throw new Error(`Unsupported format for --format: ${value} (text | json)`);
162
+ }
163
+ options.format = value;
164
+ i += 1;
165
+ continue;
166
+ }
167
+ throw new Error(`Unknown option for rules: ${arg}`);
168
+ }
169
+
170
+ return options;
171
+ }
172
+
137
173
  function showHelp() {
138
174
  console.log(`
139
175
  Sparkle Design CLI
@@ -144,8 +180,11 @@ Sparkle Design CLI
144
180
  Commands:
145
181
  generate sparkle.config.json から CSS を生成
146
182
  check Sparkle Design のアンチパターンを検査
183
+ rules 現在有効なアンチパターンルールを一覧表示(プラグイン由来のものも含む)
147
184
  setup Sparkle Design プロジェクトをセットアップ(パッケージ導入 + 初期ファイル + AI ガード + generate)
148
- stop-hook AI assistant の Stop hook 用 internal subcommand(findings 検出時に exit 2 で 1 度だけ停止をブロック / 再発火は自動回避)
185
+ stop-hook AI assistant の Stop hook 用 internal subcommand(severity=error の findings か、
186
+ 実行に失敗して未検査のルールがあるときに exit 2 で 1 度だけ停止をブロック / 再発火は自動回避。
187
+ warning / info はブロックせず件数の要約を stderr に出す)
149
188
  plugin-spec アンチパターン拡張プラグインの契約仕様を出力(--list で導入済みプラグインを表示)
150
189
 
151
190
  Generate:
@@ -154,7 +193,7 @@ Generate:
154
193
  sparkle-design-cli generate --output ./styles/design.css
155
194
  sparkle-design-cli generate -c ./config/custom.json -o ./dist/styles.css
156
195
 
157
- # 単一バンドル内でランタイムにテーマ切替したい場合(--scope。詳細は README 参照)
196
+ # 単一バンドル内でランタイムにテーマ切替したい場合(--scope。詳細は docs/theming.md 参照)
158
197
  sparkle-design-cli generate -c ./config/sparkle.employee.json -o ./src/styles/sparkle-employee.css
159
198
  sparkle-design-cli generate -c ./config/sparkle.admin.json --scope '[data-tenant-theme="admin"]' -o ./src/styles/sparkle-admin-scope.css
160
199
 
@@ -164,6 +203,10 @@ Check:
164
203
  sparkle-design-cli check src --format json
165
204
  sparkle-design-cli check src/components src/features
166
205
 
206
+ Rules:
207
+ sparkle-design-cli rules # 有効なルールを severity 別に一覧表示
208
+ sparkle-design-cli rules --format json # CI / AI 向け
209
+
167
210
  Plugin spec:
168
211
  sparkle-design-cli plugin-spec # アンチパターン拡張プラグインの契約仕様を出力
169
212
  sparkle-design-cli plugin-spec --list # 現在のプロジェクトで発見されたプラグインを表示
@@ -193,8 +236,7 @@ Generate options:
193
236
  だけを実値までリテラル化して指定セレクタの中にラップ出力する
194
237
  (例: '[data-tenant-theme="admin"]')。-o/--output の指定が必須。
195
238
  --strict / --globals-path とは併用不可(グローバル CSS を
196
- パッチしないため)。詳細は README の「単一バンドルでランタイム
197
- 切替する場合」を参照
239
+ パッチしないため)。詳細は docs/theming.md の「ケース B」を参照
198
240
 
199
241
  sparkle.config.json の設定フィールド:
200
242
 
@@ -202,7 +244,7 @@ sparkle.config.json の設定フィールド:
202
244
  primary プライマリカラー (必須。blue, red, orange, yellow, purple, green, pink の
203
245
  いずれか。未指定、またはそれ以外の値は generate 実行時にエラーになります。
204
246
  7色にないブランドカラーを使いたい場合は extend.custom-css で
205
- --color-primary-* / --color-gray-* を再定義してください。詳細は README を参照)
247
+ --color-primary-* / --color-gray-* を再定義してください。詳細は docs/config.md を参照)
206
248
  font-pro プロポーショナルフォント (Google Fonts の名前)
207
249
  font-mono モノスペースフォント (Google Fonts の名前)
208
250
  radius 角丸設定 (必須。none, xs, sm, md, lg, xl, 2xl, 3xl のいずれか。
@@ -229,7 +271,8 @@ sparkle.config.json の設定フィールド:
229
271
 
230
272
  Check options:
231
273
  -h, --help このヘルプメッセージを表示
232
- --strict 違反があれば exit code 1 で終了
274
+ --strict severity=error の違反があれば exit code 1 で終了
275
+ (warning / info は報告のみで exit code に影響しない)
233
276
  --format <text|json> 出力形式 (default: text)
234
277
 
235
278
  Setup options:
@@ -252,23 +295,12 @@ Setup の動作:
252
295
  4. AI アシスタントのガードブロックを指定ファイルに挿入(存在しなければ新規作成)
253
296
  5. sparkle-design-cli generate を実行し、sparkle-design.css と SparkleHead.tsx を生成
254
297
 
255
- Check で検出する主なパターン:
256
- - フォーム入力に Dialog を使っている
257
- - DialogCancel / DialogAction を <Button> で二重ラップしている
258
- - children なしの Button に prefixIcon / suffixIcon を使っている
259
- - Material Symbols を className 直書きしている
260
- - shadcn/ui 既定 token(text-muted-foreground 等)を持ち込んでいる
261
- - Tailwind デフォルト typography(text-sm, font-medium 等)を使っている
262
- - CardTitle に typography を上書きしている
263
- - CardControl に Button / IconButton 以外を入れている
264
- - CardHeader / CardContent の padding を上書きしている
265
- - asChild と prefixIcon / suffixIcon / isLoading を併用している
266
- - disabled を isDisabled の代わりに使っている
267
- - Button の prefixIcon に JSX を渡している
268
- - Icon の children にテキストを渡している
269
- - <Card> を <button> / <a> / role="button" でラップしている(ClickableCard を使う)
270
- - エントリ CSS にフォント @import が残っている(SparkleHead への移行が必要)
271
- - CSP ヘッダーで Google Fonts がブロックされている可能性
298
+ 検出するルールの一覧:
299
+ sparkle-design-cli rules
300
+
301
+ ここに列挙しないのは、有効なルールがプロジェクトごとに違うため。プラグインは
302
+ コンシューマの package.json から自動発見されるので、固定の一覧はプラグインを
303
+ 入れた利用者にとって最初から不正確になる。
272
304
 
273
305
  JSON 出力では manualReviewReminders も返すため、AI から実行する場合は --format json を推奨
274
306
 
@@ -353,11 +385,14 @@ async function main() {
353
385
  process.exit(0);
354
386
  }
355
387
 
356
- const hasFindings = await checkProject(options.targets, {
388
+ // checkProject の戻り値は「findings があるか」ではなく
389
+ // 「exit code を 1 にすべきか」(error severity か、実行に失敗したルールがあるか)。
390
+ // en: The return value means "should this fail", not "were there findings".
391
+ const hasBlockingIssues = await checkProject(options.targets, {
357
392
  strict: options.strict,
358
393
  format: options.format,
359
394
  });
360
- if (hasFindings && options.strict) {
395
+ if (hasBlockingIssues && options.strict) {
361
396
  process.exit(1);
362
397
  }
363
398
  return;
@@ -377,14 +412,34 @@ async function main() {
377
412
 
378
413
  if (command === 'stop-hook') {
379
414
  // setup で各 agent の hook 設定ファイルから呼ばれる internal subcommand。
380
- // 第 2 引数は lint 対象 path(setup 時に決まったもの)。option flag は未対応。
381
- // en: Internal subcommand invoked by agent stop hooks. The single positional
382
- // arg is the lint target path determined at setup time.
383
- const target = args[1];
384
- const exitCode = await runStopHook(target);
415
+ // 以降の引数はすべて lint 対象 path。setup が書き出す hook は 1 つだが、
416
+ // 利用側が `stop-hook apps/web/app apps/web/components` のように手で複数
417
+ // 並べている実例があり、以前は args[1] しか読まず 2 つ目以降が黙って
418
+ // 未検査になっていた(issue #85)。option flag は未対応なので、`-` 始まりは
419
+ // path として渡さず警告する。
420
+ // en: Forward every positional arg. Previously only the first was used, so
421
+ // extra paths in a hand-written hook command were silently never checked.
422
+ const positional = args.slice(1).filter((arg) => !arg.startsWith('-'));
423
+ const flags = args.slice(1).filter((arg) => arg.startsWith('-'));
424
+ if (flags.length > 0) {
425
+ console.warn(
426
+ `⚠️ stop-hook はオプションを受け付けません。無視します: ${flags.join(' ')} / stop-hook takes target paths only.`
427
+ );
428
+ }
429
+ const exitCode = await runStopHook(positional);
385
430
  process.exit(exitCode);
386
431
  }
387
432
 
433
+ if (command === 'rules') {
434
+ const options = parseRulesOptions(args.slice(1));
435
+ if (options.help) {
436
+ showHelp();
437
+ process.exit(0);
438
+ }
439
+ await runRules(options);
440
+ return;
441
+ }
442
+
388
443
  if (command === 'plugin-spec') {
389
444
  await runPluginSpec(args.slice(1));
390
445
  return;
@@ -0,0 +1,92 @@
1
+ # アンチパターン検査の詳細
2
+
3
+ `sparkle-design-cli check` が検出する内容の背景と、個別ルールの扱いをまとめています。
4
+
5
+ **現在有効なルールの一覧は `npx sparkle-design-cli rules` で確認してください。** ここには一覧を置きません。プラグインで増減するため、書いた時点で古くなるからです。
6
+
7
+ ## 抑制する
8
+
9
+ 特定の箇所だけ検査から外すには、ESLint と同じ形のコメントを使います。
10
+
11
+ ```tsx
12
+ {/* sparkle-disable-next-line legacy-color-token */}
13
+ <div className="bg-primary-600" />
14
+
15
+ <div className="bg-primary-600" /> {/* sparkle-disable-line legacy-color-token */}
16
+ ```
17
+
18
+ ルール ID はカンマ区切りで複数指定できます。ID は `rules` コマンドの出力と `check` の指摘に出ているものです。
19
+
20
+ ## 新セマンティックトークンへの移行(beta 期間中)
21
+
22
+ 移行ルールは検出するだけでなく、**マッチ 1 件ごとに移行先を提示**します。
23
+
24
+ ```text
25
+ src/Button.tsx:12 [warning] [legacy-color-token] 旧セマンティックカラーのユーティリティクラスを使っています
26
+ Recommendation: [自動変換可] bg-primary-600 → bg-surface-primary-high-enabled
27
+
28
+ src/Card.tsx:8 [warning] [legacy-color-token] 旧セマンティックカラーのユーティリティクラスを使っています
29
+ Recommendation: [要判断] bg-primary-200 の移行先候補: surface-primary-high-disabled / surface-primary-middle-active。…
30
+ ```
31
+
32
+ 新体系は「用途 × 意味 × 強度 × **状態**」で、状態がトークン名に内包されます。そのため**同じ旧クラスでも文脈によって移行先が変わり**、機械的に決まらないものは `[要判断]` として候補の列挙に留めます(推測で置換しません)。
33
+
34
+ ユーティリティ接頭辞がそのまま用途に対応します。
35
+
36
+ | 接頭辞 | 用途 | 例 |
37
+ | -------------------------------------------- | -------------------- | ---------------------------------------------------- |
38
+ | `bg-` | surface(背景) | `bg-primary-600` → `bg-surface-primary-high-enabled` |
39
+ | `text-` | text(文字) | `text-primary-600` → `text-text-primary-enabled` |
40
+ | `border-` / `divide-` / `outline-` / `ring-` | border(枠線) | `border-primary-300` → `border-border-primary-high` |
41
+ | `fill-` / `stroke-` | object(アイコン等) | `fill-primary-600` → `fill-object-primary-enabled` |
42
+
43
+ `secondary` は `neutral` に統合されました。旧トークンは beta 期間中はテンプレートに併存するので、警告が出ていても即座には壊れません。beta 終了時に旧トークンを削除するタイミングで `error` に昇格します。
44
+
45
+ 移行ルールは `.tsx` などのソースに加えて **`.css` も検査します**(`var(--color-ring-normal)` のような CSS 変数の直接参照は `.css` にこそ書かれ、しかも beta 終了で消えたときにビルドエラーにならず見た目だけ壊れるため)。
46
+
47
+ 他のルール(`dialog-form` などの構造ルール)は従来どおりソースファイルのみが対象で、CSS 検査の追加による影響はありません。
48
+
49
+ **報告しないもの**(いずれも「移行が必要な使用箇所」ではないため):
50
+
51
+ - **そのファイル自身が宣言している変数への参照**。CLI が生成した CSS は旧トークンを「定義している側」なので、これで丸ごと除外されます。ファイル名ではなく内容で判定しているため、`generate --scope`(`-o` が必須で出力名が任意)の生成物にも効きます。自前で `:root` に定義を持つプロジェクトの theme ファイルも同じ理由で対象外になります(自分で定義している以上、beta 終了で壊れません)
52
+ - **行頭から始まるブロックコメントの中身**。「旧 `--color-ring-normal` の移行先」のような説明文を拾わないためです。行末コメントは対象外にしていません(任意位置の `/*` をコメント開始とみなすと、`matcher: ['/dashboard/*']` のような文字列リテラルを起点に実コードを黙って検査対象から外してしまうため)。逆に、複数行文字列(テンプレートリテラル)の中に行頭から `/*` を書いた場合は、閉じるまでの範囲が検査されません
53
+
54
+ > 移行ルールはクラス名らしき文字列に対する正規表現マッチのため、`url(/img/fill-base-100.svg)` のようにクラス名でない文字列を拾うことがあります。`warning` 止まりで exit code には影響しませんが、抑制したい箇所には `sparkle-disable-line legacy-color-token` のコメントを使えます。
55
+ >
56
+ > 旧→新の対応表は `lib/token-migration.js` に一元化してあり、`test/token-migration.test.js` が**実際に生成した CSS を読んで**「表に書いた移行先が実在し、旧トークンと同じ実値に解決される」ことを検証しています。表とトークン定義が食い違ったらテストが落ちるので、案内が嘘になりません。検出パターンがマッチする文字列は必ず対応表で解決できることも全数検証しています(片方だけ更新すると「検出したのに黙って捨てる」穴になるため)。
57
+
58
+ ## 余白のスケール(`use-figma-spacing-scale`)
59
+
60
+ Figma の余白は 17 段(0 / 2 / 4 / 6 / 8 / 12 / 16 / 20 / 24 / 32 / 40 / 48 / 56 / 72 / 88 / 104 / 120 px)です。Tailwind の 1 ステップは 4px なので、`p-4` = 16px、`p-6` = 24px が対応します。このスケールから外れた値を `info` で報告し、前後の候補を提示します。
61
+
62
+ ```text
63
+ src/Popover.stories.tsx:194 [info] [use-figma-spacing-scale] Figma の Spacing: Primitives に無い余白ステップを使っています
64
+ Recommendation: [要判断] gap-y-20 は 80px で、Figma の Spacing: Primitives に無い値です。近いステップは gap-y-18(72px) / gap-y-22(88px)。…
65
+ ```
66
+
67
+ **Tailwind の数字と Figma の数字は一致しません。** Figma の `padding/16` は 16px、Tailwind の `p-16` は 64px で 4 倍違います。Figma を見ながら同じ数字を書くと 4 倍の余白になります。
68
+
69
+ | Figma | px | Tailwind |
70
+ | ------------- | ----- | -------- |
71
+ | `padding/8` | 8px | `p-2` |
72
+ | `padding/16` | 16px | `p-4` |
73
+ | `padding/24` | 24px | `p-6` |
74
+ | `padding/120` | 120px | `p-30` |
75
+
76
+ このスケールは **CSS 変数として出力していません**。`--spacing-16` のような名前付きキーを定義すると Tailwind の `p-16` がその値に差し替わり、既存コードの余白がビルドも警告も通ったまま変わってしまうためです。だから「変数を使う」ではなく「使ってよい値を守る」という形の制約になっています(`sparkle-variables` 側でも `--spacing-*` を定義していないことを検査しています)。
77
+
78
+ 対象は `p` / `m` / `gap` / `space` 系の数値ユーティリティで、`.tsx` などのソースに加えて `.css` の `@apply` も見ます。Figma のコレクション名は `padding/*` ですが、`gap-7` のようにスケールから外れた値は padding でなくても同じだけレイアウトを崩すため、margin / gap / space も対象にしています。**これは Figma がそう定義しているのではなく運用上の判断**なので、`severity` は `info` です。
79
+
80
+ 提示する代替は、variant 連鎖・負のマージンの `-`・important の `!` をすべて保ったまま数字だけを差し替えます(`hover:p-7!` → `hover:p-6!` / `hover:p-8!`)。`[&>*]:` のような角括弧だけの arbitrary variant も保持します。案内どおりに書き換えた結果 important が消えていた、という事故を避けるためです。
81
+
82
+ `p-[13px]` のような arbitrary **value** は対象外です(`calc()` など正当な用途と機械的に区別できないため)。
83
+
84
+ Tailwind では `w-*` / `h-*` / `inset-*` / `translate-*` / `scroll-p-*` なども同じ spacing スケールから値を引きますが、**このルールの対象外**です。Figma のコレクションが定義しているのは余白(`padding/*`)で、サイズや位置まで同じ制約に含めると別の判断が混ざるためです。
85
+
86
+ クラス名の直前に `/` が来る文字列(`https://example.com/v1/p-7` や `import x from './gap-7'`)は誤検出を避けるため対象外にしています。
87
+
88
+ デザイン上どうしてもスケール外の値が必要な場合は Figma 側にステップを追加するのが本筋ですが、個別に外すなら `sparkle-disable-next-line use-figma-spacing-scale` を使えます。
89
+
90
+ ---
91
+
92
+ [← README に戻る](../README.md)(docs/anti-patterns.md)
package/docs/config.md ADDED
@@ -0,0 +1,133 @@
1
+ # 設定ファイルの拡張(`extend`)
2
+
3
+ Figma プラグインが出力する基本4項目に加えて、プロジェクト固有の拡張を `extend` セクションにまとめます。`extend` はオブジェクト直書き(推奨)またはファイルパスを指定できます。
4
+
5
+ ```json
6
+ {
7
+ "primary": "blue",
8
+ "font-pro": "Montserrat",
9
+ "font-mono": "Roboto Mono",
10
+ "radius": "md",
11
+ "extend": {
12
+ "fonts": {
13
+ "pro": [
14
+ { "family": "Montserrat", "weights": [500, 600, 700] },
15
+ { "family": "Noto Sans JP", "weights": [400, 500, 600, 700] }
16
+ ],
17
+ "mono": [{ "family": "Roboto Mono", "weights": [400, 700] }]
18
+ },
19
+ "source-packages": ["@goodpatch/sparkle-design-internal"],
20
+ "custom-css": "./src/app/custom-tokens.css"
21
+ }
22
+ }
23
+ ```
24
+
25
+ ファイル参照も可能です:
26
+
27
+ ```json
28
+ {
29
+ "primary": "blue",
30
+ "font-pro": "Montserrat",
31
+ "font-mono": "Roboto Mono",
32
+ "radius": "md",
33
+ "extend": "./sparkle.extend.json"
34
+ }
35
+ ```
36
+
37
+ ## extend.globals-path
38
+
39
+ Tailwind のエントリポイント CSS(`@import "tailwindcss";` を書いているファイル)のパスです。未指定なら自動検出します。CLI の `--globals-path` でも同じ指定ができ、そちらが優先されます。
40
+
41
+ ```json
42
+ {
43
+ "primary": "blue",
44
+ "extend": {
45
+ "globals-path": "src/styles/app.css"
46
+ }
47
+ }
48
+ ```
49
+
50
+ 指定すると次の 2 つに効きます。
51
+
52
+ | 効き先 | 内容 |
53
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
54
+ | `@source` と `sparkle-design.css` の import 先 | そのファイルにパッチする(**フォントの `@import` は入りません**。フォント読み込みは `SparkleHead.tsx` 側に移っています) |
55
+ | `generate` の出力先 | `-o/--output` 未指定なら**そのファイルと同じディレクトリ**に `sparkle-design.css` と `SparkleHead.tsx` を出す |
56
+
57
+ 後者が重要です。既定の `src/app/` に固定されるのは globals-path を明示していない場合だけなので、`src/index.css` や `src/styles/app.css` 構成のプロジェクトで意図しない `src/app/` ディレクトリが増えることはありません。
58
+
59
+ > [!WARNING]
60
+ > **指定したパスが存在しない場合は `--strict` の有無に関わらず常に exit 1** です。typo に気付かないまま誤ったディレクトリを掘るより、その場で止めるほうが安全なためです。相対パスのみで、`..` を含む traversal や絶対パスは弾かれます。
61
+
62
+ ## extend.fonts
63
+
64
+ フォントごとにウェイトを個別指定。`extend.fonts` がない場合は `font-pro` / `font-mono` + デフォルトウェイト `[400, 700]` が使われます。`extend.fonts` が存在しても `fonts.pro` / `fonts.mono` のどちらかが未指定なら、そのスロットのみ `font-pro` / `font-mono` にフォールバックします。同じフォントファミリーが `pro` と `mono` で重複する場合、ウェイトはマージされ import は 1 行に統合されます。
65
+
66
+ ## extend.source-packages
67
+
68
+ 既知のデザインシステムパッケージ(`sparkle-design` など)は `package.json` から**自動検出**されるので、通常は指定不要です。クライアント固有のパッケージなど自動検出の対象外を追加したいときだけ指定してください。指定分は自動検出分とマージされ、Tailwind エントリ CSS(自動検出)に `@source` ディレクティブとして挿入されます。`@source` をまったく出さないのは、**自動検出がゼロで、かつ `source-packages` キー自体を書いていない**ときだけです(キーがあれば空配列でも既定の `sparkle-design` 1 行が出ます)。
69
+
70
+ ## extend.custom-css
71
+
72
+ プロジェクト固有のカスタムトークンの CSS ファイルパス。Tailwind エントリ CSS に `@import` が自動挿入されます。`sparkle-design.css` に直接追加すると `generate` 実行時に上書きされるため、必ず別ファイルに分離してください。
73
+
74
+ この `@import` は `sparkle-design.css` の直後に挿入されるため、`custom-css` 側で書いた `:root { --color-xxx: ... }` は CSS の cascade(後勝ち)でそのまま `sparkle-design.css` のトークンを上書きします。属性セレクタや `!important` を使う必要はありません。
75
+
76
+ ### カスタムブランドカラーを primary にしたい場合
77
+
78
+ `primary` は 7 色のいずれかしか受け付けません([README の「設定オプション」](../README.md#設定オプション) を参照)。7 色にないブランドカラーを使いたい場合は、`primary` は 7 色から見た目が近いものを仮に選んだ上で、`extend.custom-css` で `--color-primary-*` と `--color-gray-*` をブランドカラー基準の値で丸ごと再定義してください。`primary` の選択自体は `custom-css` 側の定義で完全に上書きされるため実質的な意味を持たなくなりますが、フィールドとしては有効な値を入れておく必要があります。
79
+
80
+ ```css
81
+ /* custom-tokens.css */
82
+ :root {
83
+ /* ブランドカラー基準の primary パレット(50〜900) */
84
+ --color-primary-50: oklch(97% 0.02 250);
85
+ /* ... */
86
+ --color-primary-500: oklch(55% 0.18 250);
87
+ /* ... */
88
+ --color-primary-900: oklch(20% 0.08 250);
89
+
90
+ /* primary に合わせた gray パレット(50〜900)も必ず一緒に定義する。
91
+ gray だけ 7 色デフォルトのままだと、primary だけ浮いて見えるトーンずれが起きる */
92
+ --color-gray-50: oklch(98% 0.005 250);
93
+ /* ... */
94
+ --color-gray-900: oklch(22% 0.01 250);
95
+ }
96
+ ```
97
+
98
+ `primary` だけを差し替えて `gray` を既定のままにすると、コンポーネントの枠線・背景・テキストに使われる gray 系トークンと primary のトーンが揃わなくなるため、gray も必ずセットで再定義してください。
99
+
100
+ ### `character-*` に無いウェイト(SemiBold 等)を使いたい場合
101
+
102
+ `character-*` utility の `font-weight` は Regular(400) / Bold(700) の 2 値のみで、SemiBold(600) や Medium(500) に対応する `character-*` は存在しません(Sparkle Design のプリミティブトークンである `fontWeights.text.regular` / `fontWeights.text.bold` がこの 2 値のみを持つため)。
103
+
104
+ 日本語見出しでは Bold(700) が視覚的に重すぎると判断して SemiBold(600) を使いたい、といったプロジェクト固有のウェイト方針がある場合は、`character-*` に SemiBold バリアントの追加を待つのではなく、`extend.custom-css` で独自のユーティリティクラスを定義してください。**`className="character-3-regular-pro font-semibold"` のように Tailwind の `font-semibold` を併用する方法は機能しません**。`sparkle-design.css` は Tailwind の後・`@layer utilities` 内に読み込まれるよう意図的に設計されており(同一詳細度なら後着のルールが勝つ cascade layers の仕様上)、`character-*` 側の `font-weight` が常に優先されるためです。
105
+
106
+ ```css
107
+ /* custom-typography.css */
108
+ @layer utilities {
109
+ /* character-3(16px/24px)の SemiBold バリアント。
110
+ font-family / font-size / letter-spacing / line-height は
111
+ character-* と同じプリミティブトークンをそのまま流用し、
112
+ font-weight だけプロジェクト独自の 600 に差し替える */
113
+ .character-3-semibold-pro {
114
+ font-family: var(--font-family-pro);
115
+ font-size: var(--font-size-16);
116
+ font-weight: 600;
117
+ letter-spacing: var(--letter-spacing-wider);
118
+ line-height: var(--line-height-24);
119
+ }
120
+ }
121
+ ```
122
+
123
+ `character-*-semibold-*` は既存の `character-*-regular-*` / `character-*-bold-*` と別名のクラスなので、cascade の競合は起きません。`extend.custom-css` は `sparkle-design.css` の直後に `@import` されるため、上記のように新規クラスを追記するだけで有効になります(上書きではないので `!important` や記述順の工夫も不要です)。
124
+
125
+ 使用するフォントファミリーが実際に 600 ウェイトのファイルを読み込んでいるかも確認してください。`extend.fonts.pro` / `extend.fonts.mono` の `weights` に `600` を含めていないと、`font-weight: 600` を指定してもブラウザによる疑似太字(faux bold)にフォールバックし、正しい SemiBold の字形になりません。
126
+
127
+ 将来 Sparkle Design 本体が正式に `character-N-semibold-*` token を追加した場合は、cascade 上は consumer 側の `custom-css` 定義が勝ち続けるため、その時点で自前定義は削除して本体の token に乗り換えてください(放置すると Sparkle 側の値が更新されても気づかず古い定義が使われ続けます)。
128
+
129
+ なお `check` の `tailwind-typography` ルールは `font-semibold` / `font-medium` を検出対象に含めていますが、`character-*-semibold-*` のような独自クラスに置き換えた行は検出パターンに一致しないため、`sparkle-disable-line` を付けなくても違反として検出されなくなります。
130
+
131
+ ---
132
+
133
+ [← README に戻る](../README.md)(docs/config.md)
@@ -0,0 +1,117 @@
1
+ # 手動セットアップ(Next.js / Vite 以外のプロジェクト向け)
2
+
3
+ `setup` コマンドは **Next.js App Router** と **Vite** を前提に scaffold / entry CSS の配置を自動判定します。Remix / Astro / TanStack Start など他のフレームワークや、独自ディレクトリ構成のプロジェクトでは自動判定が外れるため、以下の手順で手動セットアップすることを推奨します。
4
+
5
+ ## 手順
6
+
7
+ **1. パッケージをインストール**
8
+
9
+ ```bash
10
+ # 本体
11
+ pnpm add sparkle-design # or npm install / yarn add / bun add
12
+
13
+ # Tailwind v4
14
+ pnpm add -D tailwindcss @tailwindcss/postcss
15
+ ```
16
+
17
+ **2. `sparkle.config.json` をプロジェクトルートに作成**
18
+
19
+ ```json
20
+ {
21
+ "primary": "blue",
22
+ "font-pro": "Inter",
23
+ "font-mono": "JetBrains Mono",
24
+ "radius": "md"
25
+ }
26
+ ```
27
+
28
+ 選択肢の詳細は [README の「設定オプション」](../README.md#設定オプション)、`extend` の各項目は [docs/config.md](config.md) を参照してください。
29
+
30
+ **3. `postcss.config.mjs` をプロジェクトルートに作成**
31
+
32
+ ```js
33
+ export default {
34
+ plugins: {
35
+ '@tailwindcss/postcss': {},
36
+ },
37
+ };
38
+ ```
39
+
40
+ **4. Tailwind エントリ CSS を自前で用意**
41
+
42
+ 既存プロジェクトに組み込む場合、`@import "tailwindcss";` を含む CSS ファイル(例: `src/styles/app.css`)があればそれを使います。無ければ作成してアプリケーションのエントリから import します。
43
+
44
+ **5. `generate` を実行**
45
+
46
+ ```bash
47
+ npx --yes sparkle-design-cli generate
48
+ ```
49
+
50
+ これで `sparkle-design.css` と `SparkleHead.tsx` が生成されます。**出力先はエントリ CSS の場所で変わります** — `extend.globals-path`(または `--globals-path`)を指定していればそのディレクトリ、未指定なら既定の `src/app/` です。上の例のように `src/styles/app.css` を使う場合は、この手順の前に `extend.globals-path` を指定してください(指定しないと `src/app/` に出ます)。`-o/--output` で直接指定することもできます。
51
+
52
+ ```json
53
+ {
54
+ "primary": "blue",
55
+ "extend": {
56
+ "globals-path": "src/styles/app.css"
57
+ }
58
+ }
59
+ ```
60
+
61
+ **6. フォントの `<link>` タグを手動で配置**
62
+
63
+ Next.js / Vite 以外ではアプリケーションフレームワークごとに「`<head>` 相当」の配置方法が異なります。自動生成される `SparkleHead.tsx` はそのまま使えるはずですが、使えない場合は中身(`preconnect` + Google Fonts の `<link>` タグ群)を自前のレイアウトにコピーしてください。
64
+
65
+ たとえば Astro なら `src/layouts/BaseLayout.astro` の `<head>` に直接書きます:
66
+
67
+ ```html
68
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
69
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
70
+ <link
71
+ rel="stylesheet"
72
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:FILL,wght@0..1,500&display=block"
73
+ />
74
+ <!-- sparkle.config.json の font-pro / font-mono に合わせた Google Fonts URL -->
75
+ <link
76
+ rel="stylesheet"
77
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
78
+ />
79
+ <link
80
+ rel="stylesheet"
81
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap"
82
+ />
83
+ ```
84
+
85
+ **7. アンチパターン検査を package.json に追加(任意)**
86
+
87
+ ```json
88
+ {
89
+ "scripts": {
90
+ "lint:sparkle": "npx --yes sparkle-design-cli check src --strict",
91
+ "lint:sparkle:json": "npx --yes sparkle-design-cli check src --format json"
92
+ }
93
+ }
94
+ ```
95
+
96
+ **8. AI エージェントを使うなら Guard ブロックと hook を手動で配置**
97
+
98
+ AI ガード(`CLAUDE.md` / `AGENTS.md`)と hook 設定(`.claude/settings.json` / `.cursor/hooks.json` / `.codex/hooks.json`)は `setup` に任せるのが最も楽です。Cursor 用 Guard は rc.4 から AGENTS.md に統一しました(`.cursor/rules/*.mdc` は条件付き読み込みのため)。
99
+
100
+ ```bash
101
+ # ガード追加・hook 設定のみ(パッケージインストールや scaffold はスキップ)
102
+ npx --yes sparkle-design-cli setup --assistant claude --skip-install --skip-scaffold --skip-generate
103
+ ```
104
+
105
+ この `--skip-*` 組み合わせは手動セットアップ済みのプロジェクトに対して Guard + hook だけ差し込むのに使えます。
106
+
107
+ ## 既知の未対応ケース
108
+
109
+ - **モノレポ(workspaces)**: `generate` は実行した cwd からパッケージマネージャーを遡及検出しないため、サブパッケージで直接実行するのが確実です。
110
+ - **CSS 以外のエントリ(CSS-in-JS / vanilla-extract 等)**: Sparkle Design は Tailwind v4 の `@source` + CSS variable 前提で作られているため、ビルドパイプラインの外で CSS を扱うソリューションは対象外です。
111
+ - **Tailwind v3**: `@source` ディレクティブ依存のため v4 必須です。
112
+
113
+ Next.js / Vite 以外で導入したい方で困ったときは Issue で教えていただけると助かります。
114
+
115
+ ---
116
+
117
+ [← README に戻る](../README.md)(docs/manual-setup.md)