@squadbase/vantage-template 0.0.1 → 0.0.2-dev.7c29258

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 (46) hide show
  1. package/README.md +14 -6
  2. package/base-template/.squadbase/skills/project-storage/SKILL.md +157 -0
  3. package/base-template/.squadbase/skills/vantage-add-feature/SKILL.md +15 -6
  4. package/base-template/.squadbase/skills/vantage-app/SKILL.md +12 -7
  5. package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +22 -0
  6. package/base-template/AGENTS.md +186 -7
  7. package/base-template/package.json +4 -3
  8. package/base-template/src/_error.tsx +10 -0
  9. package/base-template/{_layout.tsx → src/_layout.tsx} +8 -1
  10. package/base-template/src/components/build-recovery.tsx +219 -0
  11. package/base-template/{styles.css → src/styles.css} +19 -0
  12. package/dist/index.js +149 -61
  13. package/package.json +18 -9
  14. package/ui-templates/CLAUDE.md +32 -21
  15. package/ui-templates/blank/preview-square.png +0 -0
  16. package/ui-templates/blank/preview-wide.png +0 -0
  17. package/ui-templates/blank-ja/preview-square.png +0 -0
  18. package/ui-templates/blank-ja/preview-wide.png +0 -0
  19. package/ui-templates/chart-grid/preview-square.png +0 -0
  20. package/ui-templates/chart-grid/preview-wide.png +0 -0
  21. package/ui-templates/chart-grid-ja/preview-square.png +0 -0
  22. package/ui-templates/chart-grid-ja/preview-wide.png +0 -0
  23. package/ui-templates/funnel/preview-square.png +0 -0
  24. package/ui-templates/funnel/preview-wide.png +0 -0
  25. package/ui-templates/funnel-ja/preview-square.png +0 -0
  26. package/ui-templates/funnel-ja/preview-wide.png +0 -0
  27. package/ui-templates/kpi-chart-advanced/preview-square.png +0 -0
  28. package/ui-templates/kpi-chart-advanced/preview-wide.png +0 -0
  29. package/ui-templates/kpi-chart-advanced-ja/preview-square.png +0 -0
  30. package/ui-templates/kpi-chart-advanced-ja/preview-wide.png +0 -0
  31. package/ui-templates/kpi-chart-simple/manifest.json +1 -1
  32. package/ui-templates/kpi-chart-simple/preview-square.png +0 -0
  33. package/ui-templates/kpi-chart-simple/preview-wide.png +0 -0
  34. package/ui-templates/kpi-chart-simple/server/kpi-summary.ts +1 -1
  35. package/ui-templates/kpi-chart-simple-ja/manifest.json +1 -1
  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/kpi-chart-simple-ja/server/kpi-summary.ts +1 -1
  39. package/ui-templates/table-focus/preview-square.png +0 -0
  40. package/ui-templates/table-focus/preview-wide.png +0 -0
  41. package/ui-templates/table-focus-ja/preview-square.png +0 -0
  42. package/ui-templates/table-focus-ja/preview-wide.png +0 -0
  43. package/base-template/_error.tsx +0 -14
  44. /package/base-template/{_404.tsx → src/_404.tsx} +0 -0
  45. /package/base-template/{components → src/components}/user-card.tsx +0 -0
  46. /package/base-template/{index.tsx → src/index.tsx} +0 -0
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 — `index.tsx`, `_layout.tsx`, `_404.tsx`, `_error.tsx`, `server/api/`, `styles.css`, `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 `.squadbase/skills/` — and installs dependencies.
18
18
 
19
19
  ```bash
20
20
  npx @squadbase/vantage-template init
@@ -26,17 +26,23 @@ npx @squadbase/vantage-template init --chart sunset # Apply a chart pr
26
26
  After initialization:
27
27
 
28
28
  ```bash
29
- npm run dev # vantage dev — dev server + API on :5173
29
+ npm run dev # vantage dev --no-overlay — dev server + API on :5173
30
30
  npm run build # vantage build → dist/
31
31
  npm run check # vantage check — static diagnostics
32
32
  npm run routes # vantage routes — page/API route map
33
33
  ```
