@squadbase/vantage 0.5.3 → 0.6.1-dev.2d25e94

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.
Files changed (134) hide show
  1. package/NOTICE +73 -0
  2. package/README.md +6 -5
  3. package/dist/add-DY4AXANK.js +6 -0
  4. package/dist/add-DY4AXANK.js.map +1 -0
  5. package/dist/{build-756WIEVI.js → build-JB4VOMUI.js} +10 -10
  6. package/dist/{build-756WIEVI.js.map → build-JB4VOMUI.js.map} +1 -1
  7. package/dist/{check-KZ5PHCA6.js → check-ZJYK6RLG.js} +8 -8
  8. package/dist/{check-KZ5PHCA6.js.map → check-ZJYK6RLG.js.map} +1 -1
  9. package/dist/chunk-5ZOP4SQR.js +47 -0
  10. package/dist/chunk-5ZOP4SQR.js.map +1 -0
  11. package/dist/{chunk-EHPJXDJU.js → chunk-6S4BAIGV.js} +4 -2
  12. package/dist/chunk-6S4BAIGV.js.map +1 -0
  13. package/dist/chunk-BCE22JSM.js +510 -0
  14. package/dist/chunk-BCE22JSM.js.map +1 -0
  15. package/dist/{chunk-C4MQRWXS.js → chunk-C2CX6GLX.js} +17 -3
  16. package/dist/chunk-C2CX6GLX.js.map +1 -0
  17. package/dist/{chunk-RA6Z3OX3.js → chunk-EP6JHXSR.js} +61 -9
  18. package/dist/chunk-EP6JHXSR.js.map +1 -0
  19. package/dist/{chunk-BAM33YZ6.js → chunk-JGRMRXXZ.js} +45 -6
  20. package/dist/chunk-JGRMRXXZ.js.map +1 -0
  21. package/dist/{chunk-VXAV2435.js → chunk-KLEIVDUV.js} +30 -7
  22. package/dist/chunk-KLEIVDUV.js.map +1 -0
  23. package/dist/{chunk-JC6MT5UU.js → chunk-MXI3PZFT.js} +3 -3
  24. package/dist/{chunk-JC6MT5UU.js.map → chunk-MXI3PZFT.js.map} +1 -1
  25. package/dist/chunk-PLST5Q7T.js +2221 -0
  26. package/dist/chunk-PLST5Q7T.js.map +1 -0
  27. package/dist/chunk-PVTHHV7X.js +105 -0
  28. package/dist/chunk-PVTHHV7X.js.map +1 -0
  29. package/dist/{chunk-HQDEYCDP.js → chunk-QI3XQKHS.js} +40 -4
  30. package/dist/chunk-QI3XQKHS.js.map +1 -0
  31. package/dist/{chunk-L3TW4FNA.js → chunk-T3FLAGI6.js} +34 -13
  32. package/dist/chunk-T3FLAGI6.js.map +1 -0
  33. package/dist/{chunk-X44ZTKID.js → chunk-U2M6HGKQ.js} +61 -9
  34. package/dist/chunk-U2M6HGKQ.js.map +1 -0
  35. package/dist/chunk-ZO62C6T4.js +91 -0
  36. package/dist/chunk-ZO62C6T4.js.map +1 -0
  37. package/dist/{chunk-D4Y5RWJA.js → chunk-ZVANIULZ.js} +81 -21
  38. package/dist/chunk-ZVANIULZ.js.map +1 -0
  39. package/dist/cli.js +18 -14
  40. package/dist/cli.js.map +1 -1
  41. package/dist/client/index.d.ts +5 -35
  42. package/dist/client/index.js +1 -1
  43. package/dist/components/index.js +3 -2212
  44. package/dist/components/index.js.map +1 -1
  45. package/dist/{dev-EILX2VTF.js → dev-FCEC3UKQ.js} +24 -13
  46. package/dist/dev-FCEC3UKQ.js.map +1 -0
  47. package/dist/doc/index.d.ts +39 -0
  48. package/dist/doc/index.js +38 -0
  49. package/dist/doc/index.js.map +1 -0
  50. package/dist/{docs-GP2ZHIQF.js → docs-EJJ3BECT.js} +4 -4
  51. package/dist/{docs-GP2ZHIQF.js.map → docs-EJJ3BECT.js.map} +1 -1
  52. package/dist/{doctor-FXZLMGC3.js → doctor-WHZXPERA.js} +9 -9
  53. package/dist/doctor-WHZXPERA.js.map +1 -0
  54. package/dist/manifest-DiOjyinq.d.ts +43 -0
  55. package/dist/{preview-WBQPCTTT.js → preview-OGMCNQUO.js} +13 -5
  56. package/dist/preview-OGMCNQUO.js.map +1 -0
  57. package/dist/query/index.js +1 -102
  58. package/dist/query/index.js.map +1 -1
  59. package/dist/router/index.d.ts +10 -0
  60. package/dist/router/index.js +2 -89
  61. package/dist/router/index.js.map +1 -1
  62. package/dist/{routes-WCN4KSZY.js → routes-GTFO5PUF.js} +17 -8
  63. package/dist/routes-GTFO5PUF.js.map +1 -0
  64. package/dist/{search-GLKCJXSI.js → search-6Y422SV6.js} +4 -4
  65. package/dist/{search-GLKCJXSI.js.map → search-6Y422SV6.js.map} +1 -1
  66. package/dist/server/node.d.ts +1 -1
  67. package/dist/server/node.js +6 -1
  68. package/dist/server/node.js.map +1 -1
  69. package/dist/slide/index.d.ts +230 -0
  70. package/dist/slide/index.js +1571 -0
  71. package/dist/slide/index.js.map +1 -0
  72. package/dist/table/index.d.ts +107 -0
  73. package/dist/table/index.js +680 -0
  74. package/dist/table/index.js.map +1 -0
  75. package/dist/types-CXFlMl8S.d.ts +121 -0
  76. package/dist/ui/index.js +3 -41
  77. package/dist/ui/index.js.map +1 -1
  78. package/dist/{upgrade-S64IXL42.js → upgrade-AL4KZZR4.js} +9 -9
  79. package/dist/upgrade-AL4KZZR4.js.map +1 -0
  80. package/dist/vite/index.d.ts +1 -1
  81. package/dist/vite/index.js +5 -5
  82. package/docs/en/agent-skills.md +22 -12
  83. package/docs/en/changelog.md +107 -0
  84. package/docs/en/cli-reference.md +42 -4
  85. package/docs/en/pages-and-metadata.md +17 -0
  86. package/docs/en/routing.md +102 -3
  87. package/docs/en/ui-and-theming.md +102 -0
  88. package/docs/index.json +2 -2
  89. package/docs/ja/agent-skills.md +21 -11
  90. package/docs/ja/changelog.md +105 -0
  91. package/docs/ja/cli-reference.md +41 -4
  92. package/docs/ja/pages-and-metadata.md +17 -0
  93. package/docs/ja/routing.md +98 -3
  94. package/docs/ja/ui-and-theming.md +100 -0
  95. package/package.json +33 -9
  96. package/skills/vantage-app/SKILL.md +4 -0
  97. package/skills/vantage-design/SKILL.md +137 -0
  98. package/skills/vantage-design/references/extract.md +110 -0
  99. package/skills/vantage-design/references/format.md +172 -0
  100. package/skills/vantage-design/references/presets.md +110 -0
  101. package/skills/vantage-design/scripts/contrast.mjs +166 -0
  102. package/skills/vantage-pitfalls/SKILL.md +41 -1
  103. package/skills/vantage-slide/SKILL.md +264 -0
  104. package/skills/vantage-slide/references/morph.md +102 -0
  105. package/skills/vantage-slide/references/steps.md +57 -0
  106. package/skills/vantage-slide/references/tokens.md +87 -0
  107. package/skills/vantage-slide/references/transitions.md +196 -0
  108. package/templates/AGENTS.md +43 -228
  109. package/templates/agents/api.md +49 -0
  110. package/templates/agents/csv.md +32 -0
  111. package/templates/agents/doc.md +73 -0
  112. package/templates/agents/page.md +187 -0
  113. package/templates/agents/slide.md +41 -0
  114. package/templates/agents/xlsx.md +33 -0
  115. package/theme.css +169 -0
  116. package/dist/add-PPCBMWEN.js +0 -6
  117. package/dist/add-PPCBMWEN.js.map +0 -1
  118. package/dist/chunk-BAM33YZ6.js.map +0 -1
  119. package/dist/chunk-C4MQRWXS.js.map +0 -1
  120. package/dist/chunk-D4Y5RWJA.js.map +0 -1
  121. package/dist/chunk-EHPJXDJU.js.map +0 -1
  122. package/dist/chunk-HQDEYCDP.js.map +0 -1
  123. package/dist/chunk-L3TW4FNA.js.map +0 -1
  124. package/dist/chunk-PSEYFYAP.js +0 -276
  125. package/dist/chunk-PSEYFYAP.js.map +0 -1
  126. package/dist/chunk-RA6Z3OX3.js.map +0 -1
  127. package/dist/chunk-VXAV2435.js.map +0 -1
  128. package/dist/chunk-X44ZTKID.js.map +0 -1
  129. package/dist/dev-EILX2VTF.js.map +0 -1
  130. package/dist/doctor-FXZLMGC3.js.map +0 -1
  131. package/dist/preview-WBQPCTTT.js.map +0 -1
  132. package/dist/routes-WCN4KSZY.js.map +0 -1
  133. package/dist/types-De3mo_hv.d.ts +0 -59
  134. package/dist/upgrade-S64IXL42.js.map +0 -1
