@squadbase/vantage 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-YLAB6UQS.js → chunk-46QI6GFC.js} +2 -2
  3. package/dist/{chunk-YLAB6UQS.js.map → chunk-46QI6GFC.js.map} +1 -1
  4. package/dist/{chunk-7IPAXPPY.js → chunk-EHPJXDJU.js} +2 -2
  5. package/dist/chunk-EHPJXDJU.js.map +1 -0
  6. package/dist/{chunk-WQZYXXQW.js → chunk-JC6MT5UU.js} +26 -3
  7. package/dist/chunk-JC6MT5UU.js.map +1 -0
  8. package/dist/{chunk-5L2MH2NG.js → chunk-UHZ7XSAN.js} +3 -3
  9. package/dist/chunk-UHZ7XSAN.js.map +1 -0
  10. package/dist/cli.js +19 -11
  11. package/dist/cli.js.map +1 -1
  12. package/dist/client/index.js +1 -1
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/components/index.d.ts +7 -1
  15. package/dist/components/index.js +109 -22
  16. package/dist/components/index.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/markdown/index.js.map +1 -1
  21. package/dist/query/index.js.map +1 -1
  22. package/dist/router/index.d.ts +1 -1
  23. package/dist/router/index.js +1 -1
  24. package/dist/router/index.js.map +1 -1
  25. package/dist/server/node.js +1 -1
  26. package/dist/server/node.js.map +1 -1
  27. package/dist/ui/index.d.ts +1 -1
  28. package/dist/ui/index.js +1 -1
  29. package/dist/ui/index.js.map +1 -1
  30. package/dist/vite/index.js +2 -2
  31. package/docs/en/agent-skills.md +12 -8
  32. package/docs/en/changelog.md +133 -0
  33. package/docs/en/cli-reference.md +1 -1
  34. package/docs/en/components.md +2 -2
  35. package/docs/en/parts/echart.md +18 -11
  36. package/docs/en/routing.md +0 -4
  37. package/docs/en/ui/select.md +16 -10
  38. package/docs/index.json +27 -5
  39. package/docs/ja/agent-skills.md +13 -9
  40. package/docs/ja/changelog.md +124 -0
  41. package/docs/ja/cli-reference.md +1 -1
  42. package/docs/ja/components.md +2 -2
  43. package/docs/ja/parts/echart.md +16 -9
  44. package/docs/ja/routing.md +0 -4
  45. package/docs/ja/ui/select.md +17 -10
  46. package/package.json +1 -1
  47. package/skills/vantage-add-feature/SKILL.md +0 -1
  48. package/skills/vantage-app/SKILL.md +65 -236
  49. package/skills/vantage-pitfalls/SKILL.md +21 -24
  50. package/templates/AGENTS.md +48 -26
  51. package/dist/chunk-5L2MH2NG.js.map +0 -1
  52. package/dist/chunk-7IPAXPPY.js.map +0 -1
  53. package/dist/chunk-WQZYXXQW.js.map +0 -1