34
34
 
35
- There is no `vite.config.ts`, no `main.tsx`, and no route table: adding a file adds a route (`index.tsx` → `/`, `sales/[id].tsx` → `/sales/:id`), and `_layout.tsx` builds the nav from `useRoutes()`.
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()`.
36
+
37
+ Since `@squadbase/vantage` v0.3.0, `src/` is the page-scan root and does not appear in URLs. `server/` and `public/` stay at the project root — `src/server/` is never scanned.
38
+
39
+ Since `@squadbase/vantage` v0.5.0, the dev terminal receives only `console.warn` / `console.error` and uncaught errors (with source-mapped positions and a code frame) — `console.log` is not forwarded. The generated `dev` script passes `--no-overlay`, keeping errors in the terminal instead of behind the full-screen browser overlay; remove the flag (or pass `--overlay`) to restore it.
36
40
 
37
41
  #### `add <template-name>`
38
42
 
39
- Apply a UI-pattern template to an existing Vantage project. The entry page lands on `index.tsx`, and template-owned files go under `components/<slug>/` (plus `server/api/` for API-backed templates).
43
+ Apply a UI-pattern template to an existing Vantage project. The entry page lands on `src/index.tsx`, and template-owned files go under `src/components/<slug>/` (plus `server/api/` at the project root for API-backed templates).
44
+
45
+ Both layouts are supported: Vantage scans `src/` when that directory exists and the project root otherwise, and `add` follows whichever one the project already uses — a project keeping its pages at the root gets `index.tsx` and `components/<slug>/` there instead, with relative imports that cross the boundary rewritten to match. `server/` and `public/` always stay at the project root.
40
46
 
41
47
  ```bash
42
48
  npx @squadbase/vantage-template add kpi-chart-simple
@@ -44,11 +50,13 @@ npx @squadbase/vantage-template add funnel --dry-run # Preview without writin
44
50
  npx @squadbase/vantage-template add funnel --force # Overwrite existing files
45
51
  ```
46
52
 
47
- `add` replaces `index.tsx`, so it warns before overwriting it, and it lists leftover files from a previously applied template (it never deletes them — you may have edited them).
53
+ `add` replaces the entry page, so it warns before overwriting it, and it lists leftover files from a previously applied template (it never deletes them — you may have edited them). A project with pages on *both* sides — `src/` present but `index.tsx` / `_layout.tsx` / `styles.css` still at the root — is refused with the list of files to move into `src/` first: Vantage ignores the root ones, so `vantage check` already fails there with `SRC_DIR_SPLIT`.
48
54
 
49
55
  #### `chart <preset-name>`
50
56
 
51
- Switch the chart color preset. Rewrites a marked block of `--chart-1`–`--chart-5` inside the project's `styles.css`, leaving any other overrides in that file intact. Re-running replaces the block rather than stacking.
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
+
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.
52
60
 
53
61
  ```bash
54
62
  npx @squadbase/vantage-template chart ocean
@@ -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・配色・ルーティングの落とし穴
@@ -16,8 +16,8 @@ CLI は雛形を出すだけで、規約の遵守はこちらの責任。**追
16
16
  ```bash
17
17
  vantage add page <name> # ルートページ(.tsx)
18
18
  vantage add api <name> # server/api/**.ts の API モジュール
19
- vantage add ui <name> # registry から UI ソースを components/ui/ にコピー
20
- vantage add block <name> # registry からブロックを components/blocks/ にコピー
19
+ vantage add ui <name> # registry から UI ソースを components/ui/ にコピー(src/ があればその中)
20
+ vantage add block <name> # registry からブロックを components/blocks/ にコピー(同上)
21
21
  ```
22
22
 
23
23
  `--force` で既存ファイルを上書き。`add` の雛形は import に `@squadbase/vantage` を焼き込む。
@@ -25,12 +25,18 @@ vantage add block <name> # registry からブロックを components/blocks/
25
25
  ## ページを追加する
26
26
 
