@squadbase/vantage-template 0.1.0-dev.47d956c → 0.1.0-dev.a6b88b8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ npx @squadbase/vantage-template <command> [options]
14
14
 
15
15
  #### `init`
16
16
 
17
- Initialize a new Vantage project in the current directory. Copies the base template — `src/index.tsx`, `src/_layout.tsx`, `src/_404.tsx`, `src/_error.tsx`, `src/styles.css`, `server/api/`, `tsconfig.json`, `squadbase.yml`, plus the agent guidance in `AGENTS.md` and `.squadbase/skills/` — and installs dependencies.
17
+ Initialize a new Vantage project in the current directory. Copies the base template — `src/index.tsx`, `src/_layout.tsx`, `src/_404.tsx`, `src/_error.tsx`, `src/styles.css`, `server/api/`, `tsconfig.json`, `squadbase.yml`, plus the agent guidance in `AGENTS.md` (and `agents/`, when the framework ships per-type knowledge files) and `.squadbase/skills/` — and installs dependencies.
18
18
 
19
19
  ```bash
20
20
  npx @squadbase/vantage-template init
@@ -1,17 +1,16 @@
1
1
  # AGENTS.md — Vantage アプリの地図
2
2
 
3
3
  このファイルは、**Vantage(`@squadbase/vantage`)アプリを作るコーディングエージェント**が
4
- 最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)が守るべき規約・不変条件・
5
- 最小レシピを 1 ページに集約しています。ここに書かれた規約だけで、最小のダッシュボードアプリを
6
- 正しく組めます。
4
+ 最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)が守るべき規約・不変条件を
5
+ 1 ページに集約し、種類ごとの詳しいレシピは `agents/` 配下の知識ファイルに分けています。
7
6
 
8
- > このファイルは **Vantage フレームワークが所有する正本**のコピーです。手で書き換えても、
9
- > `vantage upgrade` を実行するとフレームワーク同梱の正本から再同期されて上書きされます。
10
- > アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
7
+ > このファイルと `agents/*.md` は、いずれも **Vantage フレームワークが所有する正本**の
8
+ > コピーです。手で書き換えても、`vantage upgrade` を実行するとフレームワーク同梱の正本から
9
+ > 再同期されて上書きされます。アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
11
10
  >
12
11
  > 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています。このファイルは
13
- > 「地図」、Skill は「手順書」という役割分担です。**配置済みの Skill を先に探す**手順は
14
- > 末尾の「さらに詳しく(同梱 Skill)」にあります。
12
+ > 「地図」、`agents/*.md` は「種類ごとの静的リファレンス」、Skill は「手順書」という役割分担です。
13
+ > **配置済みの Skill を先に探す**手順は末尾の「さらに詳しく(同梱 Skill)」にあります。
15
14
 
16
15
  ## Vantage とは
17
16
 
@@ -39,8 +38,8 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
39
38
  - **色はセマンティックトークンで書く。** `text-foreground`・`text-muted-foreground`・`bg-card`・
40
39
  `border-border`。トークンがライト / ダークの両方を持つので `dark:` を並べる必要が無い。
41
40
  素のパレット + `dark:`(`text-slate-900 dark:text-slate-100`)を混ぜると、その 1 か所だけ
42
- 手動で 2 モードを管理することになる(→「ダークモードは opt-in」)。`--muted`・`--accent`・
43
- `--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
41
+ 手動で 2 モードを管理することになる(→ `agents/page.md` の「ダークモードは opt-in」)。
42
+ `--muted`・`--accent`・`--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
44
43
  - **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
45
44
  コマンド、または `vantage upgrade` で再生成される。
46
45
  - **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
@@ -93,6 +92,8 @@ root/
93
92
  | `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
94
93
  | `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
95
94
  | `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
95
+ | `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
96
+ | `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`(スライド専用。1920×1080 の固定キャンバス) |
96
97
  | `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
97
98
  | `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
98
99
 
@@ -101,207 +102,24 @@ root/
101
102
  `server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
102
103
  `mode` はここから自動で決まり、手で書く設定は無い。**SPA から始めて fullstack へ「昇格」する
103
104
  必要はない** ― テンプレートから始めた場合は最初から `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
- ```
136
-
137
- `index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
138
-
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
- ```
148
-
149
- ```bash
150
- pnpm install
151
- pnpm dev # → http://localhost:5173(HMR)
152
- pnpm check # 静的診断(エラーがあれば exit 1)
153
- pnpm routes # ページ/API の URL マップ
154
- ```
105
+ 下の 2 つは段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
155
106
 
156
- `server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
157
- `src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
158
- どちらか一方に寄せること(→「ディレクトリ構造」)。
107
+ - `server/` が**ある** → データは自前の `/api/*` から `useApiQuery` で取る(→ `agents/api.md`)。
108
+ - `server/` が**無い** → 外部 API を直接叩くなら `useQuery`(→ `agents/page.md`)。自前の API が
109
+ 要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
159
110
 
160
- ## ルーティング規約(ファイル名 → ルート)
111
+ ## 何を書くか → どのファイルを読むか
161
112
 
162
- ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
163
- URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
164
- `MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
113
+ **書こうとしている種類のファイルだけを開けばよい。** まず該当する 1 ファイルを readFiles で
114
+ 読んでから書き始める。
165
115
 
166
- | ファイル | ルート |
116
+ | 書こうとしているもの | 読むファイル |
167
117
  | --- | --- |
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`。
118
+ | ダッシュボード / 通常ページ(`.tsx`) | `agents/page.md` |
119
+ | API(`server/`) | `agents/api.md` |
120
+ | スライド(`.slide.tsx`) | `agents/slide.md` |
121
+ | `.xlsx` ページ | `agents/xlsx.md` |
122
+ | `.csv` ページ | `agents/csv.md` |
305
123
 
306
124
  ## 環境変数
307
125
 
@@ -309,30 +127,6 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
309
127
  のみ。** `VITE_` 系の公開 API は無い。
310
128
  - サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
311
129
 
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
130
  ## ドキュメントを引く(`vantage docs` / `vantage search`)
337
131
 
338
132
  ```bash
@@ -378,6 +172,7 @@ body・レスポンスの status と形)まで出る。`vantage check --json` /
378
172
 
379
173
  - **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
380
174
  - **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
175
+ - **`vantage-slide`** — `.slide.tsx` でスライドデッキを書く(キャンバスの予算・段階表示・pptx 書き出し)
381
176
  - **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
382
177
  - **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
383
178
 
@@ -399,6 +194,9 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
399
194
  配置済みのコピーを新しいバージョンに更新したいときだけ `--force` を付ける(見つかった場所を
400
195
  そのまま書き換える)。
401
196
 
197
+ **役割分担:** Skill = 手順書(踏み込んだワークフロー) / `agents/*.md` = 種類ごとの静的
198
+ リファレンス / `AGENTS.md`(このファイル) = 地図。
199
+
402
200
  ---
403
201
 
404
202
  # テンプレート固有の規約(Squadbase テンプレート)
@@ -515,4 +313,4 @@ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
515
313
  `ErrorState` は見出しを `div` / `p` で描くのでそのまま使ってよい。
516
314
  - **データ取得の失敗は各ページが `ErrorState` で出す。** `BuildRecovery` が拾うのは
517
315
  「レンダリングが throw した」場合だけで、`useApiQuery` の `isError` はページの責任
518
- (→ フレームワーク規約の「データ取得」)。
316
+ (→ `agents/page.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`。
@@ -0,0 +1,32 @@
1
+ # csv ページ
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.csv` を
4
+ ページとして置くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## `.csv` を置くだけでページになる
8
+
9
+ **`.csv` を `src/` に置くだけで、他のページと同格のルートになります。** URL の作り方は
10
+ `.tsx` と同じ(`src/sales-export.csv` → `/sales-export`、`index.csv` はそのディレクトリ)で、
11
+ ナビにも `vantage routes` にも並びます。開くとフレームワーク同梱の表ビューアが描き、
12
+ 元ファイルをダウンロードできます。ビューアは**表計算ソフトの見た目**です —— 列は A / B / C、
13
+ 左に行番号、ファイルの 1 行目はヘッダとして固定。読み取り専用で、編集もソートもできません
14
+ (セルを選んで `Cmd/Ctrl+C` でコピーは可)。
15
+
16
+ - **タイトルはファイル名から導かれる**(`sales-export.csv` → 「Sales Export」)。データファイルに
17
+ `definePage` は書けません。名前を変えたいときはファイルをリネームします
18
+ - **ページにしたくないファイルは `_` を前置する**(`_sales-export.csv`)。`components/`・`hooks/`・
19
+ `lib/` に置いたものも同じくページになりません
20
+ - **既定はファイルをそのまま置く(tsx を書かない)。** 自分でタイトルや説明を付けたいときだけ、`_` を
21
+ 前置してルート化を止め、tsx から描きます:
22
+ ```tsx
23
+ import { FilePageView } from "@squadbase/vantage/table"
24
+ import src from "./_sales-export.csv?url&no-inline"
25
+ export const page = definePage({ title: "売上エクスポート" })
26
+ export default () => <FilePageView src={src} kind="csv" fileName="sales-export.csv" />
27
+ ```
28
+ - 数式・グラフ・セル書式といった概念は csv には無いので、値がそのまま読めます。ブラウザで
29
+ パースするので 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは
30
+ `server/api/` から配って `DataTable` で描いてください
31
+ - **ビューアの無い拡張子(`.tsv` など)はルートになりません。** `vantage check` の
32
+ `UNSUPPORTED_PAGE_FILE` 警告が知らせます
@@ -0,0 +1,185 @@
1
+ # ダッシュボード / 通常ページ(.tsx)
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.tsx` の通常ページ
4
+ (ダッシュボード)を書くときに読んでください。前提となる不変条件・ディレクトリ構造は
5
+ `AGENTS.md` を先に読んでいる想定です。
6
+
7
+ ## 最小アプリ(2 ファイル)
8
+
9
+ `package.json` と `index.tsx` の 2 ファイルだけで動く。
10
+
11
+ `package.json`:
12
+
13
+ ```json
14
+ {
15
+ "name": "my-dashboard",
16
+ "private": true,
17
+ "type": "module",
18
+ "scripts": {
19
+ "dev": "vantage dev",
20
+ "build": "vantage build",
21
+ "preview": "vantage preview",
22
+ "check": "vantage check",
23
+ "routes": "vantage routes"
24
+ },
25
+ "dependencies": {
26
+ "@squadbase/vantage": "^0.5.0",
27
+ "react": "^19.2.7",
28
+ "react-dom": "^19.2.7"
29
+ }
30
+ }
31
+ ```
32
+
33
+ `index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
34
+
35
+ ```tsx
36
+ export default function Dashboard() {
37
+ return (
38
+ <main className="p-6">
39
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
40
+ </main>
41
+ )
42
+ }
43
+ ```
44
+
45
+ ```bash
46
+ pnpm install
47
+ pnpm dev # → http://localhost:5173(HMR)
48
+ pnpm check # 静的診断(エラーがあれば exit 1)
49
+ pnpm routes # ページ/API の URL マップ
50
+ ```
51
+
52
+ `server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
53
+ `src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
54
+ どちらか一方に寄せること(→ `AGENTS.md` の「ディレクトリ構造」)。
55
+
56
+ ## ルーティング規約(ファイル名 → ルート)
57
+
58
+ ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
59
+ URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
60
+ `MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
61
+
62
+ | ファイル | ルート |
63
+ | --- | --- |
64
+ | `index.tsx` | `/` |
65
+ | `monthly-analysis.tsx` | `/monthly-analysis` |
66
+ | `sales/index.tsx` | `/sales` |
67
+ | `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
68
+ | `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト(`Outlet` で子を描く) |
69
+ | `_404.tsx` | Not Found ページ |
70
+ | `_error.tsx` | ルートが throw したときのエラーページ |
71
+ | `monthly-report.slide.tsx` | `/monthly-report`(スライドデッキ。詳しくは `agents/slide.md`) |
72
+ | `sales-export.csv` | `/sales-export`(詳しくは `agents/csv.md`) |
73
+ | `data/june.xlsx` | `/data/june`(詳しくは `agents/xlsx.md`) |
74
+
75
+ `components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
76
+ (ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→ `AGENTS.md` の
77
+ 「ディレクトリ構造」)。
78
+
79
+ ```tsx
80
+ import { definePage } from "@squadbase/vantage"
81
+
82
+ // title は document.title、navLabel は useRoutes() で組むナビの表示名(既定は title)
83
+ export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
84
+
85
+ export default function MonthlyAnalysis() {
86
+ return <main className="p-6">…</main>
87
+ }
88
+ ```
89
+
90
+ ### ナビはルート一覧から組む
91
+
92
+ `useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
93
+ `useCurrentRoute()` は現在のルート(404 なら `undefined`)。
94
+
95
+ ```tsx
96
+ import { Link, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
97
+
98
+ // 動的ルートは URL が定まらないので外す。label は navLabel → title → path の順
99
+ const routes = useRoutes().filter((r) => !r.dynamic)
100
+ const current = useCurrentRoute()
101
+
102
+ routes.map((r) => (
103
+ <Link key={r.path} to={r.to} activeClassName="font-semibold">
104
+ {r.label}
105
+ </Link>
106
+ ))
107
+ ```
108
+
109
+ `RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
110
+ `title`・`description`・`navLabel`・`kind`(`"module"` | `"file"`)・`variant`(`"slide"`)。
111
+ 後ろの 2 つで、ナビをダッシュボード / スライド / データに分けたりアイコンを変えたりできます。
112
+
113
+ ### フィルタ状態は URL に置く
114
+
115
+ リロードで消えず、URL をそのまま共有できる。`useState` と同じ形。
116
+
117
+ ```tsx
118
+ import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
119
+
120
+ const [region, setRegion] = useSearchParam("region", "all") // 常に string
121
+ const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON になる値
122
+ ```
123
+
124
+ デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
125
+ (`{ replace: false }` で push)。
126
+
127
+ ### 動的パラメータの「3 つの綴り」を同期させる
128
+
129
+ 崩すと静かにマッチしなくなる不変条件:
130
+
131
+ - ファイル名 `sales/[customerId].tsx`
132
+ - 表示ルート `/sales/:customerId`
133
+ - リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
134
+
135
+ ```tsx
136
+ import { Link, useParams } from "@squadbase/vantage/router"
137
+
138
+ <Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
139
+
140
+ // 詳細ページ側で取り出す
141
+ const { customerId } = useParams()
142
+ ```
143
+
144
+ ## データ取得(server を持たない fetch)
145
+
146
+ 外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
147
+
148
+ ```tsx
149
+ import { useQuery } from "@squadbase/vantage/query"
150
+ import { Loading, ErrorState } from "@squadbase/vantage/ui"
151
+
152
+ const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
153
+ if (q.isPending) return <Loading />
154
+ if (q.isError) return <ErrorState message={(q.error as Error).message} />
155
+ ```
156
+
157
+ `QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
158
+ refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
159
+ 上書きする。
160
+ 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→ `agents/api.md`)。
161
+
162
+ ## 組み込み UI / コンポーネントの入口
163
+
164
+ - **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
165
+ `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
166
+ - **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
167
+ `DataTablePreset`・`EChart` ほか)。
168
+ - **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
169
+ リファレンスはパッケージに同梱されていて、オフラインでも読める(→ `AGENTS.md` の
170
+ 「ドキュメントを引く」)。**名前が分からないとき**はやりたいことで `vantage search` する
171
+ (→ 同節)。
172
+ - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
173
+ ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
174
+ `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
175
+ `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
176
+
177
+ ### ダークモードは opt-in
178
+
179
+ ダークのトークンが効くのは、祖先に `.dark` か `[data-theme="dark"]` が付いているときだけ。
180
+ **OS / ブラウザの設定(`prefers-color-scheme`)は見ない。** `dark:` ユーティリティも同じ
181
+ スイッチに繋いであるので(`theme.css` の `@custom-variant dark`)、自分でクラスを付けない限り
182
+ アプリは常にライトで描かれる ― `dark:` を書いてもそこだけダークになることはない。
183
+
184
+ 明暗を切り替えたいときは、レイアウトのルート要素などに `.dark` / `data-theme="dark"` を
185
+ 自分で付ける。
@@ -0,0 +1,34 @@
1
+ # スライド(.slide.tsx)
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.slide.tsx` で
4
+ スライドデッキを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## スライドは `.slide.tsx` で書く
8
+
9
+ `src/monthly-report.slide.tsx` → `/monthly-report`。中身は `<SlideDeck>` を返す普通の React
10
+ コンポーネントで、`definePage` もナビも通常のページと同じように効きます。
11
+
12
+ ```tsx
13
+ import { Slide, SlideDeck } from "@squadbase/vantage/slide"
14
+
15
+ export const page = definePage({ title: "6月度レビュー", navLabel: "6月度" })
16
+ export default function Deck() {
17
+ return (
18
+ <SlideDeck>
19
+ <Slide>…</Slide>
20
+ <Slide>…</Slide>
21
+ </SlideDeck>
22
+ )
23
+ }
24
+ ```
25
+
26
+ 1920×1080 の固定キャンバスに絶対 px で書き、画面サイズに合わせて縮小されます。キー / クリックで
27
+ 送れ、左のスライド一覧(サムネイル列)は `L` で開閉、`F` で全画面、ツールバーから pptx を
28
+ 書き出せます。**書き方の作法は同梱 Skill の
29
+ `vantage-slide`** にあります(キャンバスの縦方向の予算、段階表示、トランジション、モーフ、
30
+ pptx が壊れる条件)。
31
+
32
+ `.slide` を付けられるのはルートになるページモジュールだけで、`_layout.slide.tsx` などは
33
+ `INVALID_PAGE_VARIANT` エラーになります。`june.slide.tsx` と `june.tsx` は同じ URL に解決するので
34
+ `ROUTE_CONFLICT` です。
@@ -0,0 +1,33 @@
1
+ # xlsx ページ
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.xlsx` を
4
+ ページとして置くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## `.xlsx` を置くだけでページになる
8
+
9
+ **`.xlsx` を `src/` に置くだけで、他のページと同格のルートになります。** URL の作り方は
10
+ `.tsx` と同じ(`src/data/june.xlsx` → `/data/june`、`index.xlsx` はそのディレクトリ)で、
11
+ ナビにも `vantage routes` にも並びます。開くとフレームワーク同梱の表ビューアが描き、複数シートを
12
+ 切り替えられ、元ファイルをダウンロードできます。ビューアは**表計算ソフトの見た目**です ——
13
+ 列は A / B / C、左に行番号、ファイルの 1 行目はヘッダとして固定、シートのタブは下端。
14
+ 読み取り専用で、編集もソートもできません(セルを選んで `Cmd/Ctrl+C` でコピーは可)。
15
+
16
+ - **タイトルはファイル名から導かれる**(`june.xlsx` → 「June」)。データファイルに `definePage` は
17
+ 書けません。名前を変えたいときはファイルをリネームします
18
+ - **ページにしたくないファイルは `_` を前置する**(`_june.xlsx`)。`components/`・`hooks/`・`lib/` に
19
+ 置いたものも同じくページになりません
20
+ - **既定はファイルをそのまま置く(tsx を書かない)。** 自分でタイトルや説明を付けたいときだけ、`_` を
21
+ 前置してルート化を止め、tsx から描きます:
22
+ ```tsx
23
+ import { FilePageView } from "@squadbase/vantage/table"
24
+ import src from "./_june.xlsx?url&no-inline"
25
+ export const page = definePage({ title: "6月度 売上レポート" })
26
+ export default () => <FilePageView src={src} kind="xlsx" fileName="june.xlsx" />
27
+ ```
28
+ - **複数シートに対応**しています。シートのタブは下端に並び、切り替えて閲覧できます。
29
+ - 数式・グラフ・セル書式は再現されません(値として読めれば十分、という設計)。ブラウザでパースするので
30
+ 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは `server/api/` から配って
31
+ `DataTable` で描いてください
32
+ - **ビューアの無い拡張子(`.xls`・`.ods` など)はルートになりません。** `vantage check` の
33
+ `UNSUPPORTED_PAGE_FILE` 警告が知らせます
File without changes
@@ -7,7 +7,7 @@
7
7
  "name": "vibe-app",
8
8
  "dependencies": {
9
9
  "@squadbase/react": "^0.1.0",
10
- "@squadbase/vantage": "^0.5.3",
10
+ "@squadbase/vantage": "0.6.0-dev.3cc6138",
11
11
  "@squadbase/vantage-sdk": "^0.1.0",
12
12
  "date-fns": "^4.1.0",
13
13
  "lucide-react": "^0.468.0",
@@ -1396,9 +1396,9 @@
1396
1396
  }
1397
1397
  },
1398
1398
  "node_modules/@squadbase/vantage": {
1399
- "version": "0.5.3",
1400
- "resolved": "https://registry.npmjs.org/@squadbase/vantage/-/vantage-0.5.3.tgz",
1401
- "integrity": "sha512-wu/qY1S1x1+zSlYU53BaLnCQdWSE6lq7Q5plBqJkYF4CR1/Y9zfb4govMmVF6w1s1oDsgWolBUgkDPh17vkHrw==",
1399
+ "version": "0.6.0-dev.3cc6138",
1400
+ "resolved": "https://registry.npmjs.org/@squadbase/vantage/-/vantage-0.6.0-dev.3cc6138.tgz",
1401
+ "integrity": "sha512-PcpmsDGkjQ5uEYTAkxiVRqCLpYl/M1zUfSdFbu+PKkk3fcP0uPq4ON4vtIMZCCYpdACQ1l3hzKb4P2zFj4tamw==",
1402
1402
  "license": "MIT",
1403
1403
  "dependencies": {
1404
1404
  "@base-ui/react": "^1.6.0",
@@ -1415,10 +1415,14 @@
1415
1415
  "date-fns": "^4.1.0",
1416
1416
  "echarts": "^6.0.0",
1417
1417
  "esbuild": "^0.28.1",
1418
- "hono": "^4.12.30",
1418
+ "fflate": "^0.8.3",
1419
+ "hono": "^4.13.5",
1420
+ "html-to-image": "^1.11.13",
1419
1421
  "lucide-react": "^0.468.0",
1422
+ "papaparse": "^5.7.0",
1420
1423
  "picocolors": "^1.1.1",
1421
1424
  "react-day-picker": "^10.0.1",
1425
+ "read-excel-file": "^9.3.10",
1422
1426
  "streamdown": "^2.5.0",
1423
1427
  "tailwind-merge": "^3.0.0",
1424
1428
  "tailwindcss": "^4.3.2",
@@ -1429,7 +1433,7 @@
1429
1433
  "vantage": "dist/cli.js"
1430
1434
  },
1431
1435
  "engines": {
1432
- "node": ">=20"
1436
+ "node": ">=22"
1433
1437
  },
1434
1438
  "peerDependencies": {
1435
1439
  "react": ">=18.2.0",
@@ -3151,6 +3155,12 @@
3151
3155
  }
3152
3156
  }
3153
3157
  },
3158
+ "node_modules/fflate": {
3159
+ "version": "0.8.3",
3160
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
3161
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
3162
+ "license": "MIT"
3163
+ },
3154
3164
  "node_modules/fsevents": {
3155
3165
  "version": "2.3.3",
3156
3166
  "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3367,6 +3377,12 @@
3367
3377
  "node": ">=16.9.0"
3368
3378
  }
3369
3379
  },
3380
+ "node_modules/html-to-image": {
3381
+ "version": "1.11.13",
3382
+ "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
3383
+ "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
3384
+ "license": "MIT"
3385
+ },
3370
3386
  "node_modules/html-url-attributes": {
3371
3387
  "version": "3.0.1",
3372
3388
  "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -4738,6 +4754,12 @@
4738
4754
  "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
4739
4755
  }
4740
4756
  },
4757
+ "node_modules/node-int64": {
4758
+ "version": "0.4.0",
4759
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
4760
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
4761
+ "license": "MIT"
4762
+ },
4741
4763
  "node_modules/oniguruma-parser": {
4742
4764
  "version": "0.12.2",
4743
4765
  "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
@@ -4761,6 +4783,12 @@
4761
4783
  "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==",
4762
4784
  "license": "MIT"
4763
4785
  },
4786
+ "node_modules/papaparse": {
4787
+ "version": "5.7.0",
4788
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.7.0.tgz",
4789
+ "integrity": "sha512-qBGxg/7Q3Kl9Wfhrz2Z74UnvnHTXLNG6jmKJFeBvP2+y4lV7So+7SR62+Zd47JvdrCkX+nDcnr0ObPzek/+6RA==",
4790
+ "license": "MIT"
4791
+ },
4764
4792
  "node_modules/parse-entities": {
4765
4793
  "version": "4.0.2",
4766
4794
  "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
@@ -4992,6 +5020,21 @@
4992
5020
  }
4993
5021
  }
4994
5022
  },
5023
+ "node_modules/read-excel-file": {
5024
+ "version": "9.3.10",
5025
+ "resolved": "https://registry.npmjs.org/read-excel-file/-/read-excel-file-9.3.10.tgz",
5026
+ "integrity": "sha512-zFcBdzunLCGBmLRT4Q3mLPJnhKPAXXfGAZ83feODlEqX2aRUbgF1h/n2oY0UF7I8tVwnJNRas6fTRD5veDUs+g==",
5027
+ "license": "MIT",
5028
+ "dependencies": {
5029
+ "fflate": "^0.8.3",
5030
+ "saxen": "^11.1.0",
5031
+ "unzipper-esm": "^0.13.3",
5032
+ "worker-f": "^0.1.12"
5033
+ },
5034
+ "engines": {
5035
+ "node": ">=18"
5036
+ }
5037
+ },
4995
5038
  "node_modules/regex": {
4996
5039
  "version": "6.1.0",
4997
5040
  "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
@@ -5195,6 +5238,15 @@
5195
5238
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
5196
5239
  "license": "MIT"
5197
5240
  },
5241
+ "node_modules/saxen": {
5242
+ "version": "11.1.1",
5243
+ "resolved": "https://registry.npmjs.org/saxen/-/saxen-11.1.1.tgz",
5244
+ "integrity": "sha512-J4BkmJFaM7VgE7pgkFGsNEcqqM3h7+Mz80vfLWFhx7uNOCOXIu6LLjQHYWNejdst3pf/3JUaBIG9+pkk1umlow==",
5245
+ "license": "MIT",
5246
+ "engines": {
5247
+ "node": ">= 20.12"
5248
+ }
5249
+ },
5198
5250
  "node_modules/scheduler": {
5199
5251
  "version": "0.27.0",
5200
5252
  "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -5535,6 +5587,19 @@
5535
5587
  "url": "https://opencollective.com/unified"
5536
5588
  }
5537
5589
  },
5590
+ "node_modules/unzipper-esm": {
5591
+ "version": "0.13.3",
5592
+ "resolved": "https://registry.npmjs.org/unzipper-esm/-/unzipper-esm-0.13.3.tgz",
5593
+ "integrity": "sha512-LUO6VZ6fCzkDbdMev0/fOhoIeVGKaOkTIOoYxVLE0SQjfvmAHK+oywl7lfhloSZIsdGJ25mJ18Mtd9CyTASjrA==",
5594
+ "license": "MIT",
5595
+ "dependencies": {
5596
+ "graceful-fs": "^4.2.2",
5597
+ "node-int64": "^0.4.0"
5598
+ },
5599
+ "engines": {
5600
+ "node": ">=8.0.0"
5601
+ }
5602
+ },
5538
5603
  "node_modules/use-callback-ref": {
5539
5604
  "version": "1.3.3",
5540
5605
  "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -5729,6 +5794,15 @@
5729
5794
  "url": "https://github.com/sponsors/wooorm"
5730
5795
  }
5731
5796
  },
5797
+ "node_modules/worker-f": {
5798
+ "version": "0.1.20",
5799
+ "resolved": "https://registry.npmjs.org/worker-f/-/worker-f-0.1.20.tgz",
5800
+ "integrity": "sha512-7z5K5z4x++FykhpDTfriT/dOu7CmSap9BBv38DVFU3CD38obopq+DoFH75yBV3butpGm+OeqR9BKdSvYJSnUfQ==",
5801
+ "license": "MIT",
5802
+ "engines": {
5803
+ "node": ">=12"
5804
+ }
5805
+ },
5732
5806
  "node_modules/zod": {
5733
5807
  "version": "4.4.3",
5734
5808
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@squadbase/react": "^0.1.0",
15
- "@squadbase/vantage": "^0.5.3",
15
+ "@squadbase/vantage": "0.6.0-dev.3cc6138",
16
16
  "@squadbase/vantage-sdk": "^0.1.0",
17
17
  "date-fns": "^4.1.0",
18
18
  "lucide-react": "^0.468.0",
@@ -0,0 +1,5 @@
1
+ [project]
2
+ name = "pipelines"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.11"
5
+ dependencies = ["adamas>=0.2.0", "pandas", "requests"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squadbase/vantage-template",
3
- "version": "0.1.0-dev.47d956c",
3
+ "version": "0.1.0-dev.a6b88b8",
4
4
  "description": "CLI tool for initializing and customizing Squadbase Vantage projects.",
5
5
  "keywords": [
6
6
  "squadbase",
@@ -1,118 +0,0 @@
1
- ---
2
- name: adamas-automation
3
- description: automation(データパイプライン)の作り方。automation/<name>/ のファイル配置、automation.yml v1 の書き方(GitHub Actions サブセット)、adamas @task / outputs / partition の宣言 API、実行と検証の流れ(lintAutomations → runAutomation dryRun → 本実行)。外部データを定期的に取り込んで Squadbase DB に同期するパイプラインを作る・直すときに参照する。
4
- ---
5
-
6
- # Automation(adamas データパイプライン)
7
-
8
- 外部データを定期取得して project の Squadbase DB へ同期するパイプライン。定義の正本は
9
- この repo の `automation/` ディレクトリで、コミットすると platform に自動同期される。
10
- ダッシュボード(`src/`)とは独立していて、互いのファイルには触れない。
11
-
12
- ## ファイル配置
13
-
14
- ```
15
- automation/
16
- ├── pyproject.toml # 共有 Python 環境(全 automation で 1 つ)。依存は `uv add`
17
- ├── uv.lock
18
- └── <name>/ # automation 名 = ディレクトリ名 = ^[a-z0-9][a-z0-9_-]{0,63}$
19
- ├── automation.yml # 宣言(トリガ・ステップ)。1 automation = 1 ファイル
20
- └── tasks.py # adamas @task の実装(pipeline step が暗黙に読む。ファイル名固定)
21
- ```
22
-
23
- - 初回だけ `automation/pyproject.toml` を作り `uv add adamas` する(全 automation で共有。
24
- automation ごとに venv を作らない)
25
- - `automation/` が無いディレクトリ構成でも、上の形で新規作成すればよい
26
-
27
- ## automation.yml v1 — GitHub Actions のサブセット
28
-
29
- GHA ワークフロー YAML の意味論をそのまま使う。**GHA との差分は次の 7 点だけ**。
30
- それ以外の書き方(`on:` / `steps:` / `- id:` / `if:` / `${{ }}` / `steps.<id>.outputs.<name>`)
31
- は GHA の知識どおりに書く:
32
-
33
- | # | 差分 |
34
- |---|---|
35
- | 1 | step は `uses:`/`run:` でなく型付きキー 1 個(`pipeline` / `agent` / `query` / `write` / `notify`)。v1 で実行できるのは `pipeline` のみ(他は宣言だけで UI に載る) |
36
- | 2 | `on.schedule` は単一オブジェクトで `timezone` / `enabled` を持つ(cron 配列は不可) |
37
- | 3 | 手動トリガは `on.manual`(GHA の `workflow_dispatch` の改名) |
38
- | 4 | inputs の `type` は `string | boolean | daterange` の 3 種 |
39
- | 5 | 式 `${{ }}` は dot 参照のみ(関数・演算子・パイプは不可)。context は `trigger.inputs.<name>` と `steps.<id>.outputs.<name>` だけ。前方・自己参照は不可 |
40
- | 6 | pipeline step のタスク実体は同ディレクトリ `tasks.py` 固定(automation 1 個につき pipeline step は最大 1) |
41
- | 7 | pipeline step に `destination`(取り込み先 Squadbase DB の connection uuid)を書ける。省略時は project 唯一の Squadbase DB が既定 |
42
-
43
- 例(実行可能な最小形):
44
-
45
- ```yaml
46
- version: 1
47
- name: Daily ad spend sync
48
- on:
49
- schedule: { cron: "0 6 * * *", timezone: Asia/Tokyo }
50
- manual:
51
- inputs:
52
- backfill: { type: daterange, required: false }
53
- steps:
54
- - id: sync
55
- pipeline:
56
- backfill: ${{ trigger.inputs.backfill }}
57
- connections: # ctx.connection(name) で使う接続の配線(任意)
58
- warehouse: <connection-uuid>
59
- ```
60
-
61
- - スケジュールは年 2 回以上の頻度が必要(それ未満は同期時に拒否される)
62
- - `agent` / `query` / `write` / `notify` step を含む automation はスケジュール実行できない
63
- (宣言と UI 表示のみ)
64
-
65
- ## tasks.py — adamas の宣言 API
66
-
67
- ```python
68
- from adamas import task, output, by_date, source
69
-
70
- @task(
71
- name="ad-spend", # 出力データセット名(省略時は関数名)
72
- table="ad_spend", # Squadbase DB のテーブル名
73
- pk=["date", "campaign_id"], # upsert / ignore では必須
74
- mode="upsert", # append | ignore(先勝ち) | upsert(後勝ち)
75
- schema={"date": "string", "campaign_id": "string", "cost": "float64"},
76
- partition=by_date("2026-01-01"), # 1 日 1 パーティション(付けると増分実行になる)
77
- inputs=[source.connection("warehouse")],
78
- )
79
- def ad_spend(ctx):
80
- date = ctx.partition.inputs["date"] # 自分のスライスは ctx.partition.inputs から読む
81
- conn = ctx.connection("warehouse") # automation.yml の connections で配線した接続
82
- yield from conn.query(
83
- f"SELECT ... WHERE date = '{date}'", chunk_rows=10_000
84
- )
85
- ```
86
-
87
- 押さえること:
88
-
89
- - 戻り値は pandas/polars DataFrame・pyarrow Table、またはそれらの yield(チャンクで
90
- メモリ有界にする)。複数出力は `outputs={...}` + `yield ("<dataset>", chunk)`
91
- - `schema` は Parquet 型を固定する列型宣言。**schema の無い出力は Squadbase DB へ
92
- 取り込まれない**ので必ず書く。pk 列は schema に含める
93
- - partition プランは `by_date(start)`(日次)か `by_list(keys)`(キー列挙)。
94
- partition 付きタスクは単一出力
95
- - 1 テーブル 1 書き手(別タスクが同じテーブルを書くとエラー)
96
- - 抽出・再試行・冪等性(どこまで取ったか)は adamas と台帳が管理する。自前で
97
- チェックポイントや重複排除を実装しない
98
-
99
- ## 検証と実行の流れ
100
-
101
- 1. ファイルを作成・編集したら **`lintAutomations`** で検証し、指摘を直す
102
- 2. **`runAutomation` を `dryRun: true` で実行**(オフライン: ローカル一時ディレクトリに
103
- 書き、platform の台帳・ストレージには何も書かない)。まず 1 パーティションだけ
104
- `partitions: ["2026-01-01"]` のように絞ると速い
105
- 3. コミットすると宣言が platform に自動同期され、スケジュールが有効になる
106
- 4. 本実行(`dryRun` なし)はユーザーがデータを求めたときだけ。結果の `adamasStatus` に
107
- タスクごとの done / failed / outstanding と失敗の内訳(error / category)が載るので、
108
- それを根拠に報告・修正する
109
- 5. 失敗を直したら `retryFailed: true` で失敗パーティションだけ再実行。過去範囲の
110
- 取り直しは `backfill: "START:END"`(両端含む)
111
-
112
- ## 落とし穴
113
-
114
- - automation 名にハイフンは使えるが、Python module 名には使わない(module は常に
115
- `tasks.py` 固定なので気にしなくてよい)
116
- - `${{ }}` に式や関数を書かない(`| length > 0` などの Jinja 方言は不可)
117
- - cron は 5 フィールド・64 文字以内
118
- - `automation.yml` に未知のキーを書くと lint エラー(黙って無視されない)