ksk-design-system 1.58.0 → 1.60.0

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.
package/AGENTS.md CHANGED
@@ -17,7 +17,7 @@
17
17
  末尾が `codex-pr-review-guidelines` のマーカー)で囲み同期対象から除外する
18
18
  - 片方だけ編集したら **`node scripts/check-agents-docs-sync.mjs`**(`npm run check` に
19
19
  組み込み済み)を実行し、乖離が無いことを確認する
20
- - `templates/CLAUDE.md` / `templates/AGENTS.md`(postinstall で配布するテンプレート)も
20
+ - `templates/CLAUDE.md` / `templates/AGENTS.md`(`npx ksk-ds init` で配布するテンプレート)も
21
21
  同じ仕組みで同期検査の対象
22
22
 
23
23
  ## 実装前セルフチェック(AI必読・最優先)
package/CLAUDE.md CHANGED
@@ -17,7 +17,7 @@
17
17
  末尾が `codex-pr-review-guidelines` のマーカー)で囲み同期対象から除外する
18
18
  - 片方だけ編集したら **`node scripts/check-agents-docs-sync.mjs`**(`npm run check` に
19
19
  組み込み済み)を実行し、乖離が無いことを確認する
20
- - `templates/CLAUDE.md` / `templates/AGENTS.md`(postinstall で配布するテンプレート)も
20
+ - `templates/CLAUDE.md` / `templates/AGENTS.md`(`npx ksk-ds init` で配布するテンプレート)も
21
21
  同じ仕組みで同期検査の対象
22
22
 
23
23
  ## 実装前セルフチェック(AI必読・最優先)
package/DESIGN.md CHANGED
@@ -292,6 +292,7 @@ Portal に載る要素(Dialog / Sheet / Popover / Toast 等)は DOM 上の
292
292
  | Product | `--Product-Page-Padding-Y` | 24px | AdminShell の `<main>` 縦 padding |
293
293
  | Chip | `--Chip-Radius` | ピル | Chip の pill 角丸 |
294
294
  | Tabs | `--Control-Height-Md` / `--Control-Padding-X-{Sm,Md}` / `--Control-Radius` / `--Field-Radius` | 40px / 12・16px / ピル / 8px | TabsList / TabsTrigger(Control・Field を再利用) |
295
+ | Typography | `--Product-Type-Scale` | 1 | `typo-*`(`src/styles/typography.css`)の font-size 全体 |
295
296
 
296
297
  - **Control と Field はスケールが別**。ksk のフィールドは元々ボタンより背が高く(Input は 48px、
297
298
  Button の既定は 40px)、横 padding も狭い。片方に畳むとどちらかの実寸が変わるので分けている。
@@ -310,6 +311,21 @@ Portal に載る要素(Dialog / Sheet / Popover / Toast 等)は DOM 上の
310
311
  `AdminShell` の `<main>` だけが `--Product-Page-Padding-Y` を持つ。
311
312
  - 色は従来どおり semantic トークン(`--Surface-*` / `--Text-*` / `--Brand-*`)と Brand ランプの
312
313
  上書きで調整する。カードの面は `--card-surface`、角丸は `--Radius-Surface` が既存の公開シーム。