@@ -0,0 +1,87 @@
1
+ # デザイントークンと書体
2
+
3
+ ## トークンの解決順
4
+
5
+ スライドの色と書体は CSS カスタムプロパティ `--slide-*` から来ます。値は 3 段で解決されます:
6
+
7
+ 1. **`theme.css`(フレームワーク)** —— `--slide-bg: var(--background)` のように、アプリ自身の
8
+ トークンへの**別名**が張ってあります。CSS 変数は遅延解決なので、これだけでデッキは
9
+ アプリのライト / ダークの切り替えに自動で追従します
10
+ 2. **アプリの `styles.css`** —— アプリがトークンを上書きしていれば、デッキもそれに従います
11
+ 3. **`<SlideDeck theme={{ … }}>`** —— そのデッキのルートにインラインで書かれ、上の 2 つに勝ちます
12
+
13
+ | プロパティ | CSS 変数 | 既定 |
14
+ | --- | --- | --- |
15
+ | `bg` | `--slide-bg` | `var(--background)` |
16
+ | `fg` | `--slide-fg` | `var(--foreground)` |
17
+ | `accent` | `--slide-accent` | `var(--primary)` |
18
+ | `muted` | `--slide-muted` | `var(--muted-foreground)` |
19
+ | `fontDisplay` | `--slide-font-display` | 日本語を含むシステムスタック |
20
+ | `fontBody` | `--slide-font-body` | `var(--slide-font-display)` |
21
+ | `sizeHero` | `--slide-size-hero` | `168px` |
22
+ | `sizeBody` | `--slide-size-body` | `36px` |
23
+ | `radius` | `--slide-radius` | `var(--radius)` |
24
+
25
+ ```tsx
26
+ <SlideDeck theme={{ bg: "#0f172a", fg: "#f8fafc", accent: "#fbbf24" }}>
27
+ ```
28
+
29
+ **使うときは変数で参照します** —— `color: "var(--slide-fg)"`、`fontSize: "var(--slide-size-hero)"`。
30
+ `theme` に渡した値を直接書かないこと。変数経由にしておけば、`theme` を 1 行変えるだけでデッキ全体が
31
+ 追従します。
32
+
33
+ **`theme` に無い色はプレーンな const に置く**(`const muted = "#94a3b8"`)。`theme` は
34
+ 「デッキの土台となる 3〜4 色」のためのもので、パレット全部を入れる場所ではありません。
35
+
36
+ ## ダークモード
37
+
38
+ `theme` を渡さないデッキは、アプリのトークンをそのまま使います —— つまり `.dark` /
39
+ `[data-theme="dark"]` が祖先に付いていればダークで描かれます。**明暗の両方を自分で管理する必要は
40
+ ありません。**
41
+
42
+ `theme` に具体的な色(`bg: "#0f172a"`)を書いた瞬間、そのデッキは明暗に追従しなくなります。
43
+ これは意図的です —— 配色を決め打ちしたデッキは、どちらのモードでもその配色であるべきだからです。
44
+ 追従させたいなら `theme` を渡さないこと。
45
+
46
+ ## 書体
47
+
48
+ **既定はシステムスタックで、まずこれを使ってください。** 日本語を含むスタックが最初から入っています。
49
+
50
+ webfont が本当に必要なとき(ブランドフォント、あるいはシステムのカバーが薄い言語):
51
+
52
+ - **`<head>` に 1 回だけ、冪等に読み込む。** スライドコンポーネントの中でスタイルシートを描かないこと。
53
+ 切り替え中は 2 枚のスライドが同時にマウントされ、pptx 書き出しでは全スライドが同時にマウントされます
54
+ —— コンポーネントの中で `<link>` を描くと `@font-face` の集合が**マウント数だけ**登録されます。
55
+ `.slide.tsx` のモジュールのトップレベルから 1 回だけ注入します:
56
+
57
+ ```tsx
58
+ const FONT_HREF = "https://fonts.googleapis.com/css2?family=...&display=swap"
59
+ const FONT_LINK_ID = "vantage-slide-font-june" // デッキごとに違う id にする
60
+ if (typeof document !== "undefined" && !document.getElementById(FONT_LINK_ID)) {
61
+ const link = document.createElement("link")
62
+ link.id = FONT_LINK_ID
63
+ link.rel = "stylesheet"
64
+ link.href = FONT_HREF
65
+ document.head.appendChild(link)
66
+ }
67
+ ```
68
+
69
+ - **実際に使うウェイトだけを列挙する。** 余分な 1 ウェイトが `@font-face` の数を倍にします。
70
+
71
+ ## webfont と pptx 書き出し —— ここが壊れやすい
72
+
73
+ pptx 書き出しは、スライドを SVG の `foreignObject` の中でラスタライズします。この隔離された
74
+ 描画コンテキストでは:
75
+
76
+ - **ローカルにインストールされたフォントはファミリ名で解決されます。** システムスタックなら
77
+ 埋め込みは 1 バイトも要りません。既定のままなら何もしなくてよい、ということです
78
+ - **webfont は data URL としてインライン化されていなければ使えません。** 書き出しは
79
+ `getFontEmbedCSS` で自動的にインライン化しますが、それには 2 つの条件があります:
80
+ - **同一オリジンか CORS が有効**であること(Google Fonts は CORS 有効)
81
+ - **サブセットしてあること。** 日本語の webfont は致命的です —— Noto Sans JP のフルセットは
82
+ 100 を超える `unicode-range` の face を持ち、全部を base64 化しようとして書き出しが
83
+ タイムアウトするか、文字が豆腐になります。デッキの文字が固定なら Google Fonts の URL に
84
+ **`&text=<使う文字>`** を付けて、単一 face の小さなサブセットを要求してください
85
+
86
+ **判断の目安**: 日本語のデッキで pptx を配る予定があるなら、**システムスタックのままにする**のが
87
+ 最も確実です。ブランドフォントが要件なら `&text=` のサブセットにすること。
@@ -0,0 +1,196 @@
1
+ # ページ間トランジション(`SlideTransition`)
2
+
3
+ スライドが切り替わるときの enter / exit アニメーションです。**既定はありません** ——
4
+ 宣言しない限りページはスナップで入れ替わり、そのスナップも十分に上品な既定です。
5
+ 動きが何かを足すときだけ opt-in してください。
6
+
7
+ `prefers-reduced-motion: reduce` は自動で尊重されます。フォールバックを書く必要はありません。
8
+
9
+ (2 枚にまたがる同一要素を動かす「Magic Move」は `morph.md`。この契約の上に載っています。)
10
+
11
+ ## 契約
12
+
13
+ トランジションは `<Slide transition={...}>` に付けます。**入ってくる側が勝ちます**:
14
+ A → B の移動では B の `transition` が使われ、その `exit` が A で、`enter` が B で再生されます。
15
+ B → A と戻るときは A の `transition` が使われるので、**両方向で動かしたいなら両方のスライドに
16
+ 同じオブジェクトを渡します**。
17
+
18
+ ```tsx
19
+ import { Slide, SlideDeck, type SlideTransition } from "@squadbase/vantage/slide"
20
+
21
+ const RISE: SlideTransition = { /* … */ }
22
+
23
+ <SlideDeck>
24
+ <Slide>…</Slide>
25
+ <Slide transition={RISE}>…</Slide>
26
+ </SlideDeck>
27
+ ```
28
+
29
+ ```ts
30
+ type TransitionPhase = {
31
+ keyframes: Keyframe[] | PropertyIndexedKeyframes // WAAPI のキーフレーム
32
+ duration?: number // ms(省略時は上位の duration)
33
+ easing?: string // CSS easing
34
+ delay?: number // ms —— exit と enter を重ねるのに使う
35
+ }
36
+ type SlideTransition = {
37
+ duration: number // 上位のフォールバック
38
+ easing?: string
39
+ enter?: TransitionPhase // 入ってくる側で再生
40
+ exit?: TransitionPhase // 出ていく側で再生
41
+ morph?: boolean | MorphTransition // 共有要素のモーフ → morph.md
42
+ }
43
+ ```
44
+
45
+ ラッパーには `--slide-dir`(前進 `1` / 後退 `-1`)と `data-slide-dir`(`"forward"` / `"backward"`)が
46
+ 出るので、JS のコールバック無しに 1 つのキーフレームで方向を反転できます。
47
+
48
+ ## 設計原則(ここは崩さない)
49
+
50
+ 「PowerPoint で作りました」と最も大きく告げるのは、1 つのデッキに 6 種類のトランジションがあることです。
51
+ 節度がリズムを作ります。
52
+
53
+ - **DNA を 1 つ選び、デッキ全体で保つ。** 同じ duration 帯、同じ easing の組、同じ out → in のずらし方。
54
+ 変えてよいのは**どのプロパティに小さな一押しを乗せるか**だけ —— Y、X、opacity、scale、blur
55
+ - **duration: 140–280ms。** exit 140–180ms、enter 200–280ms、enter を 80ms ほど遅らせて重ねる。
56
+ 350ms を超えると動画編集の領域で、本当の状態変化のために取っておく
57
+ - **振れ幅の上限: 12px か 3% スケール。** 6px の Y 上昇は「次の考え」に読める。1920px の translateX は
58
+ 「別の書類」に読める。上質なツールは、気づく分だけしか動かさない
59
+ - **opacity は常に混ぜる。** transform だけのトランジションは硬く、opacity だけのものは最も安全な既定
60
+ - **easing: exit は ease-in、enter は ease-out。** 出るとき `cubic-bezier(0.4, 0, 1, 1)`、
61
+ 入るとき `cubic-bezier(0, 0, 0.2, 1)`。`linear` は使わない(スライドショーに見える)。
62
+ 対称な ease-in-out は状態が繋がるモーフのために取っておく
63
+
64
+ ## 上品な family —— 6 つ、1 つの DNA
65
+
66
+ ここから選んでください。1 つをデッキの標準にし、必要ならヒーロー / 表紙用に 2 つ目、
67
+ 本当の章の切れ目に 3 つ目を取っておきます。
68
+
69
+ ```tsx
70
+ const EASE_OUT = "cubic-bezier(0, 0, 0.2, 1)"
71
+ const EASE_IN = "cubic-bezier(0.4, 0, 1, 1)"
72
+
73
+ // RISE —— 標準の静けさ。Y に 6px。デッキの既定にする
74
+ const RISE: SlideTransition = {
75
+ duration: 200,
76
+ exit: {
77
+ duration: 140, easing: EASE_IN,
78
+ keyframes: [
79
+ { opacity: 1, transform: "translateY(0)" },
80
+ { opacity: 0, transform: "translateY(-4px)" },
81
+ ],
82
+ },
83
+ enter: {
84
+ duration: 200, delay: 80, easing: EASE_OUT,
85
+ keyframes: [
86
+ { opacity: 0, transform: "translateY(6px)" },
87
+ { opacity: 1, transform: "translateY(0)" },
88
+ ],
89
+ },
90
+ }
91
+
92
+ // DISSOLVE —— opacity だけ。最も静か
93
+ const DISSOLVE: SlideTransition = {
94
+ duration: 240,
95
+ exit: { duration: 200, easing: EASE_IN, keyframes: [{ opacity: 1 }, { opacity: 0 }] },
96
+ enter: { duration: 240, delay: 40, easing: EASE_OUT, keyframes: [{ opacity: 0 }, { opacity: 1 }] },
97
+ }
98
+
99
+ // SETTLE —— 表紙向き。上昇 + enter だけにわずかな blur
100
+ const SETTLE: SlideTransition = {
101
+ duration: 280,
102
+ exit: {
103
+ duration: 160, easing: EASE_IN,
104
+ keyframes: [
105
+ { opacity: 1, transform: "translateY(0)" },
106
+ { opacity: 0, transform: "translateY(-6px)" },
107
+ ],
108
+ },
109
+ enter: {
110
+ duration: 280, delay: 100, easing: EASE_OUT,
111
+ keyframes: [
112
+ { opacity: 0, transform: "translateY(12px)", filter: "blur(4px)" },
113
+ { opacity: 1, transform: "translateY(0)", filter: "blur(0)" },
114
+ ],
115
+ },
116
+ }
117
+
118
+ // BLOOM —— scale 0.97 → 1、移動なし。その場で像を結ぶ
119
+ const BLOOM: SlideTransition = {
120
+ duration: 240,
121
+ exit: {
122
+ duration: 160, easing: EASE_IN,
123
+ keyframes: [
124
+ { opacity: 1, transform: "scale(1)" },
125
+ { opacity: 0, transform: "scale(1.01)" },
126
+ ],
127
+ },
128
+ enter: {
129
+ duration: 240, delay: 80, easing: EASE_OUT,
130
+ keyframes: [
131
+ { opacity: 0, transform: "scale(0.97)" },
132
+ { opacity: 1, transform: "scale(1)" },
133
+ ],
134
+ },
135
+ }
136
+
137
+ // FALL —— RISE の鏡像。上から降りてくる
138
+ const FALL: SlideTransition = {
139
+ duration: 200,
140
+ exit: {
141
+ duration: 140, easing: EASE_IN,
142
+ keyframes: [
143
+ { opacity: 1, transform: "translateY(0)" },
144
+ { opacity: 0, transform: "translateY(4px)" },
145
+ ],
146
+ },
147
+ enter: {
148
+ duration: 200, delay: 80, easing: EASE_OUT,
149
+ keyframes: [
150
+ { opacity: 0, transform: "translateY(-6px)" },
151
+ { opacity: 1, transform: "translateY(0)" },
152
+ ],
153
+ },
154
+ }
155
+
156
+ // BREATH —— 章の切れ目。完全に消してから 120ms 置いて入る
157
+ // 本当の区切りにだけ、デッキ 1 本で 1〜2 回まで
158
+ const BREATH: SlideTransition = {
159
+ duration: 460,
160
+ exit: { duration: 180, easing: EASE_IN, keyframes: [{ opacity: 1 }, { opacity: 0 }] },
161
+ enter: {
162
+ duration: 240, delay: 300, easing: EASE_OUT,
163
+ keyframes: [
164
+ { opacity: 0, transform: "translateY(8px)" },
165
+ { opacity: 1, transform: "translateY(0)" },
166
+ ],
167
+ },
168
+ }
169
+ ```
170
+
171
+ 6 つは同じ DNA を共有していて、小さな一押しをどのプロパティに乗せるかだけが違います。
172
+ 読み手には変化に見え、それでいて 1 つの手つきに読めます。
173
+
174
+ ## 方向を意識したキーフレーム(控えめに)
175
+
176
+ 上品なツールの多くは後退時に鏡像化しません。本当に必要なとき(横に流れるスライドを逆再生したい等)
177
+ だけ `--slide-dir` を `calc()` の中で使います:
178
+
179
+ ```tsx
180
+ { transform: "translateX(calc(var(--slide-dir, 1) * 8px))" },
181
+ { transform: "translateX(0)" },
182
+ ```
183
+
184
+ 毎回これに手が伸びるなら、たぶん設計しすぎです。前進 = 後退の方が洗練された既定。
185
+
186
+ ## アンチパターン
187
+
188
+ - ❌ 6 ページに 6 種類のトランジション —— 最も大きな「PowerPoint 感」の合図
189
+ - ❌ `translateX(100%)` の横スライド —— iOS のモーダルや PowerPoint の Push であって、スライドの切り替えではない
190
+ - ❌ 強いスケールのポップ(`0.85 → 1`)+ blur —— ライトボックスの語彙で、何かに「ズームイン」する意味になる
191
+ - ❌ `clip-path: inset(…)` のワイプ —— After Effects の語彙。芝居がかる
192
+ - ❌ 両方のレイヤーに同時に blur —— 視覚的な濁り。目が焦点を結べない
193
+ - ❌ 通常の切り替えで 350ms を超える duration —— 間延びする
194
+ - ❌ 12px / 3% を超える移動 —— 連続ではなく断絶に読める
195
+ - ❌ `linear` easing —— プロダクトではなくスライドショーに見える
196
+ - ❌ とりあえずトランジションを宣言する。**理由が無いなら書かない。** スナップで十分
@@ -1,17 +1,18 @@
1
1
  # AGENTS.md — Vantage アプリの地図