@@ -40,24 +40,31 @@ import {
40
40
  > <Select onValueChange={(value) => value !== null && setRegion(value)}>
41
41
  > ```
42
42
 
43
- > [!WARNING]
44
- > `SelectValue` は既定で**選択中の `value` をそのまま表示します** — `SelectItem` の中身は見ません。
45
- > `value="kanto"` なら、トリガーには「関東」ではなく `kanto` と出ます。値とラベルの対応を
46
- > `items` に渡してください。
43
+ > [!NOTE]
44
+ > Base UI の `SelectValue` は**選択中の `value` をそのまま表示する**部品で、`SelectItem` の中身は
45
+ > 見ません。`value="kanto"` ならトリガーには `kanto` と出ます。
47
46
  >
48
- > ```tsx
49
- > const regions = { hokkaido: "北海道", kanto: "関東", kansai: "関西" };
47
+ > Vantage の `Select` はこの癖を**吸収します** — children を辿って `SelectItem` の `value` →
48
+ > ラベルの対応を組み、Base UI `items` に渡すので、下の書き方でトリガーに「関東」と出ます。
49
+ > `SegmentedControl` が `ToggleGroup` の配列 API を吸収しているのと同じ扱いです。
50
50
  >
51
- > <Select items={regions} value={region} onValueChange={…}>
51
+ > ```tsx
52
+ > <Select value={region} onValueChange={…}>
52
53
  > <SelectTrigger><SelectValue placeholder="地域を選ぶ" /></SelectTrigger>
53
- >
54
+ > <SelectContent>
55
+ > <SelectItem value="kanto">関東</SelectItem>
56
+ > </SelectContent>
54
57
  > </Select>;
55
58
  > ```
56
59
  >
57
- > `items` を持たせたくない場合は、`SelectValue` の children に関数を渡しても同じことができます。
60
+ > 自動で拾えるのは、**その場に書かれた `SelectItem`** だけです(`map` で並べるのは拾えます)。
61
+ > 項目を別のコンポーネントが返す形にしているときは `items` を明示してください。明示した `items`
62
+ > は常に優先されます。
58
63
  >
59
64
  > ```tsx
60
- > <SelectValue>{(value) => regions[value] ?? "地域を選ぶ"}</SelectValue>
65
+ > const regions = { hokkaido: "北海道", kanto: "関東", kansai: "関西" };
66
+ >
67
+ > <Select items={regions} value={region} onValueChange={…}>
61
68
  > ```
62
69
 
63
70
  `SelectContent` は既定で選択中の項目をトリガーに重ねて開きます。素直に下へ開かせたいときは
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squadbase/vantage",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Data dashboard framework for Squadbase Editor. Vite-powered, config-free, one-file first.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -121,7 +121,6 @@ vantage add block sales-overview # → components/blocks/sales-overview.tsx
121
121
  import { Button, Loading, ErrorState } from "@squadbase/vantage/ui"
122
122
  import { DashboardCardPreset, DataTablePreset, EChart } from "@squadbase/vantage/components"
123
123
  ```
124
- - コピーしたソースの相対 import は**ランタイムの `.js` 指定子**で書く(`./cn.js` など)。
125
124
  - **props は `vantage docs <name>` で確認してから書く**(`vantage docs button`・
126
125
  `vantage docs parts/data-table`。名前が分からなければ `vantage search <やりたいこと>` で
127
126
  探してから `vantage docs` に渡す)。
@@ -1,53 +1,32 @@
1
1
  ---
2
2
  name: vantage-app
3
- description: Vantage(@squadbase/vantage)ダッシュボードアプリの新規作成と、index.tsx 1ファイルの SPA から server/api・ネストレイアウト・動的ルート・404/error を備えた fullstack への拡張手順。Vantage アプリを一から作る/構成を広げるとき、ルーティング規約や definePage の書き方に迷ったときに使う。
3
+ description: Vantage(@squadbase/vantage)ダッシュボードアプリを一から作る / 既存アプリの構成を広げるときの進め方。骨組みの用意、ルートの決め方、データ取得の選択(外部 API か自前の server/api か)、どこで手を止めて検証するかの順序を扱う。規約そのものはアプリルートの AGENTS.md が正本。
4
4
  ---
5
5
 
6
- # Vantage アプリの作成と fullstack 拡張
6
+ # Vantage アプリの作り方 進め方と検証の順序
7
7
 
8
- Vantage は設定ファイル不要(config-free)な React ダッシュボードフレームワーク。アプリ作者が
9
- 書くのは `index.tsx`(と任意の追加ファイル)だけで、Vite・ルーティング・TanStack Query・
10
- Tailwind・UI キット・開発サーバー・API サーバー・ビルドはすべて Vantage が所有する。
8
+ 規約・不変条件・import サブパスの一覧・各 API の書式は、アプリルートの **`AGENTS.md`** が正本。
9
+ **先にそれを読む**(無ければ `vantage add agents` で置ける)。このスキルは「どの順で作り、どこで
10
+ 手を止めて検証するか」だけを扱い、AGENTS.md の内容は繰り返さない。
11
11
 
12
- このスキルは「アプリを一から作る」「最小の SPAfullstack に広げる」ワークフローを扱う。
13
- 個別のページ/API/UI の追加は `vantage-add-feature` スキル、実装中に踏みやすい落とし穴は
14
- `vantage-pitfalls` スキルを参照。
12
+ - ページ / API / UI 1 つ足すだけなら → `vantage-add-feature`
13
+ - 実装したのに静かに壊れたら `vantage-pitfalls`
15
14
 
16
- ## 大原則(先に頭に入れる)
15
+ ## 全体の流れ
17
16
 
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 解決に必要。
17
+ 1. **骨組み** `pnpm dev` が上がるところまで
18
+ 2. **ルートを決める** — ページファイルを置き、`vantage routes` で URL を確認
19
+ 3. **データを繋ぐ** — 外部 API か、自前の `server/api` か
20
+ 4. **仕上げ** `check` `build` → `preview`
27
21
 
28
- ## サブパスの地図
22
+ **各段階の終わりに `vantage check` を通す。** 静的診断(禁止ファイル・ルート衝突・境界違反・
23
+ API export・env 誤用)はユーザーコードを実行しないので速く、エラーがあれば exit 1 になる。
24
+ まとめて最後に回すと、原因の切り分けが難しくなる。
29
25
 
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/ 側でのみ使う) |
26
+ ## Step 1 骨組み
39
27
 
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`:
28
+ 新規なら `package.json` `index.tsx` の 2 ファイルだけ。設定ファイルは**作らない**
29
+ (`vite.config.*` 等は `vantage check` がエラーにする)。
51
30
 
52
31
  ```json
53
32
  {
@@ -62,230 +41,80 @@ Tailwind・UI キット・開発サーバー・API サーバー・ビルドは
62
41
  "routes": "vantage routes"
63
42
  },
64
43
  "dependencies": {
65
- "@squadbase/vantage": "^0.1.0",
44
+ "@squadbase/vantage": "^0.2.0",
66
45
  "react": "^19.2.7",
67
46
  "react-dom": "^19.2.7"
68
47
  }
69
48
  }
70
49
  ```
71
50
 
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
- ```
51
+ `index.tsx` はデフォルトエクスポートの React コンポーネント 1 つ(これが `/` になる)。あとは
52
+ `pnpm install && pnpm dev` で http://localhost:5173 が上がる。
83
53
 
84
- 起動と検証:
54
+ **既存アプリに合流したときは、作る前に現状を読む:**
85
55
 
86
56
  ```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
- }
57
+ vantage routes # 既にあるページと API の URL マップ
58
+ vantage check # いま壊れていないか(これから出すエラーと切り分ける)
59
+ ls server/ # あれば fullstack モード。無ければ SPA
60
+ ls .claude/skills # 配置済みの skill(このファイルの仲間)
165
61
  ```
166
62
 
167
- ### 動的パラメータの「3つの綴り」を必ず同期させる
63
+ ## Step 2 — ルートを決める
168
64
 
169
- これは崩すと壊れる不変条件:
65
+ ファイル名がそのままルートになる(対応表は AGENTS.md「ルーティング規約」)。**ルーターを設定
66
+ する場所は無い**ので、決めるのは「どんなファイル名で置くか」だけ。
170
67
 
171
- - ファイル名 `sales/[customerId].tsx`
172
- - 表示ルート `/sales/:customerId`
173
- - リンク: `to="/sales/$customerId"` + `params={{ customerId }}`
68
+ - ページを置いたら `vantage routes` で URL を確かめる。意図と違うなら**ファイル名が違う**。
69
+ - ナビゲーションはリンク配列を手で持たず、`useRoutes()` から組む。この形にしておくと以後は
70
+ ページファイルを足すだけでナビが増える(AGENTS.md「ナビはルート一覧から組む」)。
71
+ - 共通の枠(ヘッダ・サイドバー)は `_layout.tsx`、Not Found と例外は `_404.tsx` / `_error.tsx`。
72
+ - 一覧 → 詳細を作るなら、**動的パラメータの「3 つの綴り」を先に決めてから**両方のファイルを
73
+ 書く。後から変えると静かにマッチしなくなる。
174
74
 
175
- ```tsx
176
- import { Link, useParams } from "@squadbase/vantage/router"
75
+ ## Step 3 — データを繋ぐ
177
76
 
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
- ```
77
+ **どちらの経路かを先に決める。** ここを間違えると後で全部書き直しになる:
255
78
 
256
- `ApiError` `message`・`status`・`body`・`requestId`(dev ターミナルのログと突き合わせられる)を
257
- 持つ。hook が使えない場所では `apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。
79
+ | データ元 | 使うもの | `server/` |
80
+ | --- | --- | --- |
81
+ | ブラウザから直接叩ける公開 API | `useQuery` + `fetch` | 不要 |
82
+ | DB / シークレットが要る / CORS で叩けない | `server/api/*.ts` + `useApiQuery` | 必要 |
258
83
 
