@squadbase/vantage-template 0.1.1-dev1.da9fa09 → 0.1.1-dev1.e87430f
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/base-template/.squadbase/skills/vantage-design/SKILL.md +10 -2
- package/base-template/AGENTS.md +57 -2
- package/base-template/agents/page.md +10 -0
- package/base-template/bun.lock +24 -24
- package/base-template/bunfig.toml +5 -0
- package/base-template/package.json +2 -2
- package/base-template/src/_layout.tsx +9 -9
- package/base-template/src/components/build-recovery.tsx +37 -1
- package/base-template/src/components/user-card.tsx +10 -10
- package/base-template/src/styles.css +6 -4
- package/package.json +16 -16
- package/ui-templates/kpi-chart-simple/pages/index.tsx +47 -47
- package/ui-templates/kpi-chart-simple-ja/pages/index.tsx +47 -47
|
@@ -77,19 +77,27 @@ Vantage アプリの見た目は 2 つのファイルで決まる。
|
|
|
77
77
|
|
|
78
78
|
```css
|
|
79
79
|
body {
|
|
80
|
-
font-family: "<Font>",
|
|
80
|
+
font-family: "<Font>", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans JP", Meiryo, ui-sans-serif, system-ui, sans-serif;
|
|
81
81
|
}
|
|
82
82
|
:root {
|
|
83
|
-
--slide-font-display: "<Font>",
|
|
83
|
+
--slide-font-display: "<Font>", -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Yu Gothic", Meiryo, system-ui, sans-serif;
|
|
84
84
|
--slide-font-body: var(--slide-font-display);
|
|
85
85
|
}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
欧文の Web フォントだけを先頭に足すときも、日本語の system face(`"Hiragino Sans"` 〜 `Meiryo`)は
|
|
89
89
|
必ず残す。消すとカナ・漢字がブラウザ任せのフォールバックになり、細く表示される。
|
|
90
|
+
`ui-sans-serif` / `system-ui` は日本語の face より**後ろ**に置く。日本語の Windows ではどちらも
|
|
91
|
+
Yu Gothic UI(UI のリサイズを避けるために字幅を詰めた書体)に解決され、カナがここで決まって
|
|
92
|
+
半角風に見える。macOS の Chrome は `-apple-system` を解釈しないので `BlinkMacSystemFont` も並べる。
|
|
93
|
+
既存アプリの `styles.css` に `ui-sans-serif, system-ui, …` が先頭にある、または `"Yu Gothic UI"` が
|
|
94
|
+
残っているときは、`body` の `font-family` を上の並びに直す(ほかの行は書き換えない)。
|
|
90
95
|
日本語の Web フォントは face が 100 を超えるので原則使わない(pptx 書き出しも壊れる。
|
|
91
96
|
`vantage-slide` の `references/tokens.md` 参照)。
|
|
92
97
|
|
|
98
|
+
長い表(ダッシュボード): カードに `max-h-*` + `overflow-y-auto` の縦スクロール領域を作らず、
|
|
99
|
+
ページング付きの `DataTablePreset`(`enablePagination`)にする(ホイールや End キーがページに届かなくなるため)。
|
|
100
|
+
|
|
93
101
|
`DESIGN.md`: `references/format.md` の書式で書く。**色コードを書かない**(トークン名だけ)。
|
|
94
102
|
150 行を超えない(超えると `vantage check` が `DESIGN_MD_TOO_LONG` を出す)。ロゴなどの素材は
|
|
95
103
|
`public/brand/logo.svg` のように `public/` 配下のパスで「ブランド素材」行に書く。
|
package/base-template/AGENTS.md
CHANGED
|
@@ -93,13 +93,25 @@ root/
|
|
|
93
93
|
| `@squadbase/vantage/router` | `Link`・`Outlet`・`useParams`・`useSearch`・`useNavigate`・`redirect`・`notFound`・`useRoutes`・`useCurrentRoute`・`useSearchParam`・`useSearchState` |
|
|
94
94
|
| `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
|
|
95
95
|
| `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
|
|
96
|
-
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`、型付きチャート `LineChart`・`BarChart`・`AreaChart`・`DonutChart`・`BarList` ほか、`EChart`) |
|
|
96
|
+
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`、KPI の `KpiCard`・`Delta`、型付きチャート `LineChart`・`BarChart`・`AreaChart`・`DonutChart`・`BarList` ほか、`EChart`、書式の `formatValue`) |
|
|
97
97
|
| `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
|
|
98
98
|
| `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`(スライド専用。1920×1080 の固定キャンバス) |
|
|
99
99
|
| `@squadbase/vantage/doc` | `Document`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
|
|
100
100
|
| `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
|
|
101
101
|
| `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
|
|
102
102
|
|
|
103
|
+
**KPI は `KpiCard`、増減は `Delta`(`comparison` と `polarity` を渡す)。** 向き・率・色は値から計算
|
|
104
|
+
されるので手で渡さない。`TrendIndicator` は非推奨(`direction` を手で渡すので値と食い違いうる)。props は
|
|
105
|
+
`vantage docs kpi-card` / `vantage docs delta` を引く。
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { KpiCard, Delta } from "@squadbase/vantage/components";
|
|
109
|
+
|
|
110
|
+
<KpiCard label="売上" value={48_200_000} format="currency"
|
|
111
|
+
comparison={{ previous: 42_900_000, vs: "prior-period" }} />
|
|
112
|
+
<Delta value={0.021} comparison={{ previous: 0.026, vs: "prior-period" }} polarity="lower-is-better" />
|
|
113
|
+
```
|
|
114
|
+
|
|
103
115
|
## モードは 2 つ、切り替えるのは `server/` の有無だけ
|
|
104
116
|
|
|
105
117
|
`server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
|
|
@@ -233,7 +245,7 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
|
|
|
233
245
|
> `vantage upgrade` はこのファイルをフレームワーク同梱の正本で丸ごと上書きするため、
|
|
234
246
|
> このセクションは消えます。消えたら squadbase-monorepo の
|
|
235
247
|
> `packages/vantage-template/agents/vantage/` から貼り直してください
|
|
236
|
-
> (リポジトリ側では `
|
|
248
|
+
> (リポジトリ側では `pnpm run agents` が正本と結合して再生成します)。
|
|
237
249
|
|
|
238
250
|
## Squadbase の Connection / Query からデータを読む
|
|
239
251
|
|
|
@@ -351,6 +363,10 @@ return Response.json((report.rows ?? []).map(toSessionsDaily))
|
|
|
351
363
|
`components/build-recovery.tsx` の `BuildRecovery` が、その画面を DOM から検知して
|
|
352
364
|
`Working on it…` 表示を被せ、dev では 1.2 秒待ってから自動でリロードして復帰させる
|
|
353
365
|
(上限 3 回。使い切ったら「再読み込み」の案内に降格し、無事に描けたら残数が戻る)。
|
|
366
|
+
**ただし `window.__VANTAGE_DRAFT__?.active` が `true` の間(AI の書きかけのファイルを下書きとして
|
|
367
|
+
プレビューに出している間)は、覆わず・リロードもしない。** 下書き中の描画エラーはフレームワークが
|
|
368
|
+
直前に通った版へ戻すので、ここで覆うとその版の表示まで隠れ、リロードすると下書きの表示ごと消える。
|
|
369
|
+
下書きが終わった時点でエラー画面が残っていれば、通常どおり覆う。
|
|
354
370
|
降格した画面にはエラー本文をそのまま出し、`Copy log` でコピーできるようにしてある —
|
|
355
371
|
自力で直らないときに本文をそのまま貼り戻してもらうため。
|
|
356
372
|
`_error.tsx` も同じ `RecoveringState` を描くので、ルートレイアウトが throw したときの
|
|
@@ -386,6 +402,9 @@ lockfile は `bun.lock`)。Squadbase のサンドボックスは bun と依存
|
|
|
386
402
|
`bun install` を一度走らせて `bun.lock` を追従させ、両方を同じコミットに入れる。
|
|
387
403
|
- スクリプトは `bun run <script>`(`bun run dev` / `bun run build` / `bun run check` /
|
|
388
404
|
`bun run routes`)。`npm run <script>` でも動くが、install に npm を使わないこと。
|
|
405
|
+
- **公開から 48 時間未満の版は入らない。** `bunfig.toml` の `minimumReleaseAge`(サプライチェーン対策)で
|
|
406
|
+
弾かれる。`bun add` / `bun install` が「バージョンが見つからない」で落ちたら、直近の版を避けて
|
|
407
|
+
少し前の版を指定する。`bunfig.toml` の設定を緩めたり `@squadbase/*` 以外の除外を足したりしない。
|
|
389
408
|
|
|
390
409
|
## このアプリで作れるもの
|
|
391
410
|
|
|
@@ -454,3 +473,39 @@ Squadbase のチャットは、回答の根拠になったグラフ・表・KPI
|
|
|
454
473
|
- **ページ内で一意にする。**
|
|
455
474
|
- **新しく作った・直した画面では付ける。更新では既存の値を変えない** — 領域の意味が変わったときだけ変える
|
|
456
475
|
(値を変えると、それを指していた過去のリンクが開けなくなる)。触っていない画面へ遡って付けることはしない。
|
|
476
|
+
|
|
477
|
+
## ファイル内の書く順序(値 → 画面本体 → 子の部品)
|
|
478
|
+
|
|
479
|
+
ページ・部品のファイルは、上から次の順に書く。
|
|
480
|
+
|
|
481
|
+
1. `import`(と `definePage(...)`)
|
|
482
|
+
2. **値** — データ・定数・列定義・小さな関数。短く
|
|
483
|
+
3. **画面本体** — `export default` のコンポーネント
|
|
484
|
+
4. **子の部品** — 大文字始まりの部品。`function` 宣言で書く
|
|
485
|
+
|
|
486
|
+
理由: ファイルは上から順に書かれ、書きかけでもプレビューに反映される。**本体が書かれた時点で画面が出る。**
|
|
487
|
+
値を本体より後ろに書くと、本体はまだ無い値を読むので、その値が書かれるまで画面が出ない。
|
|
488
|
+
子の部品は後ろでよい — 未定義の部品はプレビューが仮の枠で埋める。
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
import { definePage } from "@squadbase/vantage"
|
|
492
|
+
|
|
493
|
+
export const page = definePage({ title: "売上" })
|
|
494
|
+
|
|
495
|
+
const KPI_ITEMS: Kpi[] = [/* … */] // 値は本体より上
|
|
496
|
+
type Kpi = { label: string; value: number }
|
|
497
|
+
|
|
498
|
+
export default function SalesPage() { // 本体
|
|
499
|
+
return <KpiRow items={KPI_ITEMS} />
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function KpiRow({ items }: { items: Kpi[] }) { /* … */ } // 子の部品は下
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 追加の不変条件
|
|
506
|
+
|
|
507
|
+
- **子の部品は `function` 宣言で書く。** 宣言は巻き上げられるので、本体より後ろでも安全。
|
|
508
|
+
`const Foo = () => …` のアロー関数は後ろに置くと、モジュール評価時に参照された瞬間に TDZ(初期化前参照)で落ちる。
|
|
509
|
+
- **値(定数・データ・列定義・小さな関数)は本体より上。** 小文字の関数・hook も値として上に置く。
|
|
510
|
+
- 型は消えるのでどこでもよい。値のそばに置く。
|
|
511
|
+
- 順序だけのために、既に並んでいるファイルを書き換えない。
|
|
@@ -177,6 +177,16 @@ refetchOnWindowFocus false・networkMode "always")。挙動を変えたいとき
|
|
|
177
177
|
`/components` の複合パーツも全部取り出せる(名前は kebab-case: `add ui bar-chart`。
|
|
178
178
|
ブロックは `add block sales-overview`。未知名で実行すると候補が一覧表示される)。
|
|
179
179
|
|
|
180
|
+
### 長い表はページングする(縦スクロール箱を作らない)
|
|
181
|
+
|
|
182
|
+
- 行が多い表は `DataTablePreset`(または `DataTable`)に `enablePagination` を付けて**ページング**する
|
|
183
|
+
(既定は 10 行 / ページ。ページングは既定で無効なので、付け忘れると全行が並ぶ)。素の `<table>` を
|
|
184
|
+
手書きして全行を流し込まない。
|
|
185
|
+
- カードの中に `max-h-*` + `overflow-y-auto` の**縦スクロール領域を作らない**。ホイールやキー
|
|
186
|
+
(End / Space)がその箱に吸われ、ページ全体がスクロールしなくなる。高さは内容に任せる。
|
|
187
|
+
- 横に広い表の `overflow-x-auto` は構わない。`SheetGrid`(`.csv` / `.xlsx` のビューア)は
|
|
188
|
+
グリッドとして意図した入れ子スクロールなので対象外。
|
|
189
|
+
|
|
180
190
|
### グラフはまず型付きチャートで描く
|
|
181
191
|
|
|
182
192
|
- 推移は `LineChart` / `AreaChart`、比較は `BarChart`、構成比は `DonutChart`。`data`(行の配列)・
|
package/base-template/bun.lock
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"name": "vibe-app",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@squadbase/react": "^0.1.0",
|
|
9
|
-
"@squadbase/vantage": "0.6.2-dev1.
|
|
10
|
-
"@squadbase/vantage-sdk": "0.1.1-dev1.
|
|
9
|
+
"@squadbase/vantage": "0.6.2-dev1.05a930b",
|
|
10
|
+
"@squadbase/vantage-sdk": "0.1.1-dev1.29787f0",
|
|
11
11
|
"date-fns": "^4.1.0",
|
|
12
12
|
"lucide-react": "^0.468.0",
|
|
13
13
|
"react": "^19.3.1",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
|
|
115
115
|
"@mermaid-js/parser": ["@mermaid-js/parser@1.2.1", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw=="],
|
|
116
116
|
|
|
117
|
-
"@oxc-project/types": ["@oxc-project/types@0.
|
|
117
|
+
"@oxc-project/types": ["@oxc-project/types@0.155.0", "", {}, "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q=="],
|
|
118
118
|
|
|
119
119
|
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="],
|
|
120
120
|
|
|
@@ -148,35 +148,35 @@
|
|
|
148
148
|
|
|
149
149
|
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rDiah9wvtqihWtWz02XreeRKIxt2EJF8y5D9rtY9l5A2zxePAtcPiOMpDugNRw5bFHz+1/8viVoc7ZVKiJknCw=="],
|
|
150
150
|
|
|
151
|
-
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.
|
|
151
|
+
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.15", "", { "os": "android", "cpu": "arm" }, "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw=="],
|
|
152
152
|
|
|
153
|
-
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.
|
|
153
|
+
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.15", "", { "os": "android", "cpu": "arm64" }, "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg=="],
|
|
154
154
|
|
|
155
|
-
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.
|
|
155
|
+
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA=="],
|
|
156
156
|
|
|
157
|
-
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.
|
|
157
|
+
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A=="],
|
|
158
158
|
|
|
159
|
-
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.
|
|
159
|
+
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ=="],
|
|
160
160
|
|
|
161
|
-
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.
|
|
161
|
+
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.15", "", { "os": "linux", "cpu": "arm" }, "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ=="],
|
|
162
162
|
|
|
163
|
-
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.
|
|
163
|
+
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw=="],
|
|
164
164
|
|
|
165
|
-
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.
|
|
165
|
+
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ=="],
|
|
166
166
|
|
|
167
|
-
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.
|
|
167
|
+
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw=="],
|
|
168
168
|
|
|
169
|
-
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.
|
|
169
|
+
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA=="],
|
|
170
170
|
|
|
171
|
-
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.
|
|
171
|
+
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.15", "", { "os": "linux", "cpu": "x64" }, "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw=="],
|
|
172
172
|
|
|
173
|
-
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.
|
|
173
|
+
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.15", "", { "os": "linux", "cpu": "x64" }, "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ=="],
|
|
174
174
|
|
|
175
|
-
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.
|
|
175
|
+
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.15", "", { "os": "none", "cpu": "arm64" }, "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w=="],
|
|
176
176
|
|
|
177
|
-
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.
|
|
177
|
+
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg=="],
|
|
178
178
|
|
|
179
|
-
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.
|
|
179
|
+
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.15", "", { "os": "win32", "cpu": "x64" }, "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ=="],
|
|
180
180
|
|
|
181
181
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
|
182
182
|
|
|
@@ -196,9 +196,9 @@
|
|
|
196
196
|
|
|
197
197
|
"@squadbase/react": ["@squadbase/react@0.1.0", "", { "dependencies": { "zod": "^4.3.6" }, "peerDependencies": { "@types/react": "17.x || 18.x || 19.x", "react": "17.x || 18.x || 19.x" }, "optionalPeers": ["@types/react"] }, "sha512-k+Dpyj7eH+Yjydw54iEluCjYDae086d12hWgBjNMpw4wLbQGz3CrJ6Yipodg8n1cKUt7zvwddlcTHd79bgKsXg=="],
|
|
198
198
|
|
|
199
|
-
"@squadbase/vantage": ["@squadbase/vantage@0.6.2-dev1.
|
|
199
|
+
"@squadbase/vantage": ["@squadbase/vantage@0.6.2-dev1.05a930b", "", { "dependencies": { "@base-ui/react": "^1.6.0", "@hono/node-server": "^2.1.5", "@streamdown/code": "^1.1.1", "@tailwindcss/vite": "^4.3.3", "@tanstack/react-query": "^5.101.2", "@tanstack/react-router": "^1.170.17", "@tanstack/react-table": "^8.21.3", "@vitejs/plugin-react": "^6.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "echarts": "^6.0.0", "esbuild": "0.28.1", "fflate": "^0.8.3", "hono": "^4.13.13", "html-to-image": "^1.11.13", "lucide-react": "^0.468.0", "papaparse": "^5.7.0", "picocolors": "^1.1.1", "react-day-picker": "^10.0.1", "read-excel-file": "^9.3.10", "shiki": "3.23.0", "streamdown": "^2.5.0", "tailwind-merge": "3.6.0", "tailwindcss": "^4.3.3", "tw-animate-css": "^1.0.0", "vite": "^8.3.4" }, "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0", "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"], "bin": { "vantage": "dist/cli.js" } }, "sha512-hA8JC3AzYaWBpXlnlZmaP1kBwU71BIppqcfbuXv57VltJX5sLtpe7mon3jwSpCgwz9LbP1OudM50c60xSdKRPg=="],
|
|
200
200
|
|
|
201
|
-
"@squadbase/vantage-sdk": ["@squadbase/vantage-sdk@0.1.1-dev1.
|
|
201
|
+
"@squadbase/vantage-sdk": ["@squadbase/vantage-sdk@0.1.1-dev1.29787f0", "", {}, "sha512-zvqv9Ctm3D62uXRQoOQCHQ/mtT6nczKDTjNfwthqHOlQ6TLcEzuDy9reBaS0/dvEJ9nTruTcru/dT45S+cCQFQ=="],
|
|
202
202
|
|
|
203
203
|
"@streamdown/code": ["@streamdown/code@1.1.1", "", { "dependencies": { "shiki": "^3.19.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-i7HTNuDgZWb+VdrNVOam9gQhIc5MSSDXKWXgbUrn/4vSRaSMM+Rtl10MQj4wLWPNpF7p80waJsAqFP8HZfb0Jg=="],
|
|
204
204
|
|
|
@@ -716,7 +716,7 @@
|
|
|
716
716
|
|
|
717
717
|
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
|
718
718
|
|
|
719
|
-
"read-excel-file": ["read-excel-file@9.3.
|
|
719
|
+
"read-excel-file": ["read-excel-file@9.3.12", "", { "dependencies": { "fflate": "^0.8.3", "saxen": "^11.1.0", "unzipper-esm": "^0.13.3", "worker-f": "^0.1.12" } }, "sha512-benNAoQbe5HqoTi3qrQkR8wMeHq0gVWLt2Br73hVvmBPGdzMf9OLGsps/812AfZ+bvzVYsUyhF77Y3o4B51yCA=="],
|
|
720
720
|
|
|
721
721
|
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
|
722
722
|
|
|
@@ -744,7 +744,7 @@
|
|
|
744
744
|
|
|
745
745
|
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
|
746
746
|
|
|
747
|
-
"rolldown": ["rolldown@1.2.
|
|
747
|
+
"rolldown": ["rolldown@1.2.15", "", { "dependencies": { "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.15", "@rolldown/binding-android-arm64": "1.2.15", "@rolldown/binding-darwin-arm64": "1.2.15", "@rolldown/binding-darwin-x64": "1.2.15", "@rolldown/binding-freebsd-x64": "1.2.15", "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", "@rolldown/binding-linux-arm64-gnu": "1.2.15", "@rolldown/binding-linux-arm64-musl": "1.2.15", "@rolldown/binding-linux-ppc64-gnu": "1.2.15", "@rolldown/binding-linux-s390x-gnu": "1.2.15", "@rolldown/binding-linux-x64-gnu": "1.2.15", "@rolldown/binding-linux-x64-musl": "1.2.15", "@rolldown/binding-openharmony-arm64": "1.2.15", "@rolldown/binding-win32-arm64-msvc": "1.2.15", "@rolldown/binding-win32-x64-msvc": "1.2.15" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g=="],
|
|
748
748
|
|
|
749
749
|
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
|
|
750
750
|
|
|
@@ -752,7 +752,7 @@
|
|
|
752
752
|
|
|
753
753
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
|
754
754
|
|
|
755
|
-
"saxen": ["saxen@11.
|
|
755
|
+
"saxen": ["saxen@11.2.0", "", {}, "sha512-2bG564AAJcm/3vyZC0Uhs/mACrH+4AHMaKHKAB2M5xMqomUZa27FQ7MYaQGFKVcJk/MVzSEQ34A8QZWW+eiWEw=="],
|
|
756
756
|
|
|
757
757
|
"scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
|
|
758
758
|
|
|
@@ -830,7 +830,7 @@
|
|
|
830
830
|
|
|
831
831
|
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
|
832
832
|
|
|
833
|
-
"vite": ["vite@8.3.
|
|
833
|
+
"vite": ["vite@8.3.5", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.30", "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ=="],
|
|
834
834
|
|
|
835
835
|
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
|
|
836
836
|
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@squadbase/react": "^0.1.0",
|
|
15
|
-
"@squadbase/vantage": "0.6.2-dev1.
|
|
16
|
-
"@squadbase/vantage-sdk": "0.1.1-dev1.
|
|
15
|
+
"@squadbase/vantage": "0.6.2-dev1.05a930b",
|
|
16
|
+
"@squadbase/vantage-sdk": "0.1.1-dev1.29787f0",
|
|
17
17
|
"date-fns": "^4.1.0",
|
|
18
18
|
"lucide-react": "^0.468.0",
|
|
19
19
|
"react": "^19.3.1",
|
|
@@ -7,15 +7,6 @@ import type { NavGroup } from "@squadbase/vantage/components"
|
|
|
7
7
|
import { BuildRecovery } from "./components/build-recovery"
|
|
8
8
|
import { UserCard } from "./components/user-card"
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* `AppShell` is router-agnostic and renders nav entries through whatever
|
|
12
|
-
* component it is handed, using an `href` prop. Adapt it to the Vantage router
|
|
13
|
-
* so navigation stays client-side.
|
|
14
|
-
*/
|
|
15
|
-
function RouterLink({ href, ...props }: { href: string } & ComponentProps<"a">) {
|
|
16
|
-
return <Link to={href} {...props} />
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
/**
|
|
20
11
|
* The nav *is* the route list — adding a page file puts it in the nav, with no
|
|
21
12
|
* second list to keep in sync. Dynamic routes (`sales/[id].tsx`) have no single
|
|
@@ -67,3 +58,12 @@ export default function RootLayout() {
|
|
|
67
58
|
</SquadbaseProvider>
|
|
68
59
|
)
|
|
69
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `AppShell` is router-agnostic and renders nav entries through whatever
|
|
64
|
+
* component it is handed, using an `href` prop. Adapt it to the Vantage router
|
|
65
|
+
* so navigation stays client-side.
|
|
66
|
+
*/
|
|
67
|
+
function RouterLink({ href, ...props }: { href: string } & ComponentProps<"a">) {
|
|
68
|
+
return <Link to={href} {...props} />
|
|
69
|
+
}
|
|
@@ -29,6 +29,12 @@ import { Button, Spinner } from "@squadbase/vantage/ui"
|
|
|
29
29
|
*/
|
|
30
30
|
const FRAMEWORK_ERROR_SELECTOR = '[role="alert"] h2'
|
|
31
31
|
|
|
32
|
+
/** `import.meta.hot` のうち、ここで使う分だけ。 */
|
|
33
|
+
interface ViteHot {
|
|
34
|
+
on(event: string, cb: () => void): void
|
|
35
|
+
off?(event: string, cb: () => void): void
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
/** 書き込みの連鎖が落ち着くのを待ってからリロードする。 */
|
|
33
39
|
const RELOAD_DELAY_MS = 1200
|
|
34
40
|
|
|
@@ -43,6 +49,21 @@ const SETTLE_MS = 5000
|
|
|
43
49
|
|
|
44
50
|
const RELOAD_COUNT_KEY = "vantage:build-recovery-reloads"
|
|
45
51
|
|
|
52
|
+
/**
|
|
53
|
+
* 書きかけのファイルを下書きとして表示している間、Vantage のクライアントが立てる目印。
|
|
54
|
+
* 下書き中の描画エラーはフレームワークが直前に通った版へ戻すので、ここでは覆わず、
|
|
55
|
+
* リロードもしない(リロードすると下書きの表示ごと消える)。
|
|
56
|
+
*/
|
|
57
|
+
declare global {
|
|
58
|
+
interface Window {
|
|
59
|
+
__VANTAGE_DRAFT__?: { active: boolean }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isDraftActive(): boolean {
|
|
64
|
+
return window.__VANTAGE_DRAFT__?.active === true
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
/** コピーしたことが伝わればいいので、確認表示はすぐ元に戻す。 */
|
|
47
68
|
const COPIED_FEEDBACK_MS = 2000
|
|
48
69
|
|
|
@@ -90,6 +111,7 @@ export function RecoveringState({ error }: { error?: unknown }) {
|
|
|
90
111
|
const attempts = readReloadCount()
|
|
91
112
|
if (attempts < MAX_RELOADS) {
|
|
92
113
|
const timer = setTimeout(() => {
|
|
114
|
+
if (isDraftActive()) return
|
|
93
115
|
writeReloadCount(attempts + 1)
|
|
94
116
|
window.location.reload()
|
|
95
117
|
}, RELOAD_DELAY_MS)
|
|
@@ -181,6 +203,11 @@ export function BuildRecovery({ children }: { children: ReactNode }) {
|
|
|
181
203
|
const host = hostRef.current
|
|
182
204
|
if (!host) return
|
|
183
205
|
const sync = () => {
|
|
206
|
+
// 下書きの表示中は覆わない。終わった時点でエラー画面が残っていれば、次の sync で覆う。
|
|
207
|
+
if (isDraftActive()) {
|
|
208
|
+
setMessage(null)
|
|
209
|
+
return
|
|
210
|
+
}
|
|
184
211
|
const heading = host.querySelector(FRAMEWORK_ERROR_SELECTOR)
|
|
185
212
|
// 覆って見えなくする以上、支援技術にも読ませない。`role="alert"` のままだと
|
|
186
213
|
// 「Something went wrong」がそのまま読み上げられてしまう。属性は observe の
|
|
@@ -192,7 +219,16 @@ export function BuildRecovery({ children }: { children: ReactNode }) {
|
|
|
192
219
|
sync()
|
|
193
220
|
const observer = new MutationObserver(sync)
|
|
194
221
|
observer.observe(host, { childList: true, subtree: true })
|
|
195
|
-
|
|
222
|
+
// 下書きの有無が変わったときに見直す(目印の更新を待つため 1 tick 遅らせる)。
|
|
223
|
+
const onDraftState = () => setTimeout(sync, 0)
|
|
224
|
+
// 生成される `ImportMeta` は `env` しか宣言しない(HMR の型を入れるとアプリが vite/client を
|
|
225
|
+
// 入れたときに二重宣言になる)ので、ここだけ局所キャストで読む。
|
|
226
|
+
const hot = (import.meta as unknown as { hot?: ViteHot }).hot
|
|
227
|
+
hot?.on("vantage:draft-state", onDraftState)
|
|
228
|
+
return () => {
|
|
229
|
+
observer.disconnect()
|
|
230
|
+
hot?.off?.("vantage:draft-state", onDraftState)
|
|
231
|
+
}
|
|
196
232
|
}, [])
|
|
197
233
|
|
|
198
234
|
const crashed = message !== null
|
|
@@ -11,6 +11,16 @@ import {
|
|
|
11
11
|
cn,
|
|
12
12
|
} from "@squadbase/vantage/ui"
|
|
13
13
|
|
|
14
|
+
export function UserCard() {
|
|
15
|
+
const userState = useUser()
|
|
16
|
+
|
|
17
|
+
if (userState.status === "success") {
|
|
18
|
+
return <UserInfo user={userState.data} />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return <UserInfoFallback />
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
/**
|
|
15
25
|
* Squadbase-authenticated user chip for the app shell.
|
|
16
26
|
*
|
|
@@ -120,13 +130,3 @@ function UserInfoFallback() {
|
|
|
120
130
|
</div>
|
|
121
131
|
)
|
|
122
132
|
}
|
|
123
|
-
|
|
124
|
-
export function UserCard() {
|
|
125
|
-
const userState = useUser()
|
|
126
|
-
|
|
127
|
-
if (userState.status === "success") {
|
|
128
|
-
return <UserInfo user={userState.data} />
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return <UserInfoFallback />
|
|
132
|
-
}
|
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/* Page typeface. Latin system faces first, then Japanese system faces so kana and
|
|
21
|
-
kanji do not fall back to a thin locale default.
|
|
22
|
-
|
|
21
|
+
kanji do not fall back to a thin locale default. `ui-sans-serif` / `system-ui`
|
|
22
|
+
come after the Japanese families on purpose: on Japanese Windows they resolve to
|
|
23
|
+
the condensed Yu Gothic UI, which would otherwise take the kana. Put a Latin
|
|
24
|
+
webfont in front if you add one, but keep the Japanese families. */
|
|
23
25
|
body {
|
|
24
26
|
font-family:
|
|
25
|
-
|
|
26
|
-
"Hiragino Kaku Gothic ProN", "Noto Sans JP",
|
|
27
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Hiragino Sans",
|
|
28
|
+
"Hiragino Kaku Gothic ProN", "Noto Sans JP", Meiryo, ui-sans-serif, system-ui, sans-serif;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/* vantage-template:chart-preset — regenerated by `chart`; edits here are lost */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squadbase/vantage-template",
|
|
3
|
-
"version": "0.1.1-dev1.
|
|
3
|
+
"version": "0.1.1-dev1.e87430f",
|
|
4
4
|
"description": "CLI tool for initializing and customizing Squadbase Vantage projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"squadbase",
|
|
@@ -36,19 +36,6 @@
|
|
|
36
36
|
"access": "public",
|
|
37
37
|
"registry": "https://registry.npmjs.org"
|
|
38
38
|
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"sync-base": "rsync -a --delete --exclude='node_modules' --exclude='dist' --exclude='.vantage' --exclude='*.tsbuildinfo' --exclude='*.log' template/ base-template/ && mv base-template/.gitignore base-template/_gitignore",
|
|
41
|
-
"build": "npm run sync-base && tsup",
|
|
42
|
-
"type-check": "tsc",
|
|
43
|
-
"agents": "node scripts/build-agents.cjs",
|
|
44
|
-
"agents:check": "node scripts/build-agents.cjs --check",
|
|
45
|
-
"template:install": "bun install --cwd template --frozen-lockfile",
|
|
46
|
-
"template:build": "npm --prefix template run build",
|
|
47
|
-
"prepublishOnly": "npm run agents:check && npm run build",
|
|
48
|
-
"release": "npm publish --@squadbase:registry=https://registry.npmjs.org",
|
|
49
|
-
"dev": "node scripts/dev.mjs",
|
|
50
|
-
"screenshot": "node scripts/screenshot.mjs"
|
|
51
|
-
},
|
|
52
39
|
"devDependencies": {
|
|
53
40
|
"@types/node": "^22.15.0",
|
|
54
41
|
"playwright": "^1.49.0",
|
|
@@ -58,7 +45,20 @@
|
|
|
58
45
|
"optionalDependencies": {
|
|
59
46
|
"@ai-sdk/anthropic": "^2.0.0",
|
|
60
47
|
"@ai-sdk/google": "^2.0.0",
|
|
61
|
-
"@ai-sdk/openai": "
|
|
48
|
+
"@ai-sdk/openai": "2.0.122",
|
|
62
49
|
"ai": "^5.0.52"
|
|
50
|
+
},
|
|
51
|
+
"gitHead": "e87430fa6bccdd2c9b34ebfba984dd801e7d1420",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"sync-base": "rsync -a --delete --exclude='node_modules' --exclude='dist' --exclude='.vantage' --exclude='*.tsbuildinfo' --exclude='*.log' template/ base-template/ && mv base-template/.gitignore base-template/_gitignore",
|
|
54
|
+
"build": "pnpm run sync-base && tsup",
|
|
55
|
+
"type-check": "tsc",
|
|
56
|
+
"agents": "node scripts/build-agents.cjs",
|
|
57
|
+
"agents:check": "node scripts/build-agents.cjs --check",
|
|
58
|
+
"template:install": "bun install --cwd template --frozen-lockfile",
|
|
59
|
+
"template:build": "bun run --cwd template build",
|
|
60
|
+
"release": "pnpm publish --@squadbase:registry=https://registry.npmjs.org",
|
|
61
|
+
"dev": "node scripts/dev.mjs",
|
|
62
|
+
"screenshot": "node scripts/screenshot.mjs"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|
|
@@ -104,6 +104,53 @@ const RANGE_OPTIONS: SegmentOption[] = [
|
|
|
104
104
|
{ label: "90 days", value: "90d" },
|
|
105
105
|
]
|
|
106
106
|
|
|
107
|
+
export default function HomePage() {
|
|
108
|
+
// Filter state belongs in the URL rather than `useState`: it survives a
|
|
109
|
+
// reload, and a pasted link reproduces exactly what you were looking at.
|
|
110
|
+
const [range, setRange] = useSearchParam("range", "30d")
|
|
111
|
+
|
|
112
|
+
// `search` goes into the query string *and* the cache key, so changing the
|
|
113
|
+
// range refetches instead of serving the previous window.
|
|
114
|
+
const summary = useApiQuery<KpiSummary>("/api/kpi-summary", { search: { range } })
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<PageShell>
|
|
118
|
+
<PageShellHeader>
|
|
119
|
+
<PageShellHeading>
|
|
120
|
+
<PageShellTitle>[Template] Performance overview</PageShellTitle>
|
|
121
|
+
<PageShellDescription>
|
|
122
|
+
Key metrics and trends for the selected period.
|
|
123
|
+
</PageShellDescription>
|
|
124
|
+
</PageShellHeading>
|
|
125
|
+
<PageShellHeaderEnd>
|
|
126
|
+
<SegmentedControl
|
|
127
|
+
options={RANGE_OPTIONS}
|
|
128
|
+
value={range}
|
|
129
|
+
onChange={setRange}
|
|
130
|
+
ariaLabel="Period"
|
|
131
|
+
/>
|
|
132
|
+
</PageShellHeaderEnd>
|
|
133
|
+
</PageShellHeader>
|
|
134
|
+
|
|
135
|
+
<PageShellContent className="space-y-6">
|
|
136
|
+
{summary.isPending ? (
|
|
137
|
+
<Loading />
|
|
138
|
+
) : summary.isError ? (
|
|
139
|
+
// `summary.error` is an `ApiError`: `message` is the text the handler
|
|
140
|
+
// passed to `HttpError`, so there is no cast and no generic wording.
|
|
141
|
+
<ErrorState
|
|
142
|
+
title="Could not load the dashboard"
|
|
143
|
+
message={summary.error.message}
|
|
144
|
+
action={<Button onClick={() => void summary.refetch()}>Retry</Button>}
|
|
145
|
+
/>
|
|
146
|
+
) : (
|
|
147
|
+
<DashboardBody data={summary.data} />
|
|
148
|
+
)}
|
|
149
|
+
</PageShellContent>
|
|
150
|
+
</PageShell>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
107
154
|
function DashboardBody({ data }: { data: KpiSummary }) {
|
|
108
155
|
return (
|
|
109
156
|
<>
|
|
@@ -151,50 +198,3 @@ function DashboardBody({ data }: { data: KpiSummary }) {
|
|
|
151
198
|
</>
|
|
152
199
|
)
|
|
153
200
|
}
|
|
154
|
-
|
|
155
|
-
export default function HomePage() {
|
|
156
|
-
// Filter state belongs in the URL rather than `useState`: it survives a
|
|
157
|
-
// reload, and a pasted link reproduces exactly what you were looking at.
|
|
158
|
-
const [range, setRange] = useSearchParam("range", "30d")
|
|
159
|
-
|
|
160
|
-
// `search` goes into the query string *and* the cache key, so changing the
|
|
161
|
-
// range refetches instead of serving the previous window.
|
|
162
|
-
const summary = useApiQuery<KpiSummary>("/api/kpi-summary", { search: { range } })
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<PageShell>
|
|
166
|
-
<PageShellHeader>
|
|
167
|
-
<PageShellHeading>
|
|
168
|
-
<PageShellTitle>[Template] Performance overview</PageShellTitle>
|
|
169
|
-
<PageShellDescription>
|
|
170
|
-
Key metrics and trends for the selected period.
|
|
171
|
-
</PageShellDescription>
|
|
172
|
-
</PageShellHeading>
|
|
173
|
-
<PageShellHeaderEnd>
|
|
174
|
-
<SegmentedControl
|
|
175
|
-
options={RANGE_OPTIONS}
|
|
176
|
-
value={range}
|
|
177
|
-
onChange={setRange}
|
|
178
|
-
ariaLabel="Period"
|
|
179
|
-
/>
|
|
180
|
-
</PageShellHeaderEnd>
|
|
181
|
-
</PageShellHeader>
|
|
182
|
-
|
|
183
|
-
<PageShellContent className="space-y-6">
|
|
184
|
-
{summary.isPending ? (
|
|
185
|
-
<Loading />
|
|
186
|
-
) : summary.isError ? (
|
|
187
|
-
// `summary.error` is an `ApiError`: `message` is the text the handler
|
|
188
|
-
// passed to `HttpError`, so there is no cast and no generic wording.
|
|
189
|
-
<ErrorState
|
|
190
|
-
title="Could not load the dashboard"
|
|
191
|
-
message={summary.error.message}
|
|
192
|
-
action={<Button onClick={() => void summary.refetch()}>Retry</Button>}
|
|
193
|
-
/>
|
|
194
|
-
) : (
|
|
195
|
-
<DashboardBody data={summary.data} />
|
|
196
|
-
)}
|
|
197
|
-
</PageShellContent>
|
|
198
|
-
</PageShell>
|
|
199
|
-
)
|
|
200
|
-
}
|
|
@@ -104,6 +104,53 @@ const RANGE_OPTIONS: SegmentOption[] = [
|
|
|
104
104
|
{ label: "90日", value: "90d" },
|
|
105
105
|
]
|
|
106
106
|
|
|
107
|
+
export default function HomePage() {
|
|
108
|
+
// 絞り込み状態は useState ではなく URL に置く。リロードで消えず、URL を
|
|
109
|
+
// そのまま共有すれば相手も同じ画面を見られる。
|
|
110
|
+
const [range, setRange] = useSearchParam("range", "30d")
|
|
111
|
+
|
|
112
|
+
// search はクエリ文字列とキャッシュキーの両方に入るので、期間を変えると
|
|
113
|
+
// 前の期間のデータを返さずに再取得される。
|
|
114
|
+
const summary = useApiQuery<KpiSummary>("/api/kpi-summary", { search: { range } })
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<PageShell>
|
|
118
|
+
<PageShellHeader>
|
|
119
|
+
<PageShellHeading>
|
|
120
|
+
<PageShellTitle>[テンプレート] パフォーマンス概要</PageShellTitle>
|
|
121
|
+
<PageShellDescription>
|
|
122
|
+
選択期間の主要指標とトレンド。
|
|
123
|
+
</PageShellDescription>
|
|
124
|
+
</PageShellHeading>
|
|
125
|
+
<PageShellHeaderEnd>
|
|
126
|
+
<SegmentedControl
|
|
127
|
+
options={RANGE_OPTIONS}
|
|
128
|
+
value={range}
|
|
129
|
+
onChange={setRange}
|
|
130
|
+
ariaLabel="期間"
|
|
131
|
+
/>
|
|
132
|
+
</PageShellHeaderEnd>
|
|
133
|
+
</PageShellHeader>
|
|
134
|
+
|
|
135
|
+
<PageShellContent className="space-y-6">
|
|
136
|
+
{summary.isPending ? (
|
|
137
|
+
<Loading />
|
|
138
|
+
) : summary.isError ? (
|
|
139
|
+
// summary.error は ApiError。message にはハンドラが HttpError に
|
|
140
|
+
// 渡した文言がそのまま入るので、キャストも汎用文言も要らない。
|
|
141
|
+
<ErrorState
|
|
142
|
+
title="ダッシュボードを読み込めませんでした"
|
|
143
|
+
message={summary.error.message}
|
|
144
|
+
action={<Button onClick={() => void summary.refetch()}>再試行</Button>}
|
|
145
|
+
/>
|
|
146
|
+
) : (
|
|
147
|
+
<DashboardBody data={summary.data} />
|
|
148
|
+
)}
|
|
149
|
+
</PageShellContent>
|
|
150
|
+
</PageShell>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
107
154
|
function DashboardBody({ data }: { data: KpiSummary }) {
|
|
108
155
|
return (
|
|
109
156
|
<>
|
|
@@ -151,50 +198,3 @@ function DashboardBody({ data }: { data: KpiSummary }) {
|
|
|
151
198
|
</>
|
|
152
199
|
)
|
|
153
200
|
}
|
|
154
|
-
|
|
155
|
-
export default function HomePage() {
|
|
156
|
-
// 絞り込み状態は useState ではなく URL に置く。リロードで消えず、URL を
|
|
157
|
-
// そのまま共有すれば相手も同じ画面を見られる。
|
|
158
|
-
const [range, setRange] = useSearchParam("range", "30d")
|
|
159
|
-
|
|
160
|
-
// search はクエリ文字列とキャッシュキーの両方に入るので、期間を変えると
|
|
161
|
-
// 前の期間のデータを返さずに再取得される。
|
|
162
|
-
const summary = useApiQuery<KpiSummary>("/api/kpi-summary", { search: { range } })
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<PageShell>
|
|
166
|
-
<PageShellHeader>
|
|
167
|
-
<PageShellHeading>
|
|
168
|
-
<PageShellTitle>[テンプレート] パフォーマンス概要</PageShellTitle>
|
|
169
|
-
<PageShellDescription>
|
|
170
|
-
選択期間の主要指標とトレンド。
|
|
171
|
-
</PageShellDescription>
|
|
172
|
-
</PageShellHeading>
|
|
173
|
-
<PageShellHeaderEnd>
|
|
174
|
-
<SegmentedControl
|
|
175
|
-
options={RANGE_OPTIONS}
|
|
176
|
-
value={range}
|
|
177
|
-
onChange={setRange}
|
|
178
|
-
ariaLabel="期間"
|
|
179
|
-
/>
|
|
180
|
-
</PageShellHeaderEnd>
|
|
181
|
-
</PageShellHeader>
|
|
182
|
-
|
|
183
|
-
<PageShellContent className="space-y-6">
|
|
184
|
-
{summary.isPending ? (
|
|
185
|
-
<Loading />
|
|
186
|
-
) : summary.isError ? (
|
|
187
|
-
// summary.error は ApiError。message にはハンドラが HttpError に
|
|
188
|
-
// 渡した文言がそのまま入るので、キャストも汎用文言も要らない。
|
|
189
|
-
<ErrorState
|
|
190
|
-
title="ダッシュボードを読み込めませんでした"
|
|
191
|
-
message={summary.error.message}
|
|
192
|
-
action={<Button onClick={() => void summary.refetch()}>再試行</Button>}
|
|
193
|
-
/>
|
|
194
|
-
) : (
|
|
195
|
-
<DashboardBody data={summary.data} />
|
|
196
|
-
)}
|
|
197
|
-
</PageShellContent>
|
|
198
|
-
</PageShell>
|
|
199
|
-
)
|
|
200
|
-
}
|