@squadbase/vantage 0.0.1 → 0.1.0
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 +21 -4
- package/dist/chunk-A2UUGASH.js +12 -0
- package/dist/chunk-A2UUGASH.js.map +1 -0
- package/dist/{chunk-ZGDU5YLH.js → chunk-PRFBSQA4.js} +9 -5
- package/dist/chunk-PRFBSQA4.js.map +1 -0
- package/dist/chunk-WQZYXXQW.js +2249 -0
- package/dist/chunk-WQZYXXQW.js.map +1 -0
- package/dist/{chunk-73J5ZD4C.js → chunk-YLAB6UQS.js} +17 -3
- package/dist/chunk-YLAB6UQS.js.map +1 -0
- package/dist/cli.js +607 -36
- package/dist/cli.js.map +1 -1
- package/dist/components/index.d.ts +554 -0
- package/dist/components/index.js +1983 -0
- package/dist/components/index.js.map +1 -0
- package/dist/markdown/index.d.ts +9 -0
- package/dist/markdown/index.js +26 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/query/index.d.ts +27 -1
- package/dist/query/index.js +18 -0
- package/dist/query/index.js.map +1 -1
- package/dist/server/node.js +6 -1
- package/dist/server/node.js.map +1 -1
- package/dist/ui/index.d.ts +316 -200
- package/dist/ui/index.js +566 -511
- package/dist/ui/index.js.map +1 -1
- package/dist/vite/index.js +2 -2
- package/docs/en/agent-skills.md +89 -0
- package/docs/en/api-and-server.md +84 -0
- package/docs/en/build-and-deploy.md +280 -0
- package/docs/en/cli-reference.md +311 -0
- package/docs/en/components.md +233 -0
- package/docs/en/data-fetching.md +78 -0
- package/docs/en/environment-variables.md +69 -0
- package/docs/en/getting-started.md +95 -0
- package/docs/en/index.md +65 -0
- package/docs/en/markdown/markdown-renderer.md +44 -0
- package/docs/en/pages-and-metadata.md +56 -0
- package/docs/en/parts/app-shell.md +54 -0
- package/docs/en/parts/dashboard-card.md +64 -0
- package/docs/en/parts/data-table.md +86 -0
- package/docs/en/parts/date-range-picker.md +41 -0
- package/docs/en/parts/echart.md +53 -0
- package/docs/en/parts/filter-bar.md +66 -0
- package/docs/en/parts/funnel-steps.md +33 -0
- package/docs/en/parts/metric-value.md +23 -0
- package/docs/en/parts/multi-select.md +27 -0
- package/docs/en/parts/page-shell.md +45 -0
- package/docs/en/parts/searchable-select.md +47 -0
- package/docs/en/parts/section-header.md +20 -0
- package/docs/en/parts/segmented-control.md +25 -0
- package/docs/en/parts/status-badge.md +44 -0
- package/docs/en/parts/trend-indicator.md +26 -0
- package/docs/en/routing.md +95 -0
- package/docs/en/ui/accordion.md +36 -0
- package/docs/en/ui/alert.md +31 -0
- package/docs/en/ui/badge.md +23 -0
- package/docs/en/ui/breadcrumb.md +38 -0
- package/docs/en/ui/button.md +39 -0
- package/docs/en/ui/calendar.md +28 -0
- package/docs/en/ui/card.md +35 -0
- package/docs/en/ui/checkbox.md +34 -0
- package/docs/en/ui/cn.md +34 -0
- package/docs/en/ui/collapsible.md +21 -0
- package/docs/en/ui/command.md +37 -0
- package/docs/en/ui/dialog.md +52 -0
- package/docs/en/ui/dropdown-menu.md +50 -0
- package/docs/en/ui/empty.md +22 -0
- package/docs/en/ui/error-state.md +50 -0
- package/docs/en/ui/input-group.md +30 -0
- package/docs/en/ui/input.md +28 -0
- package/docs/en/ui/label.md +19 -0
- package/docs/en/ui/loading.md +19 -0
- package/docs/en/ui/popover.md +34 -0
- package/docs/en/ui/progress.md +28 -0
- package/docs/en/ui/scroll-area.md +23 -0
- package/docs/en/ui/select.md +63 -0
- package/docs/en/ui/separator.md +15 -0
- package/docs/en/ui/sheet.md +28 -0
- package/docs/en/ui/sidebar.md +92 -0
- package/docs/en/ui/skeleton.md +16 -0
- package/docs/en/ui/slider.md +21 -0
- package/docs/en/ui/spinner.md +14 -0
- package/docs/en/ui/switch.md +19 -0
- package/docs/en/ui/table.md +24 -0
- package/docs/en/ui/tabs.md +23 -0
- package/docs/en/ui/textarea.md +15 -0
- package/docs/en/ui/toggle-group.md +26 -0
- package/docs/en/ui/toggle.md +19 -0
- package/docs/en/ui/tooltip.md +22 -0
- package/docs/en/ui/use-is-mobile.md +18 -0
- package/docs/en/ui-and-theming.md +95 -0
- package/docs/index.json +1306 -0
- package/docs/ja/agent-skills.md +87 -0
- package/docs/ja/api-and-server.md +84 -0
- package/docs/ja/build-and-deploy.md +279 -0
- package/docs/ja/cli-reference.md +305 -0
- package/docs/ja/components.md +229 -0
- package/docs/ja/data-fetching.md +78 -0
- package/docs/ja/environment-variables.md +68 -0
- package/docs/ja/getting-started.md +95 -0
- package/docs/ja/index.md +65 -0
- package/docs/ja/markdown/markdown-renderer.md +43 -0
- package/docs/ja/pages-and-metadata.md +56 -0
- package/docs/ja/parts/app-shell.md +54 -0
- package/docs/ja/parts/dashboard-card.md +64 -0
- package/docs/ja/parts/data-table.md +85 -0
- package/docs/ja/parts/date-range-picker.md +41 -0
- package/docs/ja/parts/echart.md +52 -0
- package/docs/ja/parts/filter-bar.md +64 -0
- package/docs/ja/parts/funnel-steps.md +33 -0
- package/docs/ja/parts/metric-value.md +25 -0
- package/docs/ja/parts/multi-select.md +27 -0
- package/docs/ja/parts/page-shell.md +45 -0
- package/docs/ja/parts/searchable-select.md +47 -0
- package/docs/ja/parts/section-header.md +20 -0
- package/docs/ja/parts/segmented-control.md +26 -0
- package/docs/ja/parts/status-badge.md +44 -0
- package/docs/ja/parts/trend-indicator.md +26 -0
- package/docs/ja/routing.md +95 -0
- package/docs/ja/ui/accordion.md +37 -0
- package/docs/ja/ui/alert.md +31 -0
- package/docs/ja/ui/badge.md +23 -0
- package/docs/ja/ui/breadcrumb.md +38 -0
- package/docs/ja/ui/button.md +39 -0
- package/docs/ja/ui/calendar.md +28 -0
- package/docs/ja/ui/card.md +35 -0
- package/docs/ja/ui/checkbox.md +34 -0
- package/docs/ja/ui/cn.md +34 -0
- package/docs/ja/ui/collapsible.md +21 -0
- package/docs/ja/ui/command.md +36 -0
- package/docs/ja/ui/dialog.md +51 -0
- package/docs/ja/ui/dropdown-menu.md +50 -0
- package/docs/ja/ui/empty.md +22 -0
- package/docs/ja/ui/error-state.md +50 -0
- package/docs/ja/ui/input-group.md +30 -0
- package/docs/ja/ui/input.md +28 -0
- package/docs/ja/ui/label.md +19 -0
- package/docs/ja/ui/loading.md +19 -0
- package/docs/ja/ui/popover.md +34 -0
- package/docs/ja/ui/progress.md +28 -0
- package/docs/ja/ui/scroll-area.md +24 -0
- package/docs/ja/ui/select.md +64 -0
- package/docs/ja/ui/separator.md +15 -0
- package/docs/ja/ui/sheet.md +28 -0
- package/docs/ja/ui/sidebar.md +92 -0
- package/docs/ja/ui/skeleton.md +16 -0
- package/docs/ja/ui/slider.md +21 -0
- package/docs/ja/ui/spinner.md +14 -0
- package/docs/ja/ui/switch.md +19 -0
- package/docs/ja/ui/table.md +24 -0
- package/docs/ja/ui/tabs.md +24 -0
- package/docs/ja/ui/textarea.md +15 -0
- package/docs/ja/ui/toggle-group.md +27 -0
- package/docs/ja/ui/toggle.md +19 -0
- package/docs/ja/ui/tooltip.md +22 -0
- package/docs/ja/ui/use-is-mobile.md +18 -0
- package/docs/ja/ui-and-theming.md +95 -0
- package/package.json +29 -4
- package/registry/blocks/sales-overview.tsx +43 -19
- package/registry/ui/data-table.tsx +702 -102
- package/skills/vantage-add-feature/SKILL.md +150 -0
- package/skills/vantage-app/SKILL.md +258 -0
- package/skills/vantage-pitfalls/SKILL.md +115 -0
- package/templates/AGENTS.md +268 -0
- package/theme.css +178 -40
- package/dist/chunk-73J5ZD4C.js.map +0 -1
- package/dist/chunk-ZGDU5YLH.js.map +0 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# AGENTS.md — Vantage アプリの地図
|
|
2
|
+
|
|
3
|
+
このファイルは、**Vantage(`@squadbase/vantage`)アプリを作るコーディングエージェント**が
|
|
4
|
+
最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)が守るべき規約・不変条件・
|
|
5
|
+
最小レシピを 1 ページに集約しています。ここに書かれた規約だけで、最小のダッシュボードアプリを
|
|
6
|
+
正しく組めます。
|
|
7
|
+
|
|
8
|
+
> このファイルは **Vantage フレームワークが所有する正本**のコピーです。手で書き換えても、
|
|
9
|
+
> `vantage upgrade` を実行するとフレームワーク同梱の正本から再同期されて上書きされます。
|
|
10
|
+
> アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
|
|
11
|
+
>
|
|
12
|
+
> 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています
|
|
13
|
+
> (`vantage add skill --all` で `.claude/skills/` に配置)。このファイルは「地図」、Skill は
|
|
14
|
+
> 「手順書」という役割分担です。詰まったら `vantage-app` / `vantage-add-feature` /
|
|
15
|
+
> `vantage-pitfalls` を参照してください。
|
|
16
|
+
|
|
17
|
+
## Vantage とは
|
|
18
|
+
|
|
19
|
+
設定ファイル不要(config-free)な React ダッシュボードフレームワーク。アプリ作者が書くのは
|
|
20
|
+
`index.tsx`(と任意の追加ファイル)だけで、Vite・ルーティング・TanStack Query・Tailwind・
|
|
21
|
+
UI キット・開発サーバー・API サーバー・ビルドはすべて Vantage が所有します。設定は
|
|
22
|
+
「ファイル名の規約」で表現します。
|
|
23
|
+
|
|
24
|
+
## 不変条件(破ると静かに壊れる)
|
|
25
|
+
|
|
26
|
+
- **設定ファイルを作らない。** `vite.config.*`・`tailwind.config.*`・`postcss.config.*`・
|
|
27
|
+
`components.json`・`vantage.config.*` はすべて禁止。存在すると `vantage check` がエラーにする
|
|
28
|
+
(`FORBIDDEN_FILE`)。テーマ調整は `styles.css` のトークンで行う。
|
|
29
|
+
- **import は必ず `@squadbase/vantage` のサブパス経由。** `@tanstack/*`・`@base-ui/react`・
|
|
30
|
+
`echarts`・`hono`・`vite`・`tailwindcss` を直接 import しない。`lucide-react` のアイコンだけは
|
|
31
|
+
直接 import してよい。
|
|
32
|
+
- **モジュール間の相対 import はランタイムの `.js` 指定子で書く**(例:
|
|
33
|
+
`./components/revenue-chart.js`)。ソースが `.tsx`/`.ts` でも拡張子は `.js` と書く。
|
|
34
|
+
ESM の Node 解決に必要。`@squadbase/vantage` のサブパスはこの限りではない。
|
|
35
|
+
- **クライアントコードは `server/` を import してはならない。** 違反は `CLIENT_IMPORTS_SERVER`
|
|
36
|
+
エラー(静的にもビルド時にも弾かれる)。共有したいコードは `lib/` に置く。
|
|
37
|
+
- **クライアントに届く env は `PUBLIC_` 接頭辞のものだけ。** `import.meta.env.PUBLIC_FOO`。
|
|
38
|
+
それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。シークレットは
|
|
39
|
+
`ApiContext.env`(server/ 内)にだけ届く。
|
|
40
|
+
- **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
|
|
41
|
+
コマンド、または `vantage upgrade` で再生成される。
|
|
42
|
+
- **`definePage` の値はリテラルで書く。** title/description はビルド時に静的抽出されるため、
|
|
43
|
+
変数・関数呼び出し・テンプレート補間は使わない。`page` エクスポートはランタイムでは読まれない。
|
|
44
|
+
|
|
45
|
+
## import サブパスの地図
|
|
46
|
+
|
|
47
|
+
| import 元 | 提供するもの |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `@squadbase/vantage` | `definePage`(ページ設定) |
|
|
50
|
+
| `@squadbase/vantage/router` | `Link`・`Outlet`・`useParams`・`useSearch`・`useNavigate`・`redirect`・`notFound` |
|
|
51
|
+
| `@squadbase/vantage/query` | `useQuery`・`useMutation`・`apiFetch`・`apiUrl` ほか TanStack Query の再エクスポート |
|
|
52
|
+
| `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
|
|
53
|
+
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
|
|
54
|
+
| `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
|
|
55
|
+
| `@squadbase/vantage/server` | `ApiContext`・`HttpError`(server/ 側でのみ使う) |
|
|
56
|
+
|
|
57
|
+
## 最小アプリ(SPA モード)
|
|
58
|
+
|
|
59
|
+
`package.json` と `index.tsx` の 2 ファイルだけで動く。
|
|
60
|
+
|
|
61
|
+
`package.json`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"name": "my-dashboard",
|
|
66
|
+
"private": true,
|
|
67
|
+
"type": "module",
|
|
68
|
+
"scripts": {
|
|
69
|
+
"dev": "vantage dev",
|
|
70
|
+
"build": "vantage build",
|
|
71
|
+
"preview": "vantage preview",
|
|
72
|
+
"check": "vantage check",
|
|
73
|
+
"routes": "vantage routes"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@squadbase/vantage": "^0.1.0",
|
|
77
|
+
"react": "^19.2.7",
|
|
78
|
+
"react-dom": "^19.2.7"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
export default function Dashboard() {
|
|
87
|
+
return (
|
|
88
|
+
<main className="p-6">
|
|
89
|
+
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
|
90
|
+
</main>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm install
|
|
97
|
+
pnpm dev # → http://localhost:5173(HMR)
|
|
98
|
+
pnpm check # 静的診断(エラーがあれば exit 1)
|
|
99
|
+
pnpm routes # ページ/API の URL マップ
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`server/` ディレクトリが無いので、この時点では **SPA モード**。
|
|
103
|
+
|
|
104
|
+
## ルーティング規約(ファイル名 → ルート)
|
|
105
|
+
|
|
106
|
+
ルートディレクトリ直下の `.tsx`/`.jsx` がそのままページになる。各ページは**デフォルト
|
|
107
|
+
エクスポートの React コンポーネントが必須**(無いと `MISSING_DEFAULT_EXPORT`)。
|
|
108
|
+
|
|
109
|
+
| ファイル | ルート |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `index.tsx` | `/` |
|
|
112
|
+
| `monthly-analysis.tsx` | `/monthly-analysis` |
|
|
113
|
+
| `sales/index.tsx` | `/sales` |
|
|
114
|
+
| `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
|
|
115
|
+
| `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト(`Outlet` で子を描く) |
|
|
116
|
+
| `_404.tsx` | Not Found ページ |
|
|
117
|
+
| `_error.tsx` | ルートが throw したときのエラーページ |
|
|
118
|
+
|
|
119
|
+
`components/`・`hooks/`・`lib/`・`server/`・`public/` はルート走査の対象外(ページにならない)。
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { definePage } from "@squadbase/vantage"
|
|
123
|
+
|
|
124
|
+
export const page = definePage({ title: "Monthly Analysis" })
|
|
125
|
+
|
|
126
|
+
export default function MonthlyAnalysis() {
|
|
127
|
+
return <main className="p-6">…</main>
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 動的パラメータの「3 つの綴り」を同期させる
|
|
132
|
+
|
|
133
|
+
崩すと静かにマッチしなくなる不変条件:
|
|
134
|
+
|
|
135
|
+
- ファイル名 `sales/[customerId].tsx`
|
|
136
|
+
- 表示ルート `/sales/:customerId`
|
|
137
|
+
- リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { Link, useParams } from "@squadbase/vantage/router"
|
|
141
|
+
|
|
142
|
+
<Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
|
|
143
|
+
|
|
144
|
+
// 詳細ページ側で取り出す
|
|
145
|
+
const { customerId } = useParams()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## データ取得(server を持たない fetch)
|
|
149
|
+
|
|
150
|
+
外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { useQuery } from "@squadbase/vantage/query"
|
|
154
|
+
import { Loading, ErrorState } from "@squadbase/vantage/ui"
|
|
155
|
+
|
|
156
|
+
const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
|
|
157
|
+
if (q.isPending) return <Loading />
|
|
158
|
+
if (q.isError) return <ErrorState message={(q.error as Error).message} />
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
|
|
162
|
+
refetchOnWindowFocus false)。挙動を変えたいときはクエリ側のオプションで上書きする。
|
|
163
|
+
|
|
164
|
+
## API を足して fullstack にする
|
|
165
|
+
|
|
166
|
+
**`server/` ディレクトリを作った瞬間に fullstack モードになる。** `server/api/**` の各ファイルが
|
|
167
|
+
API ルートになる。
|
|
168
|
+
|
|
169
|
+
`server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import type { ApiContext } from "@squadbase/vantage/server"
|
|
173
|
+
import { HttpError } from "@squadbase/vantage/server"
|
|
174
|
+
|
|
175
|
+
export async function GET({ params, env }: ApiContext) {
|
|
176
|
+
if (!env.API_KEY) throw new HttpError(500, "API_KEY missing")
|
|
177
|
+
return Response.json({ ok: true })
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- API モジュールは**大文字の HTTP メソッド**(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
|
|
182
|
+
エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
|
|
183
|
+
- 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
|
|
184
|
+
`params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
|
|
185
|
+
- **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
|
|
186
|
+
ログに記録され汎用の 500 に丸められる。
|
|
187
|
+
- **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
|
|
188
|
+
|
|
189
|
+
クライアント側からは `apiFetch` で同一オリジンの `/api/*` を叩く:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { apiFetch, useQuery } from "@squadbase/vantage/query"
|
|
193
|
+
|
|
194
|
+
const q = useQuery({
|
|
195
|
+
queryKey: ["customer", customerId],
|
|
196
|
+
queryFn: async () => {
|
|
197
|
+
const res = await apiFetch(`/api/customers/${customerId}`)
|
|
198
|
+
if (!res.ok) throw new Error(`Customer ${customerId} not found`)
|
|
199
|
+
return res.json()
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 環境変数
|
|
205
|
+
|
|
206
|
+
- **クライアントで読めるのは `import.meta.env.PUBLIC_*` と `MODE`/`DEV`/`PROD`/`SSR`/`BASE_URL`
|
|
207
|
+
のみ。** `VITE_` 系の公開 API は無い。
|
|
208
|
+
- サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
|
|
209
|
+
|
|
210
|
+
## 組み込み UI / コンポーネントの入口
|
|
211
|
+
|
|
212
|
+
- **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
|
|
213
|
+
`ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。編集したいコピーが要るなら
|
|
214
|
+
`vantage add ui <name>` で `components/ui/` に取り出す。
|
|
215
|
+
- **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
|
|
216
|
+
`DataTablePreset`・`EChart` ほか)。ブロックは `vantage add block <name>` で取り出す。
|
|
217
|
+
- **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
|
|
218
|
+
リファレンスはパッケージに同梱されていて、オフラインでも読める(→「ドキュメントを引く」)。
|
|
219
|
+
**名前が分からないとき**はやりたいことで `vantage search` する(→ 同節)。
|
|
220
|
+
- `vantage add ui`・`vantage add block` でコピーできる名前は、未知名で実行すると候補が一覧表示
|
|
221
|
+
される。
|
|
222
|
+
|
|
223
|
+
## ドキュメントを引く(`vantage docs` / `vantage search`)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
vantage docs # 全ページの一覧(ガイド + コンポーネント)
|
|
227
|
+
vantage docs button # 1 ページ表示(短縮名。ui/button に解決される)
|
|
228
|
+
vantage docs parts/data-table # 完全な slug でも引ける
|
|
229
|
+
vantage docs data-table --json # { slug, title, description, section, content }
|
|
230
|
+
vantage docs --all # 全ページを連結(まとめて読み込む用)
|
|
231
|
+
vantage docs routing --lang en # 英語版(既定は ja)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
props の表・使用例・落とし穴はコンポーネントのページに載っている。**推測で props を書く前に
|
|
235
|
+
`vantage docs <name>` を引くこと。** 本文中のリンクは slug なので、`[DataTable](parts/data-table)`
|
|
236
|
+
は `vantage docs parts/data-table` で開ける。
|
|
237
|
+
|
|
238
|
+
**名前が分からないときは検索する。** 対象は `vantage docs` と同じ同梱ドキュメント。
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
vantage search 期間を選ぶ UI が欲しい # 自然言語(BM25)。やりたいことで引く
|
|
242
|
+
vantage search "enable[A-Z]\w+" --regex # 正規表現で props を横断(行番号付き)
|
|
243
|
+
vantage search テーブル --limit 5 --json # 機械可読(slug + score + snippet)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
結果の slug はそのまま `vantage docs <slug>` に渡せる。
|
|
247
|
+
|
|
248
|
+
## 検証・ビルド・プレビュー
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pnpm check # 静的診断(禁止ファイル・ルート衝突・境界・API export・env 誤用)
|
|
252
|
+
pnpm routes # ページ + API の URL マップ
|
|
253
|
+
pnpm build # dist/ に client(+ server)+ vantage-manifest.json
|
|
254
|
+
pnpm preview # 本番ビルドをローカル実行
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`vantage-manifest.json` の `mode`(`spa` / `fullstack`)は `server/` の有無から自動で決まる
|
|
258
|
+
(手で書かない)。`vantage check --json` / `vantage routes --json` はエージェント向けの機械
|
|
259
|
+
可読出力。`console.*` とランタイムエラーは開発ターミナルに `[browser:…]` として転送される。
|
|
260
|
+
|
|
261
|
+
## さらに詳しく(同梱 Skill)
|
|
262
|
+
|
|
263
|
+
`vantage add skill --all` で `.claude/skills/` に配置される:
|
|
264
|
+
|
|
265
|
+
- **`vantage-app`** — アプリを一から作る / SPA を fullstack に広げる手順
|
|
266
|
+
- **`vantage-add-feature`** — 既存アプリに page / api / ui / block を 1 つ足す定型
|
|
267
|
+
- **`vantage-pitfalls`** — Base UI(≠ Radix)の癖、`SelectValue` の挙動、EChart のテーマ
|
|
268
|
+
非追従など、静かに壊れる落とし穴のリファレンス
|
package/theme.css
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vantage design tokens. Imported after `@import "tailwindcss"` by the
|
|
3
|
-
* generated runtime.css. Defines CSS variables (light + dark)
|
|
4
|
-
* Tailwind v4 color/radius utilities via `@theme inline
|
|
3
|
+
* generated runtime.css. Defines CSS variables (light + dark), maps them to
|
|
4
|
+
* Tailwind v4 color/radius utilities via `@theme inline`, and declares the
|
|
5
|
+
* `data-*` custom variants the Base UI component kit styles itself with.
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
@import "tw-animate-css";
|
|
9
|
+
|
|
10
|
+
/* --------------------------------------------------------------- tokens */
|
|
11
|
+
/* shadcn/ui's neutral base palette (oklch), plus the chart hues Vantage adds
|
|
12
|
+
on top — charts need distinguishable colours, not a greyscale ramp. */
|
|
13
|
+
|
|
7
14
|
:root {
|
|
8
|
-
--background:
|
|
9
|
-
--foreground:
|
|
10
|
-
--card:
|
|
11
|
-
--card-foreground:
|
|
12
|
-
--popover:
|
|
13
|
-
--popover-foreground:
|
|
14
|
-
--primary:
|
|
15
|
-
--primary-foreground:
|
|
16
|
-
--secondary:
|
|
17
|
-
--secondary-foreground:
|
|
18
|
-
--muted:
|
|
19
|
-
--muted-foreground:
|
|
20
|
-
--accent:
|
|
21
|
-
--accent-foreground:
|
|
22
|
-
--destructive:
|
|
23
|
-
--destructive-foreground:
|
|
24
|
-
--border:
|
|
25
|
-
--input:
|
|
26
|
-
--ring:
|
|
15
|
+
--background: oklch(1 0 0);
|
|
16
|
+
--foreground: oklch(0.145 0 0);
|
|
17
|
+
--card: oklch(1 0 0);
|
|
18
|
+
--card-foreground: oklch(0.145 0 0);
|
|
19
|
+
--popover: oklch(1 0 0);
|
|
20
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
21
|
+
--primary: oklch(0.205 0 0);
|
|
22
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
23
|
+
--secondary: oklch(0.97 0 0);
|
|
24
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
25
|
+
--muted: oklch(0.97 0 0);
|
|
26
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
27
|
+
--accent: oklch(0.97 0 0);
|
|
28
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
29
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
30
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
31
|
+
--border: oklch(0.922 0 0);
|
|
32
|
+
--input: oklch(0.922 0 0);
|
|
33
|
+
--ring: oklch(0.708 0 0);
|
|
27
34
|
--radius: 0.625rem;
|
|
28
35
|
|
|
29
36
|
--chart-1: hsl(221 83% 53%);
|
|
@@ -31,35 +38,53 @@
|
|
|
31
38
|
--chart-3: hsl(31 90% 55%);
|
|
32
39
|
--chart-4: hsl(280 65% 60%);
|
|
33
40
|
--chart-5: hsl(340 75% 55%);
|
|
41
|
+
|
|
42
|
+
--sidebar: oklch(0.985 0 0);
|
|
43
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
44
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
45
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
46
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
47
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
48
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
49
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
.dark,
|
|
37
53
|
[data-theme="dark"] {
|
|
38
|
-
--background:
|
|
39
|
-
--foreground:
|
|
40
|
-
--card:
|
|
41
|
-
--card-foreground:
|
|
42
|
-
--popover:
|
|
43
|
-
--popover-foreground:
|
|
44
|
-
--primary:
|
|
45
|
-
--primary-foreground:
|
|
46
|
-
--secondary:
|
|
47
|
-
--secondary-foreground:
|
|
48
|
-
--muted:
|
|
49
|
-
--muted-foreground:
|
|
50
|
-
--accent:
|
|
51
|
-
--accent-foreground:
|
|
52
|
-
--destructive:
|
|
53
|
-
--destructive-foreground:
|
|
54
|
-
--border:
|
|
55
|
-
--input:
|
|
56
|
-
--ring:
|
|
54
|
+
--background: oklch(0.145 0 0);
|
|
55
|
+
--foreground: oklch(0.985 0 0);
|
|
56
|
+
--card: oklch(0.205 0 0);
|
|
57
|
+
--card-foreground: oklch(0.985 0 0);
|
|
58
|
+
--popover: oklch(0.205 0 0);
|
|
59
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
60
|
+
--primary: oklch(0.922 0 0);
|
|
61
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
62
|
+
--secondary: oklch(0.269 0 0);
|
|
63
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
64
|
+
--muted: oklch(0.269 0 0);
|
|
65
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
66
|
+
--accent: oklch(0.269 0 0);
|
|
67
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
68
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
69
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
70
|
+
--border: oklch(1 0 0 / 10%);
|
|
71
|
+
--input: oklch(1 0 0 / 15%);
|
|
72
|
+
--ring: oklch(0.556 0 0);
|
|
57
73
|
|
|
58
74
|
--chart-1: hsl(217 91% 60%);
|
|
59
75
|
--chart-2: hsl(160 60% 45%);
|
|
60
76
|
--chart-3: hsl(31 90% 55%);
|
|
61
77
|
--chart-4: hsl(280 65% 65%);
|
|
62
78
|
--chart-5: hsl(340 75% 60%);
|
|
79
|
+
|
|
80
|
+
--sidebar: oklch(0.205 0 0);
|
|
81
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
82
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
83
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
84
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
85
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
86
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
87
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
@theme inline {
|
|
@@ -87,10 +112,119 @@
|
|
|
87
112
|
--color-chart-3: var(--chart-3);
|
|
88
113
|
--color-chart-4: var(--chart-4);
|
|
89
114
|
--color-chart-5: var(--chart-5);
|
|
115
|
+
--color-sidebar: var(--sidebar);
|
|
116
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
117
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
118
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
119
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
120
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
121
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
122
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
90
123
|
--radius-sm: calc(var(--radius) - 4px);
|
|
91
124
|
--radius-md: calc(var(--radius) - 2px);
|
|
92
125
|
--radius-lg: var(--radius);
|
|
93
126
|
--radius-xl: calc(var(--radius) + 4px);
|
|
127
|
+
|
|
128
|
+
/* Accordion open/close, driven by Base UI's measured panel height. */
|
|
129
|
+
@keyframes accordion-down {
|
|
130
|
+
from {
|
|
131
|
+
height: 0;
|
|
132
|
+
}
|
|
133
|
+
to {
|
|
134
|
+
height: var(--accordion-panel-height, auto);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
@keyframes accordion-up {
|
|
138
|
+
from {
|
|
139
|
+
height: var(--accordion-panel-height, auto);
|
|
140
|
+
}
|
|
141
|
+
to {
|
|
142
|
+
height: 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ------------------------------------------------------- custom variants */
|
|
148
|
+
/* Base UI reports state through `data-open`, `data-checked`, … rather than
|
|
149
|
+
Radix's `data-state="open"`. These variants accept both spellings so the
|
|
150
|
+
component classes read the same either way. */
|
|
151
|
+
|
|
152
|
+
@custom-variant data-open {
|
|
153
|
+
&:where([data-state="open"]),
|
|
154
|
+
&:where([data-open]:not([data-open="false"])) {
|
|
155
|
+
@slot;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@custom-variant data-closed {
|
|
160
|
+
&:where([data-state="closed"]),
|
|
161
|
+
&:where([data-closed]:not([data-closed="false"])) {
|
|
162
|
+
@slot;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@custom-variant data-checked {
|
|
167
|
+
&:where([data-state="checked"]),
|
|
168
|
+
&:where([data-checked]:not([data-checked="false"])) {
|
|
169
|
+
@slot;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@custom-variant data-unchecked {
|
|
174
|
+
&:where([data-state="unchecked"]),
|
|
175
|
+
&:where([data-unchecked]:not([data-unchecked="false"])) {
|
|
176
|
+
@slot;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@custom-variant data-selected {
|
|
181
|
+
&:where([data-selected="true"]) {
|
|
182
|
+
@slot;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@custom-variant data-disabled {
|
|
187
|
+
&:where([data-disabled="true"]),
|
|
188
|
+
&:where([data-disabled]:not([data-disabled="false"])) {
|
|
189
|
+
@slot;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@custom-variant data-active {
|
|
194
|
+
&:where([data-state="active"]),
|
|
195
|
+
&:where([data-active]:not([data-active="false"])) {
|
|
196
|
+
@slot;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@custom-variant data-horizontal {
|
|
201
|
+
&:where([data-orientation="horizontal"]) {
|
|
202
|
+
@slot;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@custom-variant data-vertical {
|
|
207
|
+
&:where([data-orientation="vertical"]) {
|
|
208
|
+
@slot;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ------------------------------------------------------------ utilities */
|
|
213
|
+
|
|
214
|
+
@utility no-scrollbar {
|
|
215
|
+
-ms-overflow-style: none;
|
|
216
|
+
scrollbar-width: none;
|
|
217
|
+
|
|
218
|
+
&::-webkit-scrollbar {
|
|
219
|
+
display: none;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* Icons that must point the other way when the document is right-to-left. */
|
|
224
|
+
.cn-rtl-flip {
|
|
225
|
+
&:where([dir="rtl"], [dir="rtl"] *) {
|
|
226
|
+
transform: scaleX(-1);
|
|
227
|
+
}
|
|
94
228
|
}
|
|
95
229
|
|
|
96
230
|
body {
|
|
@@ -99,3 +233,7 @@ body {
|
|
|
99
233
|
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
100
234
|
-webkit-font-smoothing: antialiased;
|
|
101
235
|
}
|
|
236
|
+
|
|
237
|
+
* {
|
|
238
|
+
border-color: var(--border);
|
|
239
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/dispatch.ts"],"names":[],"mappings":"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","file":"chunk-73J5ZD4C.js","sourcesContent":["import type { Segment } from \"../core/types.js\"\nimport type { ApiContext, ApiHandler, ApiModule } from \"./index.js\"\nimport { isHttpError } from \"./index.js\"\n\n/** A resolvable API route: its segments plus a lazy module loader. */\nexport interface ApiRouteEntry {\n routePath: string\n segments: Segment[]\n hasCatchall: boolean\n staticCount: number\n load: () => Promise<ApiModule>\n}\n\n/** Order routes so the most specific matches first (static > param > catchall). */\nexport function sortApiRoutes<T extends ApiRouteEntry>(routes: T[]): T[] {\n return [...routes].sort((a, b) => {\n if (a.hasCatchall !== b.hasCatchall) return a.hasCatchall ? 1 : -1\n if (a.staticCount !== b.staticCount) return b.staticCount - a.staticCount\n return b.segments.length - a.segments.length\n })\n}\n\nfunction matchSegments(segments: Segment[], parts: string[]): Record<string, string> | null {\n const params: Record<string, string> = {}\n let i = 0\n for (const seg of segments) {\n if (seg.kind === \"catchall\") {\n params[seg.name] = parts.slice(i).map(decode).join(\"/\")\n return params\n }\n if (i >= parts.length) return null\n if (seg.kind === \"static\") {\n if (parts[i] !== seg.value) return null\n } else {\n params[seg.name] = decode(parts[i]!)\n }\n i++\n }\n return i === parts.length ? params : null\n}\n\nfunction decode(s: string): string {\n try {\n return decodeURIComponent(s)\n } catch {\n return s\n }\n}\n\n/** Match a pathname (e.g. `/api/customers/42`) against the API routes. */\nexport function matchApiRoute(\n routes: ApiRouteEntry[],\n pathname: string,\n): { route: ApiRouteEntry; params: Record<string, string> } | null {\n if (pathname !== \"/api\" && !pathname.startsWith(\"/api/\")) return null\n let rest = pathname.slice(4)\n if (rest.startsWith(\"/\")) rest = rest.slice(1)\n // Normalize a single trailing slash so `/api/customers/` doesn't match a\n // `:id` route with an empty-string param.\n if (rest.endsWith(\"/\")) rest = rest.slice(0, -1)\n const parts = rest === \"\" ? [] : rest.split(\"/\")\n for (const route of routes) {\n const params = matchSegments(route.segments, parts)\n if (params) return { route, params }\n }\n return null\n}\n\n/** Unique per-request id used to correlate the terminal log with the response. */\nfunction requestId(): string {\n const g = globalThis as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) return g.crypto.randomUUID()\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\n/**\n * Invoke a matched API module for a request. Shared by the dev middleware and\n * the production Node server so behavior is identical across environments.\n */\nexport async function dispatchApi(\n mod: ApiModule,\n request: Request,\n params: Record<string, string>,\n env: Record<string, string | undefined>,\n): Promise<Response> {\n const method = request.method.toUpperCase() as keyof ApiModule\n const handler = mod[method] as ApiHandler | undefined\n const rid = requestId()\n\n if (typeof handler !== \"function\") {\n const allowed = ([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"] as const).filter(\n (m) => typeof mod[m] === \"function\",\n )\n return Response.json(\n { error: \"Method Not Allowed\", method: request.method, requestId: rid },\n { status: 405, headers: { Allow: allowed.join(\", \"), \"x-vantage-request-id\": rid } },\n )\n }\n\n const ctx: ApiContext = {\n request,\n params,\n env,\n // Fire-and-forget: runs after the response is sent, on the server's own\n // event loop; errors are swallowed so background work can't crash it.\n waitUntil: (p) => {\n void Promise.resolve(p).catch(() => undefined)\n },\n }\n\n try {\n const res = await handler(ctx)\n // waitUntil work runs after the response is returned, never blocking it.\n const headers = new Headers(res.headers)\n headers.set(\"x-vantage-request-id\", rid)\n return new Response(res.body, { status: res.status, statusText: res.statusText, headers })\n } catch (err) {\n const status = isHttpError(err) ? err.status : 500\n // Only surface intentional HttpError messages; internal errors are logged\n // server-side but returned generically to avoid information disclosure.\n const message = isHttpError(err) ? err.message : \"Internal Server Error\"\n // Logged with request id so agents can correlate terminal + response.\n console.error(`[vantage] API error (${rid}) ${request.method} ${new URL(request.url).pathname}:`, err)\n return Response.json(\n { error: message, requestId: rid },\n { status, headers: { \"x-vantage-request-id\": rid } },\n )\n }\n}\n"]}
|