259
- ### server/ とクライアントの境界(絶対に守る)
84
+ **API キーが要る時点で後者しかない** — クライアントに届く env は `PUBLIC_*` だけで、
85
+ シークレットは `ApiContext.env`(= `server/` の中)にしか届かない。
260
86
 
261
- - **クライアントコードは `server/` を import してはならない。** 共有したいコードは `lib/` に置く。
262
- 違反は `CLIENT_IMPORTS_SERVER` エラー(静的にもビルド時にも弾かれる)。
263
- - サーバー専用のデータ/ヘルパは `server/utils.ts` などに置き、`server/api/**` からのみ import する。
87
+ 後者の手順:
264
88
 
265
- ## Step 5 環境変数
89
+ 1. `server/api/<name>.ts` `GET` を書く。**このディレクトリを作った時点で fullstack モード**
90
+ になる(設定変更は不要)。
91
+ 2. `vantage routes` に `/api/<name>` が出ることを確認する。
92
+ 3. dev サーバーか `curl` で叩き、**返す JSON の形を確定させてから** UI を書く。エラー応答は
93
+ `HttpError(status, msg)` を throw して作る。
94
+ 4. クライアントから `useApiQuery<T>("/api/<name>")` で受ける。`isPending` / `isError` の分岐を
95
+ 最初から書く(`Loading` / `ErrorState` が `ui/` にある)。
266
96
 
