@squadbase/vantage-template 0.0.2 → 0.1.0-dev.47d956c
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 +2 -0
- package/base-template/.squadbase/skills/adamas-automation/SKILL.md +118 -0
- package/base-template/.squadbase/skills/project-storage/SKILL.md +157 -0
- package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +22 -0
- package/base-template/AGENTS.md +49 -2
- package/base-template/package-lock.json +5767 -0
- package/base-template/package.json +3 -3
- 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 +1 -1
- package/package.json +18 -12
- 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/preview-square.png +0 -0
- package/ui-templates/chart-grid-ja/preview-wide.png +0 -0
- package/ui-templates/funnel/preview-square.png +0 -0
- package/ui-templates/funnel/preview-wide.png +0 -0
- 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
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ npx @squadbase/vantage-template add funnel --force # Overwrite existing fil
|
|
|
56
56
|
|
|
57
57
|
Switch the chart color preset. Rewrites a marked block of `--chart-1`–`--chart-5` inside the project's `styles.css` (under `src/` when that layout is in use), leaving any other overrides in that file intact. Re-running replaces the block rather than stacking.
|
|
58
58
|
|
|
59
|
+
A new project starts on the `blue` preset — the base template ships that block already written, so `init` and the gallery previews show the same colors. This command swaps it for another preset.
|
|
60
|
+
|
|
59
61
|
```bash
|
|
60
62
|
npx @squadbase/vantage-template chart ocean
|
|
61
63
|
npx @squadbase/vantage-template chart forest --dry-run
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adamas-automation
|
|
3
|
+
description: automation(データパイプライン)の作り方。automation/<name>/ のファイル配置、automation.yml v1 の書き方(GitHub Actions サブセット)、adamas @task / outputs / partition の宣言 API、実行と検証の流れ(lintAutomations → runAutomation dryRun → 本実行)。外部データを定期的に取り込んで Squadbase DB に同期するパイプラインを作る・直すときに参照する。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Automation(adamas データパイプライン)
|
|
7
|
+
|
|
8
|
+
外部データを定期取得して project の Squadbase DB へ同期するパイプライン。定義の正本は
|
|
9
|
+
この repo の `automation/` ディレクトリで、コミットすると platform に自動同期される。
|
|
10
|
+
ダッシュボード(`src/`)とは独立していて、互いのファイルには触れない。
|
|
11
|
+
|
|
12
|
+
## ファイル配置
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
automation/
|
|
16
|
+
├── pyproject.toml # 共有 Python 環境(全 automation で 1 つ)。依存は `uv add`
|
|
17
|
+
├── uv.lock
|
|
18
|
+
└── <name>/ # automation 名 = ディレクトリ名 = ^[a-z0-9][a-z0-9_-]{0,63}$
|
|
19
|
+
├── automation.yml # 宣言(トリガ・ステップ)。1 automation = 1 ファイル
|
|
20
|
+
└── tasks.py # adamas @task の実装(pipeline step が暗黙に読む。ファイル名固定)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- 初回だけ `automation/pyproject.toml` を作り `uv add adamas` する(全 automation で共有。
|
|
24
|
+
automation ごとに venv を作らない)
|
|
25
|
+
- `automation/` が無いディレクトリ構成でも、上の形で新規作成すればよい
|
|
26
|
+
|
|
27
|
+
## automation.yml v1 — GitHub Actions のサブセット
|
|
28
|
+
|
|
29
|
+
GHA ワークフロー YAML の意味論をそのまま使う。**GHA との差分は次の 7 点だけ**。
|
|
30
|
+
それ以外の書き方(`on:` / `steps:` / `- id:` / `if:` / `${{ }}` / `steps.<id>.outputs.<name>`)
|
|
31
|
+
は GHA の知識どおりに書く:
|
|
32
|
+
|
|
33
|
+
| # | 差分 |
|
|
34
|
+
|---|---|
|
|
35
|
+
| 1 | step は `uses:`/`run:` でなく型付きキー 1 個(`pipeline` / `agent` / `query` / `write` / `notify`)。v1 で実行できるのは `pipeline` のみ(他は宣言だけで UI に載る) |
|
|
36
|
+
| 2 | `on.schedule` は単一オブジェクトで `timezone` / `enabled` を持つ(cron 配列は不可) |
|
|
37
|
+
| 3 | 手動トリガは `on.manual`(GHA の `workflow_dispatch` の改名) |
|
|
38
|
+
| 4 | inputs の `type` は `string | boolean | daterange` の 3 種 |
|
|
39
|
+
| 5 | 式 `${{ }}` は dot 参照のみ(関数・演算子・パイプは不可)。context は `trigger.inputs.<name>` と `steps.<id>.outputs.<name>` だけ。前方・自己参照は不可 |
|
|
40
|
+
| 6 | pipeline step のタスク実体は同ディレクトリ `tasks.py` 固定(automation 1 個につき pipeline step は最大 1) |
|
|
41
|
+
| 7 | pipeline step に `destination`(取り込み先 Squadbase DB の connection uuid)を書ける。省略時は project 唯一の Squadbase DB が既定 |
|
|
42
|
+
|
|
43
|
+
例(実行可能な最小形):
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
version: 1
|
|
47
|
+
name: Daily ad spend sync
|
|
48
|
+
on:
|
|
49
|
+
schedule: { cron: "0 6 * * *", timezone: Asia/Tokyo }
|
|
50
|
+
manual:
|
|
51
|
+
inputs:
|
|
52
|
+
backfill: { type: daterange, required: false }
|
|
53
|
+
steps:
|
|
54
|
+
- id: sync
|
|
55
|
+
pipeline:
|
|
56
|
+
backfill: ${{ trigger.inputs.backfill }}
|
|
57
|
+
connections: # ctx.connection(name) で使う接続の配線(任意)
|
|
58
|
+
warehouse: <connection-uuid>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- スケジュールは年 2 回以上の頻度が必要(それ未満は同期時に拒否される)
|
|
62
|
+
- `agent` / `query` / `write` / `notify` step を含む automation はスケジュール実行できない
|
|
63
|
+
(宣言と UI 表示のみ)
|
|
64
|
+
|
|
65
|
+
## tasks.py — adamas の宣言 API
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from adamas import task, output, by_date, source
|
|
69
|
+
|
|
70
|
+
@task(
|
|
71
|
+
name="ad-spend", # 出力データセット名(省略時は関数名)
|
|
72
|
+
table="ad_spend", # Squadbase DB のテーブル名
|
|
73
|
+
pk=["date", "campaign_id"], # upsert / ignore では必須
|
|
74
|
+
mode="upsert", # append | ignore(先勝ち) | upsert(後勝ち)
|
|
75
|
+
schema={"date": "string", "campaign_id": "string", "cost": "float64"},
|
|
76
|
+
partition=by_date("2026-01-01"), # 1 日 1 パーティション(付けると増分実行になる)
|
|
77
|
+
inputs=[source.connection("warehouse")],
|
|
78
|
+
)
|
|
79
|
+
def ad_spend(ctx):
|
|
80
|
+
date = ctx.partition.inputs["date"] # 自分のスライスは ctx.partition.inputs から読む
|
|
81
|
+
conn = ctx.connection("warehouse") # automation.yml の connections で配線した接続
|
|
82
|
+
yield from conn.query(
|
|
83
|
+
f"SELECT ... WHERE date = '{date}'", chunk_rows=10_000
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
押さえること:
|
|
88
|
+
|
|
89
|
+
- 戻り値は pandas/polars DataFrame・pyarrow Table、またはそれらの yield(チャンクで
|
|
90
|
+
メモリ有界にする)。複数出力は `outputs={...}` + `yield ("<dataset>", chunk)`
|
|
91
|
+
- `schema` は Parquet 型を固定する列型宣言。**schema の無い出力は Squadbase DB へ
|
|
92
|
+
取り込まれない**ので必ず書く。pk 列は schema に含める
|
|
93
|
+
- partition プランは `by_date(start)`(日次)か `by_list(keys)`(キー列挙)。
|
|
94
|
+
partition 付きタスクは単一出力
|
|
95
|
+
- 1 テーブル 1 書き手(別タスクが同じテーブルを書くとエラー)
|
|
96
|
+
- 抽出・再試行・冪等性(どこまで取ったか)は adamas と台帳が管理する。自前で
|
|
97
|
+
チェックポイントや重複排除を実装しない
|
|
98
|
+
|
|
99
|
+
## 検証と実行の流れ
|
|
100
|
+
|
|
101
|
+
1. ファイルを作成・編集したら **`lintAutomations`** で検証し、指摘を直す
|
|
102
|
+
2. **`runAutomation` を `dryRun: true` で実行**(オフライン: ローカル一時ディレクトリに
|
|
103
|
+
書き、platform の台帳・ストレージには何も書かない)。まず 1 パーティションだけ
|
|
104
|
+
`partitions: ["2026-01-01"]` のように絞ると速い
|
|
105
|
+
3. コミットすると宣言が platform に自動同期され、スケジュールが有効になる
|
|
106
|
+
4. 本実行(`dryRun` なし)はユーザーがデータを求めたときだけ。結果の `adamasStatus` に
|
|
107
|
+
タスクごとの done / failed / outstanding と失敗の内訳(error / category)が載るので、
|
|
108
|
+
それを根拠に報告・修正する
|
|
109
|
+
5. 失敗を直したら `retryFailed: true` で失敗パーティションだけ再実行。過去範囲の
|
|
110
|
+
取り直しは `backfill: "START:END"`(両端含む)
|
|
111
|
+
|
|
112
|
+
## 落とし穴
|
|
113
|
+
|
|
114
|
+
- automation 名にハイフンは使えるが、Python module 名には使わない(module は常に
|
|
115
|
+
`tasks.py` 固定なので気にしなくてよい)
|
|
116
|
+
- `${{ }}` に式や関数を書かない(`| length > 0` などの Jinja 方言は不可)
|
|
117
|
+
- cron は 5 フィールド・64 文字以内
|
|
118
|
+
- `automation.yml` に未知のキーを書くと lint エラー(黙って無視されない)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-storage
|
|
3
|
+
description: Squadbase の Project Storage(アップロード済みファイル)を @squadbase/react の useStorage() でブラウザから扱う手順。ファイル一覧、画像 / 動画 / PDF の埋め込み、中身の読み取り、アップロードと削除・移動。ページやコンポーネントがアップロード済みファイルを表示・埋め込み・アップロードするときに参照する。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project Storage(ブラウザ側)
|
|
7
|
+
|
|
8
|
+
Squadbase の **Project Storage** を React コンポーネントから読み書きする手順。Squadbase 上で
|
|
9
|
+
動くアプリでは資格情報も設定も要らず、そのまま動く。サーバー(`server/api/**`)からストレージを
|
|
10
|
+
触る API は別物で、ここでは扱わない。
|
|
11
|
+
|
|
12
|
+
## import は `@squadbase/react` から直接
|
|
13
|
+
|
|
14
|
+
「import は `@squadbase/vantage` のサブパス経由」はフレームワークが所有するものの話。
|
|
15
|
+
**`@squadbase/react` は Squadbase プラットフォームの SDK なので直接 import する**
|
|
16
|
+
(`SquadbaseProvider` / `useUser` と同じ)。
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { useStorage } from "@squadbase/react"
|
|
20
|
+
|
|
21
|
+
const storage = useStorage()
|
|
22
|
+
// 呼び出しはすべて storage.uploads.* の下
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`useStorage()` が返すクライアントは安定なので、コンポーネント先頭で呼んで `useQuery` /
|
|
26
|
+
`useMutation` の中で使ってよい。データ取得のフックは `@squadbase/vantage/query` から取る
|
|
27
|
+
(`@tanstack/*` は直接 import しない)。
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
すべて async。`key` はストレージ内の**相対**パス(例: `reports/2025-q1.pdf`)で、先頭に
|
|
32
|
+
スラッシュを付けない。
|
|
33
|
+
|
|
34
|
+
読み取り:
|
|
35
|
+
|
|
36
|
+
- `list(prefix?, options?)` → `{ objects, commonPrefixes, nextCursor? }`。`objects` は
|
|
37
|
+
`{ key, size, lastModified }[]`。`options.delimiter = "/"` でフォルダ表示(サブフォルダが
|
|
38
|
+
`commonPrefixes` に入る)、省略すると再帰的なフラット一覧。`options.limit` / `options.cursor`
|
|
39
|
+
でページング(`cursor` は前回の `nextCursor`)
|
|
40
|
+
- `head(key)` → `{ key, size, contentType, createdAt }`。メタ情報だけで本体は取らない
|
|
41
|
+
- `getUrl(key)` → `{ url, expiresAt }`。埋め込み用の一時 URL
|
|
42
|
+
- `get(key)` → 生の `Response`。`.json()` / `.text()` / `.blob()` / `.arrayBuffer()` で読む
|
|
43
|
+
|
|
44
|
+
書き込み:
|
|
45
|
+
|
|
46
|
+
- `put(key, data, options?)` → `void`。`data` は `Blob`(`File` を含む) / `ArrayBuffer` /
|
|
47
|
+
`Uint8Array` / `string`
|
|
48
|
+
- `delete(key)` → `void`
|
|
49
|
+
- `copy(srcKey, destKey, options?)` / `move(srcKey, destKey, options?)` → `void`
|
|
50
|
+
|
|
51
|
+
## 一覧を出す
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { useStorage } from "@squadbase/react"
|
|
55
|
+
import { useQuery } from "@squadbase/vantage/query"
|
|
56
|
+
|
|
57
|
+
const storage = useStorage()
|
|
58
|
+
const { data, isPending, isError } = useQuery({
|
|
59
|
+
queryKey: ["storage", "list", prefix],
|
|
60
|
+
queryFn: () => storage.uploads.list(prefix, { delimiter: "/" }),
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`data.commonPrefixes` をフォルダ、`data.objects` をファイルとして描く。プレフィックス配下を
|
|
65
|
+
まとめて出すなら `delimiter` を省く。
|
|
66
|
+
|
|
67
|
+
## 画像 / 動画 / PDF を埋め込む
|
|
68
|
+
|
|
69
|
+
埋め込みには URL が要るので `getUrl` を使う。**URL には期限がある**ので、切れる前に取り直す:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { useStorage } from "@squadbase/react"
|
|
73
|
+
import { useQuery } from "@squadbase/vantage/query"
|
|
74
|
+
|
|
75
|
+
function useStorageUrl(key: string) {
|
|
76
|
+
const storage = useStorage()
|
|
77
|
+
return useQuery({
|
|
78
|
+
queryKey: ["storage", "url", key],
|
|
79
|
+
queryFn: () => storage.uploads.getUrl(key),
|
|
80
|
+
refetchInterval: (query) => {
|
|
81
|
+
const expiresAt = query.state.data?.expiresAt
|
|
82
|
+
if (!expiresAt) return false
|
|
83
|
+
// 期限の 60 秒前に取り直す
|
|
84
|
+
return Math.max(0, new Date(expiresAt).getTime() - Date.now() - 60_000)
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { ErrorState, Loading } from "@squadbase/vantage/ui"
|
|
92
|
+
|
|
93
|
+
const { data, isPending, isError } = useStorageUrl("reports/cover.png")
|
|
94
|
+
if (isPending) return <Loading />
|
|
95
|
+
if (isError) return <ErrorState message="ファイルを読み込めませんでした" />
|
|
96
|
+
return <img src={data.url} alt="" className="w-full rounded-lg" />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
動画は `<video src={data.url} controls className="w-full rounded-lg" />`、PDF は
|
|
100
|
+
`<iframe src={data.url} className="h-[80vh] w-full rounded-lg border border-border" />`。
|
|
101
|
+
|
|
102
|
+
## 中身を JS で読む
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const { data } = useQuery({
|
|
106
|
+
queryKey: ["storage", "content", key],
|
|
107
|
+
queryFn: async () => {
|
|
108
|
+
const res = await storage.uploads.get(key)
|
|
109
|
+
return (await res.json()) as Row[] // または res.text()
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## アップロードする
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { useStorage } from "@squadbase/react"
|
|
118
|
+
import { useMutation, useQueryClient } from "@squadbase/vantage/query"
|
|
119
|
+
|
|
120
|
+
const storage = useStorage()
|
|
121
|
+
const queryClient = useQueryClient()
|
|
122
|
+
const upload = useMutation({
|
|
123
|
+
mutationFn: (file: File) =>
|
|
124
|
+
storage.uploads.put(`uploads/${file.name}`, file, { contentType: file.type }),
|
|
125
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage", "list"] }),
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
<input
|
|
129
|
+
type="file"
|
|
130
|
+
onChange={(e) => {
|
|
131
|
+
const file = e.target.files?.[0]
|
|
132
|
+
if (file) upload.mutate(file)
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`File` はそのまま `put` に渡す。書き込み後は該当する `["storage", "list", …]` を invalidate して
|
|
138
|
+
UI を追従させる。
|
|
139
|
+
|
|
140
|
+
## 落とし穴
|
|
141
|
+
|
|
142
|
+
- **`getUrl` の URL は短命。** 結果を永続化したり長寿命のキャッシュに入れたりしない。
|
|
143
|
+
`expiresAt` の前に取り直す(上のフック)。`<img src>` に一度入れて放置すると、開いたままの
|
|
144
|
+
画面で後から読み込まれた分が失敗する
|
|
145
|
+
- **`put` の `contentType` の既定は `application/octet-stream`。** 後で埋め込むもの(画像・動画・
|
|
146
|
+
PDF)には `file.type` を必ず渡す。付けないとブラウザが表示せずダウンロードに落ちる
|
|
147
|
+
- **`key` は相対パス。** 先頭にスラッシュを付けない。`getUrl` を通さず `<img src="/foo.png">` の
|
|
148
|
+
ような静的パスで参照しない ― ストレージはアプリの `public/` ではない
|
|
149
|
+
- **`copy` / `move` は既定で既存の宛先を上書きする。** 残したいときは `{ overwrite: false }`
|
|
150
|
+
- **サーバー(`server/api/**`)からは呼ばない。** `useStorage` はブラウザ側の API
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 関連スキル
|
|
155
|
+
|
|
156
|
+
- `vantage-add-feature` — ページ / API / コンポーネントを 1 つ足す手順
|
|
157
|
+
- `vantage-pitfalls` — UI・配色・ルーティングの落とし穴
|
|
@@ -35,6 +35,28 @@ Base UI の `SelectValue` は選択中の **value をそのまま描き**、`Sel
|
|
|
35
35
|
|
|
36
36
|
トリガーに `kanto` や `/sales` と生の値が出たら、まずこれを疑う。
|
|
37
37
|
|
|
38
|
+
## 配色:素のパレット + `dark:` ではなく、セマンティックトークンで書く
|
|
39
|
+
|
|
40
|
+
色は `text-foreground` / `text-muted-foreground` / `bg-card` / `border-border` などの
|
|
41
|
+
トークンで書く。トークンはライト / ダークの両方を持っているので、`dark:` を並べる必要が無い。
|
|
42
|
+
|
|
43
|
+
`text-slate-900 dark:text-slate-100` のような素の Tailwind パレット + `dark:` の組は、
|
|
44
|
+
**トークンで塗られた周囲と食い違う**。ダークトークンが効くのは祖先に `.dark` /
|
|
45
|
+
`[data-theme="dark"]` が付いているときだけで、`dark:` ユーティリティも同じスイッチに
|
|
46
|
+
繋がっている(`theme.css` の `@custom-variant dark`)。つまり自分でクラスを付けない限り
|
|
47
|
+
アプリは常にライトで動くのに、素のパレットを使うとその 1 か所だけ手動で両モードを
|
|
48
|
+
管理することになる。
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// ✗ 手動で 2 モードを管理することになる
|
|
52
|
+
<div className="font-medium text-slate-900 dark:text-slate-100">{name}</div>
|
|
53
|
+
// ✓ トークンなら 1 つで済む
|
|
54
|
+
<div className="font-medium text-foreground">{name}</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`--muted` / `--accent` / `--secondary` / `--border` は**背景・境界用**で、ライトではほぼ白。
|
|
58
|
+
文字色に使うと読めなくなる ― 弱い文字色は `text-muted-foreground`。
|
|
59
|
+
|
|
38
60
|
## チャート:`EChart` の配色はトークン追従。上書きは `option.color`
|
|
39
61
|
|
|
40
62
|
系列色は `--chart-1..5`、軸・凡例・ツールチップは文字色/境界色のトークンから組まれる
|
package/base-template/AGENTS.md
CHANGED
|
@@ -36,6 +36,11 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
36
36
|
- **クライアントに届く env は `PUBLIC_` 接頭辞のものだけ。** `import.meta.env.PUBLIC_FOO`。
|
|
37
37
|
それ以外を `import.meta.env` で読むと `PUBLIC_ENV_MISUSE` 警告。シークレットは
|
|
38
38
|
`ApiContext.env`(server/ 内)にだけ届く。
|
|
39
|
+
- **色はセマンティックトークンで書く。** `text-foreground`・`text-muted-foreground`・`bg-card`・
|
|
40
|
+
`border-border`。トークンがライト / ダークの両方を持つので `dark:` を並べる必要が無い。
|
|
41
|
+
素のパレット + `dark:`(`text-slate-900 dark:text-slate-100`)を混ぜると、その 1 か所だけ
|
|
42
|
+
手動で 2 モードを管理することになる(→「ダークモードは opt-in」)。`--muted`・`--accent`・
|
|
43
|
+
`--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
|
|
39
44
|
- **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
|
|
40
45
|
コマンド、または `vantage upgrade` で再生成される。
|
|
41
46
|
- **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
|
|
@@ -318,6 +323,16 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
|
|
|
318
323
|
`ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
|
|
319
324
|
`add` の対象ではない(未知名で実行すると候補が一覧表示される)。
|
|
320
325
|
|
|
326
|
+
### ダークモードは opt-in
|
|
327
|
+
|
|
328
|
+
ダークのトークンが効くのは、祖先に `.dark` か `[data-theme="dark"]` が付いているときだけ。
|
|
329
|
+
**OS / ブラウザの設定(`prefers-color-scheme`)は見ない。** `dark:` ユーティリティも同じ
|
|
330
|
+
スイッチに繋いであるので(`theme.css` の `@custom-variant dark`)、自分でクラスを付けない限り
|
|
331
|
+
アプリは常にライトで描かれる ― `dark:` を書いてもそこだけダークになることはない。
|
|
332
|
+
|
|
333
|
+
明暗を切り替えたいときは、レイアウトのルート要素などに `.dark` / `data-theme="dark"` を
|
|
334
|
+
自分で付ける。
|
|
335
|
+
|
|
321
336
|
## ドキュメントを引く(`vantage docs` / `vantage search`)
|
|
322
337
|
|
|
323
338
|
```bash
|
|
@@ -364,6 +379,7 @@ body・レスポンスの status と形)まで出る。`vantage check --json` /
|
|
|
364
379
|
- **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
|
|
365
380
|
- **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
|
|
366
381
|
- **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
|
|
382
|
+
- **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
|
|
367
383
|
|
|
368
384
|
手順書の本体は `SKILL.md` というファイルで、置き場所はアプリによって違う(ルート直下・
|
|
369
385
|
`.claude/skills/`・`.squadbase/skills/` など)。**探す必要はない ― CLI が探す:**
|
|
@@ -391,8 +407,9 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
|
|
|
391
407
|
> フレームワーク規約と矛盾する場合は**こちらが優先**します。
|
|
392
408
|
>
|
|
393
409
|
> `vantage upgrade` はこのファイルをフレームワーク同梱の正本で丸ごと上書きするため、
|
|
394
|
-
> このセクションは消えます。消えたら squadbase-
|
|
395
|
-
>
|
|
410
|
+
> このセクションは消えます。消えたら squadbase-monorepo の
|
|
411
|
+
> `packages/vantage-template/agents/vantage/` から貼り直してください
|
|
412
|
+
> (リポジトリ側では `npm run agents` が正本と結合して再生成します)。
|
|
396
413
|
|
|
397
414
|
## Squadbase の Connection / Query からデータを読む
|
|
398
415
|
|
|
@@ -469,3 +486,33 @@ const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
|
|
|
469
486
|
body: JSON.stringify({ customerId: "c_123" }),
|
|
470
487
|
})
|
|
471
488
|
```
|
|
489
|
+
|
|
490
|
+
## 構築中の一時的なエラーを「進行中」に見せる(`components/build-recovery.tsx`)
|
|
491
|
+
|
|
492
|
+
ページを書いている途中は、ファイルが書き終わる順番の都合で一瞬だけページが throw する
|
|
493
|
+
(まだ存在しないモジュールの import、書きかけのコンポーネント)。これを受け止めるのは
|
|
494
|
+
**Vantage の既定エラー画面**(`Something went wrong`)で、**テンプレートからは差し替えられない** —
|
|
495
|
+
`_error.tsx` はルートルート専用で、ページごとの `errorComponent` は現行プロトタイプでは
|
|
496
|
+
まだ結線されていない。しかも掴んでいるのは TanStack の CatchBoundary なので、HMR で
|
|
497
|
+
ソースが差し替わってもリセットされず、**リロードするまでエラー画面が貼り付く**。
|
|
498
|
+
|
|
499
|
+
`components/build-recovery.tsx` の `BuildRecovery` が、その画面を DOM から検知して
|
|
500
|
+
`Working on it…` 表示を被せ、dev では 1.2 秒待ってから自動でリロードして復帰させる
|
|
501
|
+
(上限 3 回。使い切ったら「再読み込み」の案内に降格し、無事に描けたら残数が戻る)。
|
|
502
|
+
降格した画面にはエラー本文をそのまま出し、`Copy log` でコピーできるようにしてある —
|
|
503
|
+
自力で直らないときに本文をそのまま貼り戻してもらうため。
|
|
504
|
+
`_error.tsx` も同じ `RecoveringState` を描くので、ルートレイアウトが throw したときの
|
|
505
|
+
見た目と復帰の仕方は揃っている。
|
|
506
|
+
|
|
507
|
+
### 追加の不変条件
|
|
508
|
+
|
|
509
|
+
- **`_layout.tsx` の `<BuildRecovery>` を外さない。** `<Outlet />` を包む形で 1 回だけ使う。
|
|
510
|
+
外すと、書き込み途中の一瞬のエラーが `Something went wrong` のまま残る。
|
|
511
|
+
- **`components/build-recovery.tsx` と `_error.tsx` を消さない・作り直さない。** ページ側に
|
|
512
|
+
独自のエラー UI を足す必要はない(足しても既定エラー画面より内側には入れない)。
|
|
513
|
+
- **`role="alert"` の中に `h2` を置かない。** これが既定エラー画面の目印なので、ページが
|
|
514
|
+
同じ形を描くと `BuildRecovery` が誤検知して中身を覆ってしまう。UI キットの `Alert` /
|
|
515
|
+
`ErrorState` は見出しを `div` / `p` で描くのでそのまま使ってよい。
|
|
516
|
+
- **データ取得の失敗は各ページが `ErrorState` で出す。** `BuildRecovery` が拾うのは
|
|
517
|
+
「レンダリングが throw した」場合だけで、`useApiQuery` の `isError` はページの責任
|
|
518
|
+
(→ フレームワーク規約の「データ取得」)。
|