@squadbase/vantage-template 0.0.2 → 0.1.0-dev.55898c0

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 (41) hide show
  1. package/README.md +9 -7
  2. package/base-template/.squadbase/skills/project-storage/SKILL.md +157 -0
  3. package/base-template/.squadbase/skills/vantage-app/SKILL.md +6 -6
  4. package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +22 -0
  5. package/base-template/AGENTS.md +83 -220
  6. package/base-template/agents/api.md +49 -0
  7. package/base-template/agents/csv.md +32 -0
  8. package/base-template/agents/page.md +185 -0
  9. package/base-template/agents/slide.md +34 -0
  10. package/base-template/agents/xlsx.md +33 -0
  11. package/base-template/automations/.gitkeep +0 -0
  12. package/base-template/bun.lock +907 -0
  13. package/base-template/package.json +3 -3
  14. package/base-template/pipelines/pyproject.toml +5 -0
  15. package/base-template/squadbase.yml +1 -0
  16. package/base-template/src/_error.tsx +7 -11
  17. package/base-template/src/_layout.tsx +8 -1
  18. package/base-template/src/components/build-recovery.tsx +219 -0
  19. package/base-template/src/styles.css +19 -0
  20. package/dist/index.js +16 -7
  21. package/package.json +18 -12
  22. package/ui-templates/chart-grid/preview-square.png +0 -0
  23. package/ui-templates/chart-grid/preview-wide.png +0 -0
  24. package/ui-templates/chart-grid-ja/preview-square.png +0 -0
  25. package/ui-templates/chart-grid-ja/preview-wide.png +0 -0
  26. package/ui-templates/funnel/preview-square.png +0 -0
  27. package/ui-templates/funnel/preview-wide.png +0 -0
  28. package/ui-templates/funnel-ja/preview-square.png +0 -0
  29. package/ui-templates/funnel-ja/preview-wide.png +0 -0
  30. package/ui-templates/kpi-chart-advanced/preview-square.png +0 -0
  31. package/ui-templates/kpi-chart-advanced/preview-wide.png +0 -0
  32. package/ui-templates/kpi-chart-advanced-ja/preview-square.png +0 -0
  33. package/ui-templates/kpi-chart-advanced-ja/preview-wide.png +0 -0
  34. package/ui-templates/kpi-chart-simple/preview-square.png +0 -0
  35. package/ui-templates/kpi-chart-simple/preview-wide.png +0 -0
  36. package/ui-templates/kpi-chart-simple-ja/preview-square.png +0 -0
  37. package/ui-templates/kpi-chart-simple-ja/preview-wide.png +0 -0
  38. package/ui-templates/table-focus/preview-square.png +0 -0
  39. package/ui-templates/table-focus/preview-wide.png +0 -0
  40. package/ui-templates/table-focus-ja/preview-square.png +0 -0
  41. package/ui-templates/table-focus-ja/preview-wide.png +0 -0
package/README.md CHANGED
@@ -14,22 +14,22 @@ 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
21
21
  npx @squadbase/vantage-template init --force # Overwrite existing files
22
- npx @squadbase/vantage-template init --skip-install # Skip npm install
22
+ npx @squadbase/vantage-template init --skip-install # Skip dependency install (bun, or npm if bun is absent)
23
23
  npx @squadbase/vantage-template init --chart sunset # Apply a chart preset during init
24
24
  ```
25
25
 
26
- After initialization:
26
+ After initialization (the template ships `bun.lock` and declares `package_manager: bun` in `squadbase.yml`; `npm run` works too once dependencies are installed):
27
27
 
28
28
  ```bash