27
27
  ```bash
28
- vantage add page monthly-analysis # → ./monthly-analysis.tsx → ルート /monthly-analysis
29
- vantage add page sales/index # → ./sales/index.tsx → ルート /sales
28
+ vantage add page monthly-analysis # → monthly-analysis.tsx → ルート /monthly-analysis
29
+ vantage add page sales/index # → sales/index.tsx → ルート /sales
30
30
  vantage add page "sales/[customerId]" # → 動的ルート /sales/:customerId
31
31
  ```
32
32
 
33
- 出力される雛形は `definePage` + デフォルトエクスポートのコンポーネント。ファイル名の規約:
33
+ **引数はページルートからの相対パス**(= ルートそのもの)。`add` は置き場所を自分で判断する ―
34
+ プロジェクトに `src/` があれば `src/monthly-analysis.tsx`、無ければ直下に書く。手でファイルを
35
+ 作るときも同じ規則で、**混ぜてはならない**(`src/` があるのに直下に置くと `SRC_DIR_SPLIT`
36
+ エラー。詳細は AGENTS.md の「ディレクトリ構造」)。
37
+
38
+ 出力される雛形は `definePage` + デフォルトエクスポートのコンポーネント。ファイル名の規約
39
+ (ページルートからの相対パス):
34
40
 
35
41
  | ファイル | ルート | 用途 |
36
42
  | --- | --- | --- |
@@ -51,6 +57,9 @@ vantage add page "sales/[customerId]" # → 動的ルート /sales/:customerI
51
57
  - 表示ルート `/foo/:id`
52
58
  - リンク `to="/foo/$id"` + `params={{ id }}`、取り出しは `const { id } = useParams()`
53
59
  4. `components/`・`hooks/`・`lib/`・`server/`・`public/` はページにならない(ルート走査対象外)。
60
+ **それ以外のディレクトリ名はそのまま URL のセグメントになる** ― `pages/`・`app/`・`utils/`
61
+ のような足場ディレクトリを切ると `/pages/report` のような URL ができる
62
+ (`SUSPICIOUS_ROUTE_DIR` 警告)。ページでないモジュールは上の 3 つに置く。
54
63
 
55
64
  ## API を追加する(fullstack)
56
65
 
@@ -130,7 +139,7 @@ vantage add block sales-overview # → components/blocks/sales-overview.tsx
130
139
  ```bash
131
140
  vantage routes # 追加したページ/API が意図した URL に出ているか
132
141
  vantage check # 規約違反が無いか(exit 1 ならエラーあり)
133
- vantage dev # 実際に動くか(console は開発ターミナルに [browser:…] で転送)
142
+ vantage dev # 実際に動くか(warn/error と未捕捉エラーは開発ターミナルへ転送される)
134
143
  ```
135
144
 
136
145
  `vantage routes` は `--pages` / `--apis` で片側だけに絞れる。`--detail` を足すと各ルートの静的な
@@ -19,14 +19,17 @@ description: Vantage(@squadbase/vantage)ダッシュボードアプリを一か
19
19
  3. **データを繋ぐ** — 外部 API か、自前の `server/api` か
20
20
  4. **仕上げ** — `check` → `build` → `preview`
21
21
 
22
- **各段階の終わりに `vantage check` を通す。** 静的診断(禁止ファイル・ルート衝突・境界違反・
23
- API export・env 誤用)はユーザーコードを実行しないので速く、エラーがあれば exit 1 になる。
22
+ **各段階の終わりに `vantage check` を通す。** 静的診断(禁止ファイル・`src/` の分裂・ルート
23
+ 衝突・境界違反・API export・env 誤用)はユーザーコードを実行しないので速く、エラーがあれば
24
+ exit 1 になる。
24
25
  まとめて最後に回すと、原因の切り分けが難しくなる。
25
26
 
26
27
  ## Step 1 — 骨組み
27
28
 
28
29
  新規なら `package.json` と `index.tsx` の 2 ファイルだけ。設定ファイルは**作らない**
29
- (`vite.config.*` 等は `vantage check` がエラーにする)。
30
+ (`vite.config.*` 等は `vantage check` がエラーにする)。ページを `src/` にまとめたいなら
31
+ `src/index.tsx` にする ― **`src/` があればページ走査はその中だけ**になり、直下に残した `.tsx`
32
+ は無視される(`SRC_DIR_SPLIT` エラー)。どちらか一方に寄せること。
30
33
 