2
2
 
3
3
  このファイルは、**Vantage(`@squadbase/vantage`)アプリを作るコーディングエージェント**が
4
- 最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)が守るべき規約・不変条件・
5
- 最小レシピを 1 ページに集約しています。ここに書かれた規約だけで、最小のダッシュボードアプリを
6
- 正しく組めます。
7
-
8
- > このファイルは **Vantage フレームワークが所有する正本**のコピーです。手で書き換えても、
9
- > `vantage upgrade` を実行するとフレームワーク同梱の正本から再同期されて上書きされます。
10
- > アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
4
+ 最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)が守るべき規約・不変条件を
5
+ 1 ページに集約し、種類ごとの詳しいレシピは `agents/` 配下の知識ファイルに分けています。
6
+
7
+ > このファイルと `agents/*.md` は、いずれも **Vantage フレームワークが所有する正本**の
8
+ > コピーです。手で書き換えても、`vantage upgrade` を実行するとフレームワーク同梱の正本から
9
+ > 再同期されて上書きされます。アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
10
+ > 例外はプロジェクトルートの `DESIGN.md` で、これは**ユーザーが所有**します(`vantage upgrade` も
11
+ > `vantage add agents` も触らない。→「デザインの意図(DESIGN.md)」)。
11
12
  >