29
- npm run dev # vantage dev --no-overlay — dev server + API on :5173
30
- npm run build # vantage build → dist/
31
- npm run check # vantage check — static diagnostics
32
- npm run routes # vantage routes — page/API route map
29
+ bun run dev # vantage dev --no-overlay — dev server + API on :5173
30
+ bun run build # vantage build → dist/
31
+ bun run check # vantage check — static diagnostics
32
+ bun run routes # vantage routes — page/API route map
33
33
  ```
34
34
 
35
35
  There is no `vite.config.ts`, no `main.tsx`, and no route table: adding a file under `src/` adds a route (`src/index.tsx` → `/`, `src/sales/[id].tsx` → `/sales/:id`), and `src/_layout.tsx` builds the nav from `useRoutes()`.
@@ -56,6 +56,8 @@ npx @squadbase/vantage-template add funnel --force # Overwrite existing fil
56
56
 
57
57
  Switch the chart color preset. Rewrites a marked block of `--chart-1`–`--chart-5` inside the project's `styles.css` (under `src/` when that layout is in use), leaving any other overrides in that file intact. Re-running replaces the block rather than stacking.
58
58
 
59
+ A new project starts on the `blue` preset — the base template ships that block already written, so `init` and the gallery previews show the same colors. This command swaps it for another preset.
60
+
59
61
  ```bash
60
62
  npx @squadbase/vantage-template chart ocean
