@squadbase/vantage 0.0.1 → 0.1.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.
Files changed (187) hide show
  1. package/README.md +24 -4
  2. package/dist/{chunk-ZGDU5YLH.js → chunk-5L2MH2NG.js} +16 -7
  3. package/dist/chunk-5L2MH2NG.js.map +1 -0
  4. package/dist/chunk-7IPAXPPY.js +51 -0
  5. package/dist/chunk-7IPAXPPY.js.map +1 -0
  6. package/dist/chunk-A2UUGASH.js +12 -0
  7. package/dist/chunk-A2UUGASH.js.map +1 -0
  8. package/dist/chunk-DTDVSFRY.js +29 -0
  9. package/dist/chunk-DTDVSFRY.js.map +1 -0
  10. package/dist/chunk-WQZYXXQW.js +2249 -0
  11. package/dist/chunk-WQZYXXQW.js.map +1 -0
  12. package/dist/{chunk-73J5ZD4C.js → chunk-YLAB6UQS.js} +17 -3
  13. package/dist/chunk-YLAB6UQS.js.map +1 -0
  14. package/dist/cli.js +607 -36
  15. package/dist/cli.js.map +1 -1
  16. package/dist/client/index.d.ts +2 -1
  17. package/dist/client/index.js +3 -1
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/components/index.d.ts +575 -0
  20. package/dist/components/index.js +2097 -0
  21. package/dist/components/index.js.map +1 -0
  22. package/dist/{define-page-B6y9TOfZ.d.ts → define-page-BfhrK99G.d.ts} +5 -0
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/markdown/index.d.ts +9 -0
  27. package/dist/markdown/index.js +26 -0
  28. package/dist/markdown/index.js.map +1 -0
  29. package/dist/query/index.d.ts +95 -2
  30. package/dist/query/index.js +102 -1
  31. package/dist/query/index.js.map +1 -1
  32. package/dist/router/index.d.ts +86 -1
  33. package/dist/router/index.js +71 -7
  34. package/dist/router/index.js.map +1 -1
  35. package/dist/server/node.js +6 -1
  36. package/dist/server/node.js.map +1 -1
  37. package/dist/ui/index.d.ts +316 -200
  38. package/dist/ui/index.js +566 -511
  39. package/dist/ui/index.js.map +1 -1
  40. package/dist/vite/index.js +2 -2
  41. package/docs/en/agent-skills.md +89 -0
  42. package/docs/en/api-and-server.md +84 -0
  43. package/docs/en/build-and-deploy.md +280 -0
  44. package/docs/en/cli-reference.md +311 -0
  45. package/docs/en/components.md +235 -0
  46. package/docs/en/data-fetching.md +142 -0
  47. package/docs/en/environment-variables.md +69 -0
  48. package/docs/en/getting-started.md +95 -0
  49. package/docs/en/index.md +65 -0
  50. package/docs/en/markdown/markdown-renderer.md +44 -0
  51. package/docs/en/pages-and-metadata.md +59 -0
  52. package/docs/en/parts/app-shell.md +54 -0
  53. package/docs/en/parts/dashboard-card.md +64 -0
  54. package/docs/en/parts/data-table.md +86 -0
  55. package/docs/en/parts/date-range-picker.md +41 -0
  56. package/docs/en/parts/echart.md +53 -0
  57. package/docs/en/parts/filter-bar.md +66 -0
  58. package/docs/en/parts/funnel-steps.md +33 -0
  59. package/docs/en/parts/metric-value.md +23 -0
  60. package/docs/en/parts/multi-select.md +27 -0
  61. package/docs/en/parts/page-shell.md +45 -0
  62. package/docs/en/parts/placeholder.md +38 -0
  63. package/docs/en/parts/searchable-select.md +47 -0
  64. package/docs/en/parts/section-header.md +20 -0
  65. package/docs/en/parts/segmented-control.md +25 -0
  66. package/docs/en/parts/sparkline.md +47 -0
  67. package/docs/en/parts/status-badge.md +44 -0
  68. package/docs/en/parts/trend-indicator.md +26 -0
  69. package/docs/en/routing.md +177 -0
  70. package/docs/en/ui/accordion.md +36 -0
  71. package/docs/en/ui/alert.md +31 -0
  72. package/docs/en/ui/badge.md +23 -0
  73. package/docs/en/ui/breadcrumb.md +38 -0
  74. package/docs/en/ui/button.md +39 -0
  75. package/docs/en/ui/calendar.md +28 -0
  76. package/docs/en/ui/card.md +35 -0
  77. package/docs/en/ui/checkbox.md +34 -0
  78. package/docs/en/ui/cn.md +34 -0
  79. package/docs/en/ui/collapsible.md +21 -0
  80. package/docs/en/ui/command.md +37 -0
  81. package/docs/en/ui/dialog.md +52 -0
  82. package/docs/en/ui/dropdown-menu.md +50 -0
  83. package/docs/en/ui/empty.md +22 -0
  84. package/docs/en/ui/error-state.md +50 -0
  85. package/docs/en/ui/input-group.md +30 -0
  86. package/docs/en/ui/input.md +28 -0
  87. package/docs/en/ui/label.md +19 -0
  88. package/docs/en/ui/loading.md +19 -0
  89. package/docs/en/ui/popover.md +34 -0
  90. package/docs/en/ui/progress.md +28 -0
  91. package/docs/en/ui/scroll-area.md +23 -0
  92. package/docs/en/ui/select.md +63 -0
  93. package/docs/en/ui/separator.md +15 -0
  94. package/docs/en/ui/sheet.md +28 -0
  95. package/docs/en/ui/sidebar.md +92 -0
  96. package/docs/en/ui/skeleton.md +16 -0
  97. package/docs/en/ui/slider.md +21 -0
  98. package/docs/en/ui/spinner.md +14 -0
  99. package/docs/en/ui/switch.md +19 -0
  100. package/docs/en/ui/table.md +24 -0
  101. package/docs/en/ui/tabs.md +23 -0
  102. package/docs/en/ui/textarea.md +15 -0
  103. package/docs/en/ui/toggle-group.md +26 -0
  104. package/docs/en/ui/toggle.md +19 -0
  105. package/docs/en/ui/tooltip.md +22 -0
  106. package/docs/en/ui/use-is-mobile.md +18 -0
  107. package/docs/en/ui-and-theming.md +95 -0
  108. package/docs/index.json +1346 -0
  109. package/docs/ja/agent-skills.md +87 -0
  110. package/docs/ja/api-and-server.md +84 -0
  111. package/docs/ja/build-and-deploy.md +279 -0
  112. package/docs/ja/cli-reference.md +305 -0
  113. package/docs/ja/components.md +231 -0
  114. package/docs/ja/data-fetching.md +142 -0
  115. package/docs/ja/environment-variables.md +68 -0
  116. package/docs/ja/getting-started.md +95 -0
  117. package/docs/ja/index.md +65 -0
  118. package/docs/ja/markdown/markdown-renderer.md +43 -0
  119. package/docs/ja/pages-and-metadata.md +59 -0
  120. package/docs/ja/parts/app-shell.md +54 -0
  121. package/docs/ja/parts/dashboard-card.md +64 -0
  122. package/docs/ja/parts/data-table.md +85 -0
  123. package/docs/ja/parts/date-range-picker.md +41 -0
  124. package/docs/ja/parts/echart.md +52 -0
  125. package/docs/ja/parts/filter-bar.md +64 -0
  126. package/docs/ja/parts/funnel-steps.md +33 -0
  127. package/docs/ja/parts/metric-value.md +25 -0
  128. package/docs/ja/parts/multi-select.md +27 -0
  129. package/docs/ja/parts/page-shell.md +45 -0
  130. package/docs/ja/parts/placeholder.md +37 -0
  131. package/docs/ja/parts/searchable-select.md +47 -0
  132. package/docs/ja/parts/section-header.md +20 -0
  133. package/docs/ja/parts/segmented-control.md +26 -0
  134. package/docs/ja/parts/sparkline.md +47 -0
  135. package/docs/ja/parts/status-badge.md +44 -0
  136. package/docs/ja/parts/trend-indicator.md +26 -0
  137. package/docs/ja/routing.md +175 -0
  138. package/docs/ja/ui/accordion.md +37 -0
  139. package/docs/ja/ui/alert.md +31 -0
  140. package/docs/ja/ui/badge.md +23 -0
  141. package/docs/ja/ui/breadcrumb.md +38 -0
  142. package/docs/ja/ui/button.md +39 -0
  143. package/docs/ja/ui/calendar.md +28 -0
  144. package/docs/ja/ui/card.md +35 -0
  145. package/docs/ja/ui/checkbox.md +34 -0
  146. package/docs/ja/ui/cn.md +34 -0
  147. package/docs/ja/ui/collapsible.md +21 -0
  148. package/docs/ja/ui/command.md +36 -0
  149. package/docs/ja/ui/dialog.md +51 -0
  150. package/docs/ja/ui/dropdown-menu.md +50 -0
  151. package/docs/ja/ui/empty.md +22 -0
  152. package/docs/ja/ui/error-state.md +50 -0
  153. package/docs/ja/ui/input-group.md +30 -0
  154. package/docs/ja/ui/input.md +28 -0
  155. package/docs/ja/ui/label.md +19 -0
  156. package/docs/ja/ui/loading.md +19 -0
  157. package/docs/ja/ui/popover.md +34 -0
  158. package/docs/ja/ui/progress.md +28 -0
  159. package/docs/ja/ui/scroll-area.md +24 -0
  160. package/docs/ja/ui/select.md +64 -0
  161. package/docs/ja/ui/separator.md +15 -0
  162. package/docs/ja/ui/sheet.md +28 -0
  163. package/docs/ja/ui/sidebar.md +92 -0
  164. package/docs/ja/ui/skeleton.md +16 -0
  165. package/docs/ja/ui/slider.md +21 -0
  166. package/docs/ja/ui/spinner.md +14 -0
  167. package/docs/ja/ui/switch.md +19 -0
  168. package/docs/ja/ui/table.md +24 -0
  169. package/docs/ja/ui/tabs.md +24 -0
  170. package/docs/ja/ui/textarea.md +15 -0
  171. package/docs/ja/ui/toggle-group.md +27 -0
  172. package/docs/ja/ui/toggle.md +19 -0
  173. package/docs/ja/ui/tooltip.md +22 -0
  174. package/docs/ja/ui/use-is-mobile.md +18 -0
  175. package/docs/ja/ui-and-theming.md +95 -0
  176. package/package.json +29 -4
  177. package/registry/blocks/sales-overview.tsx +43 -19
  178. package/registry/ui/data-table.tsx +702 -102
  179. package/skills/vantage-add-feature/SKILL.md +153 -0
  180. package/skills/vantage-app/SKILL.md +291 -0
  181. package/skills/vantage-pitfalls/SKILL.md +139 -0
  182. package/templates/AGENTS.md +313 -0
  183. package/theme.css +178 -40
  184. package/dist/chunk-73J5ZD4C.js.map +0 -1
  185. package/dist/chunk-ATYZ45XL.js +0 -19
  186. package/dist/chunk-ATYZ45XL.js.map +0 -1
  187. package/dist/chunk-ZGDU5YLH.js.map +0 -1