12
13
  > 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています。このファイルは
13
- > 「地図」、Skill は「手順書」という役割分担です。**配置済みの Skill を先に探す**手順は
14
- > 末尾の「さらに詳しく(同梱 Skill)」にあります。
14
+ > 「地図」、`agents/*.md` は「種類ごとの静的リファレンス」、Skill は「手順書」という役割分担です。
15
+ > **配置済みの Skill を先に探す**手順は末尾の「さらに詳しく(同梱 Skill)」にあります。
15
16
 
16
17
  ## Vantage とは
17
18
 
@@ -39,8 +40,8 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
39
40
  - **色はセマンティックトークンで書く。** `text-foreground`・`text-muted-foreground`・`bg-card`・
40
41
  `border-border`。トークンがライト / ダークの両方を持つので `dark:` を並べる必要が無い。
41
42
  素のパレット + `dark:`(`text-slate-900 dark:text-slate-100`)を混ぜると、その 1 か所だけ
42
- 手動で 2 モードを管理することになる(→「ダークモードは opt-in」)。`--muted`・`--accent`・
43
- `--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
43
+ 手動で 2 モードを管理することになる(→ `agents/page.md` の「ダークモードは opt-in」)。
44
+ `--muted`・`--accent`・`--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
44
45
  - **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