61
63
  npx @squadbase/vantage-template chart forest --dry-run
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: project-storage
3
+ description: Squadbase の Project Storage(アップロード済みファイル)を @squadbase/react の useStorage() でブラウザから扱う手順。ファイル一覧、画像 / 動画 / PDF の埋め込み、中身の読み取り、アップロードと削除・移動。ページやコンポーネントがアップロード済みファイルを表示・埋め込み・アップロードするときに参照する。
4
+ ---
5
+
6
+ # Project Storage(ブラウザ側)
7
+
8
+ Squadbase の **Project Storage** を React コンポーネントから読み書きする手順。Squadbase 上で
9
+ 動くアプリでは資格情報も設定も要らず、そのまま動く。サーバー(`server/api/**`)からストレージを
10
+ 触る API は別物で、ここでは扱わない。
11
+
12
+ ## import は `@squadbase/react` から直接
13
+
14
+ 「import は `@squadbase/vantage` のサブパス経由」はフレームワークが所有するものの話。
15
+ **`@squadbase/react` は Squadbase プラットフォームの SDK なので直接 import する**
16
+ (`SquadbaseProvider` / `useUser` と同じ)。
17
+
18
+ ```tsx
19
+ import { useStorage } from "@squadbase/react"
20
+
21
+ const storage = useStorage()
22
+ // 呼び出しはすべて storage.uploads.* の下
23
+ ```
24
+
25
+ `useStorage()` が返すクライアントは安定なので、コンポーネント先頭で呼んで `useQuery` /
26
+ `useMutation` の中で使ってよい。データ取得のフックは `@squadbase/vantage/query` から取る
27
+ (`@tanstack/*` は直接 import しない)。
28
+
29
+ ## API
30
+
31
+ すべて async。`key` はストレージ内の**相対**パス(例: `reports/2025-q1.pdf`)で、先頭に
32
+ スラッシュを付けない。
33
+
34
+ 読み取り:
35
+
36
+ - `list(prefix?, options?)` → `{ objects, commonPrefixes, nextCursor? }`。`objects` は
37
+ `{ key, size, lastModified }[]`。`options.delimiter = "/"` でフォルダ表示(サブフォルダが
38
+ `commonPrefixes` に入る)、省略すると再帰的なフラット一覧。`options.limit` / `options.cursor`
39
+ でページング(`cursor` は前回の `nextCursor`)
40
+ - `head(key)` → `{ key, size, contentType, createdAt }`。メタ情報だけで本体は取らない
41
+ - `getUrl(key)` → `{ url, expiresAt }`。埋め込み用の一時 URL
42
+ - `get(key)` → 生の `Response`。`.json()` / `.text()` / `.blob()` / `.arrayBuffer()` で読む
43
+
44
+ 書き込み:
45
+
46
+ - `put(key, data, options?)` → `void`。`data` は `Blob`(`File` を含む) / `ArrayBuffer` /
47
+ `Uint8Array` / `string`
48
+ - `delete(key)` → `void`
49
+ - `copy(srcKey, destKey, options?)` / `move(srcKey, destKey, options?)` → `void`
50
+
51
+ ## 一覧を出す
52
+
53
+ ```tsx
54
+ import { useStorage } from "@squadbase/react"
55
+ import { useQuery } from "@squadbase/vantage/query"
56
+
57
+ const storage = useStorage()
58
+ const { data, isPending, isError } = useQuery({
59
+ queryKey: ["storage", "list", prefix],
60
+ queryFn: () => storage.uploads.list(prefix, { delimiter: "/" }),
61
+ })
62
+ ```
63
+
64
+ `data.commonPrefixes` をフォルダ、`data.objects` をファイルとして描く。プレフィックス配下を
65
+ まとめて出すなら `delimiter` を省く。
66
+
67
+ ## 画像 / 動画 / PDF を埋め込む
68
+
69
+ 埋め込みには URL が要るので `getUrl` を使う。**URL には期限がある**ので、切れる前に取り直す:
70
+
71
+ ```tsx
72
+ import { useStorage } from "@squadbase/react"
73
+ import { useQuery } from "@squadbase/vantage/query"
74
+
75
+ function useStorageUrl(key: string) {
76
+ const storage = useStorage()
77
+ return useQuery({
78
+ queryKey: ["storage", "url", key],
79
+ queryFn: () => storage.uploads.getUrl(key),
80
+ refetchInterval: (query) => {
81
+ const expiresAt = query.state.data?.expiresAt
82
+ if (!expiresAt) return false
83
+ // 期限の 60 秒前に取り直す
84
+ return Math.max(0, new Date(expiresAt).getTime() - Date.now() - 60_000)
85
+ },
86
+ })
87
+ }
88
+ ```
89
+
90
+ ```tsx
91
+ import { ErrorState, Loading } from "@squadbase/vantage/ui"
92
+
93
+ const { data, isPending, isError } = useStorageUrl("reports/cover.png")
94
+ if (isPending) return <Loading />
95
+ if (isError) return <ErrorState message="ファイルを読み込めませんでした" />
96
+ return <img src={data.url} alt="" className="w-full rounded-lg" />
97
+ ```
98
+
99
+ 動画は `<video src={data.url} controls className="w-full rounded-lg" />`、PDF は
100
+ `<iframe src={data.url} className="h-[80vh] w-full rounded-lg border border-border" />`。
101
+
102
+ ## 中身を JS で読む
103
+
104
+ ```tsx
105
+ const { data } = useQuery({
106
+ queryKey: ["storage", "content", key],
107
+ queryFn: async () => {
108
+ const res = await storage.uploads.get(key)
109
+ return (await res.json()) as Row[] // または res.text()
110
+ },
111
+ })
112
+ ```
113
+
114
+ ## アップロードする
115
+
116
+ ```tsx
117
+ import { useStorage } from "@squadbase/react"
118
+ import { useMutation, useQueryClient } from "@squadbase/vantage/query"
119
+
120
+ const storage = useStorage()
121
+ const queryClient = useQueryClient()
122
+ const upload = useMutation({
123
+ mutationFn: (file: File) =>
124
+ storage.uploads.put(`uploads/${file.name}`, file, { contentType: file.type }),
125
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage", "list"] }),
126
+ })
127
+
128
+ <input
129
+ type="file"
130
+ onChange={(e) => {
131
+ const file = e.target.files?.[0]
132
+ if (file) upload.mutate(file)
133
+ }}
134
+ />
135
+ ```
136
+
137
+ `File` はそのまま `put` に渡す。書き込み後は該当する `["storage", "list", …]` を invalidate して
138
+ UI を追従させる。
139
+
140
+ ## 落とし穴
141
+
142
+ - **`getUrl` の URL は短命。** 結果を永続化したり長寿命のキャッシュに入れたりしない。
143
+ `expiresAt` の前に取り直す(上のフック)。`<img src>` に一度入れて放置すると、開いたままの
144
+ 画面で後から読み込まれた分が失敗する
145
+ - **`put` の `contentType` の既定は `application/octet-stream`。** 後で埋め込むもの(画像・動画・
146
+ PDF)には `file.type` を必ず渡す。付けないとブラウザが表示せずダウンロードに落ちる
147
+ - **`key` は相対パス。** 先頭にスラッシュを付けない。`getUrl` を通さず `<img src="/foo.png">` の
148
+ ような静的パスで参照しない ― ストレージはアプリの `public/` ではない
149
+ - **`copy` / `move` は既定で既存の宛先を上書きする。** 残したいときは `{ overwrite: false }`
150
+ - **サーバー(`server/api/**`)からは呼ばない。** `useStorage` はブラウザ側の API
151
+
152
+ ---
153
+
154
+ ## 関連スキル
155
+
156
+ - `vantage-add-feature` — ページ / API / コンポーネントを 1 つ足す手順
157
+ - `vantage-pitfalls` — UI・配色・ルーティングの落とし穴
@@ -14,7 +14,7 @@ description: Vantage(@squadbase/vantage)ダッシュボードアプリを一か
14
14
 