@@ -0,0 +1,153 @@
1
+ ---
2
+ name: vantage-add-feature
3
+ description: 既存の Vantage(@squadbase/vantage)アプリにページ・API エンドポイント・UI コンポーネント・ブロックを追加する定型ワークフロー。vantage add コマンドで雛形を出し、規約(動的パラメータの綴り・server 境界・大文字メソッド)を守り、vantage check / routes で確認するまでの一連。Vantage アプリに機能を1つ足すときに使う。
4
+ ---
5
+
6
+ # Vantage への機能追加(page / api / ui / block)
7
+
8
+ 既存の Vantage アプリに要素を1つ追加するときの定型手順。アプリの新規作成や fullstack 化の
9
+ 全体像は `vantage-app` スキル、実装で踏みやすい罠は `vantage-pitfalls` スキルを参照。
10
+
11
+ CLI は雛形を出すだけで、規約の遵守はこちらの責任。**追加したら必ず `vantage check` と
12
+ `vantage routes` で確認する**。
13
+
14
+ ## `vantage add` の4種別
15
+
16
+ ```bash
17
+ vantage add page <name> # ルートページ(.tsx)
18
+ vantage add api <name> # server/api/**.ts の API モジュール
19
+ vantage add ui <name> # registry から UI ソースを components/ui/ にコピー
20
+ vantage add block <name> # registry からブロックを components/blocks/ にコピー
21
+ ```
22
+
23
+ `--force` で既存ファイルを上書き。`add` の雛形は import に `@squadbase/vantage` を焼き込む。
24
+
25
+ ## ページを追加する
26
+
27
+ ```bash
28
+ vantage add page monthly-analysis # → ./monthly-analysis.tsx → ルート /monthly-analysis
29
+ vantage add page sales/index # → ./sales/index.tsx → ルート /sales
30
+ vantage add page "sales/[customerId]" # → 動的ルート /sales/:customerId
31
+ ```
32
+
33
+ 出力される雛形は `definePage` + デフォルトエクスポートのコンポーネント。ファイル名の規約:
34
+
35
+ | ファイル | ルート | 用途 |
36
+ | --- | --- | --- |
37
+ | `foo.tsx` | `/foo` | 通常ページ |
38
+ | `foo/index.tsx` | `/foo` | ディレクトリのインデックス |
39
+ | `foo/[id].tsx` | `/foo/:id` | 動的パラメータ |
40
+ | `_layout.tsx` | — | 配下を包むネストレイアウト(`Outlet` を描く) |
41
+ | `_404.tsx` / `_error.tsx` | — | Not Found / エラーページ |
42
+
43
+ チェックリスト:
44
+
45
+ 1. **デフォルトエクスポートは必須**(無いと `MISSING_DEFAULT_EXPORT` エラー)。
46
+ 2. `definePage({ title })` の値は**リテラル**で書く(ビルド時に静的抽出されるため)。ナビに出す
47
+ 短い名前が要るなら `navLabel` も足す(`useRoutes()` が読む。既定は `title`)。
48
+ `_layout.tsx` のナビを `useRoutes()` で組んでいれば、ページを足すだけでリンクも増える。
49
+ 3. 動的ページなら **3つの綴りを同期**:
50
+ - ファイル `foo/[id].tsx`
51
+ - 表示ルート `/foo/:id`
52
+ - リンク `to="/foo/$id"` + `params={{ id }}`、取り出しは `const { id } = useParams()`
53
+ 4. `components/`・`hooks/`・`lib/`・`server/`・`public/` はページにならない(ルート走査対象外)。
54
+
55
+ ## API を追加する(fullstack)
56
+
57
+ ```bash
58
+ vantage add api monthly-analysis # → server/api/monthly-analysis.ts → GET /api/monthly-analysis
59
+ vantage add api "customers/[id]" # → server/api/customers/[id].ts → GET /api/customers/:id
60
+ ```
61
+
62
+ **`server/` を作った時点でアプリは fullstack モードに切り替わる**(SPA → fullstack)。
63
+ 雛形は `GET` ハンドラ:
64
+
65
+ ```ts
66
+ import type { ApiContext } from "@squadbase/vantage/server"
67
+
68
+ export async function GET({ request, params }: ApiContext) {
69
+ return Response.json({ ok: true, params })
70
+ }
71
+ ```
72
+
73
+ チェックリスト:
74
+
75
+ 1. **エクスポート名は大文字の HTTP メソッド**のみ(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)。
76
+ 他の名前は `INVALID_API_EXPORT` エラー。
77
+ 2. `ApiContext` は Web 標準ベース: `request`(`Request`)・`params`・`env`・`waitUntil`。
78
+ 3. 動的パラメータは `params.id`。`noUncheckedIndexedAccess` が効くので `params.id!` などで narrowing。
79
+ 4. **クライアントに見せるエラーは `HttpError(status, msg)` を throw**。それ以外の throw は
80
+ 500 に丸められログに出る。
81
+ ```ts
82
+ import { HttpError, type ApiContext } from "@squadbase/vantage/server"
83
+ export async function GET({ params }: ApiContext) {
84
+ const row = findCustomer(params.id!)
85
+ if (!row) throw new HttpError(404, `Customer ${params.id} not found`)
86
+ return Response.json(row)
87
+ }
88
+ ```
89
+ 5. **シークレットは `ctx.env` からのみ**読む。クライアントには決して渡さない。
90
+ 6. サーバー専用の共有コードは `server/utils.ts` 等に置く。**クライアントから `server/` を
91
+ import しない**(`CLIENT_IMPORTS_SERVER` エラー。共有は `lib/` に置く)。
92
+
93
+ クライアント側から叩くときは `useApiQuery`(ベース URL 解決・JSON パース・非 2xx の `ApiError`
94
+ 化・クエリキー `["api", url]` が入っている):
95
+
96
+ ```tsx
97
+ import { useApiQuery } from "@squadbase/vantage/query"
98
+
99
+ const q = useApiQuery<Customer>(`/api/customers/${id}`)
100
+ if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
101
+
102
+ // クエリ文字列は search で(undefined の項目は落ちる)
103
+ const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
104
+ ```
105
+
106
+ 書き込みは `useApiMutation(path, { method: "POST" })` — 変数がそのまま JSON ボディになる。
107
+
108
+ ## UI コンポーネント / ブロックを追加する
109
+
110
+ ```bash
111
+ vantage add ui data-table # → components/ui/data-table.tsx(編集可能なソースをコピー)
112
+ vantage add block sales-overview # → components/blocks/sales-overview.tsx
113
+ ```
114
+
115
+ - **多くの UI コンポーネントは「import 専用」で、`add ui` でコピーできる名前は限られる。**
116
+ 現在 registry からコピー可能なのは `data-table`(ui)と `sales-overview`(block)のみ。
117
+ 未知の名前を渡すと利用可能な名前一覧を出して失敗する。
118
+ - コピーが要らない大半のプリミティブは `@squadbase/vantage/ui` から、複合パーツは
119
+ `@squadbase/vantage/components` から**そのまま import**するのが基本(コピー不要)。
120
+ ```tsx
121
+ import { Button, Loading, ErrorState } from "@squadbase/vantage/ui"
122
+ import { DashboardCardPreset, DataTablePreset, EChart } from "@squadbase/vantage/components"
123
+ ```
124
+ - コピーしたソースの相対 import は**ランタイムの `.js` 指定子**で書く(`./cn.js` など)。
125
+ - **props は `vantage docs <name>` で確認してから書く**(`vantage docs button`・
126
+ `vantage docs parts/data-table`。名前が分からなければ `vantage search <やりたいこと>` で
127
+ 探してから `vantage docs` に渡す)。
128
+
129
+ ## 追加後に必ず確認する
130
+
131
+ ```bash
132
+ vantage routes # 追加したページ/API が意図した URL に出ているか
133
+ vantage check # 規約違反が無いか(exit 1 ならエラーあり)
134
+ vantage dev # 実際に動くか(console は開発ターミナルに [browser:…] で転送)
135
+ ```
136
+
137
+ エージェントで自動処理するなら機械可読出力を使う:
138
+
139
+ ```bash
140
+ vantage routes --json # { pages, layouts, notFound, error, apis, hasServer }
141
+ vantage check --json # { ok, errorCount, warningCount, diagnostics[] }
142
+ ```
143
+
144
+ `vantage check` が出しうる診断コード(参考):
145
+
146
+ | code | 意味 |
147
+ | --- | --- |
148
+ | `FORBIDDEN_FILE` | `vite.config.*` などの禁止設定ファイルがある |
149
+ | `ROUTE_CONFLICT` | 同じルートを指すファイルが複数ある |
150
+ | `MISSING_DEFAULT_EXPORT` | ページ/レイアウトにデフォルトエクスポートが無い |
151
+ | `INVALID_API_EXPORT` | API モジュールが大文字メソッド以外をエクスポート |
152
+ | `CLIENT_IMPORTS_SERVER` | クライアントが `server/` を import している |
153
+ | `PUBLIC_ENV_MISUSE`(warn) | `PUBLIC_*` 以外の env をクライアントで読んでいる |
@@ -0,0 +1,291 @@
1
+ ---
2
+ name: vantage-app
3
+ description: Vantage(@squadbase/vantage)ダッシュボードアプリの新規作成と、index.tsx 1ファイルの SPA から server/api・ネストレイアウト・動的ルート・404/error を備えた fullstack への拡張手順。Vantage アプリを一から作る/構成を広げるとき、ルーティング規約や definePage の書き方に迷ったときに使う。
4
+ ---
5
+
6
+ # Vantage アプリの作成と fullstack 拡張
7
+
8
+ Vantage は設定ファイル不要(config-free)な React ダッシュボードフレームワーク。アプリ作者が
9
+ 書くのは `index.tsx`(と任意の追加ファイル)だけで、Vite・ルーティング・TanStack Query・
10
+ Tailwind・UI キット・開発サーバー・API サーバー・ビルドはすべて Vantage が所有する。
11
+
12
+ このスキルは「アプリを一から作る」「最小の SPA を fullstack に広げる」ワークフローを扱う。
13
+ 個別のページ/API/UI の追加は `vantage-add-feature` スキル、実装中に踏みやすい落とし穴は
14
+ `vantage-pitfalls` スキルを参照。
15
+
16
+ ## 大原則(先に頭に入れる)
17
+
18
+ - **設定ファイルを作らない。** `vite.config.*`・`tailwind.config.*`・`postcss.config.*`・
19
+ `components.json`・`vantage.config.*` はすべて禁止。存在すると `vantage check` がエラーにする。
20
+ 設定は「ファイル名の規約」で表現する。
21
+ - **import は必ず `@squadbase/vantage` のサブパス経由。** `@tanstack/*`・`@base-ui/react`・
22
+ `echarts`・`hono`・`vite`・`tailwindcss` を直接 import しない。`lucide-react` のアイコンだけは
23
+ 直接 import してよい。
24
+ - **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。
25
+ - **モジュール間 import はランタイムの `.js` 指定子を使う**(例: `./components/revenue-chart.js`)。
26
+ ソースは `.tsx`/`.ts` でも、相対 import の拡張子は `.js` と書く。ESM の Node 解決に必要。
27
+
28
+ ## サブパスの地図
29
+
30
+ | import 元 | 提供するもの |
31
+ | --- | --- |
32
+ | `@squadbase/vantage` | `definePage`(ページ設定) |
33
+ | `@squadbase/vantage/router` | `Link`・`Outlet`・`useParams`・`useSearch`・`useNavigate`・`redirect`・`notFound`・`useRoutes`・`useCurrentRoute`・`useSearchParam`・`useSearchState` |
34
+ | `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
35
+ | `@squadbase/vantage/ui` | shadcn/ui 系プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
36
+ | `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
37
+ | `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するため専用サブパス) |
38
+ | `@squadbase/vantage/server` | `ApiContext`・`HttpError`(server/ 側でのみ使う) |
39
+
40
+ どのコンポーネントがあるか、props が何かは **`vantage docs` で引く**(パッケージ同梱。
41
+ `vantage docs` で一覧、`vantage docs button` / `vantage docs parts/data-table` で個別ページ、
42
+ `--json` で機械可読)。**props を推測で書かない。** 名前が分からないときは
43
+ **`vantage search <やりたいこと>`**(例: `vantage search 期間を選ぶ UI が欲しい`)で探し、
44
+ 出てきた slug を `vantage docs` に渡す。
45
+
46
+ ## Step 1 — 最小アプリ(SPA モード)
47
+
48
+ `package.json` と `index.tsx` の2ファイルだけで動く。
49
+
50
+ `package.json`:
51
+
52
+ ```json
53
+ {
54
+ "name": "my-dashboard",
55
+ "private": true,
56
+ "type": "module",
57
+ "scripts": {
58
+ "dev": "vantage dev",
59
+ "build": "vantage build",
60
+ "preview": "vantage preview",
61
+ "check": "vantage check",
62
+ "routes": "vantage routes"
63
+ },
64
+ "dependencies": {
65
+ "@squadbase/vantage": "^0.1.0",
66
+ "react": "^19.2.7",
67
+ "react-dom": "^19.2.7"
68
+ }
69
+ }
70
+ ```
71
+
72
+ `index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
73
+
74
+ ```tsx
75
+ export default function Dashboard() {
76
+ return (
77
+ <main className="p-6">
78
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
79
+ </main>
80
+ )
81
+ }
82
+ ```
83
+
84
+ 起動と検証:
85
+
86
+ ```bash
87
+ pnpm install
88
+ pnpm dev # → http://localhost:5173(HMR)
89
+ pnpm check # 静的診断。エラーがあれば exit 1
90
+ pnpm routes # ページ/API の URL マップ
91
+ ```
92
+
93
+ `server/` ディレクトリが無いので、この時点では **SPA モード**(`vantage-manifest.json` の
94
+ `mode: "spa"`)。
95
+
96
+ ## Step 2 — ページを足してファイルベースルーティングにする
97
+
98
+ ルートディレクトリ直下の `.tsx`/`.jsx` がそのままページになる。規約:
99
+
100
+ | ファイル | ルート |
101
+ | --- | --- |
102
+ | `index.tsx` | `/` |
103
+ | `monthly-analysis.tsx` | `/monthly-analysis` |
104
+ | `sales/index.tsx` | `/sales` |
105
+ | `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
106
+ | `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト |
107
+ | `_404.tsx` | Not Found ページ |
108
+ | `_error.tsx` | ルートが throw したときのエラーページ |
109
+
110
+ `components/`・`hooks/`・`lib/`・`server/`・`public/` はルート走査の対象外(ページにならない)。
111
+
112
+ 各ページは **デフォルトエクスポートの React コンポーネントが必須**。タイトル等は `definePage`:
113
+
114
+ ```tsx
115
+ import { definePage } from "@squadbase/vantage"
116
+
117
+ // navLabel は useRoutes() で組むナビの表示名(省略時は title)
118
+ export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
119
+
120
+ export default function MonthlyAnalysis() {
121
+ return <main className="p-6">…</main>
122
+ }
123
+ ```
124
+
125
+ > `page` エクスポートはランタイムでは読まれない。title/description/navLabel はビルド時に
126
+ > **静的抽出**されるので、値はリテラルで書く(変数や関数呼び出しにしない)。
127
+
128
+ ### ネストレイアウトと特殊ページ
129
+
130
+ `_layout.tsx` は `Outlet` で子ルートを描く。ナビは `useRoutes()` から組むと、ページファイルを
131
+ 足すだけでリンクが増える(手で持つリンク配列を作らない):
132
+
133
+ ```tsx
134
+ import { Link, Outlet, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
135
+
136
+ export default function RootLayout() {
137
+ // 動的ルート(/sales/:id)は URL が定まらないので外す。label は navLabel → title → path
138
+ const routes = useRoutes().filter((r) => !r.dynamic)
139
+ const current = useCurrentRoute()
140
+
141
+ return (
142
+ <div className="min-h-screen">
143
+ <header>
144
+ {routes.map((r) => (
145
+ <Link key={r.path} to={r.to} activeClassName="font-semibold">
146
+ {r.label}
147
+ </Link>
148
+ ))}
149
+ </header>
150
+ <Outlet />
151
+ </div>
152
+ )
153
+ }
154
+ ```
155
+
156
+ `_404.tsx` / `_error.tsx` は `ui/` の状態コンポーネントを使うと早い:
157
+
158
+ ```tsx
159
+ // _error.tsx
160
+ import { ErrorState } from "@squadbase/vantage/ui"
161
+ export default function RouteError({ error }: { error: unknown }) {
162
+ const message = error instanceof Error ? error.message : String(error)
163
+ return <ErrorState title="This page failed to render" message={message} />
164
+ }
165
+ ```
166
+
167
+ ### 動的パラメータの「3つの綴り」を必ず同期させる
168
+
169
+ これは崩すと壊れる不変条件:
170
+
171
+ - ファイル名 `sales/[customerId].tsx`
172
+ - 表示ルート `/sales/:customerId`
173
+ - リンク: `to="/sales/$customerId"` + `params={{ customerId }}`
174
+
175
+ ```tsx
176
+ import { Link, useParams } from "@squadbase/vantage/router"
177
+
178
+ // 一覧側のリンク
179
+ <Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
180
+
181
+ // 詳細ページ側で取り出す
182
+ const { customerId } = useParams()
183
+ ```
184
+
185
+ ## Step 3 — サーバー状態(API を持たない fetch)
186
+
187
+ 外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
188
+
189
+ ```tsx
190
+ import { useQuery } from "@squadbase/vantage/query"
191
+
192
+ const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
193
+ if (q.isPending) return <Loading />
194
+ if (q.isError) return <ErrorState message={(q.error as Error).message} />
195
+ ```
196
+
197
+ `QueryClient` は Vantage が1つだけ管理する(staleTime 30s・retry 1・refetchOnWindowFocus false・
198
+ networkMode "always" = ブラウザのオフライン判定に従わず必ず投げる)。挙動を変えたいときは
199
+ クエリ側のオプションで上書きする(クライアントごと差し替える口は無い)。
200
+ 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→ Step 4)。
201
+
202
+ ### フィルタ状態は URL に置く
203
+
204
+ ダッシュボードの絞り込みは `useState` ではなく `useSearchParam` にする。リロードで消えず、
205
+ URL をそのまま共有できる:
206
+
207
+ ```tsx
208
+ import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
209
+
210
+ const [region, setRegion] = useSearchParam("region", "all") // 値は常に string
211
+ const [segments, setSegments] = useSearchState<string[]>("segments", []) // 配列・オブジェクト
212
+ ```
213
+
214
+ デフォルト値(または `null`)を書き込むとキーは URL から消える。履歴は既定で `replace`
215
+ (`{ replace: false }` で push)。動的ルートの上でもパスパラメータは保たれる。
216
+
217
+ ## Step 4 — fullstack へ拡張(server/api を足す)
218
+
219
+ **`server/` ディレクトリを作った瞬間に fullstack モードになる。** `server/api/**` の各ファイルが
220
+ API ルートになり、ビルドは client + server バンドル + `mode: "fullstack"` の manifest を出す。
221
+
222
+ `server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
223
+
224
+ ```ts
225
+ import type { ApiContext } from "@squadbase/vantage/server"
226
+
227
+ export async function GET(_ctx: ApiContext) {
228
+ return Response.json({ ok: true })
229
+ }
230
+ ```
231
+
232
+ - API モジュールは大文字の HTTP メソッド(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
233
+ エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
234
+ - 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
235
+ `params.id` で取り出す(`noUncheckedIndexedAccess` が効くので `params.id!` 等で narrowing)。
236
+ - クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。それ以外の throw は
237
+ ログに記録され汎用の 500 になる。
238
+ - **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
239
+
240
+ クライアント側からは `useApiQuery` で `/api/*` を叩く(ベース URL 解決・JSON パース・非 2xx の
241
+ `ApiError` 化・クエリキー `["api", url]` が入っている):
242
+
243
+ ```tsx
244
+ import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
245
+
246
+ const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
247
+ if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージが入る
248
+
249
+ // クエリ文字列は search で渡す(undefined の項目は落ちる → URL にもキーにも出ない)
250
+ const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
251
+
252
+ // 書き込み。変数がそのまま JSON ボディになる
253
+ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
254
+ ```
255
+
256
+ `ApiError` は `message`・`status`・`body`・`requestId`(dev ターミナルのログと突き合わせられる)を
257
+ 持つ。hook が使えない場所では `apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。
258
+
259
+ ### server/ とクライアントの境界(絶対に守る)
260
+
261
+ - **クライアントコードは `server/` を import してはならない。** 共有したいコードは `lib/` に置く。
262
+ 違反は `CLIENT_IMPORTS_SERVER` エラー(静的にもビルド時にも弾かれる)。
263
+ - サーバー専用のデータ/ヘルパは `server/utils.ts` などに置き、`server/api/**` からのみ import する。
264
+
265
+ ## Step 5 — 環境変数
266
+
267
+ - **クライアントに届くのは `PUBLIC_` 接頭辞の付いた env のみ**。`import.meta.env.PUBLIC_FOO`。
268
+ `VITE_` 系の API は無い。それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。
269
+ - サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
270
+
271
+ ## Step 6 — 検証・ビルド・プレビュー
272
+
273
+ ```bash
274
+ pnpm check # 静的診断(禁止ファイル・ルート衝突・境界・API export・env 誤用)
275
+ pnpm routes # ページ + API の URL マップを確認
276
+ pnpm build # dist/ に client(+ server)+ vantage-manifest.json
277
+ pnpm preview # 本番ビルドをローカル実行(fullstack:4173 / spa は Vite preview)
278
+ ```
279
+
280
+ `vantage-manifest.json` の `mode` は `server/` の有無から自動で決まる(手で書かない)。
281
+ デプロイまでの詳細は別途デプロイ手順を参照。
282
+
283
+ ## 詰まったら
284
+
285
+ - `console.*` とランタイムエラーは開発ターミナルに `[browser:…]` として転送される。
286
+ - `vantage check --json` / `vantage routes --json` はエージェント向けの機械可読出力。
287
+ - 規約や props を確かめたいときは `vantage docs <name>`(ガイドは `vantage docs routing` など、
288
+ 一覧は `vantage docs`)。名前が思い出せないときは `vantage search <やりたいこと>`、
289
+ 綴りを横断で確かめたいときは `vantage search "<regex>" --regex`。
290
+ - Base UI(≠ Radix)固有の罠、`SelectValue` の挙動、EChart のテーマ非追従などは
291
+ `vantage-pitfalls` スキルにまとまっている。
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: vantage-pitfalls
3
+ description: Vantage(@squadbase/vantage)アプリを書くときに静かに壊れる落とし穴のリファレンス。Base UI は Radix ではない(asChild 無し・render プロップ)、SelectValue は value を描く、クライアントから server/ を import しない、EChart はテーマ非追従、相対 import は .js 指定子、動的パラメータの3綴り同期、PUBLIC_ env、HttpError など。Vantage の UI コンポーネントやルーティングが思った通りに動かないとき、ビルドやスタイルが静かに欠落するときに参照する。
4
+ ---
5
+
6
+ # Vantage アプリの落とし穴リファレンス
7
+
8
+ Vantage アプリで「型は通るのに実行時/ビルド時に静かに壊れる」パターン集。症状が出たとき、
9
+ または該当機能を書く前に参照する。アプリ作成の流れは `vantage-app`、機能追加は
10
+ `vantage-add-feature` スキル。個々のコンポーネントの props と使用例は
11
+ `vantage docs <name>`(例: `vantage docs ui/select`)で引ける。
12
+
13
+ ## UI:Base UI は Radix ではない
14
+
15
+ Vantage の UI キットは shadcn/ui の **Base UI バリアント**。Radix の癖で書くと動かない。
16
+
17
+ - **`asChild` は無い。** 代わりに `render` プロップを使う。
18
+ - **`Select` の `onValueChange` は `string | null`** を渡す(null が来うる)。
19
+ - **`Checkbox` の `checked` は `boolean` のみ。** 中間状態は `indeterminate` プロップ。
20
+ - **`ToggleGroup` の `value` は配列。**
21
+
22
+ ### `SelectValue` は「選択中の value」を描く。`SelectItem` の children は見ない
23
+
24
+ 値とラベルが違うと、トリガーに `kanto` や `/sales` といった**生の値**が出てしまう。ラベルを
25
+ 出したいときは `items`(value→label のマップ)を渡す:
26
+
27
+ ```tsx
28
+ <Select items={{ kanto: "関東", kansai: "関西" }} … />
29
+ ```
30
+
31
+ `FilterBarSelect` / `AppShell` の header variant / `DataTablePagination` は内部でこの `items` を
32
+ 組み立てている。自前で `Select` を使うときは忘れやすい。
33
+
34
+ ## チャート:`EChart` はテーマに自動追従しない
35
+
36
+ `EChart` は薄いラッパー(init/resize/dispose・loading・`onEvents`・PNG コピー/ダウンロードだけ)。
37
+ 配色は**各アプリが `option.color`** で決める(または `theme` に echarts テーマを渡す)。
38
+
39
+ - `--chart-1..5` を読んで明暗テーマを自動で組む機構は**意図的に無い**(ECharts はキャンバス
40
+ 描画で CSS 変数を読めない)。ダークモード追従が要るなら自前で色を切り替える。
41
+ - キャンバス系なので、コンテナに高さを与えないと何も見えない。
42
+
43
+ ## 境界:クライアントから `server/` を import しない
44
+
45
+ - クライアント側のどのファイルも `server/` 配下を import してはいけない(`CLIENT_IMPORTS_SERVER`
46
+ エラー。静的にもビルド時にも弾かれる)。**共有したいコードは `lib/` に置く。**
47
+ - シークレット等の env は `ApiContext.env`(server/ 内)にだけ届く。クライアントには渡らない。
48
+
49
+ ## env:クライアントに届くのは `PUBLIC_` 接頭辞だけ
50
+
51
+ - クライアントで読めるのは `import.meta.env.PUBLIC_*` と `MODE`/`DEV`/`PROD`/`SSR`/`BASE_URL` のみ。
52
+ それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。
53
+ - `VITE_` 系の公開 API は無い。サーバーのシークレットは `ctx.env` で読む。
54
+
55
+ ## import:相対 import はランタイムの `.js` 指定子で書く
56
+
57
+ ソースが `.tsx`/`.ts` でも、**モジュール間の相対 import は `.js`** と書く(ESM の Node 解決に必要):
58
+
59
+ ```tsx
60
+ import { RevenueChart } from "./components/revenue-chart.js" // ← .tsx ではなく .js
61
+ ```
62
+
63
+ `@squadbase/vantage` のサブパス import はこの限りではない(`@squadbase/vantage/ui` 等)。
64
+
65
+ ## ルーティング:動的パラメータは「3つの綴り」を同期させる
66
+
67
+ ズレると静かにマッチしなくなる:
68
+
69
+ - ファイル `sales/[customerId].tsx`
70
+ - 表示ルート `/sales/:customerId`
71
+ - リンク `to="/sales/$customerId"` + `params={{ customerId }}` / 取り出し `useParams().customerId`
72
+
73
+ ## ページ:`definePage` の値はリテラルで書く
74
+
75
+ title/description/navLabel は**ビルド時に静的抽出**される。変数・関数呼び出し・テンプレート補間を
76
+ 使うと抽出できない。`export const page = definePage({ title: "…" })` の右辺はリテラルにする。
77
+ なお `page` エクスポートはランタイムでは読まれない(抽出専用)。
78
+
79
+ ## ナビ:`useRoutes()` は動的ルートも返す
80
+
81
+ `/sales/:customerId` のような動的ルートは URL が 1 つに定まらない。ナビに出すなら
82
+ `useRoutes().filter((r) => !r.dynamic)` で外す。リンク先は `r.path`(`:id` 形式)ではなく
83
+ **`r.to`**(`$id` 形式)を `Link` に渡す。表示名は `r.label`(`navLabel` → `title` → `path` の順)。
84
+
85
+ `useCurrentRoute()` は 404 のとき `undefined` を返す。動的ルートを開いているときに親を
86
+ アクティブにしたいなら `current?.path.startsWith("/sales")` のように前方一致で判定する。
87
+
88
+ ## URL 状態:`useSearchParam` の値は常に string、既定値はURLから消える
89
+
90
+ - TanStack は `?year=2024` を数値としてパースするが、`useSearchParam` は必ず `string` に戻す
91
+ (数値が欲しければ自分で `Number(...)`)。配列やオブジェクトは `useSearchState` を使う。
92
+ - **デフォルト値または `null` を書き込むとキーが URL から消える**。「明示的に既定値を選んだ」
93
+ 状態を URL に残すことはできない。
94
+ - 履歴は既定で `replace`。戻るボタンで1手ずつ戻したいときだけ `{ replace: false }`。
95
+
96
+ ## API 呼び出し:`useApiQuery` のエラーは `ApiError`
97
+
98
+ `(q.error as Error)` のキャストは要らない。`q.error` は `ApiError | null` で、`message` には
99
+ サーバーが `HttpError` で明示したメッセージが入る(それ以外は汎用のステータス文言)。
100
+ `status`・`body`・`requestId` も持つ。クエリキーは `["api", url]` なので、
101
+ `invalidateQueries({ queryKey: ["api"] })` で API 由来のキャッシュを一括で捨てられる。
102
+
103
+ ## API:見せたいエラーは `HttpError` を throw する
104
+
105
+ ```ts
106
+ import { HttpError } from "@squadbase/vantage/server"
107
+ throw new HttpError(404, "Not found") // → クライアントに 404 + message
108
+ throw new Error("boom") // → ログに出て汎用 500 に丸められる
109
+ ```
110
+
111
+ `instanceof HttpError` は dev の module realm 跨ぎで壊れることがある。ライブラリ側の判定は
112
+ `isHttpError` を使う設計。アプリ側は素直に `throw new HttpError(...)` すればよい。
113
+
114
+ ## 設定ファイル:作った時点で `vantage check` がエラーにする
115
+
116
+ 以下はすべて禁止(`FORBIDDEN_FILE`)。Vantage が所有しているので置かない:
117
+
118
+ - `vite.config.*` — Vite 設定は Vantage が所有
119
+ - `tailwind.config.*` — テーマは `styles.css` のトークンで調整
120
+ - `postcss.config.*` — Vantage が管理
121
+ - `components.json` — UI は `vantage add ui <name>` でコピー
122
+ - `vantage.config.*` — v1 に設定ファイルは無い(規約で表現)
123
+
124
+ ## Markdown:`MarkdownRenderer` は `@squadbase/vantage/markdown` から
125
+
126
+ Markdown 描画は専用サブパスに隔離されている(Shiki の全言語文法を引き込むため)。
127
+ `components/` から import しない。使うアプリだけが `@squadbase/vantage/markdown` から取り込む。
128
+
129
+ ## テーブル:`ColumnDef<Row>[]` を明示的に注釈する
130
+
131
+ ```tsx
132
+ import { type ColumnDef } from "@squadbase/vantage/components"
133
+ const columns: ColumnDef<Row>[] = [ … ] // 注釈しないと accessorKey が string に広がり弾かれる
134
+ ```
135
+
136
+ ## 生成物:`.vantage/` と `dist/` は触らない
137
+
138
+ どちらも gitignore 済みの生成物。編集しても次のコマンドで上書きされる。`.vantage/` は任意の CLI
139
+ コマンド、または `vantage upgrade` で再生成される。