45
46
  コマンド、または `vantage upgrade` で再生成される。
46
47
  - **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
@@ -93,6 +94,9 @@ root/
93
94
  | `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
94
95
  | `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
95
96
  | `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
97
+ | `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
98
+ | `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`(スライド専用。1920×1080 の固定キャンバス) |
99
+ | `@squadbase/vantage/doc` | `Document`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
96
100
  | `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
97
101
  | `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
98
102
 
@@ -101,207 +105,37 @@ root/
101
105
  `server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
102
106
  `mode` はここから自動で決まり、手で書く設定は無い。**SPA から始めて fullstack へ「昇格」する
103
107
  必要はない** ― テンプレートから始めた場合は最初から `server/` があり、その時点で fullstack。
104
- 下の 2 節は段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
105
-
106
- - `server/` が**ある** → 「API を持つ(fullstack モード)」を見る。データは自前の `/api/*` から
107
- `useApiQuery` で取る。
108
- - `server/` が**無い** → 「最小アプリ(2 ファイル)」の構成。外部 API を直接叩くなら `useQuery`。
109
- 自前の API が要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
110
-
111
- ## 最小アプリ(2 ファイル)
112
-
113
- `package.json` と `index.tsx` の 2 ファイルだけで動く。
114
-
115
- `package.json`:
116
-
117
- ```json
118
- {
119
- "name": "my-dashboard",
120
- "private": true,
121
- "type": "module",
122
- "scripts": {
123
- "dev": "vantage dev",
124
- "build": "vantage build",
125
- "preview": "vantage preview",
126
- "check": "vantage check",
127
- "routes": "vantage routes"
128
- },
129
- "dependencies": {
130
- "@squadbase/vantage": "^0.5.0",
131
- "react": "^19.2.7",
132
- "react-dom": "^19.2.7"
133
- }
134
- }
135
- ```
108
+ 下の 2 つは段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
136
109
 
137
- `index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
110
+ - `server/` が**ある** → データは自前の `/api/*` から `useApiQuery` で取る(→ `agents/api.md`)。
111
+ - `server/` が**無い** → 外部 API を直接叩くなら `useQuery`(→ `agents/page.md`)。自前の API が
112
+ 要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
138
113
 
139
- ```tsx
140
- export default function Dashboard() {
141
- return (
142
- <main className="p-6">
143
- <h1 className="text-2xl font-semibold">Dashboard</h1>
144
- </main>
145
- )
146
- }
147
- ```
114
+ ## デザインの意図(DESIGN.md)
148
115
 
149
- ```bash
150
- pnpm install
151
- pnpm dev # → http://localhost:5173(HMR)
152
- pnpm check # 静的診断(エラーがあれば exit 1)
153
- pnpm routes # ページ/API の URL マップ
154
- ```
116
+ プロジェクトルートに `DESIGN.md` があれば、**UI(ページ・スライド・コンポーネント)を書く前に
117
+ 読む。** 色・チャート・書体・レイアウトの使い分けがトークン名で書いてあるので、それに従って
118
+ トークンで書く(値は `styles.css` にあり、`DESIGN.md` には色コードを書かない)。
155
119
 
156
- `server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
157
- `src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
158
- どちらか一方に寄せること(→「ディレクトリ構造」)。
120
+ - **会話中の指示は `DESIGN.md` より優先する。** 同じ指示が繰り返されたら、`DESIGN.md` への
121
+ 反映をユーザーに提案する。
122
+ - **「色を変えて」「ブランドに合わせて」などデザイン全体の変更は、ファイルごとに直さない。**
123
+ Skill `vantage-design` の手順で `DESIGN.md` と `styles.css` をまとめて更新する。
124
+ - `DESIGN.md` はユーザーの持ち物。`vantage upgrade` は書き換えない。
159
125
 
160
- ## ルーティング規約(ファイル名 → ルート)
126
+ ## 何を書くか → どのファイルを読むか
161
127
 
162
- ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
163
- URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
164
- `MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
128
+ **書こうとしている種類のファイルだけを開けばよい。** まず該当する 1 ファイルを readFiles で
129
+ 読んでから書き始める。
165
130
 
166
- | ファイル | ルート |
131
+ | 書こうとしているもの | 読むファイル |
167
132
  | --- | --- |
168
- | `index.tsx` | `/` |
169
- | `monthly-analysis.tsx` | `/monthly-analysis` |
170
- | `sales/index.tsx` | `/sales` |
171
- | `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
172
- | `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト(`Outlet` で子を描く) |
173
- | `_404.tsx` | Not Found ページ |
174
- | `_error.tsx` | ルートが throw したときのエラーページ |
175
-
176
- `components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
177
- (ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→「ディレクトリ構造」)。
178
-
179
- ```tsx
180
- import { definePage } from "@squadbase/vantage"
181
-
182
- // title は document.title、navLabel は useRoutes() で組むナビの表示名(既定は title)
183
- export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
184
-
185
- export default function MonthlyAnalysis() {
186
- return <main className="p-6">…</main>
187
- }
188
- ```
189
-
190
- ### ナビはルート一覧から組む
191
-
192
- `useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
193
- `useCurrentRoute()` は現在のルート(404 なら `undefined`)。
194
-
195
- ```tsx
196
- import { Link, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
197
-
198
- // 動的ルートは URL が定まらないので外す。label は navLabel → title → path の順
199
- const routes = useRoutes().filter((r) => !r.dynamic)
200
- const current = useCurrentRoute()
201
-
202
- routes.map((r) => (
203
- <Link key={r.path} to={r.to} activeClassName="font-semibold">
204
- {r.label}
205
- </Link>
206
- ))
207
- ```
208
-
209
- `RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
210
- `title`・`description`・`navLabel`。
211
-
212
- ### フィルタ状態は URL に置く
213
-
214
- リロードで消えず、URL をそのまま共有できる。`useState` と同じ形。
215
-
216
- ```tsx
217
- import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
218
-
219
- const [region, setRegion] = useSearchParam("region", "all") // 常に string
220
- const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON になる値
221
- ```
222
-
223
- デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
224
- (`{ replace: false }` で push)。
225
-
226
- ### 動的パラメータの「3 つの綴り」を同期させる
227
-
228
- 崩すと静かにマッチしなくなる不変条件:
229
-
230
- - ファイル名 `sales/[customerId].tsx`
231
- - 表示ルート `/sales/:customerId`
232
- - リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
233
-
234
- ```tsx
235
- import { Link, useParams } from "@squadbase/vantage/router"
236
-
237
- <Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
238
-
239
- // 詳細ページ側で取り出す
240
- const { customerId } = useParams()
241
- ```
242
-
243
- ## データ取得(server を持たない fetch)
244
-
245
- 外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
246
-
247
- ```tsx
248
- import { useQuery } from "@squadbase/vantage/query"
249
- import { Loading, ErrorState } from "@squadbase/vantage/ui"
250
-
251
- const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
252
- if (q.isPending) return <Loading />
253
- if (q.isError) return <ErrorState message={(q.error as Error).message} />
254
- ```
255
-
256
- `QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
257
- refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
258
- 上書きする。
259
- 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→「API を持つ(fullstack
260
- モード)」)。
261
-
262
- ## API を持つ(fullstack モード)
263
-
264
- **`server/` ディレクトリがあれば fullstack モード**(無いアプリに足せば、その時点で切り替わる)。
265
- `server/api/**` の各ファイルが API ルートになる。
266
-
267
- `server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
268
-
269
- ```ts
270
- import type { ApiContext } from "@squadbase/vantage/server"
271
- import { HttpError } from "@squadbase/vantage/server"
272
-
273
- export async function GET({ params, env }: ApiContext) {
274
- if (!env.API_KEY) throw new HttpError(500, "API_KEY missing")
275
- return Response.json({ ok: true })
276
- }
277
- ```
278
-
279
- - API モジュールは**大文字の HTTP メソッド**(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
280
- エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
281
- - 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
282
- `params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
283
- - **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
284
- ログに記録され汎用の 500 に丸められる。
285
- - **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
286
-
287
- クライアント側からは `useApiQuery` で `/api/*` を叩く。ベース URL の解決・JSON パース・
288
- 非 2xx の `ApiError` 化・クエリキー(`["api", url]`)が入っている:
289
-
290
- ```tsx
291
- import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
292
-
293
- const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
294
- if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
295
-
296
- // クエリ文字列は search で。undefined の項目は落ちる
297
- const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
298
-
299
- // 書き込み。変数がそのまま JSON ボディになる
300
- const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
301
- ```
302
-
303
- `ApiError` は `message`・`status`・`body`・`requestId` を持つ。hook が使えない場所では
304
- `apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。
133
+ | ダッシュボード / 通常ページ(`.tsx`) | `agents/page.md` |
134
+ | API(`server/`) | `agents/api.md` |
135
+ | スライド(`.slide.tsx`) | `agents/slide.md` |
136
+ | ドキュメント(`.doc.tsx`) | `agents/doc.md` |
137
+ | `.xlsx` ページ | `agents/xlsx.md` |
138
+ | `.csv` ページ | `agents/csv.md` |
305
139
 
306
140
  ## 環境変数
307
141
 
@@ -309,30 +143,6 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
309
143
  のみ。** `VITE_` 系の公開 API は無い。
310
144
  - サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
311
145
 
312
- ## 組み込み UI / コンポーネントの入口
313
-
314
- - **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
315
- `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
316
- - **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
317
- `DataTablePreset`・`EChart` ほか)。
318
- - **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
319
- リファレンスはパッケージに同梱されていて、オフラインでも読める(→「ドキュメントを引く」)。
320
- **名前が分からないとき**はやりたいことで `vantage search` する(→ 同節)。
321
- - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
322
- ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
323
- `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
324
- `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
325
-
326
- ### ダークモードは opt-in
327
-
328
- ダークのトークンが効くのは、祖先に `.dark` か `[data-theme="dark"]` が付いているときだけ。
329
- **OS / ブラウザの設定(`prefers-color-scheme`)は見ない。** `dark:` ユーティリティも同じ
330
- スイッチに繋いであるので(`theme.css` の `@custom-variant dark`)、自分でクラスを付けない限り
331
- アプリは常にライトで描かれる ― `dark:` を書いてもそこだけダークになることはない。
332
-
333
- 明暗を切り替えたいときは、レイアウトのルート要素などに `.dark` / `data-theme="dark"` を
334
- 自分で付ける。
335
-
336
146
  ## ドキュメントを引く(`vantage docs` / `vantage search`)
337
147
 
338
148
  ```bash
@@ -378,6 +188,8 @@ body・レスポンスの status と形)まで出る。`vantage check --json` /
378
188
 
379
189
  - **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
380
190
  - **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
191
+ - **`vantage-slide`** — `.slide.tsx` でスライドデッキを書く(キャンバスの予算・段階表示・pptx 書き出し)
192
+ - **`vantage-design`** — `DESIGN.md` と `styles.css` をまとめて作る / 直す(配色・書体・ブランド合わせ)
381
193
  - **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
382
194
  - **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
383
195
 
@@ -398,3 +210,6 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
398
210
  `add skill` はプロジェクト内を走査してから動くので、**再実行しても二重に配置されない**。
399
211
  配置済みのコピーを新しいバージョンに更新したいときだけ `--force` を付ける(見つかった場所を
400
212
  そのまま書き換える)。
213
+
214
+ **役割分担:** Skill = 手順書(踏み込んだワークフロー) / `agents/*.md` = 種類ごとの静的
215
+ リファレンス / `AGENTS.md`(このファイル) = 地図。
@@ -0,0 +1,49 @@
1
+ # API(server/)
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`server/api/**`
4
+ に API ルートを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## API を持つ(fullstack モード)
8
+
9
+ **`server/` ディレクトリがあれば fullstack モード**(無いアプリに足せば、その時点で切り替わる)。
10
+ `server/api/**` の各ファイルが API ルートになる。
11
+
12
+ `server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
13
+
14
+ ```ts
15
+ import type { ApiContext } from "@squadbase/vantage/server"
16
+ import { HttpError } from "@squadbase/vantage/server"
17
+
18
+ export async function GET({ params, env }: ApiContext) {
19
+ if (!env.API_KEY) throw new HttpError(500, "API_KEY missing")
20
+ return Response.json({ ok: true })
21
+ }
22
+ ```
23
+
24
+ - API モジュールは**大文字の HTTP メソッド**(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
25
+ エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
26
+ - 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
27
+ `params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
28
+ - **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
29
+ ログに記録され汎用の 500 に丸められる。
30
+ - **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
31
+
32
+ クライアント側からは `useApiQuery` で `/api/*` を叩く。ベース URL の解決・JSON パース・
33
+ 非 2xx の `ApiError` 化・クエリキー(`["api", url]`)が入っている:
34
+
35
+ ```tsx
36
+ import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
37
+
38
+ const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
39
+ if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
40
+
41
+ // クエリ文字列は search で。undefined の項目は落ちる
42
+ const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
43
+
44
+ // 書き込み。変数がそのまま JSON ボディになる
45
+ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
46
+ ```
47
+
48
+ `ApiError` は `message`・`status`・`body`・`requestId` を持つ。hook が使えない場所では
49
+ `apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。