15
15
  ## 全体の流れ
16
16
 
17
- 1. **骨組み** — `pnpm dev` が上がるところまで
17
+ 1. **骨組み** — `bun run dev` が上がるところまで
18
18
  2. **ルートを決める** — ページファイルを置き、`vantage routes` で URL を確認
19
19
  3. **データを繋ぐ** — 外部 API か、自前の `server/api` か
20
20
  4. **仕上げ** — `check` → `build` → `preview`
@@ -52,7 +52,7 @@ exit 1 になる。
52
52
  ```
53
53
 
54
54
  `index.tsx` はデフォルトエクスポートの React コンポーネント 1 つ(これが `/` になる)。あとは
55
- `pnpm install && pnpm dev` で http://localhost:5173 が上がる。
55
+ `bun install && bun run dev` で http://localhost:5173 が上がる(このプロジェクトの package manager は bun。`bun.lock` を持つ)。
56
56
 
57
57
  **既存アプリに合流したときは、作る前に現状を読む:**
58
58
 
@@ -105,10 +105,10 @@ vantage add skill # 配置済みの skill(このファイルの仲間)と、そ
105
105
  ## Step 4 — 仕上げ
106
106
 
107
107
  ```bash
108
- pnpm check # エラーが残っていないか(exit 1 なら残っている)
109
- pnpm routes # 公開される URL の最終確認
110
- pnpm build # dist/ に client(+ server)+ vantage-manifest.json
111
- pnpm preview # 本番ビルドをローカルで動かす
108
+ bun run check # エラーが残っていないか(exit 1 なら残っている)
109
+ bun run routes # 公開される URL の最終確認
110
+ bun run build # dist/ に client(+ server)+ vantage-manifest.json
111
+ bun run preview # 本番ビルドをローカルで動かす
112
112
  ```
113
113
 
114
114
  `vantage-manifest.json` の `mode` は `server/` の有無から自動で決まる(手で書かない)。
@@ -35,6 +35,28 @@ Base UI の `SelectValue` は選択中の **value をそのまま描き**、`Sel
35
35
 
36
36
  トリガーに `kanto` や `/sales` と生の値が出たら、まずこれを疑う。
37
37
 
38
+ ## 配色:素のパレット + `dark:` ではなく、セマンティックトークンで書く
39
+
40
+ 色は `text-foreground` / `text-muted-foreground` / `bg-card` / `border-border` などの
41
+ トークンで書く。トークンはライト / ダークの両方を持っているので、`dark:` を並べる必要が無い。
42
+
43
+ `text-slate-900 dark:text-slate-100` のような素の Tailwind パレット + `dark:` の組は、
44
+ **トークンで塗られた周囲と食い違う**。ダークトークンが効くのは祖先に `.dark` /
45
+ `[data-theme="dark"]` が付いているときだけで、`dark:` ユーティリティも同じスイッチに
46
+ 繋がっている(`theme.css` の `@custom-variant dark`)。つまり自分でクラスを付けない限り
47
+ アプリは常にライトで動くのに、素のパレットを使うとその 1 か所だけ手動で両モードを
48
+ 管理することになる。
49
+
50
+ ```tsx
51
+ // ✗ 手動で 2 モードを管理することになる
52
+ <div className="font-medium text-slate-900 dark:text-slate-100">{name}</div>
53
+ // ✓ トークンなら 1 つで済む
54
+ <div className="font-medium text-foreground">{name}</div>
55
+ ```
56
+
57
+ `--muted` / `--accent` / `--secondary` / `--border` は**背景・境界用**で、ライトではほぼ白。
58
+ 文字色に使うと読めなくなる ― 弱い文字色は `text-muted-foreground`。
59
+
38
60
  ## チャート:`EChart` の配色はトークン追従。上書きは `option.color`