267
- - **クライアントに届くのは `PUBLIC_` 接頭辞の付いた env のみ**。`import.meta.env.PUBLIC_FOO`。
268
- `VITE_` 系の API は無い。それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。
269
- - サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
97
+ ダッシュボードの**絞り込みは `useState` ではなく `useSearchParam` / `useSearchState`** に置く。
98
+ リロードで消えず、URL をそのまま共有できる。これも後から差し替えると全ページに波及するので、
99
+ 最初のフィルタを作る時点で決める。
270
100
 
271
- ## Step 6検証・ビルド・プレビュー
101
+ ## Step 4仕上げ
272
102
 
273
103
  ```bash
274
- pnpm check # 静的診断(禁止ファイル・ルート衝突・境界・API export・env 誤用)
275
- pnpm routes # ページ + API の URL マップを確認
104
+ pnpm check # エラーが残っていないか(exit 1 なら残っている)
105
+ pnpm routes # 公開される URL の最終確認
276
106
  pnpm build # dist/ に client(+ server)+ vantage-manifest.json
277
- pnpm preview # 本番ビルドをローカル実行(fullstack:4173 / spa は Vite preview)
107
+ pnpm preview # 本番ビルドをローカルで動かす
278
108
  ```
279
109
 
280
110
  `vantage-manifest.json` の `mode` は `server/` の有無から自動で決まる(手で書かない)。
281
- デプロイまでの詳細は別途デプロイ手順を参照。
111
+ `preview` まで通れば、そのまま同じ成果物がデプロイされる。
282
112
 
283
113
  ## 詰まったら
