@squadbase/vantage-template 0.1.1 → 0.2.0-dev1.0c40f36
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 +19 -9
- package/base-template/.squadbase/skills/vantage-add-feature/SKILL.md +14 -7
- package/base-template/.squadbase/skills/vantage-app/SKILL.md +4 -0
- package/base-template/.squadbase/skills/vantage-design/SKILL.md +62 -52
- package/base-template/.squadbase/skills/vantage-design/references/extract.md +26 -27
- package/base-template/.squadbase/skills/vantage-design/references/format.md +12 -172
- package/base-template/.squadbase/skills/vantage-design/references/presets.md +50 -110
- package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +50 -1
- package/base-template/.squadbase/skills/vantage-slide/SKILL.md +299 -0
- package/base-template/.squadbase/skills/vantage-slide/references/morph.md +102 -0
- package/base-template/.squadbase/skills/vantage-slide/references/steps.md +57 -0
- package/base-template/.squadbase/skills/vantage-slide/references/tokens.md +87 -0
- package/base-template/.squadbase/skills/vantage-slide/references/transitions.md +196 -0
- package/base-template/AGENTS.md +224 -17
- package/base-template/agents/api.md +36 -0
- package/base-template/agents/csv.md +2 -1
- package/base-template/agents/doc.md +52 -3
- package/base-template/agents/page.md +91 -10
- package/base-template/agents/slide.md +90 -4
- package/base-template/agents/xlsx.md +2 -1
- package/base-template/bun.lock +917 -0
- package/base-template/bunfig.toml +5 -0
- package/base-template/package.json +6 -6
- package/base-template/squadbase.yml +1 -0
- package/base-template/src/_layout.tsx +16 -7
- package/base-template/src/components/build-recovery.tsx +37 -1
- package/base-template/src/components/user-card.tsx +11 -11
- package/base-template/src/index.tsx +8 -3
- package/base-template/src/styles.css +108 -12
- package/dist/index.js +971 -6
- package/package.json +20 -17
- package/ui-templates/CLAUDE.md +2 -0
- package/ui-templates/kpi-chart-simple/pages/index.tsx +48 -48
- package/ui-templates/kpi-chart-simple-ja/pages/index.tsx +48 -48
- package/base-template/DESIGN.md +0 -108
- package/base-template/package-lock.json +0 -5841
package/base-template/AGENTS.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
> このファイルと `agents/*.md` は、いずれも **Vantage フレームワークが所有する正本**の
|
|
8
8
|
> コピーです。手で書き換えても、`vantage upgrade` を実行するとフレームワーク同梱の正本から
|
|
9
9
|
> 再同期されて上書きされます。アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
|
|
10
|
-
>
|
|
11
|
-
> `vantage add agents`
|
|
10
|
+
> 例外はプロジェクトルートの任意の `DESIGN.md` で、これは**ユーザーが所有**します(`vantage upgrade` も
|
|
11
|
+
> `vantage add agents` も触らない。→「見た目の規約」)。
|
|
12
12
|
>
|
|
13
13
|
> 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています。このファイルは
|
|
14
14
|
> 「地図」、`agents/*.md` は「種類ごとの静的リファレンス」、Skill は「手順書」という役割分担です。
|
|
@@ -47,6 +47,9 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
47
47
|
- **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
|
|
48
48
|
ため、変数・関数呼び出し・テンプレート補間は使わない。`page` エクスポートはランタイムでは
|
|
49
49
|
読まれない。
|
|
50
|
+
- **アプリの表示言語は、ルート `_layout.tsx` の `export const lang = "en"`(リテラル。BCP 47)で指定する。**
|
|
51
|
+
`<html lang>` になり、日本語の文節改行とスライドの文言切り替えに効く。未指定は `"en"`。
|
|
52
|
+
画面の文言・docs・コメントは、ユーザーが使う言語に合わせる(`lang` もその言語にする)。
|
|
50
53
|
|
|
51
54
|
## ディレクトリ構造(ページをどこに置くか)
|
|
52
55
|
|
|
@@ -93,13 +96,25 @@ root/
|
|
|
93
96
|
| `@squadbase/vantage/router` | `Link`・`Outlet`・`useParams`・`useSearch`・`useNavigate`・`redirect`・`notFound`・`useRoutes`・`useCurrentRoute`・`useSearchParam`・`useSearchState` |
|
|
94
97
|
| `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
|
|
95
98
|
| `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
|
|
96
|
-
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart`
|
|
99
|
+
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`、KPI の `KpiCard`・`Delta`、型付きチャート `LineChart`・`BarChart`・`AreaChart`・`DonutChart`・`BarList` ほか、`EChart`、書式の `formatValue`) |
|
|
97
100
|
| `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
|
|
98
|
-
| `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`(スライド専用。1920×1080 の固定キャンバス) |
|
|
99
|
-
| `@squadbase/vantage/doc` | `Document`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
|
|
101
|
+
| `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`、1 枚分の block `SlideCover`・`SlideExecSummary`・`SlideKpiHero`・`SlideKpiGrid`・`SlideChartInsight`・`SlideScorecardTable`・`SlideNextActions`・`SlideSection`・`SlideComparison`・`SlideChartFull`、手書きの 1 枚に使う部品 `SlideHeading`・`SlideSourceLine`・`SlideColumns`・`SlideColumn`・`DeltaText`(スライド専用。1920×1080 の固定キャンバス) |
|
|
102
|
+
| `@squadbase/vantage/doc` | `Document`、ドキュメント用の block `DocReportHeader`・`DocExecutiveSummary`・`DocKpiScorecard`・`DocFigure`・`DocDataTable`・`DocCallout`・`DocKeyFindings`・`DocActionItems`・`DocNotesAndSources`、部品 `DeltaText`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
|
|
100
103
|
| `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
|
|
101
104
|
| `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
|
|
102
105
|
|
|
106
|
+
**KPI は `KpiCard`、増減は `Delta`(`comparison` を渡し、下がって良い指標は `betterWhen="down"`)。** 向き・率・色は値から計算
|
|
107
|
+
されるので手で渡さない。KPI カードを自作しない。`TrendIndicator` は非推奨(`direction` を手で渡すので値と食い違いうる)。props は
|
|
108
|
+
`vantage docs kpi-card` / `vantage docs delta`、数値の書式は `vantage docs format` を引く。
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { KpiCard, Delta } from "@squadbase/vantage/components";
|
|
112
|
+
|
|
113
|
+
<KpiCard label="売上" value={48_200_000} format="currency"
|
|
114
|
+
comparison={{ previous: 42_900_000, vs: "prior-period" }} />
|
|
115
|
+
<Delta value={0.021} comparison={{ previous: 0.026, vs: "prior-period" }} polarity="lower-is-better" />
|
|
116
|
+
```
|
|
117
|
+
|
|
103
118
|
## モードは 2 つ、切り替えるのは `server/` の有無だけ
|
|
104
119
|
|
|
105
120
|
`server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
|
|
@@ -111,17 +126,31 @@ root/
|
|
|
111
126
|
- `server/` が**無い** → 外部 API を直接叩くなら `useQuery`(→ `agents/page.md`)。自前の API が
|
|
112
127
|
要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
|
|
113
128
|
|
|
114
|
-
##
|
|
129
|
+
## 見た目の規約
|
|
130
|
+
|
|
131
|
+
色・書体・角丸などの**値と色の割り当ては、テーマ(`styles.css` の `/* theme: <name> */` マーカー以降の
|
|
132
|
+
上書き)だけが持つ。** ここには値を書かず、使い分けの規則だけを置く。UI(ページ・スライド・
|
|
133
|
+
コンポーネント)はこの規則に従い、色は必ずトークンで指定する(色コード・素のパレットクラスは書かない)。
|
|
134
|
+
|
|
135
|
+
- **意味トークン:** 良い = `positive`、悪い = `negative`、注意 = `warning`、情報 = `info`、
|
|
136
|
+
中立 = `neutral`。系列色は `chart-1`〜`chart-5` を番号順に使う(5 系列まで。超えるなら「その他」に
|
|
137
|
+
まとめる)。増減の良否は指標の性質(コスト減・解約率減は良い)で決めて
|
|
138
|
+
positive / negative に分け(`KpiCard` / `Delta` の `betterWhen`。下がって良い指標は `betterWhen="down"`)、向きは ▲▼ や符号で表す。
|
|
139
|
+
- **レイアウト:** 内容はカードで包む(ページのチャートはタイトル付きの `DashboardCardPreset` に入れる。
|
|
140
|
+
`EChart` は親の幅に追従する)。順序は KPI → チャート → 表。値が 1 つなら KPI にする
|
|
141
|
+
(1 本の棒グラフにしない)。強調は 1 画面(スライドは 1 枚)に 1 か所。数値には単位と比較対象を添える。
|
|
142
|
+
- **Do:** トークンで色を指定する / 色の意味をアプリ全体でそろえる / チャートのタイトルに結論を書く。
|
|
143
|
+
- **Don't:** 色コードや素のパレットクラスを書く / `dark:` を並べる / 3D・装飾目的のグラデーション・
|
|
144
|
+
円グラフの多用 / 同じチャートで `negative` を普通の系列色に使う。
|
|
145
|
+
- スライド固有の規則は `agents/slide.md`。
|
|
115
146
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
**任意の `DESIGN.md`:** プロジェクトルートにあれば読んで従う(密度・スライドの見出し方針・
|
|
148
|
+
ブランド素材などユーザー固有の意図)。AGENTS.md の規則に加えて従うが、**色の割り当てはテーマを優先**
|
|
149
|
+
する。新規には作らない。ユーザーの持ち物で、`vantage upgrade` は書き換えも削除もしない。
|
|
119
150
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
Skill `vantage-design` の手順で `DESIGN.md` と `styles.css` をまとめて更新する。
|
|
124
|
-
- `DESIGN.md` はユーザーの持ち物。`vantage upgrade` は書き換えない。
|
|
151
|
+
**サイト全体のデザイン変更の依頼**(「もっと落ち着いた色に」「ブランドに合わせて」、配色・フォントの変更など)は、
|
|
152
|
+
ファイルごとに直さず、テーマの切り替えかスタイルシートの上書きで完結させる。`DESIGN.md` には記録しない。今のテーマ名は
|
|
153
|
+
`styles.css` の `/* theme: <name> */` マーカーから読む。手順は Skill `vantage-design` に従う。
|
|
125
154
|
|
|
126
155
|
## 何を書くか → どのファイルを読むか
|
|
127
156
|
|
|
@@ -137,6 +166,15 @@ root/
|
|
|
137
166
|
| `.xlsx` ページ | `agents/xlsx.md` |
|
|
138
167
|
| `.csv` ページ | `agents/csv.md` |
|
|
139
168
|
|
|
169
|
+
**ページになるのは `.tsx` / `.jsx`(`.slide.tsx`・`.doc.tsx` を含む)・`.csv`・`.xlsx` だけ。**
|
|
170
|
+
|
|
171
|
+
- `.xls`・`.xlsm`・`.xlsb`・`.ods`・`.tsv`・`.numbers`・`.parquet` はルートにならず、404 にもならない。
|
|
172
|
+
`vantage check` の `UNSUPPORTED_PAGE_FILE` 警告だけが知らせる。`.xlsx` / `.csv` に変換して置く
|
|
173
|
+
- `.md`・`.json`・`.txt` は何も言わずに無視される。Markdown を見せたいときは `.tsx` のページで
|
|
174
|
+
`MarkdownRenderer`(`@squadbase/vantage/markdown`)に渡す
|
|
175
|
+
- `.csv` / `.xlsx` はブラウザでパースする。25MB を超えるとそのページにエラーが出る
|
|
176
|
+
(`FILE_PAGE_TOO_LARGE` 警告)。20 万行を超えるシートは先頭 20 万行だけが表示される
|
|
177
|
+
|
|
140
178
|
## 環境変数
|
|
141
179
|
|
|
142
180
|
- **クライアントで読めるのは `import.meta.env.PUBLIC_*` と `MODE`/`DEV`/`PROD`/`SSR`/`BASE_URL`
|
|
@@ -151,7 +189,7 @@ vantage docs button # 1 ページ表示(短縮名。ui/button に解
|
|
|
151
189
|
vantage docs parts/data-table # 完全な slug でも引ける
|
|
152
190
|
vantage docs data-table --json # { slug, title, description, section, content }
|
|
153
191
|
vantage docs --all # 全ページを連結(まとめて読み込む用)
|
|
154
|
-
vantage docs routing --lang en # 英語版(
|
|
192
|
+
vantage docs routing --lang en # 英語版(`--lang` 省略時は ja。アプリの文言の言語とは無関係)
|
|
155
193
|
```
|
|
156
194
|
|
|
157
195
|
props の表・使用例・落とし穴はコンポーネントのページに載っている。**推測で props を書く前に
|
|
@@ -189,7 +227,7 @@ body・レスポンスの status と形)まで出る。`vantage check --json` /
|
|
|
189
227
|
- **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
|
|
190
228
|
- **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
|
|
191
229
|
- **`vantage-slide`** — `.slide.tsx` でスライドデッキを書く(キャンバスの予算・段階表示・pptx 書き出し)
|
|
192
|
-
- **`vantage-design`** —
|
|
230
|
+
- **`vantage-design`** — テーマの切り替え・`styles.css` の上書きで見た目を直す(配色・書体・ブランド合わせ)
|
|
193
231
|
- **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
|
|
194
232
|
- **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
|
|
195
233
|
|
|
@@ -224,7 +262,7 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
|
|
|
224
262
|
> `vantage upgrade` はこのファイルをフレームワーク同梱の正本で丸ごと上書きするため、
|
|
225
263
|
> このセクションは消えます。消えたら squadbase-monorepo の
|
|
226
264
|
> `packages/vantage-template/agents/vantage/` から貼り直してください
|
|
227
|
-
> (リポジトリ側では `
|
|
265
|
+
> (リポジトリ側では `pnpm run agents` が正本と結合して再生成します)。
|
|
228
266
|
|
|
229
267
|
## Squadbase の Connection / Query からデータを読む
|
|
230
268
|
|
|
@@ -302,6 +340,34 @@ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
|
|
|
302
340
|
})
|
|
303
341
|
```
|
|
304
342
|
|
|
343
|
+
### 生の応答から行を取り出す(`noUncheckedIndexedAccess`)
|
|
344
|
+
|
|
345
|
+
テンプレートの `tsconfig.json` は `noUncheckedIndexedAccess: true`。**配列の添字はすべて
|
|
346
|
+
`T | undefined`** なので、HTTP Query が通す上流の生の応答(GA4 の `rows[].metricValues[]` など)を
|
|
347
|
+
`row.metricValues[0].value` と書くと `Object is possibly 'undefined'.` で落ちる。書く前に添字
|
|
348
|
+
アクセスを頭の中で型検査し、応答の型と行の変換関数を先に決めて `?.` と `??` で取り出す:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
type Ga4Row = {
|
|
352
|
+
dimensionValues?: { value?: string }[]
|
|
353
|
+
metricValues?: { value?: string }[]
|
|
354
|
+
}
|
|
355
|
+
type SessionsDaily = { date: string; sessions: number; pageViews: number }
|
|
356
|
+
|
|
357
|
+
const toSessionsDaily = (row: Ga4Row): SessionsDaily => ({
|
|
358
|
+
date: row.dimensionValues?.[0]?.value ?? "",
|
|
359
|
+
sessions: Number(row.metricValues?.[0]?.value ?? "0"),
|
|
360
|
+
pageViews: Number(row.metricValues?.[1]?.value ?? "0"),
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
const report = (await res.json()) as { rows?: Ga4Row[] }
|
|
364
|
+
return Response.json((report.rows ?? []).map(toSessionsDaily))
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- 値が欠けた行を落としたいときは `if (!date) throw new HttpError(502, …)` ではなく、変換後に
|
|
368
|
+
`.filter((r) => r.date !== "")` で除く。`date!` のような非 null 断言で黙らせない。
|
|
369
|
+
- SQL Query の `rows` も同じ。`rows[0]` を使うなら `const first = rows[0]; if (!first) …` で先に絞る。
|
|
370
|
+
|
|
305
371
|
## 構築中の一時的なエラーを「進行中」に見せる(`components/build-recovery.tsx`)
|
|
306
372
|
|
|
307
373
|
ページを書いている途中は、ファイルが書き終わる順番の都合で一瞬だけページが throw する
|
|
@@ -314,6 +380,10 @@ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
|
|
|
314
380
|
`components/build-recovery.tsx` の `BuildRecovery` が、その画面を DOM から検知して
|
|
315
381
|
`Working on it…` 表示を被せ、dev では 1.2 秒待ってから自動でリロードして復帰させる
|
|
316
382
|
(上限 3 回。使い切ったら「再読み込み」の案内に降格し、無事に描けたら残数が戻る)。
|
|
383
|
+
**ただし `window.__VANTAGE_DRAFT__?.active` が `true` の間(AI の書きかけのファイルを下書きとして
|
|
384
|
+
プレビューに出している間)は、覆わず・リロードもしない。** 下書き中の描画エラーはフレームワークが
|
|
385
|
+
直前に通った版へ戻すので、ここで覆うとその版の表示まで隠れ、リロードすると下書きの表示ごと消える。
|
|
386
|
+
下書きが終わった時点でエラー画面が残っていれば、通常どおり覆う。
|
|
317
387
|
降格した画面にはエラー本文をそのまま出し、`Copy log` でコピーできるようにしてある —
|
|
318
388
|
自力で直らないときに本文をそのまま貼り戻してもらうため。
|
|
319
389
|
`_error.tsx` も同じ `RecoveringState` を描くので、ルートレイアウトが throw したときの
|
|
@@ -331,3 +401,140 @@ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
|
|
|
331
401
|
- **データ取得の失敗は各ページが `ErrorState` で出す。** `BuildRecovery` が拾うのは
|
|
332
402
|
「レンダリングが throw した」場合だけで、`useApiQuery` の `isError` はページの責任
|
|
333
403
|
(→ `agents/page.md` の「データ取得」)。
|
|
404
|
+
|
|
405
|
+
## 依存の追加とスクリプトの実行は bun で
|
|
406
|
+
|
|
407
|
+
このプロジェクトの package manager は **bun** です(`squadbase.yml` の `package_manager: bun`、
|
|
408
|
+
lockfile は `bun.lock`)。Squadbase のサンドボックスは bun と依存のキャッシュを持っており、
|
|
409
|
+
`bun install` はキャッシュだけで終わります。上のフレームワーク規約に出てくる
|
|
410
|
+
`pnpm install` / `pnpm dev` / `pnpm check` は、そのまま `bun install` / `bun run dev` /
|
|
411
|
+
`bun run check` に読み替えてください。
|
|
412
|
+
|
|
413
|
+
### 追加の不変条件
|
|
414
|
+
|
|
415
|
+
- **依存を足すときは `bun add <pkg>`(開発用は `bun add -d <pkg>`)。** `npm install` や
|
|
416
|
+
`pnpm add` を使わない — `package-lock.json` / `pnpm-lock.yaml` が生えて `bun.lock` と別の断面に
|
|
417
|
+
なり、次回起動時の install が lockfile を作り直す。
|
|
418
|
+
- **`bun.lock` を消さない・手で編集しない。** `package.json` の依存を直接書き換えたときは
|
|
419
|
+
`bun install` を一度走らせて `bun.lock` を追従させ、両方を同じコミットに入れる。
|
|
420
|
+
- スクリプトは `bun run <script>`(`bun run dev` / `bun run build` / `bun run check` /
|
|
421
|
+
`bun run routes`)。`npm run <script>` でも動くが、install に npm を使わないこと。
|
|
422
|
+
- **公開から 48 時間未満の版は入らない。** `bunfig.toml` の `minimumReleaseAge`(サプライチェーン対策)で
|
|
423
|
+
弾かれる。`bun add` / `bun install` が「バージョンが見つからない」で落ちたら、直近の版を避けて
|
|
424
|
+
少し前の版を指定する。`bunfig.toml` の設定を緩めたり `@squadbase/*` 以外の除外を足したりしない。
|
|
425
|
+
|
|
426
|
+
## このアプリで作れるもの
|
|
427
|
+
|
|
428
|
+
ユーザーの目的に合わせて、どの形にするかを選ぶ。ここに書かれていない形は約束しない。
|
|
429
|
+
|
|
430
|
+
- **ダッシュボード** — 数字を繰り返し見に来て、変化に気づきたいとき。指標と推移・内訳を 1 画面に
|
|
431
|
+
置き、`server/api/` で実データを取る。
|
|
432
|
+
- 書き込む場所: `src/<slug>.tsx`, `server/api/<slug>/**`
|
|
433
|
+
- ページ: あり
|
|
434
|
+
- 作り方: `src/` にページ(`.tsx`)を置く。書く前に `agents/page.md`(API は `agents/api.md`)を読む。
|
|
435
|
+
- **スライド** — 分析の示唆をまとめて誰かに伝えるとき(調査結果の報告、会議資料、施策の提案)。
|
|
436
|
+
結論 → 根拠 → 次の一手の順にまとめる。
|
|
437
|
+
- 書き込む場所: `src/<slug>.slide.tsx`, `vantage-slide/**`
|
|
438
|
+
- ページ: あり
|
|
439
|
+
- 作り方: `src/<slug>.slide.tsx` に `<SlideDeck>` を返すページを置く(`@squadbase/vantage/slide`。
|
|
440
|
+
ルートは `.slide` を除いた `/<slug>`)。
|
|
441
|
+
書く前に `agents/slide.md` を読み、`vantage-slide` Skill が未配置なら
|
|
442
|
+
`vantage add skill vantage-slide` で置いてから読む。
|
|
443
|
+
- **ドキュメント** — 報告書・調査のまとめ・説明資料のように、文章と図表を上から読む形で残し
|
|
444
|
+
共有したいとき。
|
|
445
|
+
- 書き込む場所: `src/<slug>.doc.tsx`
|
|
446
|
+
- ページ: あり
|
|
447
|
+
- 作り方: `src/<slug>.doc.tsx` に `<Document>` を返すページを置く(`@squadbase/vantage/doc`。
|
|
448
|
+
ルートは `.doc` を除いた `/<slug>`)。数値はコードに直接書き、チャートは `@squadbase/vantage/components` のチャート部品を文中に置く。
|
|
449
|
+
書く前に `agents/doc.md` を読む。
|
|
450
|
+
- **オートメーション** — 決まった時刻に集計・通知・定期レポートを回したいとき、しきい値で
|
|
451
|
+
アラートしたいとき。
|
|
452
|
+
- 書き込む場所: `automations/<slug>/**`, `pipelines/**`
|
|
453
|
+
- ページ: なし
|
|
454
|
+
- 作り方: `automations/<名前>/automation.yml` に定義し、処理のコードは `pipelines/` に置く。
|
|
455
|
+
書く前に Automation のガイド(`readAutomationGuide`)を読む。
|
|
456
|
+
|
|
457
|
+
CSV / Excel(`.csv` / `.xlsx`)は上の項目とは別で、書いて作るものではない。受け取ったファイルを
|
|
458
|
+
`src/` に置くだけで表として読めるページになる(`src/data/june.xlsx` → `/data/june`。置く前に
|
|
459
|
+
`agents/csv.md` / `agents/xlsx.md` を読む)。値だけが表示され、数式・書式は再現されない。25MB を
|
|
460
|
+
超えるとエラー、20 万行を超えると先頭 20 万行だけが出る。`.xls`・`.ods`・`.tsv` などはページにならない
|
|
461
|
+
(`vantage check` の `UNSUPPORTED_PAGE_FILE` 警告)。
|
|
462
|
+
|
|
463
|
+
## 表示領域に安定 ID を付ける(`data-sq-ref`)
|
|
464
|
+
|
|
465
|
+
Squadbase のチャットは、回答の根拠になったグラフ・表・KPI カードを
|
|
466
|
+
`squadbase://page/<path>#<ref>`(スライド内なら `?p=<N>#<ref>`)のリンクで指し、その領域を開く。
|
|
467
|
+
`<ref>` はページのソースに書かれた `data-sq-ref` の値で、過去の回答のリンクもこの値で今の画面を開く。
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
<DashboardCardPreset data-sq-ref="revenue-trend" title="売上の推移">
|
|
471
|
+
<EChart option={trendOption(data)} height="320px" />
|
|
472
|
+
</DashboardCardPreset>
|
|
473
|
+
|
|
474
|
+
{kpis.map((kpi) => (
|
|
475
|
+
<DashboardCard key={kpi.id} data-sq-ref={kpi.id}>...</DashboardCard>
|
|
476
|
+
))}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 追加の不変条件
|
|
480
|
+
|
|
481
|
+
- **意味のまとまった表示領域(グラフ・表・KPI カードなど)のルート要素に付ける。** 通常は
|
|
482
|
+
`DashboardCard` / `DashboardCardPreset` のように props を DOM に流す部品。`EChart` と ECharts で描く
|
|
483
|
+
チャート部品(`LineChart` / `BarChart` / `AreaChart` / `DonutChart`)・`DataTable` / `SectionHeader` は
|
|
484
|
+
付けた属性を DOM に流さないので、それらを包むカード(または `div`)に付ける。
|
|
485
|
+
- **値の形式は `^[A-Za-z0-9][A-Za-z0-9_-]{0,99}$`**(ASCII の英数字・ハイフン・アンダースコア、先頭は英数字、
|
|
486
|
+
100 文字以内)。これ以外の値はリンクから開けない。推奨は意味を表す小文字 kebab-case の名前
|
|
487
|
+
(例: `revenue-trend`, `churn-by-segment`)。表示順・ソース上の位置・index・連番から作らない。
|
|
488
|
+
- **`.map` で描く領域は、データの安定したキー(`kpi.id` など)がこの形式を満たすときだけ `data-sq-ref={kpi.id}`
|
|
489
|
+
のように使う。** 日本語や空白を含むキーはそのまま使わず、変換もしない(変換規則が変わると値が変わる)。
|
|
490
|
+
その場合は静的な意味名を付けるか、付けない。
|
|
491
|
+
- **ページ内で一意にする。**
|
|
492
|
+
- **新しく作った・直した画面では付ける。更新では既存の値を変えない** — 領域の意味が変わったときだけ変える
|
|
493
|
+
(値を変えると、それを指していた過去のリンクが開けなくなる)。触っていない画面へ遡って付けることはしない。
|
|
494
|
+
|
|
495
|
+
## ファイル内の書く順序(値 → 画面本体 → 子の部品)
|
|
496
|
+
|
|
497
|
+
ページ・部品のファイルは、上から次の順に書く。
|
|
498
|
+
|
|
499
|
+
1. `import`(と `definePage(...)`)
|
|
500
|
+
2. **値** — データ・定数・列定義・小さな関数。短く
|
|
501
|
+
3. **画面本体** — `export default` のコンポーネント
|
|
502
|
+
4. **子の部品** — 大文字始まりの部品。`function` 宣言で書く
|
|
503
|
+
|
|
504
|
+
理由: ファイルは上から順に書かれ、書きかけでもプレビューに反映される。**本体が書かれた時点で画面が出る。**
|
|
505
|
+
値を本体より後ろに書くと、本体はまだ無い値を読むので、その値が書かれるまで画面が出ない。
|
|
506
|
+
子の部品は後ろでよい — 未定義の部品はプレビューが仮の枠で埋める。
|
|
507
|
+
|
|
508
|
+
```tsx
|
|
509
|
+
import { definePage } from "@squadbase/vantage"
|
|
510
|
+
|
|
511
|
+
export const page = definePage({ title: "売上" })
|
|
512
|
+
|
|
513
|
+
const KPI_ITEMS: Kpi[] = [/* … */] // 値は本体より上
|
|
514
|
+
type Kpi = { label: string; value: number }
|
|
515
|
+
|
|
516
|
+
export default function SalesPage() { // 本体
|
|
517
|
+
return <KpiRow items={KPI_ITEMS} />
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function KpiRow({ items }: { items: Kpi[] }) { /* … */ } // 子の部品は下
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### 追加の不変条件
|
|
524
|
+
|
|
525
|
+
- **子の部品は `function` 宣言で書く。** 宣言は巻き上げられるので、本体より後ろでも安全。
|
|
526
|
+
`const Foo = () => …` のアロー関数は後ろに置くと、モジュール評価時に参照された瞬間に TDZ(初期化前参照)で落ちる。
|
|
527
|
+
- **値(定数・データ・列定義・小さな関数)は本体より上。** 小文字の関数・hook も値として上に置く。
|
|
528
|
+
- 型は消えるのでどこでもよい。値のそばに置く。
|
|
529
|
+
- 順序だけのために、既に並んでいるファイルを書き換えない。
|
|
530
|
+
|
|
531
|
+
## アプリの表示言語(`lang`)
|
|
532
|
+
|
|
533
|
+
ルートの `src/_layout.tsx` の `export const lang = "en"` が `<html lang>` になる。**アプリの内容の言語に合わせて書き換える**
|
|
534
|
+
(ユーザーが日本語で依頼している・画面の文言が日本語なら `"ja"`)。
|
|
535
|
+
|
|
536
|
+
- `lang` は**リテラル文字列**(BCP 47。`"ja"` / `"en"` / `"ja-JP"`)。変数・テンプレート補間は読まれない。
|
|
537
|
+
- 読まれるのは**ルートの `src/_layout.tsx` だけ**。入れ子の `_layout.tsx` やページには書かない。
|
|
538
|
+
- 日本語アプリで `"en"` のままだと、スライドの文節改行(`word-break: auto-phrase`)が効かず、見出しが単語の途中で折れる。
|
|
539
|
+
スライドのツールバー文言も `lang` で切り替わる(`"ja"` なら日本語、`en*` なら英語)。
|
|
540
|
+
- 値は消さない。指定が無いと `"en"` 扱いになる。
|
|
@@ -25,8 +25,16 @@ export async function GET({ params, env }: ApiContext) {
|
|
|
25
25
|
エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
|
|
26
26
|
- 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
|
|
27
27
|
`params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
|
|
28
|
+
- **`noUncheckedIndexedAccess` は配列・レコードの添字すべてに効く。** `arr[0]` も `obj[key]` も
|
|
29
|
+
`T | undefined` になる。最初に書く前に、添字アクセスと `useApiQuery` の `data` が
|
|
30
|
+
undefined を扱っているかを頭の中で型検査してから書く。上流の生の応答の値は
|
|
31
|
+
`Number(row.metricValues?.[0]?.value ?? "0")` のように `?.` と `??` で取り出す
|
|
32
|
+
(`!` で黙らせない)。
|
|
28
33
|
- **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
|
|
29
34
|
ログに記録され汎用の 500 に丸められる。
|
|
35
|
+
- **入力の誤りは 400 で返し、`msg` には何がどう誤りかを書く。** `msg` はそのまま `ErrorState` に
|
|
36
|
+
出る。「開始日(10/20)が終了日(10/01)より後です」のように、利用者が条件を直せる文にする。
|
|
37
|
+
許す範囲だけを並べた文(「最大366日で指定」)では、どこを直せばよいか伝わらない。
|
|
30
38
|
- **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
|
|
31
39
|
|
|
32
40
|
クライアント側からは `useApiQuery` で `/api/*` を叩く。ベース URL の解決・JSON パース・
|
|
@@ -34,9 +42,12 @@ export async function GET({ params, env }: ApiContext) {
|
|
|
34
42
|
|
|
35
43
|
```tsx
|
|
36
44
|
import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
|
|
45
|
+
import { Loading, ErrorState } from "@squadbase/vantage/ui"
|
|
37
46
|
|
|
38
47
|
const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
|
|
48
|
+
if (q.isPending) return <Loading />
|
|
39
49
|
if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
|
|
50
|
+
q.data // ここで初めて Customer(undefined ではない)
|
|
40
51
|
|
|
41
52
|
// クエリ文字列は search で。undefined の項目は落ちる
|
|
42
53
|
const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
|
|
@@ -45,5 +56,30 @@ const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
|
|
|
45
56
|
const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
|
|
46
57
|
```
|
|
47
58
|
|
|
59
|
+
**`data` は、同じ結果オブジェクトで `isPending` と `isError` を判定したあとでしか
|
|
60
|
+
narrowing されない。** 判定前や、配列の `.find` などでまとめた判定のあとでは `T | undefined` のまま。
|
|
61
|
+
複数のクエリは 1 本ずつ判定してから `data` を使う(`data!` で黙らせない):
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
const sessions = useApiQuery<SessionsDaily[]>("/api/sessions-daily")
|
|
65
|
+
const metrics = useApiQuery<MetricsDaily[]>("/api/metrics-daily")
|
|
66
|
+
|
|
67
|
+
if (sessions.isPending || metrics.isPending) return <Loading />
|
|
68
|
+
if (sessions.isError) return <ErrorState message={sessions.error.message} />
|
|
69
|
+
if (metrics.isError) return <ErrorState message={metrics.error.message} />
|
|
70
|
+
// ここから sessions.data: SessionsDaily[] / metrics.data: MetricsDaily[]
|
|
71
|
+
return <Chart sessions={sessions.data} metrics={metrics.data} />
|
|
72
|
+
|
|
73
|
+
// NG: const failed = [sessions, metrics].find((q) => q.isError) のように配列でまとめて判定すると
|
|
74
|
+
// 各 q.data は narrowing されず、`T[] | undefined` を T[] の props に渡して型エラーになる
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
カードごとに別々に読み込み中・エラーを出すなら、`data` を渡す側で `q.data ?? []` にするか、
|
|
78
|
+
カードのコンポーネントの中で上と同じ判定をする。
|
|
79
|
+
|
|
80
|
+
期間などの絞り込みを持つページでは、上の早期 return をページの先頭に置かない。失敗したときに
|
|
81
|
+
絞り込みの欄まで消え、条件を直せなくなる。判定はデータを描く子コンポーネントの中で行う
|
|
82
|
+
(→ `agents/page.md` の「絞り込みの欄は、読み込み中も失敗したときも出し続ける」)。
|
|
83
|
+
|
|
48
84
|
`ApiError` は `message`・`status`・`body`・`requestId` を持つ。hook が使えない場所では
|
|
49
85
|
`apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
```
|
|
28
28
|
- 数式・グラフ・セル書式といった概念は csv には無いので、値がそのまま読めます。ブラウザで
|
|
29
29
|
パースするので 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは
|
|
30
|
-
`server/api/` から配って `DataTable`
|
|
30
|
+
`server/api/` から配って `DataTable` で描いてください。20 万行を超えると先頭
|
|
31
|
+
20 万行だけが表示されます(残りはダウンロードして見る)
|
|
31
32
|
- **ビューアの無い拡張子(`.tsv` など)はルートになりません。** `vantage check` の
|
|
32
33
|
`UNSUPPORTED_PAGE_FILE` 警告が知らせます
|
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
14
|
import { definePage } from "@squadbase/vantage"
|
|
15
|
-
import {
|
|
15
|
+
import { BarChart } from "@squadbase/vantage/components"
|
|
16
16
|
import { Document } from "@squadbase/vantage/doc"
|
|
17
17
|
|
|
18
|
+
const weekly = [
|
|
19
|
+
{ week: "第1週", 売上: 1180 },
|
|
20
|
+
{ week: "第2週", 売上: 1240 },
|
|
21
|
+
{ week: "第3週", 売上: 1210 },
|
|
22
|
+
{ week: "第4週", 売上: 1620 },
|
|
23
|
+
]
|
|
24
|
+
|
|
18
25
|
export const page = definePage({ title: "6月度サマリー", navLabel: "6月度サマリー" })
|
|
19
26
|
export default function JuneSummary() {
|
|
20
27
|
return (
|
|
@@ -23,7 +30,7 @@ export default function JuneSummary() {
|
|
|
23
30
|
<p>6月の売上は <strong>5,250 万円</strong>で、目標を 5% 上回りました。</p>
|
|
24
31
|
<h2>週次の売上</h2>
|
|
25
32
|
<figure>
|
|
26
|
-
<
|
|
33
|
+
<BarChart data={weekly} index="week" categories={["売上"]} height={280} ariaLabel="6月の週次売上" />
|
|
27
34
|
<figcaption>第4週はキャンペーン初週</figcaption>
|
|
28
35
|
</figure>
|
|
29
36
|
<table>…</table>
|
|
@@ -42,13 +49,38 @@ export default function JuneSummary() {
|
|
|
42
49
|
見た目を変えたいときはユーティリティを並べず、下の `--doc-*` を変えます。
|
|
43
50
|
- **数値はコードに直接書く。** ドキュメントは書いた時点の内容を残すページです。
|
|
44
51
|
`useApiQuery` などで読み込むと、開くたびに値が変わります。
|
|
45
|
-
-
|
|
52
|
+
- **チャートは型付きチャート(`BarChart`・`LineChart` など。`@squadbase/vantage/components`)を
|
|
53
|
+
文中に置く**(選び方は `agents/page.md` の「グラフはまず型付きチャートで描く」)。`figure` で囲めば
|
|
46
54
|
`figcaption` がキャプションになります。
|
|
47
55
|
- **PDF の保存ボタンは作らない。** `<Document>` が右下に PDF 保存のボタンを出し、印刷時は
|
|
48
56
|
本文だけをライトの配色で出します。
|
|
49
57
|
- **ページ全体を包むのは `<Document>` だけ。** `PageShell` などのダッシュボード用の枠で包まない
|
|
50
58
|
(本文の幅と余白は `<Document>` が決めます)。
|
|
51
59
|
|
|
60
|
+
## まず block を探す
|
|
61
|
+
|
|
62
|
+
1 節分の中身は、手で組む前に `@squadbase/vantage/doc` の block から探します(`<Document>` の直下に
|
|
63
|
+
並べる。多くは 1 block = 1 節。`DocCallout`・`DocFigure`・`DocDataTable` は節ではなく、節の中(本文の間)に置く部品)。props は `vantage docs <name>`(例: `vantage docs doc-kpi-scorecard`)で引く。
|
|
64
|
+
|
|
65
|
+
| block | 用途 |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `DocReportHeader` | レポートの冒頭(タイトル・サブタイトル・対象期間・データ時点・作成者・出典) |
|
|
68
|
+
| `DocExecutiveSummary` | 結論を先に述べる要約の節 |
|
|
69
|
+
| `DocKpiScorecard` | 主要指標をまとめたスコアカード |
|
|
70
|
+
| `DocFigure` | 図表 1 つとキャプション(チャートは children で渡す) |
|
|
71
|
+
| `DocDataTable` | 素の `table` による表 |
|
|
72
|
+
| `DocCallout` | 補足・示唆・注意・決定の囲み(`kind`: `note` / `insight` / `warning` / `decision`) |
|
|
73
|
+
| `DocKeyFindings` | 主要な発見の箇条 |
|
|
74
|
+
| `DocActionItems` | 次のアクション(担当・期限つき) |
|
|
75
|
+
| `DocNotesAndSources` | 末尾の注記・用語の定義・データソースをまとめた節 |
|
|
76
|
+
|
|
77
|
+
**props の渡し方は block 間で共通。** 数値は `value: number | null` と `format`(整形は block がする。表示を上書きするときだけ `display`。`null` は「—」)。前期比などは `delta: { value, format? }` と `comparisonLabel`(例 `"前年同期比"`)、良し悪しは指標ごとの `betterWhen`(`"up"` が既定、コストなど下がるほど良いものは `"down"`。向きは block が符号から出す)。目標は `target: number`。出典は `source`、時点は `asOf` に**日付だけ**(例 `"2026-09-30"`。「データ時点」は block が付けるので「時点」を書かない)。時系列は、単一系列なら `{ x, y }[]`(`trend`)。
|
|
78
|
+
|
|
79
|
+
本文の中で増減を書くときは `DeltaText`(部品。「▲ +3.2%」の形で、文字サイズは持たず置かれた行に従う。`value` `format` `betterWhen` `comparisonLabel`)を使う。節ではなく文の中に置く。
|
|
80
|
+
|
|
81
|
+
block が合わない**1 節だけ**を素の HTML 要素で手で書く。import 元は `@squadbase/vantage/doc` のままで、
|
|
82
|
+
block のための新しい import 元はありません。本文は文字列か ReactNode で渡し、Markdown 文字列は渡さない。
|
|
83
|
+
|
|
52
84
|
## 見た目を変える
|
|
53
85
|
|
|
54
86
|
配色・書体・本文の幅は `--doc-*` の CSS 変数です。既定値はアプリのトークンへの別名なので、
|
|
@@ -73,3 +105,20 @@ export default function JuneSummary() {
|
|
|
73
105
|
`.doc` を付けられるのはルートになるページモジュールだけで、`_layout.doc.tsx` などは
|
|
74
106
|
`INVALID_PAGE_VARIANT` エラーになります。`june.doc.tsx` と `june.tsx` は同じ URL に解決するので
|
|
75
107
|
`ROUTE_CONFLICT` です。
|
|
108
|
+
|
|
109
|
+
## ファイル形式の規約
|
|
110
|
+
|
|
111
|
+
置き場所・URL・default export・`definePage` の書き方は全ページ共通(`agents/page.md` の
|
|
112
|
+
「ページファイルの規約」)。ここではドキュメント固有のものだけを挙げます。
|
|
113
|
+
|
|
114
|
+
**MUST**
|
|
115
|
+
|
|
116
|
+
- ファイル名は `<name>.doc.tsx`(`.jsx` も可)。サフィックスは小文字で 1 つだけ。`.doc.mdx` は
|
|
117
|
+
`INVALID_PAGE_VARIANT`、`.DOC.tsx`・二重サフィックスは黙って通常ページになる
|
|
118
|
+
- default export は `<Document>` をルートに返す。フレームワークは包まない
|
|
119
|
+
- ドキュメントの部品は `@squadbase/vantage/doc` から import する
|
|
120
|
+
|
|
121
|
+
**SHOULD**
|
|
122
|
+
|
|
123
|
+
- `PageShell` で包まない・PDF の保存ボタンを自作しない(上の「書き方」)
|
|
124
|
+
- 数値はコードに直接書き、いつ時点の値かを本文に書く
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(ダッシュボード)を書くときに読んでください。前提となる不変条件・ディレクトリ構造は
|
|
5
5
|
`AGENTS.md` を先に読んでいる想定です。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
書き始める前に `AGENTS.md` の「見た目の規約」(と、あればプロジェクトルートの `DESIGN.md`)に従う。
|
|
8
8
|
|
|
9
9
|
## 最小アプリ(2 ファイル)
|
|
10
10
|
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@squadbase/vantage": "^0.5.0",
|
|
29
|
-
"react": "^19.
|
|
30
|
-
"react-dom": "^19.
|
|
29
|
+
"react": "^19.3.1",
|
|
30
|
+
"react-dom": "^19.3.1"
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
```
|
|
@@ -71,6 +71,7 @@ URL になる。各ページは**デフォルトエクスポートの React コ
|
|
|
71
71
|
| `_404.tsx` | Not Found ページ |
|
|
72
72
|
| `_error.tsx` | ルートが throw したときのエラーページ |
|
|
73
73
|
| `monthly-report.slide.tsx` | `/monthly-report`(スライドデッキ。詳しくは `agents/slide.md`) |
|
|
74
|
+
| `june-summary.doc.tsx` | `/june-summary`(ドキュメント。詳しくは `agents/doc.md`) |
|
|
74
75
|
| `sales-export.csv` | `/sales-export`(詳しくは `agents/csv.md`) |
|
|
75
76
|
| `data/june.xlsx` | `/data/june`(詳しくは `agents/xlsx.md`) |
|
|
76
77
|
|
|
@@ -89,6 +90,26 @@ export default function MonthlyAnalysis() {
|
|
|
89
90
|
}
|
|
90
91
|
```
|
|
91
92
|
|
|
93
|
+
### ページファイルの規約(`.tsx` / `.slide.tsx` / `.doc.tsx` 共通)
|
|
94
|
+
|
|
95
|
+
**MUST**
|
|
96
|
+
|
|
97
|
+
- ページルートの配下に置く。`components/` などの走査対象外のディレクトリに置いたファイルはページに
|
|
98
|
+
ならず、エラーも出ない。`_` 始まりの名前は予約名(`_layout` / `_404` / `_error`)にだけ使う
|
|
99
|
+
(それ以外の `_` 始まりはページにならない。予約名にサフィックスを付けると `INVALID_PAGE_VARIANT`)
|
|
100
|
+
- URL はサフィックスと拡張子を除いたパスで決まる。同じ URL になる別ファイル(`june.tsx` /
|
|
101
|
+
`june.slide.tsx` / `june.doc.tsx` / `june.csv`)を並べない(`ROUTE_CONFLICT`)。テンプレートの
|
|
102
|
+
未編集の `index.tsx` だけは例外で、`index.slide.tsx` / `index.doc.tsx` が `/` を取れる
|
|
103
|
+
- default export は React の関数コンポーネント(無いと `MISSING_DEFAULT_EXPORT`。関数かどうかは
|
|
104
|
+
検査されない)
|
|
105
|
+
- `definePage({ ... })` にはオブジェクトリテラルを直接渡し、`title` / `description` / `navLabel` は
|
|
106
|
+
文字列リテラルで書く。ビルド時に静的抽出されるので、変数・関数呼び出し・spread・shorthand・
|
|
107
|
+
`${}` を含むテンプレート文字列は**エラーにならず黙ってメタなし**になる
|
|
108
|
+
|
|
109
|
+
**SHOULD**
|
|
110
|
+
|
|
111
|
+
- `definePage` に `title` と `navLabel` を書く(ナビの表示名とブラウザのタブ名になる)
|
|
112
|
+
|
|
92
113
|
### ナビはルート一覧から組む
|
|
93
114
|
|
|
94
115
|
`useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
|
|
@@ -109,7 +130,7 @@ routes.map((r) => (
|
|
|
109
130
|
```
|
|
110
131
|
|
|
111
132
|
`RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
|
|
112
|
-
`title`・`description`・`navLabel`・`kind`(`"module"` | `"file"`)・`variant`(`"slide"`)。
|
|
133
|
+
`title`・`description`・`navLabel`・`kind`(`"module"` | `"file"`)・`variant`(`"slide"` | `"doc"`)。
|
|
113
134
|
後ろの 2 つで、ナビをダッシュボード / スライド / データに分けたりアイコンを変えたりできます。
|
|
114
135
|
|
|
115
136
|
### フィルタ状態は URL に置く
|
|
@@ -126,6 +147,42 @@ const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON
|
|
|
126
147
|
デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
|
|
127
148
|
(`{ replace: false }` で push)。
|
|
128
149
|
|
|
150
|
+
### 絞り込みの欄は、読み込み中も失敗したときも出し続ける
|
|
151
|
+
|
|
152
|
+
ページの先頭で `if (q.isError) return <ErrorState …/>` と書くと、取得に失敗したときに
|
|
153
|
+
期間などの絞り込みの欄まで消える。利用者は条件を直せず、初期値に戻すしかなくなる。
|
|
154
|
+
絞り込みの欄はページに置いたままにし、取得と `Loading` / `ErrorState` はデータを描く
|
|
155
|
+
子コンポーネントに入れる:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
export default function Traffic() {
|
|
159
|
+
const [from, setFrom] = useSearchParam("from", DEFAULT_FROM)
|
|
160
|
+
const [to, setTo] = useSearchParam("to", DEFAULT_TO)
|
|
161
|
+
return (
|
|
162
|
+
<>
|
|
163
|
+
{/* 失敗しても消えない。ここから条件を直せる */}
|
|
164
|
+
<PeriodFilter from={from} to={to} onChange={…} />
|
|
165
|
+
<TrafficBody from={from} to={to} />
|
|
166
|
+
</>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function TrafficBody({ from, to }: { from: string; to: string }) {
|
|
171
|
+
const q = useApiQuery<Row[]>("/api/traffic", { search: { from, to } })
|
|
172
|
+
if (q.isPending) return <Loading />
|
|
173
|
+
if (q.isError) return <ErrorState message={q.error.message} />
|
|
174
|
+
return <BarChart data={q.data} index="channel" categories={["sessions"]} />
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **期間は `DateRangePicker`(`@squadbase/vantage/components`)で選ばせる。** カレンダーで
|
|
179
|
+
範囲を選ぶので開始と終了が逆にならず、選べる日を `minDate` / `maxDate` で絞れる。
|
|
180
|
+
`<input type="date">` を 2 つ並べて自前で組まない。
|
|
181
|
+
- 値をそのまま打ち込める欄を置くなら、開始が終了より後・選べる範囲の外といった誤りは、
|
|
182
|
+
API へ送る前に欄のそばで理由を出す。誤りがあるあいだはデータの子コンポーネントを描かない
|
|
183
|
+
(送れば失敗するだけで、`ErrorState` には直し方が出ない)。
|
|
184
|
+
- サーバー側の検証で弾くときも、`HttpError(400, …)` の文で何がどう誤りかを言う(→ `agents/api.md`)。
|
|
185
|
+
|
|
129
186
|
### 動的パラメータの「3 つの綴り」を同期させる
|
|
130
187
|
|
|
131
188
|
崩すと静かにマッチしなくなる不変条件:
|
|
@@ -164,17 +221,41 @@ refetchOnWindowFocus false・networkMode "always")。挙動を変えたいとき
|
|
|
164
221
|
## 組み込み UI / コンポーネントの入口
|
|
165
222
|
|
|
166
223
|
- **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
|
|
167
|
-
`ErrorState`・`Empty`・`Select`・`Checkbox
|
|
224
|
+
`ErrorState`・`Empty`・`Select`・`Checkbox`・`RadioGroup`・`Field`・`Combobox`・`Pagination`・
|
|
225
|
+
`Avatar`・`Kbd`・`HoverCard`・`AlertDialog`・`Drawer`・`Toaster` + `toast.add()` ほか)。
|
|
168
226
|
- **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
|
|
169
|
-
`DataTablePreset
|
|
227
|
+
`DataTablePreset`・型付きチャート(下の「グラフ」)・`EChart` ほか)。
|
|
170
228
|
- **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
|
|
171
229
|
リファレンスはパッケージに同梱されていて、オフラインでも読める(→ `AGENTS.md` の
|
|
172
230
|
「ドキュメントを引く」)。**名前が分からないとき**はやりたいことで `vantage search` する
|
|
173
231
|
(→ 同節)。
|
|
174
|
-
- **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui
|
|
175
|
-
|
|
176
|
-
`
|
|
177
|
-
`add
|
|
232
|
+
- **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui <name>` は
|
|
233
|
+
ソースを手元にコピーして**改造したいとき専用**のコマンド。`/ui` のプリミティブも
|
|
234
|
+
`/components` の複合パーツも全部取り出せる(名前は kebab-case: `add ui bar-chart`。
|
|
235
|
+
ブロックは `add block sales-overview`。未知名で実行すると候補が一覧表示される)。
|
|
236
|
+
|
|
237
|
+
### 長い表はページングする(縦スクロール箱を作らない)
|
|
238
|
+
|
|
239
|
+
- 行が多い表は `DataTablePreset`(または `DataTable`)に `enablePagination` を付けて**ページング**する
|
|
240
|
+
(既定は 10 行 / ページ。ページングは既定で無効なので、付け忘れると全行が並ぶ)。素の `<table>` を
|
|
241
|
+
手書きして全行を流し込まない。
|
|
242
|
+
- カードの中に `max-h-*` + `overflow-y-auto` の**縦スクロール領域を作らない**。ホイールやキー
|
|
243
|
+
(End / Space)がその箱に吸われ、ページ全体がスクロールしなくなる。高さは内容に任せる。
|
|
244
|
+
- 横に広い表の `overflow-x-auto` は構わない。`SheetGrid`(`.csv` / `.xlsx` のビューア)は
|
|
245
|
+
グリッドとして意図した入れ子スクロールなので対象外。
|
|
246
|
+
|
|
247
|
+
### グラフはまず型付きチャートで描く
|
|
248
|
+
|
|
249
|
+
- 推移は `LineChart` / `AreaChart`、比較は `BarChart`、構成比は `DonutChart`。`data`(行の配列)・
|
|
250
|
+
`index`(x 軸の列)・`categories`(系列の列)を渡すだけで、option は書かない
|
|
251
|
+
(`DonutChart` は `category` / `value`)。
|
|
252
|
+
- 上位 N 件は `BarList`、判定帯や予算消化は `CategoryBar`、達成率は `ProgressCircle`、
|
|
253
|
+
日ごとの状態は `Tracker`。
|
|
254
|
+
- `EChart` + `EChartsOption` は、二軸・散布図・ヒートマップ・注釈線など型付きチャートの props で
|
|
255
|
+
表せないときだけ使う。
|
|
256
|
+
- 色は省略してテーマトークンに任せる。指定するなら `"chart-1"`〜`"chart-5"` か
|
|
257
|
+
`"var(--destructive)"` などの CSS 色。hex の直書きや Tailwind クラスは使わない。
|
|
258
|
+
- 数値の書式(`¥`・`%`・桁区切り)は `valueFormatter` に渡す。
|
|
178
259
|
|
|
179
260
|
### ダークモードは opt-in
|
|
180
261
|
|