31
34
  ```json
32
35
  {
@@ -41,7 +44,7 @@ API export・env 誤用)はユーザーコードを実行しないので速く
41
44
  "routes": "vantage routes"
42
45
  },
43
46
  "dependencies": {
44
- "@squadbase/vantage": "^0.2.0",
47
+ "@squadbase/vantage": "^0.5.0",
45
48
  "react": "^19.2.7",
46
49
  "react-dom": "^19.2.7"
47
50
  }
@@ -54,8 +57,9 @@ API export・env 誤用)はユーザーコードを実行しないので速く
54
57
  **既存アプリに合流したときは、作る前に現状を読む:**
55
58
 
56
59
  ```bash
57
- vantage routes # 既にあるページと API の URL マップ
60
+ vantage routes # 既にあるページと API の URL マップ(file 列がページの置き場所)
58
61
  vantage check # いま壊れていないか(これから出すエラーと切り分ける)
62
+ ls src/ # あればページは src/ の中だけ。無ければプロジェクトルート直下
59
63
  ls server/ # あれば fullstack モード。無ければ SPA
60
64
  vantage add skill # 配置済みの skill(このファイルの仲間)と、その場所
61
65
  ```
@@ -112,8 +116,9 @@ pnpm preview # 本番ビルドをローカルで動かす
112
116
 
113
117
  ## 詰まったら
114
118
 
115
- - ブラウザの `console.*` とランタイムエラーは、**開発ターミナルに `[browser:…]` として転送
116
- される**。ブラウザの devtools を開かなくても読める。
119
+ - ブラウザの `console.warn` / `console.error` とランタイムエラーは、**開発ターミナルへ転送
120
+ される**(`[console.warn] …` / `[Unhandled error] …` + コードフレーム)。devtools を開かずに
121
+ 読める。`console.log` は転送されないので、**ターミナルで見たいログは `console.warn`** で出す。
117
122
  - `vantage check --json` / `vantage routes --json` は機械可読出力。
118
123
  - props や規約を確かめたいときは `vantage docs <name>`、名前が思い出せないときは
119
124
  `vantage search <やりたいこと>`。**推測で props を書かない。**
@@ -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`、軸・凡例・ツールチップは文字色/境界色のトークンから組まれる
@@ -22,6 +22,9 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
22
22
 
23
23
  ## 不変条件(破ると静かに壊れる)
24
24
 
25
+ - **ページは `src/` の中か、プロジェクトルート直下か、どちらか一方。** `src/` があれば走査は
26
+ `src/` だけになり、ルート直下に残した `.tsx` は無視される(`SRC_DIR_SPLIT` エラー)。
27
+ 新しいファイルは既にある側に置く(→「ディレクトリ構造」)。
25
28
  - **設定ファイルを作らない。** `vite.config.*`・`tailwind.config.*`・`postcss.config.*`・
26
29
  `components.json`・`vantage.config.*` はすべて禁止。存在すると `vantage check` がエラーにする
27
30
  (`FORBIDDEN_FILE`)。テーマ調整は `styles.css` のトークンで行う。
@@ -33,12 +36,54 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
33
36
  - **クライアントに届く env は `PUBLIC_` 接頭辞のものだけ。** `import.meta.env.PUBLIC_FOO`。
34
37
  それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。シークレットは
35
38
  `ApiContext.env`(server/ 内)にだけ届く。