39
61
 
40
62
  系列色は `--chart-1..5`、軸・凡例・ツールチップは文字色/境界色のトークンから組まれる
@@ -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
 
@@ -36,6 +35,11 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
36
35
  - **クライアントに届く env は `PUBLIC_` 接頭辞のものだけ。** `import.meta.env.PUBLIC_FOO`。
37
36
  それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。シークレットは
38
37
  `ApiContext.env`(server/ 内)にだけ届く。
38
+ - **色はセマンティックトークンで書く。** `text-foreground`・`text-muted-foreground`・`bg-card`・
39
+ `border-border`。トークンがライト / ダークの両方を持つので `dark:` を並べる必要が無い。
40
+ 素のパレット + `dark:`(`text-slate-900 dark:text-slate-100`)を混ぜると、その 1 か所だけ
41
+ 手動で 2 モードを管理することになる(→ `agents/page.md` の「ダークモードは opt-in」)。
42
+ `--muted`・`--accent`・`--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
39
43
  - **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
40
44
  コマンド、または `vantage upgrade` で再生成される。
41
45
  - **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
@@ -88,6 +92,8 @@ root/
88
92
  | `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
89
93
  | `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
90
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 の固定キャンバス) |
91
97
  | `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
92
98
  | `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
93
99
 
@@ -96,207 +102,24 @@ root/
96
102
  `server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
97
103
  `mode` はここから自動で決まり、手で書く設定は無い。**SPA から始めて fullstack へ「昇格」する
98
104
  必要はない** ― テンプレートから始めた場合は最初から `server/` があり、その時点で fullstack。