284
114
 
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` スキルにまとまっている。
115
+ - ブラウザの `console.*` とランタイムエラーは、**開発ターミナルに `[browser:…]` として転送
116
+ される**。ブラウザの devtools を開かなくても読める。
117
+ - `vantage check --json` / `vantage routes --json` は機械可読出力。
118
+ - props や規約を確かめたいときは `vantage docs <name>`、名前が思い出せないときは
119
+ `vantage search <やりたいこと>`。**推測で props を書かない。**
120
+ - Base UI(≠ Radix)の癖など、静かに壊れる系は `vantage-pitfalls` にまとまっている。
@@ -1,6 +1,6 @@
1
1
  ---
2
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 コンポーネントやルーティングが思った通りに動かないとき、ビルドやスタイルが静かに欠落するときに参照する。
3
+ description: Vantage(@squadbase/vantage)アプリを書くときに静かに壊れる落とし穴のリファレンス。Base UI は Radix ではない(asChild 無し・render プロップ)、Select value/label、クライアントから server/ を import しない、EChart の配色と高さ、動的パラメータの3綴り同期、PUBLIC_ env、HttpError など。Vantage の UI コンポーネントやルーティングが思った通りに動かないとき、ビルドやスタイルが静かに欠落するときに参照する。
4
4
  ---
5
5
 
6
6
  # Vantage アプリの落とし穴リファレンス
@@ -19,26 +19,33 @@ Vantage の UI キットは shadcn/ui の **Base UI バリアント**。Radix
19
19
  - **`Checkbox` の `checked` は `boolean` のみ。** 中間状態は `indeterminate` プロップ。
20
20
  - **`ToggleGroup` の `value` は配列。**
21
21
 
22
- ### `SelectValue` は「選択中の value」を描く。`SelectItem` children は見ない
22
+ ### `SelectValue` value/label Vantage 側が吸収済み(残る 1 ケースだけ注意)
23
23
 
24
- 値とラベルが違うと、トリガーに `kanto` `/sales` といった**生の値**が出てしまう。ラベルを
25
- 出したいときは `items`(value→label のマップ)を渡す:
24
+ Base UI の `SelectValue` は選択中の **value をそのまま描き**、`SelectItem` の children を見ない
25
+ 部品。Vantage `Select` は children から `value` ラベルを集めて渡すので、普通に書けば
26
+ トリガーに「関東」と出る。**手当てが要るのは 1 ケースだけ** — `SelectItem` を別のコンポーネント
27
+ が返している場合は集められないので、`items` を明示する:
26
28
 
27
29
  ```tsx