39
+ - **色はセマンティックトークンで書く。** `text-foreground`・`text-muted-foreground`・`bg-card`・
40
+ `border-border`。トークンがライト / ダークの両方を持つので `dark:` を並べる必要が無い。
41
+ 素のパレット + `dark:`(`text-slate-900 dark:text-slate-100`)を混ぜると、その 1 か所だけ
42
+ 手動で 2 モードを管理することになる(→「ダークモードは opt-in」)。`--muted`・`--accent`・
43
+ `--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
36
44
  - **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
37
45
  コマンド、または `vantage upgrade` で再生成される。
38
46
  - **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
39
47
  ため、変数・関数呼び出し・テンプレート補間は使わない。`page` エクスポートはランタイムでは
40
48
  読まれない。
41
49
 
50
+ ## ディレクトリ構造(ページをどこに置くか)
51
+
52
+ **ページの置き場所は 2 通りあり、`src/` があるかどうかだけで決まる。混ぜてはならない。**
53
+
54
+ - `src/` が**ある** → ページは `src/` の中だけ。ルート直下に置いた `.tsx` はルートにならず、
55
+ `SRC_DIR_SPLIT` エラーになる。
56
+ - `src/` が**無い** → ページはプロジェクトルート直下。
57
+
58
+ **ファイルを足す前に `src/` の有無を確認し、既にある側に置く**(`ls` するだけでよい)。URL は
59
+ どちらでも同じで、`src/` は URL に現れない(`src/sales/index.tsx` → `/sales`)。
60
+
61
+ ```text
62
+ root/
63
+ ├── src/ # ある場合、ページはこの中だけ(無ければ以下が root 直下)
64
+ │ ├── index.tsx # /
65
+ │ ├── monthly-analysis.tsx # /monthly-analysis
66
+ │ ├── sales/index.tsx # /sales
67
+ │ ├── _layout.tsx # ルートレイアウト
68
+ │ ├── components/ # ページ走査の対象外(URL にならない)
69
+ │ ├── hooks/ lib/ # 同上
70
+ │ └── styles.css # 任意のグローバル CSS
71
+ ├── server/ # 常にプロジェクトルート直下。src/ の中ではない
72
+ │ ├── api/… # /api/*
73
+ │ └── utils.ts
74
+ ├── public/ # 静的ファイル。常にプロジェクトルート直下
75
+ ├── package.json # managed
76
+ └── tsconfig.json # managed
77
+ ```
78
+
79
+ - **`server/` と `public/` は常にプロジェクトルート直下。** `src/server/` は決してスキャン
80
+ されない(`SRC_DIR_SPLIT` エラー)。
81
+ - **`styles.css` はページと同じ側**に置く(`src/` があれば `src/styles.css`)。
82
+ - **ディレクトリ名はそのまま URL のセグメントになる。** `pages/`・`app/`・`utils/` のような
83
+ 足場ディレクトリを切ると、その名前が URL に出る(`pages/report.tsx` → `/pages/report`)。
84
+ `SUSPICIOUS_ROUTE_DIR` 警告で知らされる。ページでないモジュールは `components/`・`hooks/`・
85
+ `lib/` に置く ― この 3 つ(と `server/`・`public/`)はどの階層でも走査されない。
86
+
42
87
  ## import サブパスの地図
43
88
 
44
89
  | import 元 | 提供するもの |
@@ -82,7 +127,7 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
82
127
  "routes": "vantage routes"
83
128
  },
84
129
  "dependencies": {
85
- "@squadbase/vantage": "^0.2.0",
130
+ "@squadbase/vantage": "^0.5.0",
86
131
  "react": "^19.2.7",
87
132
  "react-dom": "^19.2.7"
88
133
  }
@@ -108,12 +153,15 @@ pnpm check # 静的診断(エラーがあれば exit 1)
108
153
  pnpm routes # ページ/API の URL マップ
109
154
  ```
110
155
 
111
- `server/` ディレクトリが無いので、この構成は **SPA モード**。
156
+ `server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
157
+ `src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
158
+ どちらか一方に寄せること(→「ディレクトリ構造」)。
112
159
 
113
160
  ## ルーティング規約(ファイル名 → ルート)
114
161
 
115
- ルートディレクトリ直下の `.tsx`/`.jsx` がそのままページになる。各ページは**デフォルト
116
- エクスポートの React コンポーネントが必須**(無いと `MISSING_DEFAULT_EXPORT`)。
162
+ ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
163
+ URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
164
+ `MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
117
165
 
118
166
  | ファイル | ルート |
119
167
  | --- | --- |
@@ -125,7 +173,8 @@ pnpm routes # ページ/API の URL マップ
125
173
  | `_404.tsx` | Not Found ページ |
126
174
  | `_error.tsx` | ルートが throw したときのエラーページ |
127
175
 
128
- `components/`・`hooks/`・`lib/`・`server/`・`public/` はルート走査の対象外(ページにならない)。
176
+ `components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
177
+ (ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→「ディレクトリ構造」)。
129
178
 