314
+ - **primitive を直接上書きしてよいのは Brand(`--Primitive-Brand-*`)と Neutral
315
+ (`--Primitive-Gray-50〜900` / `--Primitive-White` / `--Primitive-Black`)の2つのアイデンティティ
316
+ パレットだけ**(issue #377)。warm gray のように DS 既定の cool gray と世界観が違うプロダクトは
317
+ Neutral ランプごと差し替える。status 系(Red / Green / Blue / Yellow / Orange)の色調整は
318
+ semantic 層(`--Surface-Caution` / `--Text-Success` / `--Caution-Base` 等)で行う — 上書き1箇所で
319
+ 済み、DS が primitive の割り当てを変えても壊れない。
320
+ **Neutral を差し替えたらコントラストは消費側の責任**。Gray は `--Text-High-Emphasis`(本文色)や
321
+ `--Surface-Secondary` を駆動するので、本文×背景が WCAG AA(4.5:1)を満たすか light / dark 両方で
322
+ 実測すること。DS の `scripts/check-contrast.mjs` は DS 既定パレットしか見ていない。
323
+ - **`--Product-Type-Scale`**(issue #371/#372 の stract-ui からの逆輸入)は `typo-*` の font-size に
324
+ `calc(<px> * var(--Product-Type-Scale, 1))` として一律で掛かる乗数。line-height は unitless、
325
+ letter-spacing は em のため font-size の変化に自動で連動し、別途スケールしない。
326
+ **Control / Field の高さ・padding には掛からない**(意図的な分離。文字だけ拡大して枠が追従しないと
327
+ 窮屈になるため、拡大するプロダクトは `--Control-Height-*` / `--Field-Height-*` も別途上げる契約)。
328
+ Web のみが対象で、React Native(`src/tokens/native/scales.ts`)には効かない。
313
329
 
314
330
  ## Components
315
331
 
package/MIGRATION.md CHANGED
@@ -53,6 +53,27 @@ npx ksk-ds check-migration ./src
53
53
 
54
54
  ## v1 系内の minor 変更(参考)
55
55
 
56
+ ### 次のリリース — `postinstall` による AI ルールファイル自動設置を廃止(要確認)
57
+
58
+ これまで `npm install ksk-design-system` の `postinstall` フックが、消費側の
59
+ プロジェクトルートに `CLAUDE.md` / `AGENTS.md`(`node_modules` 内の DS ルールを指す
60
+ 薄いポインタ)を自動設置していた。**この自動設置を廃止し、明示コマンドに一本化した。**
61
+
62
+ ```bash
63
+ npx ksk-ds init # 設置(既存ファイルはスキップ)
64
+ npx ksk-ds init --force # 既存ファイルを最新テンプレートで上書き
65
+ ```
66
+
67
+ **既に設置済みの消費リポは対応不要。** 元々 `postinstall` は既存ファイルを上書きしない
68
+ 仕様だったため、設置済みのプロジェクトでは実質 no-op だった。影響を受けるのは
69
+ **新規に DS を導入するプロジェクトの初回だけ**で、`npm install` の後に上記を 1 回実行する。
70
+
71
+ 廃止の理由は、install 時にプロジェクトルートへ AI エージェント向け指示ファイルを書き込む
72
+ 挙動が、サプライチェーン検査で「同意なき AI エージェント制御面の設置」として Critical 判定
73
+ されるため。LPM Firewall が 1.49.2 / 1.51.1 をこの理由でブロック判定しており、
74
+ `postinstall` を持つ限りバージョンを上げても判定が引き継がれる。あわせて
75
+ `INIT_CWD` 参照も削除し、書き込み先はコマンドを実行したディレクトリに固定した。
76
+
56
77
  ### 次のリリース — PillToggle の onChange/onValueChange 統一(破壊変更なし)
57
78
 
58
79
  `PillToggle` のみ他コンポーネント(Switch 等の native 系を除く)と異なり `onValueChange`
package/README.md CHANGED
@@ -48,6 +48,21 @@ Tailwind 4.1 で導入された `@source inline()` を使うため)
48
48
  npm install ksk-design-system
49
49
  ```
50
50
 
51
+ AI コーディング(Claude Code / Codex)を使うプロジェクトでは、続けて 1 回だけ実行します。
52
+
53
+ ```bash
54
+ npx ksk-ds init
55
+ ```
56
+
57
+ プロジェクトルートに `CLAUDE.md` / `AGENTS.md`(`node_modules` 内の DS ルールを指す薄いポインタ)を
58
+ 設置します。AI エージェントは `node_modules` 配下を自動では読まないため、このファイルが無いと
59
+ DS のルールが適用されません。既存ファイルはスキップされ、更新は `--force` で上書きします。
60
+
61
+ > **v1.60.0 で `postinstall` による自動設置を廃止しました。** install 時にプロジェクトルートへ
62
+ > AI 指示ファイルを書き込む挙動は、サプライチェーン検査で「同意なき AI エージェント制御面の設置」
63
+ > として Critical 判定されるためです(LPM Firewall が 1.49.2 / 1.51.1 をこの理由でブロック判定)。
64
+ > v1.59.0 以前から更新する場合、既に設置済みのファイルはそのまま使えます。
65
+
51
66
  ```css
52
67
  /* globals.css / app.css(CSS の場所に応じて ../../ の数を調整) */
53
68
  @import "tailwindcss";
package/bin/init.js CHANGED
@@ -6,6 +6,12 @@
6
6
  // プロジェクトルートに AGENTS.md / CLAUDE.md(node_modules 内の DS ルールを
7
7
  // 参照する薄いポインタ)を置く必要がある。
8
8
  //
9
+ // 設置は **明示実行のみ**(`npx ksk-ds init`)。install 時に自動で置く
10
+ // postinstall フックは v1.60.0 で廃止した。install-time にプロジェクトルートへ
11
+ // AI 指示ファイルを書き込む挙動は、サプライチェーン検査で
12
+ // 「同意なき AI エージェント制御面の設置」として Critical 判定されるため
13
+ // (LPM Firewall が 1.49.2 / 1.51.1 をこの理由でブロック判定)。
14
+ //
9
15
  // Usage:
10
16
  // npx ksk-design-system init # AGENTS.md + CLAUDE.md を設置
11
17
  // npx ksk-design-system init --force # 既存ファイルを上書き
@@ -13,7 +19,6 @@
13
19
  // npx ksk-ds lint src # contracts/rules.json に基づき consumer UI を検査
14
20
  // npx ksk-ds check-duplicates src # DS と同名のローカル実装を検査
15
21
  // npx ksk-ds codemod <name> ./src # scripts/codemod/<name>.mjs を実行
16
- // npx ksk-design-system postinstall # npm postinstall から呼ばれる silent モード
17
22
 
18
23
  import { copyFileSync, existsSync, readdirSync } from "node:fs"
19
24
  import { dirname, join, resolve } from "node:path"
@@ -82,7 +87,7 @@ if (cmd === "demo") {
82
87
  process.exit(0)
83
88
  }
84
89
 
85
- if (cmd !== "init" && cmd !== "postinstall") {
90
+ if (cmd !== "init") {
86
91
  console.error(`未知のコマンド: ${cmd}`)
87
92
  console.error(`npx ksk-design-system help を参照してください`)
88
93
  process.exit(1)
@@ -209,32 +214,18 @@ function runDemo(rest) {
209
214
  console.log(``)
210
215
  }
211
216
 
212
- // ─── postinstall モードのガード ─────────────────────────────
213
- // INIT_CWD は npm install が実行されたディレクトリ(consumer のルート)。
214
- // 未設定、または pkgRoot と同じ場合は DS 自身のインストールコンテキストなのでスキップ。
215
- const isPostinstall = cmd === "postinstall"
216
- const consumerRoot = process.env.INIT_CWD || process.cwd()
217
-
218
- if (isPostinstall) {
219
- // DS 自身を開発している場合(INIT_CWD が pkgRoot)はスキップ
220
- if (!process.env.INIT_CWD || resolve(consumerRoot) === pkgRoot) {
221
- process.exit(0)
222
- }
223
- // consumer のルートに package.json が無ければスキップ(安全のため)
224
- if (!existsSync(join(consumerRoot, "package.json"))) {
225
- process.exit(0)
226
- }
227
- }
217
+ // ─── init(明示実行のみ)─────────────────────────────────────
218
+ // 書き込み先はコマンドを叩いたディレクトリ。install 時の暗黙実行が無くなったので
219
+ // INIT_CWD 等の環境変数は参照しない。
220
+ const consumerRoot = process.cwd()
228
221
 
229
222
  const files = [
230
223
  { src: "templates/AGENTS.md", dest: "AGENTS.md", label: "Codex 用" },
231
224
  { src: "templates/CLAUDE.md", dest: "CLAUDE.md", label: "Claude Code 用" },
232
225
  ]
233
226
 
234
- if (!isPostinstall) {
235
- console.log("ksk-design-system init")
236
- console.log("AI (Claude / Codex) 向けルールファイルを設置します。\n")
237
- }
227
+ console.log("ksk-design-system init")
228
+ console.log("AI (Claude / Codex) 向けルールファイルを設置します。\n")
238
229
 
239
230
  let created = 0
240
231
  let skipped = 0
@@ -244,33 +235,21 @@ for (const { src, dest, label } of files) {
244
235
  const destPath = join(consumerRoot, dest)
245
236
 
246
237
  if (!existsSync(srcPath)) {
247
- if (!isPostinstall) {
248
- console.error(` ✗ テンプレートが見つかりません: ${srcPath}`)
249
- }
250
- process.exit(isPostinstall ? 0 : 1)
238
+ console.error(` ✗ テンプレートが見つかりません: ${srcPath}`)
239
+ process.exit(1)
251
240
  }
252
241
 
253
242
  if (existsSync(destPath) && !force) {
254
- if (!isPostinstall) {
255
- console.log(` ⏭ ${dest} は既に存在するためスキップ(--force で上書き)`)
256
- }
243
+ console.log(` ⏭ ${dest} は既に存在するためスキップ(--force で上書き)`)
257
244
  skipped++
258
245
  continue
259
246
  }
260
247
 
261
248
  copyFileSync(srcPath, destPath)
262
- if (isPostinstall) {
263
- console.log(`[ksk-design-system] ${dest} をプロジェクトルートに設置しました`)
264
- } else {
265
- console.log(` ✓ ${dest} を設置しました(${label})`)
266
- }
249
+ console.log(` ✓ ${dest} を設置しました(${label})`)
267
250
  created++
268
251
  }
269
252
 
270
- if (isPostinstall) {
271
- process.exit(0)
272
- }
273
-
274
253
  console.log(`\n完了: ${created} 作成 / ${skipped} スキップ`)
275
254
 
276
255
  if (created > 0) {
package/bin/lint.js CHANGED
@@ -10,16 +10,36 @@ const DEFAULT_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx"])
10
10
  * engine: "product-theme-override" を持つルールだけをここに流す(issue #364)。
11
11
  */
12
12
  const CSS_EXTENSIONS = new Set([".css"])
13
+ // ビルド生成物。ここを走査すると、バンドルされた DS 自身の CSS を
14
+ // 「consumer が DS 変数を上書きしている」と誤認して P049 が大量に出る(#378)。
15
+ // Next.js の `output: "export"` は out/、Nuxt は .output/、Vercel/Turbo は
16
+ // それぞれ .vercel/ .turbo/ を既定の出力先にする。
13
17
  const DEFAULT_IGNORES = [
14
18
  ".git",
15
19
  ".next",
20
+ ".nuxt",
21
+ ".output",
22
+ ".svelte-kit",
23
+ ".turbo",
24
+ ".vercel",
16
25
  "build",
17
26
  "coverage",
18
27
  "dist",
28
+ ".claude",
19
29
  "node_modules",
30
+ "out",
20
31
  "storybook-static",
21
32
  ]
22
33
 
34
+ // パス単位で除外するもの(セグメント名だけでは絞れないケース)。
35
+ // Capacitor は web のビルド成果物をネイティブプロジェクト配下へコピーするので、
36
+ // そこも DS 自身の CSS を含む(#378)。`public` をセグメントで除外すると
37
+ // Next.js の public/ まで巻き込むため、パス形で限定する。
38
+ const DEFAULT_IGNORE_PATHS = [
39
+ "ios/App/App/public",
40
+ "android/app/src/main/assets/public",
41
+ ]
42
+
23
43
  export async function runLintCli(argv, { cwd = process.cwd(), pkgRoot = resolve(".") } = {}) {
24
44
  const options = parseArgs(argv)
25
45
  const rulesPath = resolve(pkgRoot, "contracts/rules.json")
@@ -182,7 +202,9 @@ function readProductThemeContract(pkgRoot) {
182
202
  const path = resolve(pkgRoot, "contracts/product-theme-overrides.json")
183
203
  if (!existsSync(path)) return null
184
204
  try {
185
- return loadProductThemeContract(JSON.parse(readFileSync(path, "utf8")))
205
+ // pkgRoot を渡すと「DS に実在する変数」だけを違反にする(issue #377)。
206
+ // DS の CSS が読めない環境では接頭辞一致だけの従来判定へフォールバックする。
207
+ return loadProductThemeContract(JSON.parse(readFileSync(path, "utf8")), { pkgRoot })
186
208
  } catch {
187
209
  return null
188
210
  }
@@ -323,6 +345,7 @@ function shouldIgnorePath(relPath, options) {
323
345
  if (!relPath || relPath === ".") return false
324
346
  const parts = relPath.split("/")
325
347
  if (parts.some((part) => DEFAULT_IGNORES.includes(part))) return true
348
+ if (DEFAULT_IGNORE_PATHS.some((ignored) => relPath.includes(ignored))) return true
326
349
  return options.excludes.some((exclude) => relPath.includes(exclude))
327
350
  }
328
351
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * P049 — product theme の許可リスト外の DS 変数上書きを検出する(issue #364)
2
+ * P049 — product theme の許可リスト外の DS 変数上書きを検出する(issue #364 / #377)
3
3
  *
4
4
  * 消費プロダクトの CSS が `:root { --Surface-Primary: … }` のように DS の
5
5
  * CSS 変数を上書きするのは、契約 `contracts/product-theme-overrides.json` の
@@ -10,31 +10,90 @@
10
10
  * 判定:
11
11
  * 1. CSS のコメントを落とす
12
12
  * 2. カスタムプロパティ宣言(`--Foo: value;`)を行番号付きで拾う
13
- * 3. `dsVariableNamespaces` のどれかで始まり、`allowedVariables` に無いものを違反とする
13
+ * 3. `dsVariableNamespaces` のどれかで始まり、`allowedVariables` に無く、
14
+ * かつ **DS 自身の CSS に実在する** ものを違反とする
14
15
  *
15
- * `var(--Foo)` の**参照**は対象外(読むのは自由)。宣言だけを見る。
16
+ * 3 の「実在する」条件は issue #377。接頭辞一致だけだと、消費側が DS の命名に
17
+ * 寄せて作った独自変数(`--Surface-Inverse-Hover` のように DS には無い名前)まで
18
+ * 「DS の上書き」と誤検出する。DS に存在しない変数は上書きしようがない。
19
+ * DS の CSS を読めない環境(パッケージ構成が変わった等)では従来どおり
20
+ * 接頭辞一致だけで判定してフォールバックする。
16
21
  */
22
+ import { existsSync, readFileSync, readdirSync } from "node:fs"
23
+ import { join, resolve } from "node:path"
24
+
25
+ /** カスタムプロパティの**宣言**。`var(--Foo, …)` の参照はコロンの前が `(` なので一致しない。 */
26
+ const DECLARATION = /(^|[;{}\s])(--[A-Za-z0-9_-]+)\s*:/g
27
+
28
+ /**
29
+ * DS 自身の CSS 変数を宣言しているファイル群(npm package の `files` にも含まれる)。
30
+ * 生成ファイルを増やさずに実行時へ集めるので、DS 側の CSS を足してもドリフトしない。
31
+ */
32
+ const DS_CSS_DIRS = ["src/styles", "src/themes"]
33
+ const DS_CSS_FILES = ["src/preset.css"]
17
34
 
18
35
  /** CSS コメントを、行数を保ったまま空白に置き換える */
19
36
  function stripCssComments(source) {
20
37
  return source.replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, " "))
21
38
  }
22
39
 
40
+ /**
41
+ * DS が実際に宣言している CSS 変数名を集める。
42
+ * 読めない場合は null を返し、呼び出し側は接頭辞一致だけの判定へフォールバックする。
43
+ *
44
+ * @param {string} pkgRoot ksk-design-system パッケージのルート
45
+ * @returns {Set<string> | null}
46
+ */
47
+ export function collectDsDeclaredVariables(pkgRoot) {
48
+ if (!pkgRoot) return null
49
+ const files = []
50
+ for (const dir of DS_CSS_DIRS) {
51
+ const abs = resolve(pkgRoot, dir)
52
+ if (!existsSync(abs)) continue
53
+ try {
54
+ for (const name of readdirSync(abs)) {
55
+ if (name.endsWith(".css")) files.push(join(abs, name))
56
+ }
57
+ } catch {
58
+ // 読めないディレクトリは黙って飛ばす(他のファイルで足りることがある)
59
+ }
60
+ }
61
+ for (const file of DS_CSS_FILES) {
62
+ const abs = resolve(pkgRoot, file)
63
+ if (existsSync(abs)) files.push(abs)
64
+ }
65
+ if (files.length === 0) return null
66
+
67
+ const names = new Set()
68
+ for (const file of files) {
69
+ let source
70
+ try {
71
+ source = readFileSync(file, "utf8")
72
+ } catch {
73
+ continue
74
+ }
75
+ for (const match of stripCssComments(source).matchAll(DECLARATION)) names.add(match[2])
76
+ }
77
+ return names.size > 0 ? names : null
78
+ }
79
+
23
80
  /**
24
81
  * @param {string} source CSS ソース
25
- * @param {{ allowed: Set<string>, namespaces: string[] }} contract
82
+ * @param {{ allowed: Set<string>, namespaces: string[], declared?: Set<string> | null }} contract
26
83
  * @returns {Array<{ line: number, name: string }>}
27
84
  */
28
85
  export function inspectProductThemeOverrides(source, contract) {
29
86
  const stripped = stripCssComments(source)
30
87
  const findings = []
31
- // 宣言のみを拾う。`var(--Foo, …)` の参照はコロンの前に `(` が来るため一致しない。
32
- const DECLARATION = /(^|[;{}\s])(--[A-Za-z0-9_-]+)\s*:/g
88
+ const declared = contract.declared ?? null
33
89
 
34
90
  for (const match of stripped.matchAll(DECLARATION)) {
35
91
  const name = match[2]
36
92
  if (contract.allowed.has(name)) continue
37
93
  if (!contract.namespaces.some((prefix) => name.startsWith(prefix))) continue
94
+ // DS に実在しない名前は「DS の上書き」ではない(issue #377)。
95
+ // 一覧が取れなかったときは従来どおり接頭辞一致だけで判定する。
96
+ if (declared && !declared.has(name)) continue
38
97
  findings.push({
39
98
  line: stripped.slice(0, match.index).split(/\r?\n/).length,
40
99
  name,
@@ -43,10 +102,16 @@ export function inspectProductThemeOverrides(source, contract) {
43
102
  return findings
44
103
  }
45
104
 
46
- /** 契約 JSON から判定に使う形へ畳む */
47
- export function loadProductThemeContract(contract) {
105
+ /**
106
+ * 契約 JSON から判定に使う形へ畳む。
107
+ *
108
+ * @param {object} contract contracts/product-theme-overrides.json の中身
109
+ * @param {{ pkgRoot?: string }} [options] pkgRoot を渡すと「DS に実在する変数」だけに絞る
110
+ */
111
+ export function loadProductThemeContract(contract, { pkgRoot } = {}) {
48
112
  return {
49
113
  allowed: new Set(Object.values(contract.allowedVariables ?? {}).flat()),
50
114
  namespaces: Array.isArray(contract.dsVariableNamespaces) ? contract.dsVariableNamespaces : [],
115
+ declared: collectDsDeclaredVariables(pkgRoot),
51
116
  }
52
117
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "KSK Design System — Component Contracts",
4
- "version": "1.58.0",
4
+ "version": "1.60.0",
5
5
  "description": "全コンポーネントの構造化定義。バリアント・アクセシビリティ要件・使用ルールを機械可読形式で管理。",
6
6
  "counts": {
7
7
  "ui": 68,
@@ -19,7 +19,8 @@
19
19
  "scope": ":root(アプリ全体)または任意の要素(その配下だけ)",
20
20
  "allowedPattern": "preset の後に、ここで許可された CSS 変数だけを上書きする",
21
21
  "forbiddenPattern": "消費プロダクト側に tokens.json / semantic.css / preset.css のコピーや独自 Tailwind preset を作らない。許可リストに無い内部変数を上書きしない",
22
- "lintRule": "P049(npx ksk-ds lint が消費側 CSS を検査する)"
22
+ "lintRule": "P049(npx ksk-ds lint が消費側 CSS を検査する)",
23
+ "contrastResponsibility": "neutralPalette(--Primitive-Gray-* / White / Black)を差し替えたら、本文×背景のコントラストは消費プロダクト側の責任になる。Gray は --Text-High-Emphasis(本文色)・--Text-Medium-Emphasis・--Surface-Secondary・--Border-Low-Emphasis など semantic.css の 33 箇所を駆動しているため、差し替え後に本文テキスト×各 Surface が WCAG AA(4.5:1、大きい文字は 3:1)を満たすか必ず実測すること。DS 側の scripts/check-contrast.mjs は DS 既定パレット向けの検査で、消費側の差し替えは見ていない。light / dark の両方を確認する。"
23
24
  },
24
25
  "allowedVariables": {
25
26
  "brandColor": [
@@ -34,23 +35,66 @@
34
35
  "--Primitive-Brand-800",
35
36
  "--Primitive-Brand-900"
36
37
  ],
38
+ "neutralPalette": [
39
+ "--Primitive-Gray-50",
40
+ "--Primitive-Gray-100",
41
+ "--Primitive-Gray-200",
42
+ "--Primitive-Gray-300",
43
+ "--Primitive-Gray-400",
44
+ "--Primitive-Gray-500",
45
+ "--Primitive-Gray-600",
46
+ "--Primitive-Gray-700",
47
+ "--Primitive-Gray-800",
48
+ "--Primitive-Gray-900",
49
+ "--Primitive-White",
50
+ "--Primitive-Black"
51
+ ],
37
52
  "semanticColor": [
38
53
  "--Surface-Primary",
39
54
  "--Surface-Secondary",
40
55
  "--Surface-Tertiary",
41
56
  "--Surface-Accent-Primary-Light",
57
+ "--Surface-Inverse",
42
58
  "--Text-High-Emphasis",
43
59
  "--Text-Medium-Emphasis",
44
60
  "--Text-Low-Emphasis",
45
61
  "--Text-Accent-Primary",
46
62
  "--Text-on-Inverse",
63
+ "--Text-on-Inverse-Secondary",
64
+ "--Object-on-Inverse",
47
65
  "--Border-Low-Emphasis",
48
66
  "--Border-Medium-Emphasis",
49
67
  "--Border-Accent-Primary",
50
68
  "--Brand-Primary",
51
69
  "--Brand-Action",
52
70
  "--Focus-High-Emphasis",
53
- "--card-surface"
71
+ "--card-surface",
72
+ "--Surface-Caution",
73
+ "--Surface-Caution-Subtle",
74
+ "--Surface-Caution-Strong",
75
+ "--Surface-Success",
76
+ "--Surface-Success-Subtle",
77
+ "--Surface-Warning",
78
+ "--Surface-Info",
79
+ "--Surface-Info-Subtle",
80
+ "--Text-Caution",
81
+ "--Text-Caution-on-Inverse",
82
+ "--Text-Success",
83
+ "--Text-Warning",
84
+ "--Text-Info",
85
+ "--Border-Caution",
86
+ "--Border-Success",
87
+ "--Border-Warning",
88
+ "--Border-Info",
89
+ "--Object-Caution",
90
+ "--Object-Success",
91
+ "--Object-Warning",
92
+ "--Object-Info",
93
+ "--Caution-Base",
94
+ "--Caution-Action",
95
+ "--Success-Base",
96
+ "--Warning-Base",
97
+ "--Info-Base"
54
98
  ],
55
99
  "controlSize": [
56
100
  "--Control-Height-Xs",
@@ -96,6 +140,9 @@
96
140
  "chip": [
97
141
  "--Chip-Radius"
98
142
  ],
143
+ "productTypography": [
144
+ "--Product-Type-Scale"
145
+ ],
99
146
  "motion": [
100
147
  "--Motion-Duration-Fast",
101
148
  "--Motion-Duration-Base",
@@ -131,7 +178,7 @@
131
178
  "--glass-",
132
179
  "--card-surface"
133
180
  ],
134
- "namespaceNote": "P049 が「DS の変数を上書きしている」と判定する接頭辞。ここに該当し allowedVariables に無い変数を消費側 CSS が宣言していたら契約違反。shadcn 互換の小文字ブリッジ(--primary / --border 等)は消費側アプリが同名変数を持つことがあるため対象外。",
181
+ "namespaceNote": "P049 が「DS の変数を上書きしている」と判定する接頭辞。ここに該当し、かつ DS 自身の CSS(src/preset.css / src/styles/*.css / src/themes/*.css)に実在し、allowedVariables に無い変数を消費側 CSS が宣言していたら契約違反。接頭辞は一致するが DS に実在しない名前(消費側が DS の命名に寄せて作った独自変数。例: --Surface-Inverse-Hover)は上書きではないので対象外(issue #377)。DS の CSS が読めない環境では接頭辞一致だけで判定する。shadcn 互換の小文字ブリッジ(--primary / --border 等)は消費側アプリが同名変数を持つことがあるため対象外。",
135
182
  "wiredComponents": {
136
183
  "Button": [
137
184
  "--Control-Height-*",
@@ -163,9 +210,12 @@
163
210
  "許可リストに無い変数(--Hover-* / --glass-* / --Z-* などの内部変数)は消費側から上書きしない。必要なら DS 側に issue を立てて公開変数を増やす。",
164
211
  "DS 側で新しくサイズ・角丸を持つコンポーネントを作るときは、固定の h-* / px-* / rounded-* ではなく Control / Field 変数を arbitrary value(h-[var(--Control-Height-Md)])で参照する。",
165
212
  "Control(Button 系)と Field(Input / Textarea / SelectTrigger)はスケールが別。ksk のフィールドは元々ボタンより背が高いため、片方に畳むと実寸が変わる。",
166
- "色は semantic トークン経由でのみ上書きする。Primitive を直接参照するのは Brand ランプ(--Primitive-Brand-*)だけが例外。",
213
+ "primitive を直接上書きしてよいのは Brand(--Primitive-Brand-*)と Neutral(--Primitive-Gray-* / --Primitive-White / --Primitive-Black)の2つのアイデンティティパレットだけ。status 系(Red / Green / Blue / Yellow / Orange)の色調整は semantic 層(--Surface-Caution / --Text-Success / --Caution-Base 等)で行う。semantic を直接上書きするほうが、上書き1箇所で済み、DS 側が primitive の割り当てを変えても壊れない。",
214
+ "neutralPalette(Gray 10段 + White / Black)は Brand と並ぶアイデンティティのパレット層。warm gray など DS 既定の cool gray と違う世界観を持つプロダクトはここを差し替える。ただし Gray は --Text-High-Emphasis(本文色)や --Surface-Secondary を駆動するため、差し替えたら本文×背景のコントラストが WCAG AA(4.5:1)を満たすか消費側で必ず確認すること(light / dark 両方)。DS 側のコントラスト検査は DS 既定パレットしか見ていない。",
215
+ "色は semantic トークン経由でのみ上書きする。Primitive を直接参照するのは Brand ランプ(--Primitive-Brand-*)と Neutral ランプ(--Primitive-Gray-* / White / Black)だけが例外。",
167
216
  "Checkbox / RadioGroup / Switch(20px の size-5 トグル)は意図的にこの契約の対象外。「コントロールの高さ」という概念が当てはまらない固定サイズのため、Control 変数を当てると意味が壊れる。",
168
217
  "Chip は角丸(--Chip-Radius)だけを配線し、高さ・横 padding は配線しない。sm/md/lg の縦 margin(my-*)は「44px タッチターゲット - 本体高さ」ちょうどの手計算値で、height を変数化すると当たり判定の math が壊れる。",
169
- "AppShell / MarketingShell / MobileAppShell はページ本文の padding をシェル自身が持たない(Container の gutter か呼び出し側の contentClassName に委ねている)ため product theme の対象外。AdminShell の <main> だけが --Product-Page-Padding-Y を持つ。"
218
+ "AppShell / MarketingShell / MobileAppShell はページ本文の padding をシェル自身が持たない(Container の gutter か呼び出し側の contentClassName に委ねている)ため product theme の対象外。AdminShell の <main> だけが --Product-Page-Padding-Y を持つ。",
219
+ "--Product-Type-Scale は typo-* の font-size に一律で掛かる乗数(既定 1)。文字倍率を上げたら Control(Button)/ Field(Input 等)の高さ・padding も --Control-Height-* / --Field-Height-* 等で別途上げること。この変数はコントロールの寸法には自動連動しない(文字だけ大きくして枠が追従しないと窮屈になるため、意図的に分離してある)。Web のみが対象で React Native(native/scales.ts)には効かない。"
170
220
  ]
171
221
  }
@@ -286,7 +286,7 @@
286
286
  "id": "P032",
287
287
  "severity": "warning",
288
288
  "category": "token",
289
- "pattern": "\\bborder(-[tblrxy])?\\b(?![^\"']*(border-(\\[|transparent)|:border-))",
289
+ "pattern": "\\bborder(-[tblrxy])?(?![-\\w\\[])(?![^\"']*(border-(\\[|transparent)|:border-))",
290
290
  "excludes": [".stories.", "border-0", "border-2", "border-4", "border-collapse", "border-separate", "border-spacing", "border-solid", "border-dashed", "border-dotted", "border-none"],
291
291
  "message": "色を併記しないボーダー幅クラス(border / border-b 等)は避ける。Tailwind v4 では border-color の既定が currentColor になったため、色を書かないと要素の文字色が透けて出て、消費側が濃色テキストを敷くと枠線が黒ずむ(job board で実際に発生)。preset.css の base layer が --border にフォールバックする保険を張るが、コンポーネントは明示が原則。",
292
292
  "fix": "border-[var(--Border-Low-Emphasis)](区切り線・淡いカード)/ border-[var(--Border-Medium-Emphasis)](入力・強調)を併記する。状態で出し分けるタブ/バッジ/チップは border-transparent を初期色に置き、data-[state=active]:border-[var(--Brand-Primary)] 等のペア状態クラスで色を上書きする。"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "KSK Design System — Semantic Token Hex Cache",
4
- "version": "1.58.0",
4
+ "version": "1.60.0",
5
5
  "description": "semantic / semanticDark トークン(var(--Primitive-*) 参照)を実 hex に解決したサイドカー生成物。hex はデフォルト(Blue)テーマでの解決値であり、Brand 系(meta.themeDependentKeys に列挙)はテーマ差し替え(orange/green/violet 等)で実色が変わる。テーマ別の完全解決値は `ksk-design-system/native` エクスポート(バンドル済み native トークンモジュール)の themes を参照。tokens.json 本体のスキーマは変更せず、AI がこのファイルだけで実色を把握できるようにし、primitive 値の変更による semantic 実色のドリフトを --check で機械検出する。",
6
6
  "generatedBy": "scripts/generate-token-hex-cache.mjs",
7
7
  "theme": "default",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ksk-design-system",
3
- "version": "1.58.0",
3
+ "version": "1.60.0",
4
4
  "type": "module",
5
5
  "description": "KSK Design System — フリーランス向けマルチテーマ対応デザインシステム",
6
6
  "license": "MIT",
@@ -174,7 +174,6 @@
174
174
  "test:a11y": "vitest run --config vitest.a11y.config.ts",
175
175
  "check": "tsc -p tsconfig.app.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit && bash scripts/lint-scratch.sh && bash scripts/check-deps.sh && node scripts/check-published-deps.mjs && node scripts/check-css-setup.mjs && bash scripts/check-drift.sh && node scripts/check-screen-contracts.mjs && node scripts/check-docs-drift.mjs && node scripts/check-agents-docs-sync.mjs && node scripts/check-contrast.mjs && node scripts/check-design-md.mjs && node scripts/check-tailwind-v4.mjs && node scripts/check-prefix-order.mjs && node scripts/check-flex-shrink.mjs && node scripts/check-deprecations.mjs && node scripts/generate-migration-doc.mjs --check && bash scripts/check-responsive.sh && node scripts/generate-component-lookup.mjs --check && node scripts/generate-platform-tokens.mjs --check && node scripts/generate-token-hex-cache.mjs --check && node scripts/generate-source-safelist.mjs --check && tsc -p tsconfig.native.json --noEmit && node scripts/check-native-parity.mjs && node scripts/generate-native-component-lookup.mjs --check",
176
176
  "check:agent": "npm run lint && npm run check && npm test && npm run lint:story-reuse",
177
- "postinstall": "node bin/init.js postinstall",
178
177
  "version": "node scripts/sync-version.mjs",
179
178
  "mcp:build": "cd mcp-server && npm install && npm run build",
180
179
  "mcp:start": "node mcp-server/dist/index.js"
@@ -87,4 +87,19 @@
87
87
  プロダクトによってはボタンを角ばらせつつチップだけピルのまま、といった
88
88
  組み合わせがあり得るため(issue #364 追補)。 */
89
89
  --Chip-Radius: 9999px; /* rounded-full 相当 */
90
+
91
+ /* ─── Product: 画面全体の文字サイズ倍率(issue #371/#372 の逆輸入) ───
92
+ `typo-*` の font-size に一律で掛かる乗数。line-height は unitless
93
+ (行間比率)なので font-size の変化に自動で追従し、letter-spacing は
94
+ em 指定なので同様に font-size に連動する。どちらも別途変数化しない。
95
+
96
+ Web 専用。React Native は CSS カスタムプロパティを解決できないため、
97
+ native 側のトークン(src/tokens/native/scales.ts の typography)は
98
+ この変数と独立に管理する。
99
+
100
+ Control(Button)/ Field(Input 等)の高さ・padding には**意図的に
101
+ 掛けない**。文字だけ拡大して枠が追従しないと窮屈になるため、消費側は
102
+ 文字を拡大するときは --Control-Height-* / --Field-Height-* も
103
+ 別途上げる契約にしてある(stract-ui と同じ判断)。 */
104
+ --Product-Type-Scale: 1;
90
105
  }
@@ -11,47 +11,53 @@
11
11
  3. カラーは別途指定: className="typo-body-sm text-[var(--Text-Low-Emphasis)]"
12
12
  4. レスポンシブ対応可: md:typo-heading-xl
13
13
  5. CVA 内でも typo-* を使用する
14
+
15
+ font-size は calc(<px> * var(--Product-Type-Scale, 1)) 形式で書く
16
+ (既定 1 = 現行と同一px。src/styles/product-theme.css の
17
+ --Product-Type-Scale を参照。プロダクト単位の文字サイズ倍率、issue #371/#372)。
18
+ line-height は unitless(比率)、letter-spacing は em のためどちらも
19
+ font-size の変化に自動連動する。別途スケールしない。
14
20
  ============================================================= */
15
21
 
16
22
  /* ─── Heading(見出し) ─── */
17
23
 
18
24
  @utility typo-heading-3xl {
19
- font-size: 28px;
25
+ font-size: calc(28px * var(--Product-Type-Scale, 1));
20
26
  line-height: 1.5;
21
27
  font-weight: 700;
22
28
  letter-spacing: 0.04em;
23
29
  }
24
30
 
25
31
  @utility typo-heading-2xl {
26
- font-size: 24px;
32
+ font-size: calc(24px * var(--Product-Type-Scale, 1));
27
33
  line-height: 1.5;
28
34
  font-weight: 700;
29
35
  letter-spacing: 0.04em;
30
36
  }
31
37
 
32
38
  @utility typo-heading-xl {
33
- font-size: 21px;
39
+ font-size: calc(21px * var(--Product-Type-Scale, 1));
34
40
  line-height: 1.5;
35
41
  font-weight: 700;
36
42
  letter-spacing: 0.04em;
37
43
  }
38
44
 
39
45
  @utility typo-heading-lg {
40
- font-size: 18px;
46
+ font-size: calc(18px * var(--Product-Type-Scale, 1));
41
47
  line-height: 1.5;
42
48
  font-weight: 700;
43
49
  letter-spacing: 0.04em;
44
50
  }
45
51
 
46
52
  @utility typo-heading-md {
47
- font-size: 16px;
53
+ font-size: calc(16px * var(--Product-Type-Scale, 1));
48
54
  line-height: 1.5;
49
55
  font-weight: 700;
50
56
  letter-spacing: 0.04em;
51
57
  }
52
58
 
53
59
  @utility typo-heading-sm {
54
- font-size: 14px;
60
+ font-size: calc(14px * var(--Product-Type-Scale, 1));
55
61
  line-height: 1.5;
56
62
  font-weight: 700;
57
63
  letter-spacing: 0.04em;
@@ -60,19 +66,19 @@
60
66
  /* ─── Body(本文) ─── */
61
67
 
62
68
  @utility typo-body-lg {
63
- font-size: 16px;
69
+ font-size: calc(16px * var(--Product-Type-Scale, 1));
64
70
  line-height: 1.75;
65
71
  font-weight: 400;
66
72
  }
67
73
 
68
74
  @utility typo-body-md {
69
- font-size: 14px;
75
+ font-size: calc(14px * var(--Product-Type-Scale, 1));
70
76
  line-height: 1.75;
71
77
  font-weight: 400;
72
78
  }
73
79
 
74
80
  @utility typo-body-sm {
75
- font-size: 12px;
81
+ font-size: calc(12px * var(--Product-Type-Scale, 1));
76
82
  line-height: 1.5;
77
83
  font-weight: 400;
78
84
  }
@@ -81,7 +87,7 @@
81
87
  本文の最小は typo-body-sm(12px)。10px を本文に使うとモバイル可読性の
82
88
  下限(iOS HIG ~11pt / WCAG 観点)を割る。 */
83
89
  @utility typo-body-xs {
84
- font-size: 10px;
90
+ font-size: calc(10px * var(--Product-Type-Scale, 1));
85
91
  line-height: 1.5;
86
92
  font-weight: 400;
87
93
  }
@@ -89,27 +95,27 @@
89
95
  /* ─── Label(ボタン・ナビ・タグ) ─── */
90
96
 
91
97
  @utility typo-label-lg {
92
- font-size: 16px;
98
+ font-size: calc(16px * var(--Product-Type-Scale, 1));
93
99
  line-height: 1.5;
94
100
  font-weight: 700;
95
101
  letter-spacing: 0.04em;
96
102
  }
97
103
 
98
104
  @utility typo-label-md {
99
- font-size: 14px;
105
+ font-size: calc(14px * var(--Product-Type-Scale, 1));
100
106
  line-height: 1.5;
101
107
  font-weight: 700;
102
108
  letter-spacing: 0.04em;
103
109
  }
104
110
 
105
111
  @utility typo-label-sm {
106
- font-size: 12px;
112
+ font-size: calc(12px * var(--Product-Type-Scale, 1));
107
113
  line-height: 1.5;
108
114
  font-weight: 500;
109
115
  }
110
116
 
111
117
  @utility typo-label-xs {
112
- font-size: 10px;
118
+ font-size: calc(10px * var(--Product-Type-Scale, 1));
113
119
  line-height: 1.5;
114
120
  font-weight: 500;
115
121
  }
@@ -117,14 +123,14 @@
117
123
  /* ─── Display(ヒーロー・ランディング用) ─── */
118
124
 
119
125
  @utility typo-display-xl {
120
- font-size: 48px;
126
+ font-size: calc(48px * var(--Product-Type-Scale, 1));
121
127
  line-height: 1.25;
122
128
  font-weight: 700;
123
129
  letter-spacing: -0.02em;
124
130
  }
125
131
 
126
132
  @utility typo-display-lg {
127
- font-size: 36px;
133
+ font-size: calc(36px * var(--Product-Type-Scale, 1));
128
134
  line-height: 1.3;
129
135
  font-weight: 700;
130
136
  letter-spacing: -0.01em;
@@ -134,7 +140,7 @@
134
140
  caption(11px) は非必須の注釈・法的表記のみ。本文には使わない(本文下限は body-sm=12px)。 */
135
141
 
136
142
  @utility typo-caption {
137
- font-size: 11px;
143
+ font-size: calc(11px * var(--Product-Type-Scale, 1));
138
144
  line-height: 1.5;
139
145
  font-weight: 400;
140
146
  }
@@ -1,5 +1,5 @@
1
1
  <!--
2
- このファイルは ksk-design-system の postinstall で自動設置されました。
2
+ このファイルは `npx ksk-ds init` で設置されました。
3
3
  最新ルールを取り込むには: npx ksk-ds init --force
4
4
  -->
5
5
 
@@ -1,5 +1,5 @@
1
1
  <!--
2
- このファイルは ksk-design-system の postinstall で自動設置されました。
2
+ このファイルは `npx ksk-ds init` で設置されました。
3
3
  最新ルールを取り込むには: npx ksk-ds init --force
4
4
  -->
5
5