28
- <Select items={{ kanto: "関東", kansai: "関西" }} … />
30
+ // SelectItem がこの場に無い(<RegionItems /> の中で作られる)ときだけ必要
31
+ <Select items={{ kanto: "関東", kansai: "関西" }} …>
32
+ <SelectContent><RegionItems /></SelectContent>
33
+ </Select>
29
34
  ```
30
35
 
31
- `FilterBarSelect` / `AppShell` header variant / `DataTablePagination` は内部でこの `items` を
32
- 組み立てている。自前で `Select` を使うときは忘れやすい。
36
+ トリガーに `kanto` `/sales` と生の値が出たら、まずこれを疑う。
33
37
 
34
- ## チャート:`EChart` はテーマに自動追従しない
38
+ ## チャート:`EChart` の配色はトークン追従。上書きは `option.color`
35
39
 
36
- `EChart` は薄いラッパー(init/resize/dispose・loading・`onEvents`・PNG コピー/ダウンロードだけ)。
37
- 配色は**各アプリが `option.color`** で決める(または `theme` に echarts テーマを渡す)。
40
+ 系列色は `--chart-1..5`、軸・凡例・ツールチップは文字色/境界色のトークンから組まれ、
41
+ ライト / ダークの切り替えにも追従する(init 時にトークンの実値を解決している)。
38
42
 
39
- - `--chart-1..5` を読んで明暗テーマを自動で組む機構は**意図的に無い**(ECharts はキャンバス
40
- 描画で CSS 変数を読めない)。ダークモード追従が要るなら自前で色を切り替える。
41
- - キャンバス系なので、コンテナに高さを与えないと何も見えない。
43
+ - **`theme` プロップを渡すと追従は完全に止まる。** 系列の色だけ変えたいなら `option.color` を
44
+ 使う option はテーマより優先されるので、軸まわりの追従は残る。
45
+ - キャンバス系なので、**コンテナに高さを与えないと何も見えない**(既定は `h-[400px]`。
46
+ `h-full` を使うなら親に確定した高さが要る)。
47
+ - `option` は `EChartsOption` を annotate するか `satisfies` を付ける。付けないと
48
+ `type: "category"` が `string` に広がってビルドが落ちる。
42
49
 
43
50
  ## 境界:クライアントから `server/` を import しない
44
51
 
@@ -52,16 +59,6 @@ Vantage の UI キットは shadcn/ui の **Base UI バリアント**。Radix
52
59
  それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。
53
60
  - `VITE_` 系の公開 API は無い。サーバーのシークレットは `ctx.env` で読む。
54
61
 
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
62
  ## ルーティング:動的パラメータは「3つの綴り」を同期させる
66
63
 
67
64
  ズレると静かにマッチしなくなる:
@@ -118,7 +115,7 @@ throw new Error("boom") // → ログに出て汎用 500 に丸
118
115
  - `vite.config.*` — Vite 設定は Vantage が所有
119
116
  - `tailwind.config.*` — テーマは `styles.css` のトークンで調整
120
117
  - `postcss.config.*` — Vantage が管理
121
- - `components.json` — UI は `vantage add ui <name>` でコピー
118
+ - `components.json` — shadcn CLI は使わない。UI は `@squadbase/vantage/ui` から import する
122
119
  - `vantage.config.*` — v1 に設定ファイルは無い(規約で表現)
123
120
 
124
121
  ## Markdown:`MarkdownRenderer` は `@squadbase/vantage/markdown` から
@@ -9,10 +9,9 @@
9
9
  > `vantage upgrade` を実行するとフレームワーク同梱の正本から再同期されて上書きされます。
10
10
  > アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
11
11
  >
12
- > 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています
13
- > (`vantage add skill --all` で `.claude/skills/` に配置)。このファイルは「地図」、Skill
14
- > 「手順書」という役割分担です。詰まったら `vantage-app` / `vantage-add-feature` /
15
- > `vantage-pitfalls` を参照してください。
12
+ > 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています。このファイルは
13
+ > 「地図」、Skill は「手順書」という役割分担です。**配置済みの Skill を先に探す**手順は
14
+ > 末尾の「さらに詳しく(同梱 Skill)」にあります。
16
15
 
17
16
  ## Vantage とは
18
17
 
@@ -29,9 +28,6 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
29
28
  - **import は必ず `@squadbase/vantage` のサブパス経由。** `@tanstack/*`・`@base-ui/react`・
30
29
  `echarts`・`hono`・`vite`・`tailwindcss` を直接 import しない。`lucide-react` のアイコンだけは
31
30
  直接 import してよい。
32
- - **モジュール間の相対 import はランタイムの `.js` 指定子で書く**(例:
33
- `./components/revenue-chart.js`)。ソースが `.tsx`/`.ts` でも拡張子は `.js` と書く。
34
- ESM の Node 解決に必要。`@squadbase/vantage` のサブパスはこの限りではない。
35
31
  - **クライアントコードは `server/` を import してはならない。** 違反は `CLIENT_IMPORTS_SERVER`
36
32
  エラー(静的にもビルド時にも弾かれる)。共有したいコードは `lib/` に置く。
37
33
  - **クライアントに届く env は `PUBLIC_` 接頭辞のものだけ。** `import.meta.env.PUBLIC_FOO`。
@@ -53,9 +49,21 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
53
49
  | `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
54
50
  | `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
55
51
  | `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
56
- | `@squadbase/vantage/server` | `ApiContext`・`HttpError`(server/ 側でのみ使う) |
52
+ | `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
57
53
 
58
- ## 最小アプリ(SPA モード)
54
+ ## モードは 2 つ、切り替えるのは `server/` の有無だけ
55
+
56
+ `server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
57
+ `mode` はここから自動で決まり、手で書く設定は無い。**SPA から始めて fullstack へ「昇格」する
58
+ 必要はない** ― テンプレートから始めた場合は最初から `server/` があり、その時点で fullstack。
59
+ 下の 2 節は段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
60
+
61
+ - `server/` が**ある** → 「API を持つ(fullstack モード)」を見る。データは自前の `/api/*` から
62
+ `useApiQuery` で取る。
63
+ - `server/` が**無い** → 「最小アプリ(2 ファイル)」の構成。外部 API を直接叩くなら `useQuery`。
64
+ 自前の API が要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
65
+
66
+ ## 最小アプリ(2 ファイル)
59
67
 
60
68
  `package.json` と `index.tsx` の 2 ファイルだけで動く。
61
69
 
@@ -74,7 +82,7 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
74
82
  "routes": "vantage routes"
75
83
  },
76
84
  "dependencies": {
77
- "@squadbase/vantage": "^0.1.0",
85
+ "@squadbase/vantage": "^0.2.0",
78
86
  "react": "^19.2.7",
79
87
  "react-dom": "^19.2.7"
80
88
  }
@@ -100,7 +108,7 @@ pnpm check # 静的診断(エラーがあれば exit 1)
100
108
  pnpm routes # ページ/API の URL マップ
101
109
  ```
102
110
 
103
- `server/` ディレクトリが無いので、この時点では **SPA モード**。
111
+ `server/` ディレクトリが無いので、この構成は **SPA モード**。
104
112
 
105
113
  ## ルーティング規約(ファイル名 → ルート)
106
114
 
@@ -199,13 +207,13 @@ if (q.isError) return <ErrorState message={(q.error as Error).message} />
199
207
  `QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
200
208
  refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
201
209
  上書きする。
202
- 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→「API を足して fullstack
203
- する」)。
210
+ 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→「API を持つ(fullstack
211
+ モード)」)
204
212
 
205
- ## API を足して fullstack にする
213
+ ## API を持つ(fullstack モード)
206
214
 
207
- **`server/` ディレクトリを作った瞬間に fullstack モードになる。** `server/api/**` の各ファイルが
208
- API ルートになる。
215
+ **`server/` ディレクトリがあれば fullstack モード**(無いアプリに足せば、その時点で切り替わる)。
216
+ `server/api/**` の各ファイルが API ルートになる。
209
217
 
210
218
  `server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
211
219
 
@@ -255,15 +263,16 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
255
263
  ## 組み込み UI / コンポーネントの入口
256
264
 
257
265
  - **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
258
- `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。編集したいコピーが要るなら
259
- `vantage add ui <name>` で `components/ui/` に取り出す。
266
+ `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)
260
267
  - **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
261
- `DataTablePreset`・`EChart` ほか)。ブロックは `vantage add block <name>` で取り出す。
268
+ `DataTablePreset`・`EChart` ほか)
262
269
  - **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
263
270
  リファレンスはパッケージに同梱されていて、オフラインでも読める(→「ドキュメントを引く」)。
264
271
  **名前が分からないとき**はやりたいことで `vantage search` する(→ 同節)。
265
- - `vantage add ui`・`vantage add block` でコピーできる名前は、未知名で実行すると候補が一覧表示
266
- される。
272
+ - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block`
273
+ ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
274
+ `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
275
+ `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
267
276
 
268
277
  ## ドキュメントを引く(`vantage docs` / `vantage search`)
269
278
 
@@ -305,9 +314,22 @@ pnpm preview # 本番ビルドをローカル実行
305
314
 
306
315
  ## さらに詳しく(同梱 Skill)
307
316
 
308
- `vantage add skill --all` で `.claude/skills/` に配置される:
317
+ - **`vantage-app`** アプリを一から作る / 構成を広げるときの進め方と検証の順序
318
+ - **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
319
+ - **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
320
+
321
+ **まず、このアプリに配置済みかを探す。** 手順書の本体は SKILL.md というファイルで、置き場所は
322
+ アプリによって違う:
323
+
324
+ ```bash
325
+ ls .claude/skills # よくある置き場所
326
+ ls -d vantage-* # ルート直下に置くのが既定
327
+ ```
328
+
329
+ 見つかったらその `SKILL.md` を読む(`vantage add skill` を再実行する必要はない)。**無いときだけ**
330
+ 配置する ― 一覧は名前を省いて実行すると出る:
309
331
 
310
- - **`vantage-app`** — アプリを一から作る / SPA を fullstack に広げる手順
311
- - **`vantage-add-feature`** 既存アプリに page / api / ui / block を 1 つ足す定型
312
- - **`vantage-pitfalls`** Base UI( Radix)の癖、`SelectValue` の挙動、EChart のテーマ
313
- 非追従など、静かに壊れる落とし穴のリファレンス
332
+ ```bash
333
+ vantage add skill # 同梱されている skill 名の一覧
334
+ vantage add skill --all --dir .claude/skills # 配置(--dir を省くとルート直下)
335
+ ```