@squadbase/vantage 0.5.3 → 0.6.1-dev.2d25e94
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/NOTICE +73 -0
- package/README.md +6 -5
- package/dist/add-DY4AXANK.js +6 -0
- package/dist/add-DY4AXANK.js.map +1 -0
- package/dist/{build-756WIEVI.js → build-JB4VOMUI.js} +10 -10
- package/dist/{build-756WIEVI.js.map → build-JB4VOMUI.js.map} +1 -1
- package/dist/{check-KZ5PHCA6.js → check-ZJYK6RLG.js} +8 -8
- package/dist/{check-KZ5PHCA6.js.map → check-ZJYK6RLG.js.map} +1 -1
- package/dist/chunk-5ZOP4SQR.js +47 -0
- package/dist/chunk-5ZOP4SQR.js.map +1 -0
- package/dist/{chunk-EHPJXDJU.js → chunk-6S4BAIGV.js} +4 -2
- package/dist/chunk-6S4BAIGV.js.map +1 -0
- package/dist/chunk-BCE22JSM.js +510 -0
- package/dist/chunk-BCE22JSM.js.map +1 -0
- package/dist/{chunk-C4MQRWXS.js → chunk-C2CX6GLX.js} +17 -3
- package/dist/chunk-C2CX6GLX.js.map +1 -0
- package/dist/{chunk-RA6Z3OX3.js → chunk-EP6JHXSR.js} +61 -9
- package/dist/chunk-EP6JHXSR.js.map +1 -0
- package/dist/{chunk-BAM33YZ6.js → chunk-JGRMRXXZ.js} +45 -6
- package/dist/chunk-JGRMRXXZ.js.map +1 -0
- package/dist/{chunk-VXAV2435.js → chunk-KLEIVDUV.js} +30 -7
- package/dist/chunk-KLEIVDUV.js.map +1 -0
- package/dist/{chunk-JC6MT5UU.js → chunk-MXI3PZFT.js} +3 -3
- package/dist/{chunk-JC6MT5UU.js.map → chunk-MXI3PZFT.js.map} +1 -1
- package/dist/chunk-PLST5Q7T.js +2221 -0
- package/dist/chunk-PLST5Q7T.js.map +1 -0
- package/dist/chunk-PVTHHV7X.js +105 -0
- package/dist/chunk-PVTHHV7X.js.map +1 -0
- package/dist/{chunk-HQDEYCDP.js → chunk-QI3XQKHS.js} +40 -4
- package/dist/chunk-QI3XQKHS.js.map +1 -0
- package/dist/{chunk-L3TW4FNA.js → chunk-T3FLAGI6.js} +34 -13
- package/dist/chunk-T3FLAGI6.js.map +1 -0
- package/dist/{chunk-X44ZTKID.js → chunk-U2M6HGKQ.js} +61 -9
- package/dist/chunk-U2M6HGKQ.js.map +1 -0
- package/dist/chunk-ZO62C6T4.js +91 -0
- package/dist/chunk-ZO62C6T4.js.map +1 -0
- package/dist/{chunk-D4Y5RWJA.js → chunk-ZVANIULZ.js} +81 -21
- package/dist/chunk-ZVANIULZ.js.map +1 -0
- package/dist/cli.js +18 -14
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +5 -35
- package/dist/client/index.js +1 -1
- package/dist/components/index.js +3 -2212
- package/dist/components/index.js.map +1 -1
- package/dist/{dev-EILX2VTF.js → dev-FCEC3UKQ.js} +24 -13
- package/dist/dev-FCEC3UKQ.js.map +1 -0
- package/dist/doc/index.d.ts +39 -0
- package/dist/doc/index.js +38 -0
- package/dist/doc/index.js.map +1 -0
- package/dist/{docs-GP2ZHIQF.js → docs-EJJ3BECT.js} +4 -4
- package/dist/{docs-GP2ZHIQF.js.map → docs-EJJ3BECT.js.map} +1 -1
- package/dist/{doctor-FXZLMGC3.js → doctor-WHZXPERA.js} +9 -9
- package/dist/doctor-WHZXPERA.js.map +1 -0
- package/dist/manifest-DiOjyinq.d.ts +43 -0
- package/dist/{preview-WBQPCTTT.js → preview-OGMCNQUO.js} +13 -5
- package/dist/preview-OGMCNQUO.js.map +1 -0
- package/dist/query/index.js +1 -102
- package/dist/query/index.js.map +1 -1
- package/dist/router/index.d.ts +10 -0
- package/dist/router/index.js +2 -89
- package/dist/router/index.js.map +1 -1
- package/dist/{routes-WCN4KSZY.js → routes-GTFO5PUF.js} +17 -8
- package/dist/routes-GTFO5PUF.js.map +1 -0
- package/dist/{search-GLKCJXSI.js → search-6Y422SV6.js} +4 -4
- package/dist/{search-GLKCJXSI.js.map → search-6Y422SV6.js.map} +1 -1
- package/dist/server/node.d.ts +1 -1
- package/dist/server/node.js +6 -1
- package/dist/server/node.js.map +1 -1
- package/dist/slide/index.d.ts +230 -0
- package/dist/slide/index.js +1571 -0
- package/dist/slide/index.js.map +1 -0
- package/dist/table/index.d.ts +107 -0
- package/dist/table/index.js +680 -0
- package/dist/table/index.js.map +1 -0
- package/dist/types-CXFlMl8S.d.ts +121 -0
- package/dist/ui/index.js +3 -41
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-S64IXL42.js → upgrade-AL4KZZR4.js} +9 -9
- package/dist/upgrade-AL4KZZR4.js.map +1 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +5 -5
- package/docs/en/agent-skills.md +22 -12
- package/docs/en/changelog.md +107 -0
- package/docs/en/cli-reference.md +42 -4
- package/docs/en/pages-and-metadata.md +17 -0
- package/docs/en/routing.md +102 -3
- package/docs/en/ui-and-theming.md +102 -0
- package/docs/index.json +2 -2
- package/docs/ja/agent-skills.md +21 -11
- package/docs/ja/changelog.md +105 -0
- package/docs/ja/cli-reference.md +41 -4
- package/docs/ja/pages-and-metadata.md +17 -0
- package/docs/ja/routing.md +98 -3
- package/docs/ja/ui-and-theming.md +100 -0
- package/package.json +33 -9
- package/skills/vantage-app/SKILL.md +4 -0
- package/skills/vantage-design/SKILL.md +137 -0
- package/skills/vantage-design/references/extract.md +110 -0
- package/skills/vantage-design/references/format.md +172 -0
- package/skills/vantage-design/references/presets.md +110 -0
- package/skills/vantage-design/scripts/contrast.mjs +166 -0
- package/skills/vantage-pitfalls/SKILL.md +41 -1
- package/skills/vantage-slide/SKILL.md +264 -0
- package/skills/vantage-slide/references/morph.md +102 -0
- package/skills/vantage-slide/references/steps.md +57 -0
- package/skills/vantage-slide/references/tokens.md +87 -0
- package/skills/vantage-slide/references/transitions.md +196 -0
- package/templates/AGENTS.md +43 -228
- package/templates/agents/api.md +49 -0
- package/templates/agents/csv.md +32 -0
- package/templates/agents/doc.md +73 -0
- package/templates/agents/page.md +187 -0
- package/templates/agents/slide.md +41 -0
- package/templates/agents/xlsx.md +33 -0
- package/theme.css +169 -0
- package/dist/add-PPCBMWEN.js +0 -6
- package/dist/add-PPCBMWEN.js.map +0 -1
- package/dist/chunk-BAM33YZ6.js.map +0 -1
- package/dist/chunk-C4MQRWXS.js.map +0 -1
- package/dist/chunk-D4Y5RWJA.js.map +0 -1
- package/dist/chunk-EHPJXDJU.js.map +0 -1
- package/dist/chunk-HQDEYCDP.js.map +0 -1
- package/dist/chunk-L3TW4FNA.js.map +0 -1
- package/dist/chunk-PSEYFYAP.js +0 -276
- package/dist/chunk-PSEYFYAP.js.map +0 -1
- package/dist/chunk-RA6Z3OX3.js.map +0 -1
- package/dist/chunk-VXAV2435.js.map +0 -1
- package/dist/chunk-X44ZTKID.js.map +0 -1
- package/dist/dev-EILX2VTF.js.map +0 -1
- package/dist/doctor-FXZLMGC3.js.map +0 -1
- package/dist/preview-WBQPCTTT.js.map +0 -1
- package/dist/routes-WCN4KSZY.js.map +0 -1
- package/dist/types-De3mo_hv.d.ts +0 -59
- package/dist/upgrade-S64IXL42.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# デザイントークンと書体
|
|
2
|
+
|
|
3
|
+
## トークンの解決順
|
|
4
|
+
|
|
5
|
+
スライドの色と書体は CSS カスタムプロパティ `--slide-*` から来ます。値は 3 段で解決されます:
|
|
6
|
+
|
|
7
|
+
1. **`theme.css`(フレームワーク)** —— `--slide-bg: var(--background)` のように、アプリ自身の
|
|
8
|
+
トークンへの**別名**が張ってあります。CSS 変数は遅延解決なので、これだけでデッキは
|
|
9
|
+
アプリのライト / ダークの切り替えに自動で追従します
|
|
10
|
+
2. **アプリの `styles.css`** —— アプリがトークンを上書きしていれば、デッキもそれに従います
|
|
11
|
+
3. **`<SlideDeck theme={{ … }}>`** —— そのデッキのルートにインラインで書かれ、上の 2 つに勝ちます
|
|
12
|
+
|
|
13
|
+
| プロパティ | CSS 変数 | 既定 |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `bg` | `--slide-bg` | `var(--background)` |
|
|
16
|
+
| `fg` | `--slide-fg` | `var(--foreground)` |
|
|
17
|
+
| `accent` | `--slide-accent` | `var(--primary)` |
|
|
18
|
+
| `muted` | `--slide-muted` | `var(--muted-foreground)` |
|
|
19
|
+
| `fontDisplay` | `--slide-font-display` | 日本語を含むシステムスタック |
|
|
20
|
+
| `fontBody` | `--slide-font-body` | `var(--slide-font-display)` |
|
|
21
|
+
| `sizeHero` | `--slide-size-hero` | `168px` |
|
|
22
|
+
| `sizeBody` | `--slide-size-body` | `36px` |
|
|
23
|
+
| `radius` | `--slide-radius` | `var(--radius)` |
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<SlideDeck theme={{ bg: "#0f172a", fg: "#f8fafc", accent: "#fbbf24" }}>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**使うときは変数で参照します** —— `color: "var(--slide-fg)"`、`fontSize: "var(--slide-size-hero)"`。
|
|
30
|
+
`theme` に渡した値を直接書かないこと。変数経由にしておけば、`theme` を 1 行変えるだけでデッキ全体が
|
|
31
|
+
追従します。
|
|
32
|
+
|
|
33
|
+
**`theme` に無い色はプレーンな const に置く**(`const muted = "#94a3b8"`)。`theme` は
|
|
34
|
+
「デッキの土台となる 3〜4 色」のためのもので、パレット全部を入れる場所ではありません。
|
|
35
|
+
|
|
36
|
+
## ダークモード
|
|
37
|
+
|
|
38
|
+
`theme` を渡さないデッキは、アプリのトークンをそのまま使います —— つまり `.dark` /
|
|
39
|
+
`[data-theme="dark"]` が祖先に付いていればダークで描かれます。**明暗の両方を自分で管理する必要は
|
|
40
|
+
ありません。**
|
|
41
|
+
|
|
42
|
+
`theme` に具体的な色(`bg: "#0f172a"`)を書いた瞬間、そのデッキは明暗に追従しなくなります。
|
|
43
|
+
これは意図的です —— 配色を決め打ちしたデッキは、どちらのモードでもその配色であるべきだからです。
|
|
44
|
+
追従させたいなら `theme` を渡さないこと。
|
|
45
|
+
|
|
46
|
+
## 書体
|
|
47
|
+
|
|
48
|
+
**既定はシステムスタックで、まずこれを使ってください。** 日本語を含むスタックが最初から入っています。
|
|
49
|
+
|
|
50
|
+
webfont が本当に必要なとき(ブランドフォント、あるいはシステムのカバーが薄い言語):
|
|
51
|
+
|
|
52
|
+
- **`<head>` に 1 回だけ、冪等に読み込む。** スライドコンポーネントの中でスタイルシートを描かないこと。
|
|
53
|
+
切り替え中は 2 枚のスライドが同時にマウントされ、pptx 書き出しでは全スライドが同時にマウントされます
|
|
54
|
+
—— コンポーネントの中で `<link>` を描くと `@font-face` の集合が**マウント数だけ**登録されます。
|
|
55
|
+
`.slide.tsx` のモジュールのトップレベルから 1 回だけ注入します:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const FONT_HREF = "https://fonts.googleapis.com/css2?family=...&display=swap"
|
|
59
|
+
const FONT_LINK_ID = "vantage-slide-font-june" // デッキごとに違う id にする
|
|
60
|
+
if (typeof document !== "undefined" && !document.getElementById(FONT_LINK_ID)) {
|
|
61
|
+
const link = document.createElement("link")
|
|
62
|
+
link.id = FONT_LINK_ID
|
|
63
|
+
link.rel = "stylesheet"
|
|
64
|
+
link.href = FONT_HREF
|
|
65
|
+
document.head.appendChild(link)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **実際に使うウェイトだけを列挙する。** 余分な 1 ウェイトが `@font-face` の数を倍にします。
|
|
70
|
+
|
|
71
|
+
## webfont と pptx 書き出し —— ここが壊れやすい
|
|
72
|
+
|
|
73
|
+
pptx 書き出しは、スライドを SVG の `foreignObject` の中でラスタライズします。この隔離された
|
|
74
|
+
描画コンテキストでは:
|
|
75
|
+
|
|
76
|
+
- **ローカルにインストールされたフォントはファミリ名で解決されます。** システムスタックなら
|
|
77
|
+
埋め込みは 1 バイトも要りません。既定のままなら何もしなくてよい、ということです
|
|
78
|
+
- **webfont は data URL としてインライン化されていなければ使えません。** 書き出しは
|
|
79
|
+
`getFontEmbedCSS` で自動的にインライン化しますが、それには 2 つの条件があります:
|
|
80
|
+
- **同一オリジンか CORS が有効**であること(Google Fonts は CORS 有効)
|
|
81
|
+
- **サブセットしてあること。** 日本語の webfont は致命的です —— Noto Sans JP のフルセットは
|
|
82
|
+
100 を超える `unicode-range` の face を持ち、全部を base64 化しようとして書き出しが
|
|
83
|
+
タイムアウトするか、文字が豆腐になります。デッキの文字が固定なら Google Fonts の URL に
|
|
84
|
+
**`&text=<使う文字>`** を付けて、単一 face の小さなサブセットを要求してください
|
|
85
|
+
|
|
86
|
+
**判断の目安**: 日本語のデッキで pptx を配る予定があるなら、**システムスタックのままにする**のが
|
|
87
|
+
最も確実です。ブランドフォントが要件なら `&text=` のサブセットにすること。
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# ページ間トランジション(`SlideTransition`)
|
|
2
|
+
|
|
3
|
+
スライドが切り替わるときの enter / exit アニメーションです。**既定はありません** ——
|
|
4
|
+
宣言しない限りページはスナップで入れ替わり、そのスナップも十分に上品な既定です。
|
|
5
|
+
動きが何かを足すときだけ opt-in してください。
|
|
6
|
+
|
|
7
|
+
`prefers-reduced-motion: reduce` は自動で尊重されます。フォールバックを書く必要はありません。
|
|
8
|
+
|
|
9
|
+
(2 枚にまたがる同一要素を動かす「Magic Move」は `morph.md`。この契約の上に載っています。)
|
|
10
|
+
|
|
11
|
+
## 契約
|
|
12
|
+
|
|
13
|
+
トランジションは `<Slide transition={...}>` に付けます。**入ってくる側が勝ちます**:
|
|
14
|
+
A → B の移動では B の `transition` が使われ、その `exit` が A で、`enter` が B で再生されます。
|
|
15
|
+
B → A と戻るときは A の `transition` が使われるので、**両方向で動かしたいなら両方のスライドに
|
|
16
|
+
同じオブジェクトを渡します**。
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { Slide, SlideDeck, type SlideTransition } from "@squadbase/vantage/slide"
|
|
20
|
+
|
|
21
|
+
const RISE: SlideTransition = { /* … */ }
|
|
22
|
+
|
|
23
|
+
<SlideDeck>
|
|
24
|
+
<Slide>…</Slide>
|
|
25
|
+
<Slide transition={RISE}>…</Slide>
|
|
26
|
+
</SlideDeck>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
type TransitionPhase = {
|
|
31
|
+
keyframes: Keyframe[] | PropertyIndexedKeyframes // WAAPI のキーフレーム
|
|
32
|
+
duration?: number // ms(省略時は上位の duration)
|
|
33
|
+
easing?: string // CSS easing
|
|
34
|
+
delay?: number // ms —— exit と enter を重ねるのに使う
|
|
35
|
+
}
|
|
36
|
+
type SlideTransition = {
|
|
37
|
+
duration: number // 上位のフォールバック
|
|
38
|
+
easing?: string
|
|
39
|
+
enter?: TransitionPhase // 入ってくる側で再生
|
|
40
|
+
exit?: TransitionPhase // 出ていく側で再生
|
|
41
|
+
morph?: boolean | MorphTransition // 共有要素のモーフ → morph.md
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
ラッパーには `--slide-dir`(前進 `1` / 後退 `-1`)と `data-slide-dir`(`"forward"` / `"backward"`)が
|
|
46
|
+
出るので、JS のコールバック無しに 1 つのキーフレームで方向を反転できます。
|
|
47
|
+
|
|
48
|
+
## 設計原則(ここは崩さない)
|
|
49
|
+
|
|
50
|
+
「PowerPoint で作りました」と最も大きく告げるのは、1 つのデッキに 6 種類のトランジションがあることです。
|
|
51
|
+
節度がリズムを作ります。
|
|
52
|
+
|
|
53
|
+
- **DNA を 1 つ選び、デッキ全体で保つ。** 同じ duration 帯、同じ easing の組、同じ out → in のずらし方。
|
|
54
|
+
変えてよいのは**どのプロパティに小さな一押しを乗せるか**だけ —— Y、X、opacity、scale、blur
|
|
55
|
+
- **duration: 140–280ms。** exit 140–180ms、enter 200–280ms、enter を 80ms ほど遅らせて重ねる。
|
|
56
|
+
350ms を超えると動画編集の領域で、本当の状態変化のために取っておく
|
|
57
|
+
- **振れ幅の上限: 12px か 3% スケール。** 6px の Y 上昇は「次の考え」に読める。1920px の translateX は
|
|
58
|
+
「別の書類」に読める。上質なツールは、気づく分だけしか動かさない
|
|
59
|
+
- **opacity は常に混ぜる。** transform だけのトランジションは硬く、opacity だけのものは最も安全な既定
|
|
60
|
+
- **easing: exit は ease-in、enter は ease-out。** 出るとき `cubic-bezier(0.4, 0, 1, 1)`、
|
|
61
|
+
入るとき `cubic-bezier(0, 0, 0.2, 1)`。`linear` は使わない(スライドショーに見える)。
|
|
62
|
+
対称な ease-in-out は状態が繋がるモーフのために取っておく
|
|
63
|
+
|
|
64
|
+
## 上品な family —— 6 つ、1 つの DNA
|
|
65
|
+
|
|
66
|
+
ここから選んでください。1 つをデッキの標準にし、必要ならヒーロー / 表紙用に 2 つ目、
|
|
67
|
+
本当の章の切れ目に 3 つ目を取っておきます。
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
const EASE_OUT = "cubic-bezier(0, 0, 0.2, 1)"
|
|
71
|
+
const EASE_IN = "cubic-bezier(0.4, 0, 1, 1)"
|
|
72
|
+
|
|
73
|
+
// RISE —— 標準の静けさ。Y に 6px。デッキの既定にする
|
|
74
|
+
const RISE: SlideTransition = {
|
|
75
|
+
duration: 200,
|
|
76
|
+
exit: {
|
|
77
|
+
duration: 140, easing: EASE_IN,
|
|
78
|
+
keyframes: [
|
|
79
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
80
|
+
{ opacity: 0, transform: "translateY(-4px)" },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
enter: {
|
|
84
|
+
duration: 200, delay: 80, easing: EASE_OUT,
|
|
85
|
+
keyframes: [
|
|
86
|
+
{ opacity: 0, transform: "translateY(6px)" },
|
|
87
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// DISSOLVE —— opacity だけ。最も静か
|
|
93
|
+
const DISSOLVE: SlideTransition = {
|
|
94
|
+
duration: 240,
|
|
95
|
+
exit: { duration: 200, easing: EASE_IN, keyframes: [{ opacity: 1 }, { opacity: 0 }] },
|
|
96
|
+
enter: { duration: 240, delay: 40, easing: EASE_OUT, keyframes: [{ opacity: 0 }, { opacity: 1 }] },
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// SETTLE —— 表紙向き。上昇 + enter だけにわずかな blur
|
|
100
|
+
const SETTLE: SlideTransition = {
|
|
101
|
+
duration: 280,
|
|
102
|
+
exit: {
|
|
103
|
+
duration: 160, easing: EASE_IN,
|
|
104
|
+
keyframes: [
|
|
105
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
106
|
+
{ opacity: 0, transform: "translateY(-6px)" },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
enter: {
|
|
110
|
+
duration: 280, delay: 100, easing: EASE_OUT,
|
|
111
|
+
keyframes: [
|
|
112
|
+
{ opacity: 0, transform: "translateY(12px)", filter: "blur(4px)" },
|
|
113
|
+
{ opacity: 1, transform: "translateY(0)", filter: "blur(0)" },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// BLOOM —— scale 0.97 → 1、移動なし。その場で像を結ぶ
|
|
119
|
+
const BLOOM: SlideTransition = {
|
|
120
|
+
duration: 240,
|
|
121
|
+
exit: {
|
|
122
|
+
duration: 160, easing: EASE_IN,
|
|
123
|
+
keyframes: [
|
|
124
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
125
|
+
{ opacity: 0, transform: "scale(1.01)" },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
enter: {
|
|
129
|
+
duration: 240, delay: 80, easing: EASE_OUT,
|
|
130
|
+
keyframes: [
|
|
131
|
+
{ opacity: 0, transform: "scale(0.97)" },
|
|
132
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// FALL —— RISE の鏡像。上から降りてくる
|
|
138
|
+
const FALL: SlideTransition = {
|
|
139
|
+
duration: 200,
|
|
140
|
+
exit: {
|
|
141
|
+
duration: 140, easing: EASE_IN,
|
|
142
|
+
keyframes: [
|
|
143
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
144
|
+
{ opacity: 0, transform: "translateY(4px)" },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
enter: {
|
|
148
|
+
duration: 200, delay: 80, easing: EASE_OUT,
|
|
149
|
+
keyframes: [
|
|
150
|
+
{ opacity: 0, transform: "translateY(-6px)" },
|
|
151
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// BREATH —— 章の切れ目。完全に消してから 120ms 置いて入る
|
|
157
|
+
// 本当の区切りにだけ、デッキ 1 本で 1〜2 回まで
|
|
158
|
+
const BREATH: SlideTransition = {
|
|
159
|
+
duration: 460,
|
|
160
|
+
exit: { duration: 180, easing: EASE_IN, keyframes: [{ opacity: 1 }, { opacity: 0 }] },
|
|
161
|
+
enter: {
|
|
162
|
+
duration: 240, delay: 300, easing: EASE_OUT,
|
|
163
|
+
keyframes: [
|
|
164
|
+
{ opacity: 0, transform: "translateY(8px)" },
|
|
165
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
6 つは同じ DNA を共有していて、小さな一押しをどのプロパティに乗せるかだけが違います。
|
|
172
|
+
読み手には変化に見え、それでいて 1 つの手つきに読めます。
|
|
173
|
+
|
|
174
|
+
## 方向を意識したキーフレーム(控えめに)
|
|
175
|
+
|
|
176
|
+
上品なツールの多くは後退時に鏡像化しません。本当に必要なとき(横に流れるスライドを逆再生したい等)
|
|
177
|
+
だけ `--slide-dir` を `calc()` の中で使います:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
{ transform: "translateX(calc(var(--slide-dir, 1) * 8px))" },
|
|
181
|
+
{ transform: "translateX(0)" },
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
毎回これに手が伸びるなら、たぶん設計しすぎです。前進 = 後退の方が洗練された既定。
|
|
185
|
+
|
|
186
|
+
## アンチパターン
|
|
187
|
+
|
|
188
|
+
- ❌ 6 ページに 6 種類のトランジション —— 最も大きな「PowerPoint 感」の合図
|
|
189
|
+
- ❌ `translateX(100%)` の横スライド —— iOS のモーダルや PowerPoint の Push であって、スライドの切り替えではない
|
|
190
|
+
- ❌ 強いスケールのポップ(`0.85 → 1`)+ blur —— ライトボックスの語彙で、何かに「ズームイン」する意味になる
|
|
191
|
+
- ❌ `clip-path: inset(…)` のワイプ —— After Effects の語彙。芝居がかる
|
|
192
|
+
- ❌ 両方のレイヤーに同時に blur —— 視覚的な濁り。目が焦点を結べない
|
|
193
|
+
- ❌ 通常の切り替えで 350ms を超える duration —— 間延びする
|
|
194
|
+
- ❌ 12px / 3% を超える移動 —— 連続ではなく断絶に読める
|
|
195
|
+
- ❌ `linear` easing —— プロダクトではなくスライドショーに見える
|
|
196
|
+
- ❌ とりあえずトランジションを宣言する。**理由が無いなら書かない。** スナップで十分
|
package/templates/AGENTS.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# AGENTS.md — Vantage アプリの地図
|
|
2
2
|
|
|
3
3
|
このファイルは、**Vantage(`@squadbase/vantage`)アプリを作るコーディングエージェント**が
|
|
4
|
-
最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
>
|
|
9
|
-
>
|
|
10
|
-
>
|
|
4
|
+
最初に読むための地図です。アプリ作者(= `index.tsx` を書く側)が守るべき規約・不変条件を
|
|
5
|
+
1 ページに集約し、種類ごとの詳しいレシピは `agents/` 配下の知識ファイルに分けています。
|
|
6
|
+
|
|
7
|
+
> このファイルと `agents/*.md` は、いずれも **Vantage フレームワークが所有する正本**の
|
|
8
|
+
> コピーです。手で書き換えても、`vantage upgrade` を実行するとフレームワーク同梱の正本から
|
|
9
|
+
> 再同期されて上書きされます。アプリ固有のメモは別ファイル(例: `README.md`)に書いてください。
|
|
10
|
+
> 例外はプロジェクトルートの `DESIGN.md` で、これは**ユーザーが所有**します(`vantage upgrade` も
|
|
11
|
+
> `vantage add agents` も触らない。→「デザインの意図(DESIGN.md)」)。
|
|
11
12
|
>
|
|
12
13
|
> 手順を伴う踏み込んだワークフローは、同梱の Claude Code Skill に分かれています。このファイルは
|
|
13
|
-
>
|
|
14
|
-
>
|
|
14
|
+
> 「地図」、`agents/*.md` は「種類ごとの静的リファレンス」、Skill は「手順書」という役割分担です。
|
|
15
|
+
> **配置済みの Skill を先に探す**手順は末尾の「さらに詳しく(同梱 Skill)」にあります。
|
|
15
16
|
|
|
16
17
|
## Vantage とは
|
|
17
18
|
|
|
@@ -39,8 +40,8 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
39
40
|
- **色はセマンティックトークンで書く。** `text-foreground`・`text-muted-foreground`・`bg-card`・
|
|
40
41
|
`border-border`。トークンがライト / ダークの両方を持つので `dark:` を並べる必要が無い。
|
|
41
42
|
素のパレット + `dark:`(`text-slate-900 dark:text-slate-100`)を混ぜると、その 1 か所だけ
|
|
42
|
-
手動で 2 モードを管理することになる(
|
|
43
|
-
`--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
|
|
43
|
+
手動で 2 モードを管理することになる(→ `agents/page.md` の「ダークモードは opt-in」)。
|
|
44
|
+
`--muted`・`--accent`・`--secondary`・`--border` は背景・境界用で、文字色に使うと読めない。
|
|
44
45
|
- **`.vantage/` と `dist/` は生成物。** 編集しない・読みにいかない(gitignore 済み)。任意の CLI
|
|
45
46
|
コマンド、または `vantage upgrade` で再生成される。
|
|
46
47
|
- **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
|
|
@@ -93,6 +94,9 @@ root/
|
|
|
93
94
|
| `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
|
|
94
95
|
| `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
|
|
95
96
|
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
|
|
97
|
+
| `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
|
|
98
|
+
| `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`(スライド専用。1920×1080 の固定キャンバス) |
|
|
99
|
+
| `@squadbase/vantage/doc` | `Document`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
|
|
96
100
|
| `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
|
|
97
101
|
| `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
|
|
98
102
|
|
|
@@ -101,207 +105,37 @@ root/
|
|
|
101
105
|
`server/` ディレクトリがあれば **fullstack**、無ければ **SPA**。`vantage-manifest.json` の
|
|
102
106
|
`mode` はここから自動で決まり、手で書く設定は無い。**SPA から始めて fullstack へ「昇格」する
|
|
103
107
|
必要はない** ― テンプレートから始めた場合は最初から `server/` があり、その時点で fullstack。
|
|
104
|
-
下の 2
|
|
105
|
-
|
|
106
|
-
- `server/` が**ある** → 「API を持つ(fullstack モード)」を見る。データは自前の `/api/*` から
|
|
107
|
-
`useApiQuery` で取る。
|
|
108
|
-
- `server/` が**無い** → 「最小アプリ(2 ファイル)」の構成。外部 API を直接叩くなら `useQuery`。
|
|
109
|
-
自前の API が要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
|
|
110
|
-
|
|
111
|
-
## 最小アプリ(2 ファイル)
|
|
112
|
-
|
|
113
|
-
`package.json` と `index.tsx` の 2 ファイルだけで動く。
|
|
114
|
-
|
|
115
|
-
`package.json`:
|
|
116
|
-
|
|
117
|
-
```json
|
|
118
|
-
{
|
|
119
|
-
"name": "my-dashboard",
|
|
120
|
-
"private": true,
|
|
121
|
-
"type": "module",
|
|
122
|
-
"scripts": {
|
|
123
|
-
"dev": "vantage dev",
|
|
124
|
-
"build": "vantage build",
|
|
125
|
-
"preview": "vantage preview",
|
|
126
|
-
"check": "vantage check",
|
|
127
|
-
"routes": "vantage routes"
|
|
128
|
-
},
|
|
129
|
-
"dependencies": {
|
|
130
|
-
"@squadbase/vantage": "^0.5.0",
|
|
131
|
-
"react": "^19.2.7",
|
|
132
|
-
"react-dom": "^19.2.7"
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
```
|
|
108
|
+
下の 2 つは段階ではなく、いま自分がどちらにいるかを確かめるための地図として読む。
|
|
136
109
|
|
|
137
|
-
`
|
|
110
|
+
- `server/` が**ある** → データは自前の `/api/*` から `useApiQuery` で取る(→ `agents/api.md`)。
|
|
111
|
+
- `server/` が**無い** → 外部 API を直接叩くなら `useQuery`(→ `agents/page.md`)。自前の API が
|
|
112
|
+
要るようになったら `server/api/*.ts` を足すだけでよい(設定変更は不要)。
|
|
138
113
|
|
|
139
|
-
|
|
140
|
-
export default function Dashboard() {
|
|
141
|
-
return (
|
|
142
|
-
<main className="p-6">
|
|
143
|
-
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
|
144
|
-
</main>
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
```
|
|
114
|
+
## デザインの意図(DESIGN.md)
|
|
148
115
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
pnpm check # 静的診断(エラーがあれば exit 1)
|
|
153
|
-
pnpm routes # ページ/API の URL マップ
|
|
154
|
-
```
|
|
116
|
+
プロジェクトルートに `DESIGN.md` があれば、**UI(ページ・スライド・コンポーネント)を書く前に
|
|
117
|
+
読む。** 色・チャート・書体・レイアウトの使い分けがトークン名で書いてあるので、それに従って
|
|
118
|
+
トークンで書く(値は `styles.css` にあり、`DESIGN.md` には色コードを書かない)。
|
|
155
119
|
|
|
156
|
-
`
|
|
157
|
-
|
|
158
|
-
|
|
120
|
+
- **会話中の指示は `DESIGN.md` より優先する。** 同じ指示が繰り返されたら、`DESIGN.md` への
|
|
121
|
+
反映をユーザーに提案する。
|
|
122
|
+
- **「色を変えて」「ブランドに合わせて」などデザイン全体の変更は、ファイルごとに直さない。**
|
|
123
|
+
Skill `vantage-design` の手順で `DESIGN.md` と `styles.css` をまとめて更新する。
|
|
124
|
+
- `DESIGN.md` はユーザーの持ち物。`vantage upgrade` は書き換えない。
|
|
159
125
|
|
|
160
|
-
##
|
|
126
|
+
## 何を書くか → どのファイルを読むか
|
|
161
127
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
`MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
|
|
128
|
+
**書こうとしている種類のファイルだけを開けばよい。** まず該当する 1 ファイルを readFiles で
|
|
129
|
+
読んでから書き始める。
|
|
165
130
|
|
|
166
|
-
|
|
|
131
|
+
| 書こうとしているもの | 読むファイル |
|
|
167
132
|
| --- | --- |
|
|
168
|
-
|
|
|
169
|
-
| `
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `_error.tsx` | ルートが throw したときのエラーページ |
|
|
175
|
-
|
|
176
|
-
`components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
|
|
177
|
-
(ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→「ディレクトリ構造」)。
|
|
178
|
-
|
|
179
|
-
```tsx
|
|
180
|
-
import { definePage } from "@squadbase/vantage"
|
|
181
|
-
|
|
182
|
-
// title は document.title、navLabel は useRoutes() で組むナビの表示名(既定は title)
|
|
183
|
-
export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
|
|
184
|
-
|
|
185
|
-
export default function MonthlyAnalysis() {
|
|
186
|
-
return <main className="p-6">…</main>
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### ナビはルート一覧から組む
|
|
191
|
-
|
|
192
|
-
`useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
|
|
193
|
-
`useCurrentRoute()` は現在のルート(404 なら `undefined`)。
|
|
194
|
-
|
|
195
|
-
```tsx
|
|
196
|
-
import { Link, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
|
|
197
|
-
|
|
198
|
-
// 動的ルートは URL が定まらないので外す。label は navLabel → title → path の順
|
|
199
|
-
const routes = useRoutes().filter((r) => !r.dynamic)
|
|
200
|
-
const current = useCurrentRoute()
|
|
201
|
-
|
|
202
|
-
routes.map((r) => (
|
|
203
|
-
<Link key={r.path} to={r.to} activeClassName="font-semibold">
|
|
204
|
-
{r.label}
|
|
205
|
-
</Link>
|
|
206
|
-
))
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
`RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
|
|
210
|
-
`title`・`description`・`navLabel`。
|
|
211
|
-
|
|
212
|
-
### フィルタ状態は URL に置く
|
|
213
|
-
|
|
214
|
-
リロードで消えず、URL をそのまま共有できる。`useState` と同じ形。
|
|
215
|
-
|
|
216
|
-
```tsx
|
|
217
|
-
import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
|
|
218
|
-
|
|
219
|
-
const [region, setRegion] = useSearchParam("region", "all") // 常に string
|
|
220
|
-
const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON になる値
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
|
|
224
|
-
(`{ replace: false }` で push)。
|
|
225
|
-
|
|
226
|
-
### 動的パラメータの「3 つの綴り」を同期させる
|
|
227
|
-
|
|
228
|
-
崩すと静かにマッチしなくなる不変条件:
|
|
229
|
-
|
|
230
|
-
- ファイル名 `sales/[customerId].tsx`
|
|
231
|
-
- 表示ルート `/sales/:customerId`
|
|
232
|
-
- リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
|
|
233
|
-
|
|
234
|
-
```tsx
|
|
235
|
-
import { Link, useParams } from "@squadbase/vantage/router"
|
|
236
|
-
|
|
237
|
-
<Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
|
|
238
|
-
|
|
239
|
-
// 詳細ページ側で取り出す
|
|
240
|
-
const { customerId } = useParams()
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## データ取得(server を持たない fetch)
|
|
244
|
-
|
|
245
|
-
外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
|
|
246
|
-
|
|
247
|
-
```tsx
|
|
248
|
-
import { useQuery } from "@squadbase/vantage/query"
|
|
249
|
-
import { Loading, ErrorState } from "@squadbase/vantage/ui"
|
|
250
|
-
|
|
251
|
-
const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
|
|
252
|
-
if (q.isPending) return <Loading />
|
|
253
|
-
if (q.isError) return <ErrorState message={(q.error as Error).message} />
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
`QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
|
|
257
|
-
refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
|
|
258
|
-
上書きする。
|
|
259
|
-
自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→「API を持つ(fullstack
|
|
260
|
-
モード)」)。
|
|
261
|
-
|
|
262
|
-
## API を持つ(fullstack モード)
|
|
263
|
-
|
|
264
|
-
**`server/` ディレクトリがあれば fullstack モード**(無いアプリに足せば、その時点で切り替わる)。
|
|
265
|
-
`server/api/**` の各ファイルが API ルートになる。
|
|
266
|
-
|
|
267
|
-
`server/api/monthly-analysis.ts` → `GET /api/monthly-analysis`:
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
import type { ApiContext } from "@squadbase/vantage/server"
|
|
271
|
-
import { HttpError } from "@squadbase/vantage/server"
|
|
272
|
-
|
|
273
|
-
export async function GET({ params, env }: ApiContext) {
|
|
274
|
-
if (!env.API_KEY) throw new HttpError(500, "API_KEY missing")
|
|
275
|
-
return Response.json({ ok: true })
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
- API モジュールは**大文字の HTTP メソッド**(`GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`)を
|
|
280
|
-
エクスポートする。それ以外の名前は `INVALID_API_EXPORT` エラー。
|
|
281
|
-
- 動的 API も `[id].ts` 記法: `server/api/customers/[id].ts` → `GET /api/customers/:id`。
|
|
282
|
-
`params.id` で取り出す(`noUncheckedIndexedAccess` が効くので narrowing が要る)。
|
|
283
|
-
- **クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。** それ以外の throw は
|
|
284
|
-
ログに記録され汎用の 500 に丸められる。
|
|
285
|
-
- **シークレットは `ApiContext.env` にだけ届く**(クライアントには決して届かない)。
|
|
286
|
-
|
|
287
|
-
クライアント側からは `useApiQuery` で `/api/*` を叩く。ベース URL の解決・JSON パース・
|
|
288
|
-
非 2xx の `ApiError` 化・クエリキー(`["api", url]`)が入っている:
|
|
289
|
-
|
|
290
|
-
```tsx
|
|
291
|
-
import { useApiQuery, useApiMutation } from "@squadbase/vantage/query"
|
|
292
|
-
|
|
293
|
-
const q = useApiQuery<Customer>(`/api/customers/${customerId}`)
|
|
294
|
-
if (q.isError) return <ErrorState message={q.error.message} /> // HttpError のメッセージ
|
|
295
|
-
|
|
296
|
-
// クエリ文字列は search で。undefined の項目は落ちる
|
|
297
|
-
const rows = useApiQuery<Row[]>("/api/customers", { search: { segment } })
|
|
298
|
-
|
|
299
|
-
// 書き込み。変数がそのまま JSON ボディになる
|
|
300
|
-
const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST" })
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
`ApiError` は `message`・`status`・`body`・`requestId` を持つ。hook が使えない場所では
|
|
304
|
-
`apiJson(path, init)`、生の `Response` が要るときは `apiFetch`。
|
|
133
|
+
| ダッシュボード / 通常ページ(`.tsx`) | `agents/page.md` |
|
|
134
|
+
| API(`server/`) | `agents/api.md` |
|
|
135
|
+
| スライド(`.slide.tsx`) | `agents/slide.md` |
|
|
136
|
+
| ドキュメント(`.doc.tsx`) | `agents/doc.md` |
|
|
137
|
+
| `.xlsx` ページ | `agents/xlsx.md` |
|
|
138
|
+
| `.csv` ページ | `agents/csv.md` |
|
|
305
139
|
|
|
306
140
|
## 環境変数
|
|
307
141
|
|
|
@@ -309,30 +143,6 @@ const save = useApiMutation<Customer, Payload>("/api/customers", { method: "POST
|
|
|
309
143
|
のみ。** `VITE_` 系の公開 API は無い。
|
|
310
144
|
- サーバー側のシークレットは `ApiContext.env.MY_SECRET` で読む(`server/` 内のみ)。
|
|
311
145
|
|
|
312
|
-
## 組み込み UI / コンポーネントの入口
|
|
313
|
-
|
|
314
|
-
- **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
|
|
315
|
-
`ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
|
|
316
|
-
- **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
|
|
317
|
-
`DataTablePreset`・`EChart` ほか)。
|
|
318
|
-
- **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
|
|
319
|
-
リファレンスはパッケージに同梱されていて、オフラインでも読める(→「ドキュメントを引く」)。
|
|
320
|
-
**名前が分からないとき**はやりたいことで `vantage search` する(→ 同節)。
|
|
321
|
-
- **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
|
|
322
|
-
ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
|
|
323
|
-
`ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
|
|
324
|
-
`add` の対象ではない(未知名で実行すると候補が一覧表示される)。
|
|
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
|
-
|
|
336
146
|
## ドキュメントを引く(`vantage docs` / `vantage search`)
|
|
337
147
|
|
|
338
148
|
```bash
|
|
@@ -378,6 +188,8 @@ body・レスポンスの status と形)まで出る。`vantage check --json` /
|
|
|
378
188
|
|
|
379
189
|
- **`vantage-app`** — アプリを一から作る / 構成を広げるときの進め方と検証の順序
|
|
380
190
|
- **`vantage-add-feature`** — 既存アプリに page / api / ui を 1 つ足す定型
|
|
191
|
+
- **`vantage-slide`** — `.slide.tsx` でスライドデッキを書く(キャンバスの予算・段階表示・pptx 書き出し)
|
|
192
|
+
- **`vantage-design`** — `DESIGN.md` と `styles.css` をまとめて作る / 直す(配色・書体・ブランド合わせ)
|
|
381
193
|
- **`vantage-pitfalls`** — Base UI(≠ Radix)の癖など、静かに壊れる落とし穴のリファレンス
|
|
382
194
|
- **`project-storage`** — Squadbase の Project Storage(アップロード済みファイル)をブラウザから読み書きする
|
|
383
195
|
|
|
@@ -398,3 +210,6 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
|
|
|
398
210
|
`add skill` はプロジェクト内を走査してから動くので、**再実行しても二重に配置されない**。
|
|
399
211
|
配置済みのコピーを新しいバージョンに更新したいときだけ `--force` を付ける(見つかった場所を
|
|
400
212
|
そのまま書き換える)。
|
|
213
|
+
|
|
214
|
+
**役割分担:** Skill = 手順書(踏み込んだワークフロー) / `agents/*.md` = 種類ごとの静的
|
|
215
|
+
リファレンス / `AGENTS.md`(このファイル) = 地図。
|
|
@@ -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`。
|