99
- 下の 2 節は段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
100
-
101
- - `server/` が**ある** → 「API を持つ(fullstack モード)」を見る。データは自前の `/api/*` から
102
- `useApiQuery` で取る。
103
- - `server/` が**無い** → 「最小アプリ(2 ファイル)」の構成。外部 API を直接叩くなら `useQuery`。
104
- 自前の API が要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
105
-
106
- ## 最小アプリ(2 ファイル)
107
-
108
- `package.json` と `index.tsx` の 2 ファイルだけで動く。
109
-
110
- `package.json`:
111
-
112
- ```json
113
- {
114
- "name": "my-dashboard",
115
- "private": true,
116
- "type": "module",
117
- "scripts": {
118
- "dev": "vantage dev",
119
- "build": "vantage build",
120
- "preview": "vantage preview",
121
- "check": "vantage check",
122
- "routes": "vantage routes"
123
- },
124
- "dependencies": {
125
- "@squadbase/vantage": "^0.5.0",
126
- "react": "^19.2.7",
127
- "react-dom": "^19.2.7"
128
- }
129
- }
130
- ```
131
-
132
- `index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
133
-
134
- ```tsx
135
- export default function Dashboard() {
136
- return (
137
- <main className="p-6">
138
- <h1 className="text-2xl font-semibold">Dashboard</h1>
139
- </main>
140
- )
141
- }
142
- ```
143
-
144
- ```bash
145
- pnpm install
146
- pnpm dev # → http://localhost:5173(HMR)
147
- pnpm check # 静的診断(エラーがあれば exit 1)
148
- pnpm routes # ページ/API の URL マップ
149
- ```
105
+ 下の 2 つは段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
150
106
 
151
- `server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
152
- `src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
153
- どちらか一方に寄せること(→「ディレクトリ構造」)。
107
+ - `server/` が**ある** → データは自前の `/api/*` から `useApiQuery` で取る(→ `agents/api.md`)。
108
+ - `server/` が**無い** → 外部 API を直接叩くなら `useQuery`(→ `agents/page.md`)。自前の API が
109
+ 要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
154
110
 
155
- ## ルーティング規約(ファイル名 → ルート)
111
+ ## 何を書くか → どのファイルを読むか
156
112
 
157
- ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
158
- URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
159
- `MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
113
+ **書こうとしている種類のファイルだけを開けばよい。** まず該当する 1 ファイルを readFiles で
114
+ 読んでから書き始める。
160
115
 
161
- | ファイル | ルート |
116
+ | 書こうとしているもの | 読むファイル |
162
117
  | --- | --- |
163
- | `index.tsx` | `/` |
164
- | `monthly-analysis.tsx` | `/monthly-analysis` |
165
- | `sales/index.tsx` | `/sales` |
166
- | `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
167
- | `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト(`Outlet` で子を描く) |
168
- | `_404.tsx` | Not Found ページ |
169
- | `_error.tsx` | ルートが throw したときのエラーページ |
170
-
171
- `components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
172
- (ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→「ディレクトリ構造」)。
173
-
174
- ```tsx
175
- import { definePage } from "@squadbase/vantage"
176
-
177
- // title は document.title、navLabel は useRoutes() で組むナビの表示名(既定は title)
178
- export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
179
-
180
- export default function MonthlyAnalysis() {
181
- return <main className="p-6">…</main>
182
- }
183
- ```
184
-
185
- ### ナビはルート一覧から組む
186
-
187
- `useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
188
- `useCurrentRoute()` は現在のルート(404 なら `undefined`)。
189
-
190
- ```tsx
191
- import { Link, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
192
-
193
- // 動的ルートは URL が定まらないので外す。label は navLabel → title → path の順
194
- const routes = useRoutes().filter((r) => !r.dynamic)
195
- const current = useCurrentRoute()
196
-
197
- routes.map((r) => (
198
- <Link key={r.path} to={r.to} activeClassName="font-semibold">
199
- {r.label}
200
- </Link>
201
- ))
202
- ```
203
-
204
- `RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
205
- `title`・`description`・`navLabel`。
206
-
207
- ### フィルタ状態は URL に置く
208
-
209
- リロードで消えず、URL をそのまま共有できる。`useState` と同じ形。
210
-
211
- ```tsx
212
- import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
213
-
214
- const [region, setRegion] = useSearchParam("region", "all") // 常に string
215
- const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON になる値
216
- ```
217
-
218
- デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
219
- (`{ replace: false }` で push)。
220
-
221
- ### 動的パラメータの「3 つの綴り」を同期させる
222
-
223
- 崩すと静かにマッチしなくなる不変条件:
224
-
225
- - ファイル名 `sales/[customerId].tsx`
226
- - 表示ルート `/sales/:customerId`
227
- - リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
228
-
229
- ```tsx
230
- import { Link, useParams } from "@squadbase/vantage/router"
231
-
232
- <Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
233
-
234
- // 詳細ページ側で取り出す
235
- const { customerId } = useParams()
236
- ```
237
-
238
- ## データ取得(server を持たない fetch)
239
-
240
- 外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
241
-
242
- ```tsx
243
- import { useQuery } from "@squadbase/vantage/query"
244
- import { Loading, ErrorState } from "@squadbase/vantage/ui"
245
-
246
- const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
247
- if (q.isPending) return <Loading />
248
- if (q.isError) return <ErrorState message={(q.error as Error).message} />
249
- ```
250
-
251
- `QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
252
- refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
253
- 上書きする。
254
- 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→「API を持つ(fullstack
255
- モード)」)。
256
-
257
- ## API を持つ(fullstack モード)
258
-
259
- **`server/` ディレクトリがあれば fullstack モード**(無いアプリに足せば、その時点で切り替わる)。
260
- `server/api/**` の各ファイルが API ルートになる。
261
-
262
- `server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
263
-
264
- ```ts
265
- import type { ApiContext } from "@squadbase/vantage/server"
266
- import { HttpError } from "@squadbase/vantage/server"
267
-
268
- export async function GET({ params, env }: ApiContext) {
269
- if (!env.API_KEY) throw new HttpError(500, "API_KEY missing")
270
- return Response.json({ ok: true })
271
- }
272
- ```
273
-
274
- - API モジュールは**大文字の HTTP メソッド**(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
275
- エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
276
- - 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
277
- `params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
278
- - **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
279
- ログに記録され汎用の 500 に丸められる。
280
- - **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
281
-
282
- クライアント側からは `useApiQuery` で `/api/*` を叩く。ベース URL の解決・JSON パース・
283
- 非 2xx の `ApiError` 化・クエリキー(`["api", url]`)が入っている:
284
-
285
- ```tsx
286
- import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
287
-
288
- const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
289
- if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
290
-
291
- // クエリ文字列は search で。undefined の項目は落ちる
292
- const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
293
-
294
- // 書き込み。変数がそのまま JSON ボディになる
295
- const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
296
- ```
297
-
298
- `ApiError` は `message`・`status`・`body`・`requestId` を持つ。hook が使えない場所では
299
- `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` |
300
123
 
301
124
  ## 環境変数
302
125
 
@@ -304,20 +127,6 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
304
127
  のみ。** `VITE_` 系の公開 API は無い。
305
128
  - サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
306
129
 
307
- ## 組み込み UI / コンポーネントの入口
308
-
309
- - **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
310
- `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
311
- - **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
312
- `DataTablePreset`・`EChart` ほか)。
313
- - **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
314
- リファレンスはパッケージに同梱されていて、オフラインでも読める(→「ドキュメントを引く」)。
315
- **名前が分からないとき**はやりたいことで `vantage search` する(→ 同節)。
316
- - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
317
- ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
318
- `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
319
- `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
320
-
321
130
  ## ドキュメントを引く(`vantage docs` / `vantage search`)
322
131
 
323
132
  ```bash
@@ -363,7 +172,9 @@ body・レスポンスの status と形)まで出る。`vantage check --json` /
363
172
 
364
173
  - **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
365
174
  - **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
175
+ - **`vantage-slide`** — `.slide.tsx` でスライドデッキを書く(キャンバスの予算・段階表示・pptx 書き出し)
366
176
  - **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
177
+ - **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
367
178
 
368
179
  手順書の本体は `SKILL.md` というファイルで、置き場所はアプリによって違う(ルート直下・
369
180
  `.claude/skills/`・`.squadbase/skills/` など)。**探す必要はない ― CLI が探す:**
@@ -383,6 +194,9 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
383
194
  配置済みのコピーを新しいバージョンに更新したいときだけ `--force` を付ける(見つかった場所を
384
195
  そのまま書き換える)。
385
196
 
197
+ **役割分担:** Skill = 手順書(踏み込んだワークフロー) / `agents/*.md` = 種類ごとの静的
198
+ リファレンス / `AGENTS.md`(このファイル) = 地図。
199
+
386
200
  ---
387
201
 
388
202
  # テンプレート固有の規約(Squadbase テンプレート)
@@ -391,8 +205,9 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
391
205
  > フレームワーク規約と矛盾する場合は**こちらが優先**します。
392
206
  >
393
207
  > `vantage upgrade` はこのファイルをフレームワーク同梱の正本で丸ごと上書きするため、
394
- > このセクションは消えます。消えたら squadbase-templates リポジトリの `agents/vantage/`
395
- > から貼り直してください(リポジトリ側では `npm run agents` が正本と結合して再生成します)。
208
+ > このセクションは消えます。消えたら squadbase-monorepo の
209
+ > `packages/vantage-template/agents/vantage/` から貼り直してください
210
+ > (リポジトリ側では `npm run agents` が正本と結合して再生成します)。
396
211
 
397
212
  ## Squadbase の Connection / Query からデータを読む
398
213
 
@@ -469,3 +284,51 @@ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
469
284
  body: JSON.stringify({ customerId: "c_123" }),
470
285
  })
471
286
  ```
287
+
288
+ ## 構築中の一時的なエラーを「進行中」に見せる(`components/build-recovery.tsx`)
289
+
290
+ ページを書いている途中は、ファイルが書き終わる順番の都合で一瞬だけページが throw する
291
+ (まだ存在しないモジュールの import、書きかけのコンポーネント)。これを受け止めるのは
292
+ **Vantage の既定エラー画面**(`Something went wrong`)で、**テンプレートからは差し替えられない** —
293
+ `_error.tsx` はルートルート専用で、ページごとの `errorComponent` は現行プロトタイプでは
294
+ まだ結線されていない。しかも掴んでいるのは TanStack の CatchBoundary なので、HMR で
295
+ ソースが差し替わってもリセットされず、**リロードするまでエラー画面が貼り付く**。
296
+
297
+ `components/build-recovery.tsx` の `BuildRecovery` が、その画面を DOM から検知して
298
+ `Working on it…` 表示を被せ、dev では 1.2 秒待ってから自動でリロードして復帰させる
299
+ (上限 3 回。使い切ったら「再読み込み」の案内に降格し、無事に描けたら残数が戻る)。
300
+ 降格した画面にはエラー本文をそのまま出し、`Copy log` でコピーできるようにしてある —
301
+ 自力で直らないときに本文をそのまま貼り戻してもらうため。
302
+ `_error.tsx` も同じ `RecoveringState` を描くので、ルートレイアウトが throw したときの
303
+ 見た目と復帰の仕方は揃っている。
304
+
305
+ ### 追加の不変条件
306
+
307
+ - **`_layout.tsx` の `<BuildRecovery>` を外さない。** `<Outlet />` を包む形で 1 回だけ使う。
308
+ 外すと、書き込み途中の一瞬のエラーが `Something went wrong` のまま残る。
309
+ - **`components/build-recovery.tsx` と `_error.tsx` を消さない・作り直さない。** ページ側に
310
+ 独自のエラー UI を足す必要はない(足しても既定エラー画面より内側には入れない)。
311
+ - **`role="alert"` の中に `h2` を置かない。** これが既定エラー画面の目印なので、ページが
312
+ 同じ形を描くと `BuildRecovery` が誤検知して中身を覆ってしまう。UI キットの `Alert` /
313
+ `ErrorState` は見出しを `div` / `p` で描くのでそのまま使ってよい。
314
+ - **データ取得の失敗は各ページが `ErrorState` で出す。** `BuildRecovery` が拾うのは
315
+ 「レンダリングが throw した」場合だけで、`useApiQuery` の `isError` はページの責任
316
+ (→ `agents/page.md` の「データ取得」)。
317
+
318
+ ## 依存の追加とスクリプトの実行は bun で
319
+
320
+ このプロジェクトの package manager は **bun** です(`squadbase.yml` の `package_manager: bun`、
321
+ lockfile は `bun.lock`)。Squadbase のサンドボックスは bun と依存のキャッシュを持っており、
322
+ `bun install` はキャッシュだけで終わります。上のフレームワーク規約に出てくる
323
+ `pnpm install` / `pnpm dev` / `pnpm check` は、そのまま `bun install` / `bun run dev` /
324
+ `bun run check` に読み替えてください。
325
+
326
+ ### 追加の不変条件
327
+
328
+ - **依存を足すときは `bun add <pkg>`(開発用は `bun add -d <pkg>`)。** `npm install` や
329
+ `pnpm add` を使わない — `package-lock.json` / `pnpm-lock.yaml` が生えて `bun.lock` と別の断面に
330
+ なり、次回起動時の install が lockfile を作り直す。
331
+ - **`bun.lock` を消さない・手で編集しない。** `package.json` の依存を直接書き換えたときは
332
+ `bun install` を一度走らせて `bun.lock` を追従させ、両方を同じコミットに入れる。
333
+ - スクリプトは `bun run <script>`(`bun run dev` / `bun run build` / `bun run check` /
334
+ `bun run routes`)。`npm run <script>` でも動くが、install に npm を使わないこと。