130
179
  ```tsx
131
180
  import { definePage } from "@squadbase/vantage"
@@ -274,6 +323,16 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
274
323
  `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
275
324
  `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
276
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
+
277
336
  ## ドキュメントを引く(`vantage docs` / `vantage search`)
278
337
 
279
338
  ```bash
@@ -302,7 +361,7 @@ vantage search テーブル --limit 5 --json # 機械可読(slug + score + sn
302
361
  ## 検証・ビルド・プレビュー
303
362
 
304
363
  ```bash
305
- pnpm check # 静的診断(禁止ファイル・ルート衝突・境界・API export・env 誤用)
364
+ pnpm check # 静的診断(禁止ファイル・src/ の分裂・ルート衝突・境界・API export・env 誤用)
306
365
  pnpm routes # ページ + API の URL マップ
307
366
  pnpm build # dist/ に client(+ server)+ vantage-manifest.json
308
367
  pnpm preview # 本番ビルドをローカル実行
@@ -312,13 +371,15 @@ pnpm preview # 本番ビルドをローカル実行
312
371
  (手で書かない)。`vantage routes` は `--pages` / `--apis` で片側だけに絞れ、`--detail` を足すと
313
372
  各ルートの静的な仕様(ページ: パスパラメータ + `definePage` のメタ / API: メソッド・query・
314
373
  body・レスポンスの status と形)まで出る。`vantage check --json` / `vantage routes --json` は
315
- エージェント向けの機械可読出力。`console.*` とランタイムエラーは開発ターミナルに `[browser:…]` として転送される。
374
+ エージェント向けの機械可読出力。dev では `console.warn` / `console.error` とランタイムエラーが開発ターミナルへ
375
+ 転送される(`console.log` は転送されない)。エラーの全画面オーバーレイは `vantage dev --no-overlay` で消せる。
316
376
 
317
377
  ## さらに詳しく(同梱 Skill)
318
378
 
319
379
  - **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
320
380
  - **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
321
381
  - **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
382
+ - **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
322
383
 
323
384
  手順書の本体は `SKILL.md` というファイルで、置き場所はアプリによって違う(ルート直下・
324
385
  `.claude/skills/`・`.squadbase/skills/` など)。**探す必要はない ― CLI が探す:**
@@ -337,3 +398,121 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
337
398
  `add skill` はプロジェクト内を走査してから動くので、**再実行しても二重に配置されない**。
338
399
  配置済みのコピーを新しいバージョンに更新したいときだけ `--force` を付ける(見つかった場所を
339
400
  そのまま書き換える)。
401
+
402
+ ---
403
+
404
+ # テンプレート固有の規約(Squadbase テンプレート)
405
+
406
+ > ここから下は、Squadbase のテンプレートがフレームワーク正本に足した規約です。上の
407
+ > フレームワーク規約と矛盾する場合は**こちらが優先**します。
408
+ >
409
+ > `vantage upgrade` はこのファイルをフレームワーク同梱の正本で丸ごと上書きするため、
410
+ > このセクションは消えます。消えたら squadbase-monorepo の
411
+ > `packages/vantage-template/agents/vantage/` から貼り直してください
412
+ > (リポジトリ側では `npm run agents` が正本と結合して再生成します)。
413
+
414
+ ## Squadbase の Connection / Query からデータを読む
415
+
416
+ **`server/api/**` のハンドラが実データを取りにいく唯一の方法**が `@squadbase/vantage-sdk`
417
+ (`@squadbase/vantage-sdk/server` の `connection`)。資格情報・接続先・SQL は Squadbase 側の
418
+ **Connection** と **Query** が持ち、ハンドラは「どの Query を、どの引数で回すか」だけを知る。
419
+
420
+ ### 追加の不変条件
421
+
422
+ - **`server/` からデータを読むときは `connection` を使う。** ハンドラに SQL・接続文字列・
423
+ DB クライアント・上流サービスの URL や API キーを書かない。データソース用のライブラリを
424
+ `package.json` に足すのも不要(足す前に、その接続が Connection にできないか疑う)。
425
+ - **`connection` は `server/` の中でしか呼べない。** クライアントから import すると
426
+ `CLIENT_IMPORTS_SERVER` エラー(フレームワーク側の不変条件と同じ)。
427
+ - **CONNECTION_ID / QUERY_ID を推測で埋めない。** 値はアプリを動かす Squadbase プロジェクト側の
428
+ 設定なので、分からなければユーザーに聞く。実データがまだ無い段階は `lib/mock-data.ts` の
429
+ モックで進め、id が確定したらハンドラだけ差し替える(クライアントの `useApiQuery` は変わらない)。
430
+
431
+ ### 追加の import サブパス
432
+
433
+ | import 元 | 提供するもの |
434
+ | --- | --- |
435
+ | `@squadbase/vantage-sdk/server` | `connection`(Connection の宛先と認証を解決する。server/ 側でのみ使う) |
436
+
437
+ ### 使い方
438
+
439
+ `connection(ctx, connectionId)` は**宛先(`baseUrl`)と認証ヘッダー(`headers`)を解決するだけ**で、
440
+ リクエストは自分で `fetch` する。
441
+
442
+ ```ts
443
+ import type { ApiContext } from "@squadbase/vantage/server"
444
+ import { HttpError } from "@squadbase/vantage/server"
445
+ import { connection } from "@squadbase/vantage-sdk/server"
446
+
447
+ const CONNECTION_ID = "..."
448
+ const QUERY_ID = "..."
449
+
450
+ export async function GET(ctx: ApiContext) {
451
+ const { baseUrl, headers } = await connection(ctx, CONNECTION_ID)
452
+
453
+ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}`, {
454
+ method: "POST",
455
+ headers: { "Content-Type": "application/json", ...headers },
456
+ body: JSON.stringify({ params: {} }),
457
+ })
458
+ if (!res.ok) throw new HttpError(502, `query execution failed: ${res.status}`)
459
+
460
+ const { columns, rows } = await res.json()
461
+ return Response.json({ columns, rows })
462
+ }
463
+ ```
464
+
465
+ - **`ctx` をそのまま渡す。** `connection` が読むのは `ctx.request` と `ctx.env` だけなので、
466
+ `ApiContext` をそのまま渡せる(第 1 引数を自作しない)。
467
+ - **`baseUrl` はバージョン接頭辞込み・末尾スラッシュ無し。** そのままパスを繋ぐ。
468
+ - **`headers` は必ずスプレッドする**(`{ "Content-Type": "application/json", ...headers }`)。
469
+ - **Query は id だけで指す**: `POST {baseUrl}/queries/{queryId}`。Connection は Query 側から
470
+ 解決されるので、**パスに connection id を入れない**(`connection` の引数として渡すだけ)。
471
+ - **プレビューでもデプロイ済みアプリでも同じコードが動く。** Squadbase の外で走らせると
472
+ `connection` は throw する。
473
+
474
+ ### SQL Query と HTTP Query で body の形が違う
475
+
476
+ | Query 種別 | リクエスト | レスポンス |
477
+ | --- | --- | --- |
478
+ | SQL Query | 引数は `{ params: { … } }` に入れる | `{ columns: [{ name, type? }], rows: [{ … }] }` |
479
+ | HTTP Query | Query が宣言した形をそのまま(`params` で包まない)。クエリ文字列は URL に載せる | 上流サービスのレスポンスをそのまま通す(非 2xx も来るので `res.ok` を必ず見る) |
480
+
481
+ ```ts
482
+ // HTTP Query — クエリ文字列は URL、body は Query の宣言どおり
483
+ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
484
+ method: "POST",
485
+ headers: { "Content-Type": "application/json", ...headers },
486
+ body: JSON.stringify({ customerId: "c_123" }),
487
+ })
488
+ ```
489
+
490
+ ## 構築中の一時的なエラーを「進行中」に見せる(`components/build-recovery.tsx`)
491
+
492
+ ページを書いている途中は、ファイルが書き終わる順番の都合で一瞬だけページが throw する
493
+ (まだ存在しないモジュールの import、書きかけのコンポーネント)。これを受け止めるのは
494
+ **Vantage の既定エラー画面**(`Something went wrong`)で、**テンプレートからは差し替えられない** —
495
+ `_error.tsx` はルートルート専用で、ページごとの `errorComponent` は現行プロトタイプでは
496
+ まだ結線されていない。しかも掴んでいるのは TanStack の CatchBoundary なので、HMR で
497
+ ソースが差し替わってもリセットされず、**リロードするまでエラー画面が貼り付く**。
498
+
499
+ `components/build-recovery.tsx` の `BuildRecovery` が、その画面を DOM から検知して
500
+ `Working on it…` 表示を被せ、dev では 1.2 秒待ってから自動でリロードして復帰させる
501
+ (上限 3 回。使い切ったら「再読み込み」の案内に降格し、無事に描けたら残数が戻る)。
502
+ 降格した画面にはエラー本文をそのまま出し、`Copy log` でコピーできるようにしてある —
503
+ 自力で直らないときに本文をそのまま貼り戻してもらうため。
504
+ `_error.tsx` も同じ `RecoveringState` を描くので、ルートレイアウトが throw したときの
505
+ 見た目と復帰の仕方は揃っている。
506
+
507
+ ### 追加の不変条件
508
+
509
+ - **`_layout.tsx` の `<BuildRecovery>` を外さない。** `<Outlet />` を包む形で 1 回だけ使う。
510
+ 外すと、書き込み途中の一瞬のエラーが `Something went wrong` のまま残る。
511
+ - **`components/build-recovery.tsx` と `_error.tsx` を消さない・作り直さない。** ページ側に
512
+ 独自のエラー UI を足す必要はない(足しても既定エラー画面より内側には入れない)。
513
+ - **`role="alert"` の中に `h2` を置かない。** これが既定エラー画面の目印なので、ページが
514
+ 同じ形を描くと `BuildRecovery` が誤検知して中身を覆ってしまう。UI キットの `Alert` /
515
+ `ErrorState` は見出しを `div` / `p` で描くのでそのまま使ってよい。
516
+ - **データ取得の失敗は各ページが `ErrorState` で出す。** `BuildRecovery` が拾うのは
517
+ 「レンダリングが throw した」場合だけで、`useApiQuery` の `isError` はページの責任
518
+ (→ フレームワーク規約の「データ取得」)。
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
6
- "dev": "vantage dev",
6
+ "dev": "vantage dev --no-overlay",
7
7
  "build": "vantage build",
8
8
  "start": "node dist/server/index.mjs",
9
9
  "preview": "vantage preview",
@@ -11,8 +11,9 @@
11
11
  "routes": "vantage routes"
12
12
  },
13
13
  "dependencies": {
14
- "@squadbase/react": "^0.0.1",
15
- "@squadbase/vantage": "^0.2.3",
14
+ "@squadbase/react": "^0.1.0",
15
+ "@squadbase/vantage": "^0.5.3",
16
+ "@squadbase/vantage-sdk": "^0.1.0",
16
17
  "date-fns": "^4.1.0",
17
18
  "lucide-react": "^0.468.0",
18
19
  "react": "^19.2.7",
@@ -0,0 +1,10 @@
1
+ import { RecoveringState } from "./components/build-recovery"
2
+
3
+ /**
4
+ * ルートルート(= `_layout.tsx`)が throw したときの画面。ページのレンダリングエラーは
5
+ * ここには来ない — フレームワークの既定エラー画面が受け止め、その上を `BuildRecovery` が
6
+ * 覆う(→ `components/build-recovery.tsx`)。どちらも見た目と復帰の仕方は揃えてある。
7
+ */
8
+ export default function RouteError({ error }: { error: unknown }) {
9
+ return <RecoveringState error={error} />
10
+ }