@squadbase/vantage-template 0.0.2 → 0.1.0-dev.0faf4cb
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 +9 -7
- package/base-template/.squadbase/skills/project-storage/SKILL.md +157 -0
- package/base-template/.squadbase/skills/vantage-app/SKILL.md +6 -6
- package/base-template/.squadbase/skills/vantage-design/SKILL.md +127 -0
- package/base-template/.squadbase/skills/vantage-design/references/extract.md +110 -0
- package/base-template/.squadbase/skills/vantage-design/references/format.md +172 -0
- package/base-template/.squadbase/skills/vantage-design/references/presets.md +82 -0
- package/base-template/.squadbase/skills/vantage-design/scripts/contrast.mjs +166 -0
- package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +22 -0
- package/base-template/AGENTS.md +98 -218
- package/base-template/DESIGN.md +108 -0
- package/base-template/agents/api.md +49 -0
- package/base-template/agents/csv.md +32 -0
- package/base-template/agents/doc.md +73 -0
- package/base-template/agents/page.md +187 -0
- package/base-template/agents/slide.md +41 -0
- package/base-template/agents/xlsx.md +33 -0
- package/base-template/automations/.gitkeep +0 -0
- package/base-template/bun.lock +907 -0
- package/base-template/package.json +3 -3
- package/base-template/pipelines/pyproject.toml +5 -0
- package/base-template/squadbase.yml +1 -0
- package/base-template/src/_error.tsx +7 -11
- package/base-template/src/_layout.tsx +8 -1
- package/base-template/src/components/build-recovery.tsx +219 -0
- package/base-template/src/styles.css +19 -0
- package/dist/index.js +16 -7
- package/package.json +18 -12
- package/ui-templates/chart-grid/pages/index.tsx +1 -1
- package/ui-templates/chart-grid/preview-square.png +0 -0
- package/ui-templates/chart-grid/preview-wide.png +0 -0
- package/ui-templates/chart-grid-ja/pages/index.tsx +1 -1
- package/ui-templates/chart-grid-ja/preview-square.png +0 -0
- package/ui-templates/chart-grid-ja/preview-wide.png +0 -0
- package/ui-templates/funnel/pages/index.tsx +2 -2
- package/ui-templates/funnel/preview-square.png +0 -0
- package/ui-templates/funnel/preview-wide.png +0 -0
- package/ui-templates/funnel-ja/pages/index.tsx +2 -2
- package/ui-templates/funnel-ja/preview-square.png +0 -0
- package/ui-templates/funnel-ja/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-advanced/preview-square.png +0 -0
- package/ui-templates/kpi-chart-advanced/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-advanced-ja/preview-square.png +0 -0
- package/ui-templates/kpi-chart-advanced-ja/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-simple/preview-square.png +0 -0
- package/ui-templates/kpi-chart-simple/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-simple-ja/preview-square.png +0 -0
- package/ui-templates/kpi-chart-simple-ja/preview-wide.png +0 -0
- package/ui-templates/table-focus/preview-square.png +0 -0
- package/ui-templates/table-focus/preview-wide.png +0 -0
- package/ui-templates/table-focus-ja/preview-square.png +0 -0
- package/ui-templates/table-focus-ja/preview-wide.png +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
DESIGN.md — このアプリの「見た目の意図」を書くファイルです。
|
|
3
|
+
|
|
4
|
+
- 実際の色・角丸・書体の値は src/styles.css にあります。ここにはトークン名(--primary など)と
|
|
5
|
+
使い分けのルールだけを書き、色コードは書きません。
|
|
6
|
+
- エージェントはページ・スライド・コンポーネントを書く前にこのファイルを読みます。
|
|
7
|
+
会話の中での指示はこのファイルより優先されます。
|
|
8
|
+
- 「このサイトに合わせて」「色を落ち着いた感じに」と頼めば、このファイルと styles.css を
|
|
9
|
+
まとめて更新します。
|
|
10
|
+
- このファイルはあなたのものです。`vantage upgrade` が書き換えることはありません。
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
# デザイン
|
|
14
|
+
|
|
15
|
+
## 全体のトーン (Overview)
|
|
16
|
+
|
|
17
|
+
- 落ち着いた無彩色のベースに、データだけが色を持つ業務ダッシュボード。
|
|
18
|
+
- 装飾より読みやすさを優先する。影・グラデーション・アニメーションは最小限。
|
|
19
|
+
- 数字が主役。1 画面で伝えたいことを 1 つに絞り、補足は小さく添える。
|
|
20
|
+
- ブランド素材: なし(ロゴを使う場合は `public/brand/logo.svg` のように `public/` 配下のパスで書く)
|
|
21
|
+
|
|
22
|
+
## 色 (Colors)
|
|
23
|
+
|
|
24
|
+
色は必ずトークン(Tailwind のセマンティッククラス)で指定する。素のパレット(`bg-blue-600` など)や
|
|
25
|
+
色コードを直接書かない。
|
|
26
|
+
|
|
27
|
+
| 役割 | トークン | 使う場所 |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| 主色 (Primary) | `--primary` / `--primary-foreground` | 主要ボタン、選択中の状態。既定は無彩色 |
|
|
30
|
+
| 強調 (Accent) | `--chart-1` | 1 つだけ目立たせたい数値・系列・リンク的な強調 |
|
|
31
|
+
| 良い (Positive) | `--chart-2` | 前期比のプラスなど、良い変化の小さな印 |
|
|
32
|
+
| 悪い (Negative) | `--destructive` | 前期比のマイナス、エラー、削除操作 |
|
|
33
|
+
| 注意 (Warning) | 専用トークンなし | 文言とアイコンで示し、色は `--muted-foreground` |
|
|
34
|
+
| 背景の段階 | `--background` → `--card` → `--muted` | ページ → カード → カード内の区画・ホバー |
|
|
35
|
+
| 境界 | `--border` / `--input` / `--ring` | 罫線、入力欄、フォーカス |
|
|
36
|
+
| 文字 | `--foreground` / `--muted-foreground` | 本文 / 補足・ラベル・単位 |
|
|
37
|
+
|
|
38
|
+
- `--muted`・`--accent`・`--secondary`・`--border` は背景・境界用。文字色に使わない。
|
|
39
|
+
- 増減(▲▼・前期比)は向きで色を分ける: 増加・プラスは `--chart-2`(または `--primary`)、
|
|
40
|
+
減少・マイナスは `--destructive`。下向きの矢印やマイナスを `--primary` で塗らない。
|
|
41
|
+
- ライトとダークの両方がトークンで決まるので `dark:` を並べない。
|
|
42
|
+
|
|
43
|
+
## チャート (Charts)
|
|
44
|
+
|
|
45
|
+
- 系列の順番は `--chart-1` → `--chart-2` → `--chart-3` → `--chart-4` → `--chart-5`
|
|
46
|
+
(現在はプリセット `blue`: 青 → ティール → フクシア → バイオレット → グレー)。
|
|
47
|
+
- 1 系列だけを目立たせるときは、その系列を `--chart-1`、残りをすべて `--chart-5`(グレー)にする。
|
|
48
|
+
- 良い / 悪いを色で塗り分けるときは `--chart-2` / `--destructive`。色だけに頼らず ▲▼ や符号も添える。
|
|
49
|
+
- 系列は 5 つまで。それ以上はグループ化するか「その他」にまとめる。
|
|
50
|
+
- 禁止: 同じチャートで `--destructive` を普通の系列色として使う(悪い値に見える)。
|
|
51
|
+
- 禁止: 隣り合う系列に `--chart-3` と `--chart-4` を並べて細い線で描く(見分けにくい)。
|
|
52
|
+
- 配色を丸ごと変えるときは `npx @squadbase/vantage-template chart <preset>` を使う
|
|
53
|
+
(`blue` / `ocean` / `sunset` / `forest` / `berry` / `mono`)。
|
|
54
|
+
|
|
55
|
+
## 書体 (Typography)
|
|
56
|
+
|
|
57
|
+
| 用途 | 指定 | 備考 |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| 見出し・本文 | システムフォント(`body` の `font-family`) | Web フォントは使わない |
|
|
60
|
+
| 数字 | 同上 + `tabular-nums` | 桁がそろうように必ず付ける |
|
|
61
|
+
| スライドの見出し | `--slide-font-display` | 既定は日本語を含むシステムフォント |
|
|
62
|
+
| スライドの本文 | `--slide-font-body` | 既定は見出しと同じ |
|
|
63
|
+
|
|
64
|
+
サイズの段階(ダッシュボード):
|
|
65
|
+
|
|
66
|
+
- ページ見出し: `text-2xl font-bold`
|
|
67
|
+
- カード見出し・ラベル: `text-sm font-medium text-muted-foreground`
|
|
68
|
+
- KPI の数値: `text-3xl font-semibold tabular-nums`
|
|
69
|
+
- 本文・表: `text-sm`
|
|
70
|
+
- 注釈・出典: `text-xs text-muted-foreground`
|
|
71
|
+
|
|
72
|
+
スライドのサイズは `--slide-size-hero`(表紙・大きな数字)と `--slide-size-body`(本文)を基準にする。
|
|
73
|
+
|
|
74
|
+
## レイアウトと密度 (Layout)
|
|
75
|
+
|
|
76
|
+
- 情報はカード(`bg-card` + `border-border`)で区切る。角丸は `--radius` に従う(個別に変えない)。
|
|
77
|
+
- グリッドの間隔は `gap-4`。セクション間は `gap-6` 程度。
|
|
78
|
+
- KPI は 1 行に 2〜4 枚。その下にチャート、最後に明細の表。
|
|
79
|
+
- 密度は「中」。余白を削って詰め込むより、ページを分ける。
|
|
80
|
+
|
|
81
|
+
## 成果物の種類ごとの規則 (Per-artifact)
|
|
82
|
+
|
|
83
|
+
### ダッシュボード
|
|
84
|
+
|
|
85
|
+
- 先頭に KPI カード、次にトレンドのチャート、最後に明細の表。
|
|
86
|
+
- 各 KPI には比較対象(前月比・目標比など)を 1 つ添える。
|
|
87
|
+
- チャートのタイトルは結論を書く(「売上推移」より「売上は 3 か月連続で増加」)。
|
|
88
|
+
|
|
89
|
+
### スライド
|
|
90
|
+
|
|
91
|
+
- 1 枚に 1 メッセージ。見出しに結論を書く。
|
|
92
|
+
- 色は `--slide-bg` / `--slide-fg` / `--slide-accent` / `--slide-muted` を使う
|
|
93
|
+
(既定では `--background` / `--foreground` / `--primary` / `--muted-foreground` に連動)。
|
|
94
|
+
- 強調色は 1 枚につき 1 か所まで。
|
|
95
|
+
|
|
96
|
+
## Do / Don't
|
|
97
|
+
|
|
98
|
+
Do:
|
|
99
|
+
|
|
100
|
+
- トークンと Tailwind のセマンティッククラスで色を指定する。
|
|
101
|
+
- 数値には単位と比較対象を付ける。
|
|
102
|
+
- 色の意味(良い / 悪い / 強調)をアプリ全体でそろえる。
|
|
103
|
+
|
|
104
|
+
Don't:
|
|
105
|
+
|
|
106
|
+
- 色コードや素のパレットクラスを直接書く。
|
|
107
|
+
- 1 画面で強調色を何か所にも使う。
|
|
108
|
+
- 3D チャート・円グラフの多用・装飾目的のグラデーション。
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# API(server/)
|
|
2
|
+
|
|
3
|
+
フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`server/api/**`
|
|
4
|
+
に API ルートを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
|
|
5
|
+
先に読んでいる想定です。
|
|
6
|
+
|
|
7
|
+
## API を持つ(fullstack モード)
|
|
8
|
+
|
|
9
|
+
**`server/` ディレクトリがあれば fullstack モード**(無いアプリに足せば、その時点で切り替わる)。
|
|
10
|
+
`server/api/**` の各ファイルが API ルートになる。
|
|
11
|
+
|
|
12
|
+
`server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import type { ApiContext } from "@squadbase/vantage/server"
|
|
16
|
+
import { HttpError } from "@squadbase/vantage/server"
|
|
17
|
+
|
|
18
|
+
export async function GET({ params, env }: ApiContext) {
|
|
19
|
+
if (!env.API_KEY) throw new HttpError(500, "API_KEY missing")
|
|
20
|
+
return Response.json({ ok: true })
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- API モジュールは**大文字の HTTP メソッド**(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
|
|
25
|
+
エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
|
|
26
|
+
- 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
|
|
27
|
+
`params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
|
|
28
|
+
- **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
|
|
29
|
+
ログに記録され汎用の 500 に丸められる。
|
|
30
|
+
- **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
|
|
31
|
+
|
|
32
|
+
クライアント側からは `useApiQuery` で `/api/*` を叩く。ベース URL の解決・JSON パース・
|
|
33
|
+
非 2xx の `ApiError` 化・クエリキー(`["api", url]`)が入っている:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
|
|
37
|
+
|
|
38
|
+
const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
|
|
39
|
+
if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
|
|
40
|
+
|
|
41
|
+
// クエリ文字列は search で。undefined の項目は落ちる
|
|
42
|
+
const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
|
|
43
|
+
|
|
44
|
+
// 書き込み。変数がそのまま JSON ボディになる
|
|
45
|
+
const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`ApiError` は `message`・`status`・`body`・`requestId` を持つ。hook が使えない場所では
|
|
49
|
+
`apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# csv ページ
|
|
2
|
+
|
|
3
|
+
フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.csv` を
|
|
4
|
+
ページとして置くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
|
|
5
|
+
先に読んでいる想定です。
|
|
6
|
+
|
|
7
|
+
## `.csv` を置くだけでページになる
|
|
8
|
+
|
|
9
|
+
**`.csv` を `src/` に置くだけで、他のページと同格のルートになります。** URL の作り方は
|
|
10
|
+
`.tsx` と同じ(`src/sales-export.csv` → `/sales-export`、`index.csv` はそのディレクトリ)で、
|
|
11
|
+
ナビにも `vantage routes` にも並びます。開くとフレームワーク同梱の表ビューアが描き、
|
|
12
|
+
元ファイルをダウンロードできます。ビューアは**表計算ソフトの見た目**です —— 列は A / B / C、
|
|
13
|
+
左に行番号、ファイルの 1 行目はヘッダとして固定。読み取り専用で、編集もソートもできません
|
|
14
|
+
(セルを選んで `Cmd/Ctrl+C` でコピーは可)。
|
|
15
|
+
|
|
16
|
+
- **タイトルはファイル名から導かれる**(`sales-export.csv` → 「Sales Export」)。データファイルに
|
|
17
|
+
`definePage` は書けません。名前を変えたいときはファイルをリネームします
|
|
18
|
+
- **ページにしたくないファイルは `_` を前置する**(`_sales-export.csv`)。`components/`・`hooks/`・
|
|
19
|
+
`lib/` に置いたものも同じくページになりません
|
|
20
|
+
- **既定はファイルをそのまま置く(tsx を書かない)。** 自分でタイトルや説明を付けたいときだけ、`_` を
|
|
21
|
+
前置してルート化を止め、tsx から描きます:
|
|
22
|
+
```tsx
|
|
23
|
+
import { FilePageView } from "@squadbase/vantage/table"
|
|
24
|
+
import src from "./_sales-export.csv?url&no-inline"
|
|
25
|
+
export const page = definePage({ title: "売上エクスポート" })
|
|
26
|
+
export default () => <FilePageView src={src} kind="csv" fileName="sales-export.csv" />
|
|
27
|
+
```
|
|
28
|
+
- 数式・グラフ・セル書式といった概念は csv には無いので、値がそのまま読めます。ブラウザで
|
|
29
|
+
パースするので 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは
|
|
30
|
+
`server/api/` から配って `DataTable` で描いてください
|
|
31
|
+
- **ビューアの無い拡張子(`.tsv` など)はルートになりません。** `vantage check` の
|
|
32
|
+
`UNSUPPORTED_PAGE_FILE` 警告が知らせます
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ドキュメント(.doc.tsx)
|
|
2
|
+
|
|
3
|
+
フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.doc.tsx` で
|
|
4
|
+
ドキュメントを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
|
|
5
|
+
先に読んでいる想定です。
|
|
6
|
+
|
|
7
|
+
## ドキュメントは `.doc.tsx` で書く
|
|
8
|
+
|
|
9
|
+
`src/june-summary.doc.tsx` → `/june-summary`。上から下へ読む文章のページで、表やチャートを
|
|
10
|
+
文中に置けます。中身は `<Document>` を返す普通の React コンポーネントで、`definePage` もナビも
|
|
11
|
+
通常のページと同じように効きます。
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { definePage } from "@squadbase/vantage"
|
|
15
|
+
import { EChart } from "@squadbase/vantage/components"
|
|
16
|
+
import { Document } from "@squadbase/vantage/doc"
|
|
17
|
+
|
|
18
|
+
export const page = definePage({ title: "6月度サマリー", navLabel: "6月度サマリー" })
|
|
19
|
+
export default function JuneSummary() {
|
|
20
|
+
return (
|
|
21
|
+
<Document>
|
|
22
|
+
<h1>6月度サマリー</h1>
|
|
23
|
+
<p>6月の売上は <strong>5,250 万円</strong>で、目標を 5% 上回りました。</p>
|
|
24
|
+
<h2>週次の売上</h2>
|
|
25
|
+
<figure>
|
|
26
|
+
<EChart option={option} height={280} ariaLabel="6月の週次売上" />
|
|
27
|
+
<figcaption>第4週はキャンペーン初週</figcaption>
|
|
28
|
+
</figure>
|
|
29
|
+
<table>…</table>
|
|
30
|
+
</Document>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 書き方
|
|
36
|
+
|
|
37
|
+
- **素の HTML 要素で書く。** `<Document>` の中の `h1`〜`h4`・`p`・`ul`/`ol`/`li`・`table`・
|
|
38
|
+
`blockquote`・`pre`/`code`・`hr`・`a`・`figure`/`figcaption`・`img` には、見出しの大きさ・余白・
|
|
39
|
+
罫線などが最初から当たっています。クラスは要りません。
|
|
40
|
+
- **Tailwind のユーティリティは、その要素だけ見た目を変えたいときに使う。** 要素の既定の
|
|
41
|
+
スタイルはユーティリティより弱いので、`className` を付ければそちらが勝ちます。ドキュメント全体の
|
|
42
|
+
見た目を変えたいときはユーティリティを並べず、下の `--doc-*` を変えます。
|
|
43
|
+
- **数値はコードに直接書く。** ドキュメントは書いた時点の内容を残すページです。
|
|
44
|
+
`useApiQuery` などで読み込むと、開くたびに値が変わります。
|
|
45
|
+
- **チャートは `EChart`(`@squadbase/vantage/components`)を文中に置く。** `figure` で囲めば
|
|
46
|
+
`figcaption` がキャプションになります。
|
|
47
|
+
- **ページ全体を包むのは `<Document>` だけ。** `PageShell` などのダッシュボード用の枠で包まない
|
|
48
|
+
(本文の幅と余白は `<Document>` が決めます)。
|
|
49
|
+
|
|
50
|
+
## 見た目を変える
|
|
51
|
+
|
|
52
|
+
配色・書体・本文の幅は `--doc-*` の CSS 変数です。既定値はアプリのトークンへの別名なので、
|
|
53
|
+
ダークモードにもそのまま追従します。
|
|
54
|
+
|
|
55
|
+
| 変数 | 既定値 | 何に効くか |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `--doc-fg` | `var(--foreground)` | 本文の文字色 |
|
|
58
|
+
| `--doc-muted` | `var(--muted-foreground)` | 引用・キャプション |
|
|
59
|
+
| `--doc-accent` | `var(--primary)` | リンク |
|
|
60
|
+
| `--doc-border` | `var(--border)` | 表・`h2` の下線・引用・`hr` |
|
|
61
|
+
| `--doc-subtle` | `var(--muted)` | コード・表の見出し行の背景 |
|
|
62
|
+
| `--doc-font-body` / `--doc-font-heading` / `--doc-font-mono` | アプリの書体 | 本文 / 見出し / コード |
|
|
63
|
+
| `--doc-size-body` | `1rem` | 本文の文字サイズ(見出しはこれに比例) |
|
|
64
|
+
| `--doc-line-height` | `1.8` | 本文の行間 |
|
|
65
|
+
| `--doc-max-width` | `48rem` | 本文の幅 |
|
|
66
|
+
|
|
67
|
+
アプリのすべてのドキュメントに効かせるなら `styles.css` の `:root` で定義し直します。1 つの
|
|
68
|
+
ドキュメントだけなら `<Document theme={{ accent: "#2563eb", maxWidth: "56rem" }}>` のように
|
|
69
|
+
`theme` で渡します(`theme` のキーは変数名から `--doc-` を外してキャメルケースにしたもの)。
|
|
70
|
+
|
|
71
|
+
`.doc` を付けられるのはルートになるページモジュールだけで、`_layout.doc.tsx` などは
|
|
72
|
+
`INVALID_PAGE_VARIANT` エラーになります。`june.doc.tsx` と `june.tsx` は同じ URL に解決するので
|
|
73
|
+
`ROUTE_CONFLICT` です。
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# ダッシュボード / 通常ページ(.tsx)
|
|
2
|
+
|
|
3
|
+
フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.tsx` の通常ページ
|
|
4
|
+
(ダッシュボード)を書くときに読んでください。前提となる不変条件・ディレクトリ構造は
|
|
5
|
+
`AGENTS.md` を先に読んでいる想定です。
|
|
6
|
+
|
|
7
|
+
プロジェクトルートに `DESIGN.md` があれば、書き始める前に読んで配色・書体・レイアウトの規則に従う。
|
|
8
|
+
|
|
9
|
+
## 最小アプリ(2 ファイル)
|
|
10
|
+
|
|
11
|
+
`package.json` と `index.tsx` の 2 ファイルだけで動く。
|
|
12
|
+
|
|
13
|
+
`package.json`:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"name": "my-dashboard",
|
|
18
|
+
"private": true,
|
|
19
|
+
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "vantage dev",
|
|
22
|
+
"build": "vantage build",
|
|
23
|
+
"preview": "vantage preview",
|
|
24
|
+
"check": "vantage check",
|
|
25
|
+
"routes": "vantage routes"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@squadbase/vantage": "^0.5.0",
|
|
29
|
+
"react": "^19.2.7",
|
|
30
|
+
"react-dom": "^19.2.7"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
export default function Dashboard() {
|
|
39
|
+
return (
|
|
40
|
+
<main className="p-6">
|
|
41
|
+
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
|
42
|
+
</main>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm install
|
|
49
|
+
pnpm dev # → http://localhost:5173(HMR)
|
|
50
|
+
pnpm check # 静的診断(エラーがあれば exit 1)
|
|
51
|
+
pnpm routes # ページ/API の URL マップ
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
|
|
55
|
+
`src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
|
|
56
|
+
どちらか一方に寄せること(→ `AGENTS.md` の「ディレクトリ構造」)。
|
|
57
|
+
|
|
58
|
+
## ルーティング規約(ファイル名 → ルート)
|
|
59
|
+
|
|
60
|
+
ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
|
|
61
|
+
URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
|
|
62
|
+
`MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
|
|
63
|
+
|
|
64
|
+
| ファイル | ルート |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `index.tsx` | `/` |
|
|
67
|
+
| `monthly-analysis.tsx` | `/monthly-analysis` |
|
|
68
|
+
| `sales/index.tsx` | `/sales` |
|
|
69
|
+
| `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
|
|
70
|
+
| `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト(`Outlet` で子を描く) |
|
|
71
|
+
| `_404.tsx` | Not Found ページ |
|
|
72
|
+
| `_error.tsx` | ルートが throw したときのエラーページ |
|
|
73
|
+
| `monthly-report.slide.tsx` | `/monthly-report`(スライドデッキ。詳しくは `agents/slide.md`) |
|
|
74
|
+
| `sales-export.csv` | `/sales-export`(詳しくは `agents/csv.md`) |
|
|
75
|
+
| `data/june.xlsx` | `/data/june`(詳しくは `agents/xlsx.md`) |
|
|
76
|
+
|
|
77
|
+
`components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
|
|
78
|
+
(ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→ `AGENTS.md` の
|
|
79
|
+
「ディレクトリ構造」)。
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { definePage } from "@squadbase/vantage"
|
|
83
|
+
|
|
84
|
+
// title は document.title、navLabel は useRoutes() で組むナビの表示名(既定は title)
|
|
85
|
+
export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
|
|
86
|
+
|
|
87
|
+
export default function MonthlyAnalysis() {
|
|
88
|
+
return <main className="p-6">…</main>
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### ナビはルート一覧から組む
|
|
93
|
+
|
|
94
|
+
`useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
|
|
95
|
+
`useCurrentRoute()` は現在のルート(404 なら `undefined`)。
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { Link, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
|
|
99
|
+
|
|
100
|
+
// 動的ルートは URL が定まらないので外す。label は navLabel → title → path の順
|
|
101
|
+
const routes = useRoutes().filter((r) => !r.dynamic)
|
|
102
|
+
const current = useCurrentRoute()
|
|
103
|
+
|
|
104
|
+
routes.map((r) => (
|
|
105
|
+
<Link key={r.path} to={r.to} activeClassName="font-semibold">
|
|
106
|
+
{r.label}
|
|
107
|
+
</Link>
|
|
108
|
+
))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
|
|
112
|
+
`title`・`description`・`navLabel`・`kind`(`"module"` | `"file"`)・`variant`(`"slide"`)。
|
|
113
|
+
後ろの 2 つで、ナビをダッシュボード / スライド / データに分けたりアイコンを変えたりできます。
|
|
114
|
+
|
|
115
|
+
### フィルタ状態は URL に置く
|
|
116
|
+
|
|
117
|
+
リロードで消えず、URL をそのまま共有できる。`useState` と同じ形。
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
|
|
121
|
+
|
|
122
|
+
const [region, setRegion] = useSearchParam("region", "all") // 常に string
|
|
123
|
+
const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON になる値
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
|
|
127
|
+
(`{ replace: false }` で push)。
|
|
128
|
+
|
|
129
|
+
### 動的パラメータの「3 つの綴り」を同期させる
|
|
130
|
+
|
|
131
|
+
崩すと静かにマッチしなくなる不変条件:
|
|
132
|
+
|
|
133
|
+
- ファイル名 `sales/[customerId].tsx`
|
|
134
|
+
- 表示ルート `/sales/:customerId`
|
|
135
|
+
- リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { Link, useParams } from "@squadbase/vantage/router"
|
|
139
|
+
|
|
140
|
+
<Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
|
|
141
|
+
|
|
142
|
+
// 詳細ページ側で取り出す
|
|
143
|
+
const { customerId } = useParams()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## データ取得(server を持たない fetch)
|
|
147
|
+
|
|
148
|
+
外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { useQuery } from "@squadbase/vantage/query"
|
|
152
|
+
import { Loading, ErrorState } from "@squadbase/vantage/ui"
|
|
153
|
+
|
|
154
|
+
const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
|
|
155
|
+
if (q.isPending) return <Loading />
|
|
156
|
+
if (q.isError) return <ErrorState message={(q.error as Error).message} />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
|
|
160
|
+
refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
|
|
161
|
+
上書きする。
|
|
162
|
+
自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→ `agents/api.md`)。
|
|
163
|
+
|
|
164
|
+
## 組み込み UI / コンポーネントの入口
|
|
165
|
+
|
|
166
|
+
- **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
|
|
167
|
+
`ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
|
|
168
|
+
- **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
|
|
169
|
+
`DataTablePreset`・`EChart` ほか)。
|
|
170
|
+
- **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
|
|
171
|
+
リファレンスはパッケージに同梱されていて、オフラインでも読める(→ `AGENTS.md` の
|
|
172
|
+
「ドキュメントを引く」)。**名前が分からないとき**はやりたいことで `vantage search` する
|
|
173
|
+
(→ 同節)。
|
|
174
|
+
- **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
|
|
175
|
+
ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
|
|
176
|
+
`ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
|
|
177
|
+
`add` の対象ではない(未知名で実行すると候補が一覧表示される)。
|
|
178
|
+
|
|
179
|
+
### ダークモードは opt-in
|
|
180
|
+
|
|
181
|
+
ダークのトークンが効くのは、祖先に `.dark` か `[data-theme="dark"]` が付いているときだけ。
|
|
182
|
+
**OS / ブラウザの設定(`prefers-color-scheme`)は見ない。** `dark:` ユーティリティも同じ
|
|
183
|
+
スイッチに繋いであるので(`theme.css` の `@custom-variant dark`)、自分でクラスを付けない限り
|
|
184
|
+
アプリは常にライトで描かれる ― `dark:` を書いてもそこだけダークになることはない。
|
|
185
|
+
|
|
186
|
+
明暗を切り替えたいときは、レイアウトのルート要素などに `.dark` / `data-theme="dark"` を
|
|
187
|
+
自分で付ける。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# スライド(.slide.tsx)
|
|
2
|
+
|
|
3
|
+
フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.slide.tsx` で
|
|
4
|
+
スライドデッキを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
|
|
5
|
+
先に読んでいる想定です。
|
|
6
|
+
|
|
7
|
+
プロジェクトルートに `DESIGN.md` があれば、書き始める前に読んで「スライド」の節と配色・書体の規則に従う。
|
|
8
|
+
|
|
9
|
+
## スライドは `.slide.tsx` で書く
|
|
10
|
+
|
|
11
|
+
`src/monthly-report.slide.tsx` → `/monthly-report`。中身は `<SlideDeck>` を返す普通の React
|
|
12
|
+
コンポーネントで、`definePage` もナビも通常のページと同じように効きます。
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Slide, SlideDeck } from "@squadbase/vantage/slide"
|
|
16
|
+
|
|
17
|
+
export const page = definePage({ title: "6月度レビュー", navLabel: "6月度" })
|
|
18
|
+
export default function Deck() {
|
|
19
|
+
return (
|
|
20
|
+
<SlideDeck>
|
|
21
|
+
<Slide>…</Slide>
|
|
22
|
+
<Slide>…</Slide>
|
|
23
|
+
</SlideDeck>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**`<Slide>` は `<SlideDeck>` の直接の子に書く。** `SlideDeck` は直下の `<Slide>` 要素だけを数えるので、
|
|
29
|
+
`<Slide>` を自作コンポーネント(`<Slide1 />` など)や Fragment で包むと 1 枚も数えられず「1 / 0」に
|
|
30
|
+
なります(`{items.map((x) => <Slide key={x.id}>…</Slide>)}` のような配列は可)。共通の部品は
|
|
31
|
+
`<Slide>` の**中**に置くコンポーネントとして切り出します(`<Slide><CoverPage /></Slide>`)。
|
|
32
|
+
|
|
33
|
+
1920×1080 の固定キャンバスに絶対 px で書き、画面サイズに合わせて縮小されます。キー / クリックで
|
|
34
|
+
送れ、左のスライド一覧(サムネイル列)は `L` で開閉、`F` で全画面、ツールバーから pptx を
|
|
35
|
+
書き出せます。**書き方の作法は同梱 Skill の
|
|
36
|
+
`vantage-slide`** にあります(キャンバスの縦方向の予算、段階表示、トランジション、モーフ、
|
|
37
|
+
pptx が壊れる条件)。
|
|
38
|
+
|
|
39
|
+
`.slide` を付けられるのはルートになるページモジュールだけで、`_layout.slide.tsx` などは
|
|
40
|
+
`INVALID_PAGE_VARIANT` エラーになります。`june.slide.tsx` と `june.tsx` は同じ URL に解決するので
|
|
41
|
+
`ROUTE_CONFLICT` です。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# xlsx ページ
|
|
2
|
+
|
|
3
|
+
フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.xlsx` を
|
|
4
|
+
ページとして置くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
|
|
5
|
+
先に読んでいる想定です。
|
|
6
|
+
|
|
7
|
+
## `.xlsx` を置くだけでページになる
|
|
8
|
+
|
|
9
|
+
**`.xlsx` を `src/` に置くだけで、他のページと同格のルートになります。** URL の作り方は
|
|
10
|
+
`.tsx` と同じ(`src/data/june.xlsx` → `/data/june`、`index.xlsx` はそのディレクトリ)で、
|
|
11
|
+
ナビにも `vantage routes` にも並びます。開くとフレームワーク同梱の表ビューアが描き、複数シートを
|
|
12
|
+
切り替えられ、元ファイルをダウンロードできます。ビューアは**表計算ソフトの見た目**です ——
|
|
13
|
+
列は A / B / C、左に行番号、ファイルの 1 行目はヘッダとして固定、シートのタブは下端。
|
|
14
|
+
読み取り専用で、編集もソートもできません(セルを選んで `Cmd/Ctrl+C` でコピーは可)。
|
|
15
|
+
|
|
16
|
+
- **タイトルはファイル名から導かれる**(`june.xlsx` → 「June」)。データファイルに `definePage` は
|
|
17
|
+
書けません。名前を変えたいときはファイルをリネームします
|
|
18
|
+
- **ページにしたくないファイルは `_` を前置する**(`_june.xlsx`)。`components/`・`hooks/`・`lib/` に
|
|
19
|
+
置いたものも同じくページになりません
|
|
20
|
+
- **既定はファイルをそのまま置く(tsx を書かない)。** 自分でタイトルや説明を付けたいときだけ、`_` を
|
|
21
|
+
前置してルート化を止め、tsx から描きます:
|
|
22
|
+
```tsx
|
|
23
|
+
import { FilePageView } from "@squadbase/vantage/table"
|
|
24
|
+
import src from "./_june.xlsx?url&no-inline"
|
|
25
|
+
export const page = definePage({ title: "6月度 売上レポート" })
|
|
26
|
+
export default () => <FilePageView src={src} kind="xlsx" fileName="june.xlsx" />
|
|
27
|
+
```
|
|
28
|
+
- **複数シートに対応**しています。シートのタブは下端に並び、切り替えて閲覧できます。
|
|
29
|
+
- 数式・グラフ・セル書式は再現されません(値として読めれば十分、という設計)。ブラウザでパースするので
|
|
30
|
+
25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは `server/api/` から配って
|
|
31
|
+
`DataTable` で描いてください
|
|
32
|
+
- **ビューアの無い拡張子(`.xls`・`.ods` など)はルートになりません。** `vantage check` の
|
|
33
|
+
`UNSUPPORTED_PAGE_FILE` 警告が知らせます
|
|
File without changes
|