@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.
Files changed (134) hide show
  1. package/NOTICE +73 -0
  2. package/README.md +6 -5
  3. package/dist/add-DY4AXANK.js +6 -0
  4. package/dist/add-DY4AXANK.js.map +1 -0
  5. package/dist/{build-756WIEVI.js → build-JB4VOMUI.js} +10 -10
  6. package/dist/{build-756WIEVI.js.map → build-JB4VOMUI.js.map} +1 -1
  7. package/dist/{check-KZ5PHCA6.js → check-ZJYK6RLG.js} +8 -8
  8. package/dist/{check-KZ5PHCA6.js.map → check-ZJYK6RLG.js.map} +1 -1
  9. package/dist/chunk-5ZOP4SQR.js +47 -0
  10. package/dist/chunk-5ZOP4SQR.js.map +1 -0
  11. package/dist/{chunk-EHPJXDJU.js → chunk-6S4BAIGV.js} +4 -2
  12. package/dist/chunk-6S4BAIGV.js.map +1 -0
  13. package/dist/chunk-BCE22JSM.js +510 -0
  14. package/dist/chunk-BCE22JSM.js.map +1 -0
  15. package/dist/{chunk-C4MQRWXS.js → chunk-C2CX6GLX.js} +17 -3
  16. package/dist/chunk-C2CX6GLX.js.map +1 -0
  17. package/dist/{chunk-RA6Z3OX3.js → chunk-EP6JHXSR.js} +61 -9
  18. package/dist/chunk-EP6JHXSR.js.map +1 -0
  19. package/dist/{chunk-BAM33YZ6.js → chunk-JGRMRXXZ.js} +45 -6
  20. package/dist/chunk-JGRMRXXZ.js.map +1 -0
  21. package/dist/{chunk-VXAV2435.js → chunk-KLEIVDUV.js} +30 -7
  22. package/dist/chunk-KLEIVDUV.js.map +1 -0
  23. package/dist/{chunk-JC6MT5UU.js → chunk-MXI3PZFT.js} +3 -3
  24. package/dist/{chunk-JC6MT5UU.js.map → chunk-MXI3PZFT.js.map} +1 -1
  25. package/dist/chunk-PLST5Q7T.js +2221 -0
  26. package/dist/chunk-PLST5Q7T.js.map +1 -0
  27. package/dist/chunk-PVTHHV7X.js +105 -0
  28. package/dist/chunk-PVTHHV7X.js.map +1 -0
  29. package/dist/{chunk-HQDEYCDP.js → chunk-QI3XQKHS.js} +40 -4
  30. package/dist/chunk-QI3XQKHS.js.map +1 -0
  31. package/dist/{chunk-L3TW4FNA.js → chunk-T3FLAGI6.js} +34 -13
  32. package/dist/chunk-T3FLAGI6.js.map +1 -0
  33. package/dist/{chunk-X44ZTKID.js → chunk-U2M6HGKQ.js} +61 -9
  34. package/dist/chunk-U2M6HGKQ.js.map +1 -0
  35. package/dist/chunk-ZO62C6T4.js +91 -0
  36. package/dist/chunk-ZO62C6T4.js.map +1 -0
  37. package/dist/{chunk-D4Y5RWJA.js → chunk-ZVANIULZ.js} +81 -21
  38. package/dist/chunk-ZVANIULZ.js.map +1 -0
  39. package/dist/cli.js +18 -14
  40. package/dist/cli.js.map +1 -1
  41. package/dist/client/index.d.ts +5 -35
  42. package/dist/client/index.js +1 -1
  43. package/dist/components/index.js +3 -2212
  44. package/dist/components/index.js.map +1 -1
  45. package/dist/{dev-EILX2VTF.js → dev-FCEC3UKQ.js} +24 -13
  46. package/dist/dev-FCEC3UKQ.js.map +1 -0
  47. package/dist/doc/index.d.ts +39 -0
  48. package/dist/doc/index.js +38 -0
  49. package/dist/doc/index.js.map +1 -0
  50. package/dist/{docs-GP2ZHIQF.js → docs-EJJ3BECT.js} +4 -4
  51. package/dist/{docs-GP2ZHIQF.js.map → docs-EJJ3BECT.js.map} +1 -1
  52. package/dist/{doctor-FXZLMGC3.js → doctor-WHZXPERA.js} +9 -9
  53. package/dist/doctor-WHZXPERA.js.map +1 -0
  54. package/dist/manifest-DiOjyinq.d.ts +43 -0
  55. package/dist/{preview-WBQPCTTT.js → preview-OGMCNQUO.js} +13 -5
  56. package/dist/preview-OGMCNQUO.js.map +1 -0
  57. package/dist/query/index.js +1 -102
  58. package/dist/query/index.js.map +1 -1
  59. package/dist/router/index.d.ts +10 -0
  60. package/dist/router/index.js +2 -89
  61. package/dist/router/index.js.map +1 -1
  62. package/dist/{routes-WCN4KSZY.js → routes-GTFO5PUF.js} +17 -8
  63. package/dist/routes-GTFO5PUF.js.map +1 -0
  64. package/dist/{search-GLKCJXSI.js → search-6Y422SV6.js} +4 -4
  65. package/dist/{search-GLKCJXSI.js.map → search-6Y422SV6.js.map} +1 -1
  66. package/dist/server/node.d.ts +1 -1
  67. package/dist/server/node.js +6 -1
  68. package/dist/server/node.js.map +1 -1
  69. package/dist/slide/index.d.ts +230 -0
  70. package/dist/slide/index.js +1571 -0
  71. package/dist/slide/index.js.map +1 -0
  72. package/dist/table/index.d.ts +107 -0
  73. package/dist/table/index.js +680 -0
  74. package/dist/table/index.js.map +1 -0
  75. package/dist/types-CXFlMl8S.d.ts +121 -0
  76. package/dist/ui/index.js +3 -41
  77. package/dist/ui/index.js.map +1 -1
  78. package/dist/{upgrade-S64IXL42.js → upgrade-AL4KZZR4.js} +9 -9
  79. package/dist/upgrade-AL4KZZR4.js.map +1 -0
  80. package/dist/vite/index.d.ts +1 -1
  81. package/dist/vite/index.js +5 -5
  82. package/docs/en/agent-skills.md +22 -12
  83. package/docs/en/changelog.md +107 -0
  84. package/docs/en/cli-reference.md +42 -4
  85. package/docs/en/pages-and-metadata.md +17 -0
  86. package/docs/en/routing.md +102 -3
  87. package/docs/en/ui-and-theming.md +102 -0
  88. package/docs/index.json +2 -2
  89. package/docs/ja/agent-skills.md +21 -11
  90. package/docs/ja/changelog.md +105 -0
  91. package/docs/ja/cli-reference.md +41 -4
  92. package/docs/ja/pages-and-metadata.md +17 -0
  93. package/docs/ja/routing.md +98 -3
  94. package/docs/ja/ui-and-theming.md +100 -0
  95. package/package.json +33 -9
  96. package/skills/vantage-app/SKILL.md +4 -0
  97. package/skills/vantage-design/SKILL.md +137 -0
  98. package/skills/vantage-design/references/extract.md +110 -0
  99. package/skills/vantage-design/references/format.md +172 -0
  100. package/skills/vantage-design/references/presets.md +110 -0
  101. package/skills/vantage-design/scripts/contrast.mjs +166 -0
  102. package/skills/vantage-pitfalls/SKILL.md +41 -1
  103. package/skills/vantage-slide/SKILL.md +264 -0
  104. package/skills/vantage-slide/references/morph.md +102 -0
  105. package/skills/vantage-slide/references/steps.md +57 -0
  106. package/skills/vantage-slide/references/tokens.md +87 -0
  107. package/skills/vantage-slide/references/transitions.md +196 -0
  108. package/templates/AGENTS.md +43 -228
  109. package/templates/agents/api.md +49 -0
  110. package/templates/agents/csv.md +32 -0
  111. package/templates/agents/doc.md +73 -0
  112. package/templates/agents/page.md +187 -0
  113. package/templates/agents/slide.md +41 -0
  114. package/templates/agents/xlsx.md +33 -0
  115. package/theme.css +169 -0
  116. package/dist/add-PPCBMWEN.js +0 -6
  117. package/dist/add-PPCBMWEN.js.map +0 -1
  118. package/dist/chunk-BAM33YZ6.js.map +0 -1
  119. package/dist/chunk-C4MQRWXS.js.map +0 -1
  120. package/dist/chunk-D4Y5RWJA.js.map +0 -1
  121. package/dist/chunk-EHPJXDJU.js.map +0 -1
  122. package/dist/chunk-HQDEYCDP.js.map +0 -1
  123. package/dist/chunk-L3TW4FNA.js.map +0 -1
  124. package/dist/chunk-PSEYFYAP.js +0 -276
  125. package/dist/chunk-PSEYFYAP.js.map +0 -1
  126. package/dist/chunk-RA6Z3OX3.js.map +0 -1
  127. package/dist/chunk-VXAV2435.js.map +0 -1
  128. package/dist/chunk-X44ZTKID.js.map +0 -1
  129. package/dist/dev-EILX2VTF.js.map +0 -1
  130. package/dist/doctor-FXZLMGC3.js.map +0 -1
  131. package/dist/preview-WBQPCTTT.js.map +0 -1
  132. package/dist/routes-WCN4KSZY.js.map +0 -1
  133. package/dist/types-De3mo_hv.d.ts +0 -59
  134. package/dist/upgrade-S64IXL42.js.map +0 -1
@@ -0,0 +1,32 @@
1
+ # csv ページ
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.csv` を
4
+ ページとして置くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## `.csv` を置くだけでページになる
8
+
9
+ **`.csv` を `src/` に置くだけで、他のページと同格のルートになります。** URL の作り方は
10
+ `.tsx` と同じ(`src/sales-export.csv` → `/sales-export`、`index.csv` はそのディレクトリ)で、
11
+ ナビにも `vantage routes` にも並びます。開くとフレームワーク同梱の表ビューアが描き、
12
+ 元ファイルをダウンロードできます。ビューアは**表計算ソフトの見た目**です —— 列は A / B / C、
13
+ 左に行番号、ファイルの 1 行目はヘッダとして固定。読み取り専用で、編集もソートもできません
14
+ (セルを選んで `Cmd/Ctrl+C` でコピーは可)。
15
+
16
+ - **タイトルはファイル名から導かれる**(`sales-export.csv` → 「Sales Export」)。データファイルに
17
+ `definePage` は書けません。名前を変えたいときはファイルをリネームします
18
+ - **ページにしたくないファイルは `_` を前置する**(`_sales-export.csv`)。`components/`・`hooks/`・
19
+ `lib/` に置いたものも同じくページになりません
20
+ - **既定はファイルをそのまま置く(tsx を書かない)。** 自分でタイトルや説明を付けたいときだけ、`_` を
21
+ 前置してルート化を止め、tsx から描きます:
22
+ ```tsx
23
+ import { FilePageView } from "@squadbase/vantage/table"
24
+ import src from "./_sales-export.csv?url&no-inline"
25
+ export const page = definePage({ title: "売上エクスポート" })
26
+ export default () => <FilePageView src={src} kind="csv" fileName="sales-export.csv" />
27
+ ```
28
+ - 数式・グラフ・セル書式といった概念は csv には無いので、値がそのまま読めます。ブラウザで
29
+ パースするので 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは
30
+ `server/api/` から配って `DataTable` で描いてください
31
+ - **ビューアの無い拡張子(`.tsv` など)はルートになりません。** `vantage check` の
32
+ `UNSUPPORTED_PAGE_FILE` 警告が知らせます
@@ -0,0 +1,73 @@
1
+ # ドキュメント(.doc.tsx)
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.doc.tsx` で
4
+ ドキュメントを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## ドキュメントは `.doc.tsx` で書く
8
+
9
+ `src/june-summary.doc.tsx` → `/june-summary`。上から下へ読む文章のページで、表やチャートを
10
+ 文中に置けます。中身は `<Document>` を返す普通の React コンポーネントで、`definePage` もナビも
11
+ 通常のページと同じように効きます。
12
+
13
+ ```tsx
14
+ import { definePage } from "@squadbase/vantage"
15
+ import { EChart } from "@squadbase/vantage/components"
16
+ import { Document } from "@squadbase/vantage/doc"
17
+
18
+ export const page = definePage({ title: "6月度サマリー", navLabel: "6月度サマリー" })
19
+ export default function JuneSummary() {
20
+ return (
21
+ <Document>
22
+ <h1>6月度サマリー</h1>
23
+ <p>6月の売上は <strong>5,250 万円</strong>で、目標を 5% 上回りました。</p>
24
+ <h2>週次の売上</h2>
25
+ <figure>
26
+ <EChart option={option} height={280} ariaLabel="6月の週次売上" />
27
+ <figcaption>第4週はキャンペーン初週</figcaption>
28
+ </figure>
29
+ <table>…</table>
30
+ </Document>
31
+ )
32
+ }
33
+ ```
34
+
35
+ ## 書き方
36
+
37
+ - **素の HTML 要素で書く。** `<Document>` の中の `h1`〜`h4`・`p`・`ul`/`ol`/`li`・`table`・
38
+ `blockquote`・`pre`/`code`・`hr`・`a`・`figure`/`figcaption`・`img` には、見出しの大きさ・余白・
39
+ 罫線などが最初から当たっています。クラスは要りません。
40
+ - **Tailwind のユーティリティは、その要素だけ見た目を変えたいときに使う。** 要素の既定の
41
+ スタイルはユーティリティより弱いので、`className` を付ければそちらが勝ちます。ドキュメント全体の
42
+ 見た目を変えたいときはユーティリティを並べず、下の `--doc-*` を変えます。
43
+ - **数値はコードに直接書く。** ドキュメントは書いた時点の内容を残すページです。
44
+ `useApiQuery` などで読み込むと、開くたびに値が変わります。
45
+ - **チャートは `EChart`(`@squadbase/vantage/components`)を文中に置く。** `figure` で囲めば
46
+ `figcaption` がキャプションになります。
47
+ - **ページ全体を包むのは `<Document>` だけ。** `PageShell` などのダッシュボード用の枠で包まない
48
+ (本文の幅と余白は `<Document>` が決めます)。
49
+
50
+ ## 見た目を変える
51
+
52
+ 配色・書体・本文の幅は `--doc-*` の CSS 変数です。既定値はアプリのトークンへの別名なので、
53
+ ダークモードにもそのまま追従します。
54
+
55
+ | 変数 | 既定値 | 何に効くか |
56
+ | --- | --- | --- |
57
+ | `--doc-fg` | `var(--foreground)` | 本文の文字色 |
58
+ | `--doc-muted` | `var(--muted-foreground)` | 引用・キャプション |
59
+ | `--doc-accent` | `var(--primary)` | リンク |
60
+ | `--doc-border` | `var(--border)` | 表・`h2` の下線・引用・`hr` |
61
+ | `--doc-subtle` | `var(--muted)` | コード・表の見出し行の背景 |
62
+ | `--doc-font-body` / `--doc-font-heading` / `--doc-font-mono` | アプリの書体 | 本文 / 見出し / コード |
63
+ | `--doc-size-body` | `1rem` | 本文の文字サイズ(見出しはこれに比例) |
64
+ | `--doc-line-height` | `1.8` | 本文の行間 |
65
+ | `--doc-max-width` | `48rem` | 本文の幅 |
66
+
67
+ アプリのすべてのドキュメントに効かせるなら `styles.css` の `:root` で定義し直します。1 つの
68
+ ドキュメントだけなら `<Document theme={{ accent: "#2563eb", maxWidth: "56rem" }}>` のように
69
+ `theme` で渡します(`theme` のキーは変数名から `--doc-` を外してキャメルケースにしたもの)。
70
+
71
+ `.doc` を付けられるのはルートになるページモジュールだけで、`_layout.doc.tsx` などは
72
+ `INVALID_PAGE_VARIANT` エラーになります。`june.doc.tsx` と `june.tsx` は同じ URL に解決するので
73
+ `ROUTE_CONFLICT` です。
@@ -0,0 +1,187 @@
1
+ # ダッシュボード / 通常ページ(.tsx)
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.tsx` の通常ページ
4
+ (ダッシュボード)を書くときに読んでください。前提となる不変条件・ディレクトリ構造は
5
+ `AGENTS.md` を先に読んでいる想定です。
6
+
7
+ プロジェクトルートに `DESIGN.md` があれば、書き始める前に読んで配色・書体・レイアウトの規則に従う。
8
+
9
+ ## 最小アプリ(2 ファイル)
10
+
11
+ `package.json` と `index.tsx` の 2 ファイルだけで動く。
12
+
13
+ `package.json`:
14
+
15
+ ```json
16
+ {
17
+ "name": "my-dashboard",
18
+ "private": true,
19
+ "type": "module",
20
+ "scripts": {
21
+ "dev": "vantage dev",
22
+ "build": "vantage build",
23
+ "preview": "vantage preview",
24
+ "check": "vantage check",
25
+ "routes": "vantage routes"
26
+ },
27
+ "dependencies": {
28
+ "@squadbase/vantage": "^0.5.0",
29
+ "react": "^19.2.7",
30
+ "react-dom": "^19.2.7"
31
+ }
32
+ }
33
+ ```
34
+
35
+ `index.tsx`(ルートのデフォルトエクスポートが `/` ページになる):
36
+
37
+ ```tsx
38
+ export default function Dashboard() {
39
+ return (
40
+ <main className="p-6">
41
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
42
+ </main>
43
+ )
44
+ }
45
+ ```
46
+
47
+ ```bash
48
+ pnpm install
49
+ pnpm dev # → http://localhost:5173(HMR)
50
+ pnpm check # 静的診断(エラーがあれば exit 1)
51
+ pnpm routes # ページ/API の URL マップ
52
+ ```
53
+
54
+ `server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
55
+ `src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
56
+ どちらか一方に寄せること(→ `AGENTS.md` の「ディレクトリ構造」)。
57
+
58
+ ## ルーティング規約(ファイル名 → ルート)
59
+
60
+ ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
61
+ URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
62
+ `MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
63
+
64
+ | ファイル | ルート |
65
+ | --- | --- |
66
+ | `index.tsx` | `/` |
67
+ | `monthly-analysis.tsx` | `/monthly-analysis` |
68
+ | `sales/index.tsx` | `/sales` |
69
+ | `sales/[customerId].tsx` | `/sales/:customerId`(動的パラメータ) |
70
+ | `_layout.tsx` | そのディレクトリ配下を包むネストレイアウト(`Outlet` で子を描く) |
71
+ | `_404.tsx` | Not Found ページ |
72
+ | `_error.tsx` | ルートが throw したときのエラーページ |
73
+ | `monthly-report.slide.tsx` | `/monthly-report`(スライドデッキ。詳しくは `agents/slide.md`) |
74
+ | `sales-export.csv` | `/sales-export`(詳しくは `agents/csv.md`) |
75
+ | `data/june.xlsx` | `/data/june`(詳しくは `agents/xlsx.md`) |
76
+
77
+ `components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
78
+ (ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→ `AGENTS.md` の
79
+ 「ディレクトリ構造」)。
80
+
81
+ ```tsx
82
+ import { definePage } from "@squadbase/vantage"
83
+
84
+ // title は document.title、navLabel は useRoutes() で組むナビの表示名(既定は title)
85
+ export const page = definePage({ title: "Monthly Analysis · Acme", navLabel: "Monthly" })
86
+
87
+ export default function MonthlyAnalysis() {
88
+ return <main className="p-6">…</main>
89
+ }
90
+ ```
91
+
92
+ ### ナビはルート一覧から組む
93
+
94
+ `useRoutes()` がページルート一覧(スキャン順)を返すので、リンク配列を手で持たない。
95
+ `useCurrentRoute()` は現在のルート(404 なら `undefined`)。
96
+
97
+ ```tsx
98
+ import { Link, useCurrentRoute, useRoutes } from "@squadbase/vantage/router"
99
+
100
+ // 動的ルートは URL が定まらないので外す。label は navLabel → title → path の順
101
+ const routes = useRoutes().filter((r) => !r.dynamic)
102
+ const current = useCurrentRoute()
103
+
104
+ routes.map((r) => (
105
+ <Link key={r.path} to={r.to} activeClassName="font-semibold">
106
+ {r.label}
107
+ </Link>
108
+ ))
109
+ ```
110
+
111
+ `RouteInfo`: `path`(`/sales/:id`)・`to`(`/sales/$id`)・`params`・`dynamic`・`index`・`label`・
112
+ `title`・`description`・`navLabel`・`kind`(`"module"` | `"file"`)・`variant`(`"slide"`)。
113
+ 後ろの 2 つで、ナビをダッシュボード / スライド / データに分けたりアイコンを変えたりできます。
114
+
115
+ ### フィルタ状態は URL に置く
116
+
117
+ リロードで消えず、URL をそのまま共有できる。`useState` と同じ形。
118
+
119
+ ```tsx
120
+ import { useSearchParam, useSearchState } from "@squadbase/vantage/router"
121
+
122
+ const [region, setRegion] = useSearchParam("region", "all") // 常に string
123
+ const [segments, setSegments] = useSearchState<string[]>("segments", []) // JSON になる値
124
+ ```
125
+
126
+ デフォルト値(または `null`)を書くとキーは URL から消える。履歴は既定で `replace`
127
+ (`{ replace: false }` で push)。
128
+
129
+ ### 動的パラメータの「3 つの綴り」を同期させる
130
+
131
+ 崩すと静かにマッチしなくなる不変条件:
132
+
133
+ - ファイル名 `sales/[customerId].tsx`
134
+ - 表示ルート `/sales/:customerId`
135
+ - リンクトークン `to="/sales/$customerId"` + `params={{ customerId }}`
136
+
137
+ ```tsx
138
+ import { Link, useParams } from "@squadbase/vantage/router"
139
+
140
+ <Link to="/sales/$customerId" params={{ customerId: row.id }}>{row.name}</Link>
141
+
142
+ // 詳細ページ側で取り出す
143
+ const { customerId } = useParams()
144
+ ```
145
+
146
+ ## データ取得(server を持たない fetch)
147
+
148
+ 外部 API を叩くだけなら `server/` は不要。`@squadbase/vantage/query` の `useQuery` を使う:
149
+
150
+ ```tsx
151
+ import { useQuery } from "@squadbase/vantage/query"
152
+ import { Loading, ErrorState } from "@squadbase/vantage/ui"
153
+
154
+ const q = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/…").then((r) => r.json()) })
155
+ if (q.isPending) return <Loading />
156
+ if (q.isError) return <ErrorState message={(q.error as Error).message} />
157
+ ```
158
+
159
+ `QueryClient` は Vantage が 1 つだけ管理する(staleTime 30s・retry 1・
160
+ refetchOnWindowFocus false・networkMode "always")。挙動を変えたいときはクエリ側のオプションで
161
+ 上書きする。
162
+ 自分の `server/api` を叩くときは `useQuery` ではなく `useApiQuery`(→ `agents/api.md`)。
163
+
164
+ ## 組み込み UI / コンポーネントの入口
165
+
166
+ - **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
167
+ `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
168
+ - **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
169
+ `DataTablePreset`・`EChart` ほか)。
170
+ - **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
171
+ リファレンスはパッケージに同梱されていて、オフラインでも読める(→ `AGENTS.md` の
172
+ 「ドキュメントを引く」)。**名前が分からないとき**はやりたいことで `vantage search` する
173
+ (→ 同節)。
174
+ - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
175
+ ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
176
+ `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
177
+ `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
178
+
179
+ ### ダークモードは opt-in
180
+
181
+ ダークのトークンが効くのは、祖先に `.dark` か `[data-theme="dark"]` が付いているときだけ。
182
+ **OS / ブラウザの設定(`prefers-color-scheme`)は見ない。** `dark:` ユーティリティも同じ
183
+ スイッチに繋いであるので(`theme.css` の `@custom-variant dark`)、自分でクラスを付けない限り
184
+ アプリは常にライトで描かれる ― `dark:` を書いてもそこだけダークになることはない。
185
+
186
+ 明暗を切り替えたいときは、レイアウトのルート要素などに `.dark` / `data-theme="dark"` を
187
+ 自分で付ける。
@@ -0,0 +1,41 @@
1
+ # スライド(.slide.tsx)
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.slide.tsx` で
4
+ スライドデッキを書くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ プロジェクトルートに `DESIGN.md` があれば、書き始める前に読んで「スライド」の節と配色・書体の規則に従う。
8
+
9
+ ## スライドは `.slide.tsx` で書く
10
+
11
+ `src/monthly-report.slide.tsx` → `/monthly-report`。中身は `<SlideDeck>` を返す普通の React
12
+ コンポーネントで、`definePage` もナビも通常のページと同じように効きます。
13
+
14
+ ```tsx
15
+ import { Slide, SlideDeck } from "@squadbase/vantage/slide"
16
+
17
+ export const page = definePage({ title: "6月度レビュー", navLabel: "6月度" })
18
+ export default function Deck() {
19
+ return (
20
+ <SlideDeck>
21
+ <Slide>…</Slide>
22
+ <Slide>…</Slide>
23
+ </SlideDeck>
24
+ )
25
+ }
26
+ ```
27
+
28
+ **`<Slide>` は `<SlideDeck>` の直接の子に書く。** `SlideDeck` は直下の `<Slide>` 要素だけを数えるので、
29
+ `<Slide>` を自作コンポーネント(`<Slide1 />` など)や Fragment で包むと 1 枚も数えられず「1 / 0」に
30
+ なります(`{items.map((x) => <Slide key={x.id}>…</Slide>)}` のような配列は可)。共通の部品は
31
+ `<Slide>` の**中**に置くコンポーネントとして切り出します(`<Slide><CoverPage /></Slide>`)。
32
+
33
+ 1920×1080 の固定キャンバスに絶対 px で書き、画面サイズに合わせて縮小されます。キー / クリックで
34
+ 送れ、左のスライド一覧(サムネイル列)は `L` で開閉、`F` で全画面、ツールバーから pptx を
35
+ 書き出せます。**書き方の作法は同梱 Skill の
36
+ `vantage-slide`** にあります(キャンバスの縦方向の予算、段階表示、トランジション、モーフ、
37
+ pptx が壊れる条件)。
38
+
39
+ `.slide` を付けられるのはルートになるページモジュールだけで、`_layout.slide.tsx` などは
40
+ `INVALID_PAGE_VARIANT` エラーになります。`june.slide.tsx` と `june.tsx` は同じ URL に解決するので
41
+ `ROUTE_CONFLICT` です。
@@ -0,0 +1,33 @@
1
+ # xlsx ページ
2
+
3
+ フレームワーク所有の知識ファイルで、`vantage upgrade` で再同期されます。`.xlsx` を
4
+ ページとして置くときに読んでください。不変条件・ディレクトリ構造は `AGENTS.md` を
5
+ 先に読んでいる想定です。
6
+
7
+ ## `.xlsx` を置くだけでページになる
8
+
9
+ **`.xlsx` を `src/` に置くだけで、他のページと同格のルートになります。** URL の作り方は
10
+ `.tsx` と同じ(`src/data/june.xlsx` → `/data/june`、`index.xlsx` はそのディレクトリ)で、
11
+ ナビにも `vantage routes` にも並びます。開くとフレームワーク同梱の表ビューアが描き、複数シートを
12
+ 切り替えられ、元ファイルをダウンロードできます。ビューアは**表計算ソフトの見た目**です ——
13
+ 列は A / B / C、左に行番号、ファイルの 1 行目はヘッダとして固定、シートのタブは下端。
14
+ 読み取り専用で、編集もソートもできません(セルを選んで `Cmd/Ctrl+C` でコピーは可)。
15
+
16
+ - **タイトルはファイル名から導かれる**(`june.xlsx` → 「June」)。データファイルに `definePage` は
17
+ 書けません。名前を変えたいときはファイルをリネームします
18
+ - **ページにしたくないファイルは `_` を前置する**(`_june.xlsx`)。`components/`・`hooks/`・`lib/` に
19
+ 置いたものも同じくページになりません
20
+ - **既定はファイルをそのまま置く(tsx を書かない)。** 自分でタイトルや説明を付けたいときだけ、`_` を
21
+ 前置してルート化を止め、tsx から描きます:
22
+ ```tsx
23
+ import { FilePageView } from "@squadbase/vantage/table"
24
+ import src from "./_june.xlsx?url&no-inline"
25
+ export const page = definePage({ title: "6月度 売上レポート" })
26
+ export default () => <FilePageView src={src} kind="xlsx" fileName="june.xlsx" />
27
+ ```
28
+ - **複数シートに対応**しています。シートのタブは下端に並び、切り替えて閲覧できます。
29
+ - 数式・グラフ・セル書式は再現されません(値として読めれば十分、という設計)。ブラウザでパースするので
30
+ 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは `server/api/` から配って
31
+ `DataTable` で描いてください
32
+ - **ビューアの無い拡張子(`.xls`・`.ods` など)はルートになりません。** `vantage check` の
33
+ `UNSUPPORTED_PAGE_FILE` 警告が知らせます
package/theme.css CHANGED
@@ -53,6 +53,46 @@
53
53
  --sidebar-accent-foreground: oklch(0.205 0 0);
54
54
  --sidebar-border: oklch(0.922 0 0);
55
55
  --sidebar-ring: oklch(0.708 0 0);
56
+
57
+ /* Slides (@squadbase/vantage/slide).
58
+ These are aliases, not values. Custom properties resolve lazily, so
59
+ pointing them at the tokens above is enough for a deck to follow `.dark`
60
+ and `[data-theme="dark"]` on its own — restating them in the dark block
61
+ below would create two places to keep in step for no gain. A deck
62
+ overrides any of them inline through `<SlideDeck theme>`. */
63
+ --slide-bg: var(--background);
64
+ --slide-fg: var(--foreground);
65
+ --slide-accent: var(--primary);
66
+ --slide-muted: var(--muted-foreground);
67
+ --slide-radius: var(--radius);
68
+ /* A system stack that includes Japanese. The pptx export rasterises through
69
+ an SVG `foreignObject`, where locally installed families resolve by name
70
+ but webfonts must be inlined — so a system default is what makes the
71
+ common path export correctly with nothing embedded. */
72
+ --slide-font-display:
73
+ system-ui, -apple-system, "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Yu Gothic", Meiryo,
74
+ sans-serif;
75
+ --slide-font-body: var(--slide-font-display);
76
+ /* Canvas pixels, on the 1920x1080 canvas — not rem: a slide is scaled as a
77
+ whole, so its type must not also follow the browser's root font size. */
78
+ --slide-size-hero: 168px;
79
+ --slide-size-body: 36px;
80
+
81
+ /* Documents (@squadbase/vantage/doc). Aliases, like --slide-*: they follow
82
+ `.dark` through the tokens they point at, so they are not restated in the
83
+ dark block. A document overrides any of them inline through
84
+ `<Document theme>`, an app through its styles.css. */
85
+ --doc-fg: var(--foreground);
86
+ --doc-muted: var(--muted-foreground);
87
+ --doc-accent: var(--primary);
88
+ --doc-border: var(--border);
89
+ --doc-subtle: var(--muted);
90
+ --doc-font-body: inherit;
91
+ --doc-font-heading: var(--doc-font-body);
92
+ --doc-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
93
+ --doc-size-body: 1rem;
94
+ --doc-line-height: 1.8;
95
+ --doc-max-width: 48rem;
56
96
  }
57
97
 
58
98
  .dark,
@@ -128,6 +168,10 @@
128
168
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
129
169
  --color-sidebar-border: var(--sidebar-border);
130
170
  --color-sidebar-ring: var(--sidebar-ring);
171
+ --color-slide-bg: var(--slide-bg);
172
+ --color-slide-fg: var(--slide-fg);
173
+ --color-slide-accent: var(--slide-accent);
174
+ --color-slide-muted: var(--slide-muted);
131
175
  --radius-sm: calc(var(--radius) - 4px);
132
176
  --radius-md: calc(var(--radius) - 2px);
133
177
  --radius-lg: var(--radius);
@@ -228,6 +272,131 @@
228
272
  }
229
273
  }
230
274
 
275
+ /* ------------------------------------------------------------ documents */
276
+
277
+ /* Typesetting for `<Document>`. Plain elements inside it need no classes.
278
+ The rules sit in the components layer and select through `:where()`, so any
279
+ Tailwind utility on an element inside a document still wins. */
280
+ @layer components {
281
+ :where([data-slot="document"]) {
282
+ max-width: var(--doc-max-width);
283
+ margin-inline: auto;
284
+ color: var(--doc-fg);
285
+ font-family: var(--doc-font-body);
286
+ font-size: var(--doc-size-body);
287
+ line-height: var(--doc-line-height);
288
+ overflow-wrap: break-word;
289
+ }
290
+ :where([data-slot="document"]) :where(h1, h2, h3, h4) {
291
+ font-family: var(--doc-font-heading);
292
+ font-weight: 700;
293
+ line-height: 1.35;
294
+ letter-spacing: -0.01em;
295
+ }
296
+ :where([data-slot="document"]) :where(h1) {
297
+ font-size: 2em;
298
+ margin-block: 0 0.8em;
299
+ }
300
+ :where([data-slot="document"]) :where(h2) {
301
+ font-size: 1.5em;
302
+ margin-block: 2em 0.75em;
303
+ padding-bottom: 0.3em;
304
+ border-bottom: 1px solid var(--doc-border);
305
+ }
306
+ :where([data-slot="document"]) :where(h3) {
307
+ font-size: 1.25em;
308
+ margin-block: 1.6em 0.6em;
309
+ }
310
+ :where([data-slot="document"]) :where(h4) {
311
+ font-size: 1em;
312
+ margin-block: 1.4em 0.5em;
313
+ }
314
+ :where([data-slot="document"]) :where(p, ul, ol, blockquote, pre, table, figure) {
315
+ margin-block: 0 1.25em;
316
+ }
317
+ :where([data-slot="document"]) :where(ul, ol) {
318
+ padding-left: 1.5em;
319
+ }
320
+ :where([data-slot="document"]) :where(ul) {
321
+ list-style: disc;
322
+ }
323
+ :where([data-slot="document"]) :where(ol) {
324
+ list-style: decimal;
325
+ }
326
+ :where([data-slot="document"]) :where(li) {
327
+ margin-block: 0.25em;
328
+ }
329
+ :where([data-slot="document"]) :where(li > ul, li > ol) {
330
+ margin-block: 0.25em 0;
331
+ }
332
+ :where([data-slot="document"]) :where(a) {
333
+ color: var(--doc-accent);
334
+ text-decoration: underline;
335
+ text-underline-offset: 0.2em;
336
+ }
337
+ :where([data-slot="document"]) :where(strong) {
338
+ font-weight: 700;
339
+ }
340
+ :where([data-slot="document"]) :where(blockquote) {
341
+ padding-left: 1em;
342
+ border-left: 3px solid var(--doc-border);
343
+ color: var(--doc-muted);
344
+ }
345
+ :where([data-slot="document"]) :where(hr) {
346
+ margin-block: 2em;
347
+ border: 0;
348
+ border-top: 1px solid var(--doc-border);
349
+ }
350
+ :where([data-slot="document"]) :where(code) {
351
+ font-family: var(--doc-font-mono);
352
+ font-size: 0.875em;
353
+ padding: 0.15em 0.35em;
354
+ border-radius: calc(var(--radius) - 4px);
355
+ background: var(--doc-subtle);
356
+ }
357
+ :where([data-slot="document"]) :where(pre) {
358
+ padding: 1em;
359
+ overflow-x: auto;
360
+ border-radius: var(--radius);
361
+ background: var(--doc-subtle);
362
+ line-height: 1.6;
363
+ }
364
+ :where([data-slot="document"]) :where(pre code) {
365
+ padding: 0;
366
+ background: none;
367
+ }
368
+ :where([data-slot="document"]) :where(table) {
369
+ display: block;
370
+ max-width: 100%;
371
+ overflow-x: auto;
372
+ border-collapse: collapse;
373
+ font-size: 0.9375em;
374
+ font-variant-numeric: tabular-nums;
375
+ }
376
+ :where([data-slot="document"]) :where(th, td) {
377
+ padding: 0.5em 0.9em;
378
+ border: 1px solid var(--doc-border);
379
+ text-align: left;
380
+ }
381
+ :where([data-slot="document"]) :where(th) {
382
+ font-weight: 600;
383
+ background: var(--doc-subtle);
384
+ }
385
+ :where([data-slot="document"]) :where(img, video) {
386
+ max-width: 100%;
387
+ height: auto;
388
+ }
389
+ :where([data-slot="document"]) :where(figcaption) {
390
+ margin-top: 0.5em;
391
+ color: var(--doc-muted);
392
+ font-size: 0.875em;
393
+ text-align: center;
394
+ }
395
+ :where([data-slot="document"]) > :where(:first-child) {
396
+ margin-top: 0;
397
+ }
398
+ }
399
+
231
400
  /* ------------------------------------------------------------ utilities */
232
401
 
233
402
  @utility no-scrollbar {
@@ -1,6 +0,0 @@
1
- export { add, syncAgentsFile } from './chunk-X44ZTKID.js';
2
- import './chunk-D4Y5RWJA.js';
3
- import './chunk-HQDEYCDP.js';
4
- import './chunk-C4MQRWXS.js';
5
- //# sourceMappingURL=add-PPCBMWEN.js.map
6
- //# sourceMappingURL=add-PPCBMWEN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"add-PPCBMWEN.js"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/source.ts","../src/core/route-spec.ts"],"names":[],"mappings":"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","file":"chunk-BAM33YZ6.js","sourcesContent":["/**\n * Low-level source scanning helpers shared by the static analyzers\n * (`diagnostics.ts`, `route-spec.ts`). Everything here is text-only — user code\n * is never imported or executed.\n */\n\n/**\n * Strip block and line comments so commented-out code doesn't produce false\n * positives (or hide real issues). Naive but adequate for presence checks:\n * URLs (`http://`) are preserved via the `[^:]` guard before `//`.\n */\nexport function stripComments(src: string): string {\n return src.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/(^|[^:\"'`\\\\])\\/\\/[^\\n]*/g, \"$1\")\n}\n\nconst OPENERS: Record<string, string> = { \"(\": \")\", \"[\": \"]\", \"{\": \"}\" }\n\n/**\n * Read a balanced bracketed region starting at `openIndex` (which must hold\n * `(`, `[` or `{`), returning the raw text *between* the brackets. Quote- and\n * nesting-aware so brackets inside string literals don't unbalance the scan.\n * Returns null when the region is never closed.\n */\nexport function readBracketBody(src: string, openIndex: number): string | null {\n const open = src[openIndex]\n if (!open || !OPENERS[open]) return null\n let depth = 0\n let quote: string | null = null\n for (let i = openIndex; i < src.length; i++) {\n const c = src[i]!\n if (quote) {\n if (c === \"\\\\\") i++\n else if (c === quote) quote = null\n continue\n }\n if (c === '\"' || c === \"'\" || c === \"`\") {\n quote = c\n continue\n }\n if (OPENERS[c]) depth++\n else if (c === \")\" || c === \"]\" || c === \"}\") {\n if (--depth === 0) return src.slice(openIndex + 1, i)\n }\n }\n return null\n}\n\n/**\n * Split an argument list or object body on top-level commas, ignoring commas\n * nested inside brackets or string literals. Empty parts are dropped.\n */\nexport function splitTopLevel(body: string): string[] {\n const parts: string[] = []\n let depth = 0\n let quote: string | null = null\n let start = 0\n for (let i = 0; i < body.length; i++) {\n const c = body[i]!\n if (quote) {\n if (c === \"\\\\\") i++\n else if (c === quote) quote = null\n continue\n }\n if (c === '\"' || c === \"'\" || c === \"`\") {\n quote = c\n continue\n }\n if (OPENERS[c]) depth++\n else if (c === \")\" || c === \"]\" || c === \"}\") depth--\n else if (c === \",\" && depth === 0) {\n parts.push(body.slice(start, i))\n start = i + 1\n }\n }\n parts.push(body.slice(start))\n return parts.map((p) => p.trim()).filter(Boolean)\n}\n","import fs from \"node:fs\"\nimport { API_METHODS, type ApiMethod } from \"./constants\"\nimport { extractPageMeta } from \"./route-tree\"\nimport { readBracketBody, splitTopLevel, stripComments } from \"./source\"\nimport type { ApiRoute, PageRoute, Segment } from \"./types\"\n\n/**\n * Best-effort *static* specs for the routes `vantage routes --detail` prints:\n * what a page takes in its path, and what an API route accepts and answers\n * with. Like `diagnostics.ts` this only reads text — user modules are never\n * imported or executed — so every field is a hint derived from the source, not\n * a guarantee. Callers pass sources through {@link stripComments} first.\n */\n\n/** A dynamic segment of a route path. */\nexport interface RouteParam {\n name: string\n kind: \"param\" | \"catchall\"\n}\n\n/** Static spec of a page route. */\nexport interface PageSpec {\n params: RouteParam[]\n title?: string\n description?: string\n navLabel?: string\n}\n\n/** The request body an API handler reads, when detectable. */\nexport interface ApiBodySpec {\n format: \"json\" | \"formData\" | \"text\" | \"binary\"\n /** Top-level keys, when the handler destructures them. */\n keys?: string[]\n}\n\n/** A response an API handler returns. */\nexport interface ApiResponseSpec {\n status: number\n /** Response content type, omitted when it can't be derived. */\n contentType?: string\n /** Top-level keys, when the payload is an object literal. */\n keys?: string[]\n}\n\n/** An `HttpError` an API handler throws. */\nexport interface ApiErrorSpec {\n status: number\n message?: string\n}\n\n/** Static spec of one HTTP method handler. */\nexport interface ApiMethodSpec {\n method: ApiMethod\n /** Query string keys read via `searchParams`. */\n query: string[]\n body?: ApiBodySpec\n responses: ApiResponseSpec[]\n errors: ApiErrorSpec[]\n}\n\n/** Static spec of an API route module. */\nexport interface ApiSpec {\n params: RouteParam[]\n methods: ApiMethodSpec[]\n}\n\n/**\n * HTTP method handlers a module exports (direct declaration or export list).\n * `src` must be comment-stripped. Shared with the `INVALID_API_EXPORT`\n * diagnostic so both agree on what counts as a handler.\n */\nexport function detectApiMethods(src: string): ApiMethod[] {\n return API_METHODS.filter(\n (m) =>\n new RegExp(`export\\\\s+(async\\\\s+)?(function|const|let|var)\\\\s+${m}\\\\b`).test(src) ||\n new RegExp(`export\\\\s*\\\\{[^}]*\\\\b${m}\\\\b[^}]*\\\\}`).test(src),\n )\n}\n\n/**\n * Slice the module into one source region per handler: from a handler's export\n * declaration to the next one (or EOF). Coarse on purpose — matching a function\n * body would have to cope with arrow bodies, overloads and trailing helpers —\n * and good enough because handlers are written one after another. A method\n * exported only through an export list gets the whole module as its region.\n */\nfunction methodRegions(src: string): Map<ApiMethod, string> {\n const re = new RegExp(\n `export\\\\s+(?:async\\\\s+)?(?:function|const|let|var)\\\\s+(${API_METHODS.join(\"|\")})\\\\b`,\n \"g\",\n )\n const decls: { method: ApiMethod; index: number }[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(src))) decls.push({ method: m[1] as ApiMethod, index: m.index })\n\n const regions = new Map<ApiMethod, string>()\n decls.forEach((d, i) => {\n regions.set(d.method, src.slice(d.index, decls[i + 1]?.index ?? src.length))\n })\n for (const method of detectApiMethods(src)) {\n if (!regions.has(method)) regions.set(method, src)\n }\n return regions\n}\n\n/** Top-level keys of an object literal expression, `…` standing in for spreads. */\nfunction objectKeys(expr: string): string[] | undefined {\n const trimmed = expr.trim()\n if (!trimmed.startsWith(\"{\")) return undefined\n const body = readBracketBody(trimmed, 0)\n if (body == null) return undefined\n const keys: string[] = []\n for (const part of splitTopLevel(body)) {\n if (part.startsWith(\"...\")) {\n keys.push(\"…\")\n continue\n }\n const name = part.match(/^[\"'`]?([A-Za-z0-9_$]+)/)\n if (name) keys.push(name[1]!)\n }\n return keys.length ? keys : undefined\n}\n\n/** Read `status: 201` out of a `ResponseInit` literal. */\nfunction initStatus(init: string | undefined): number | undefined {\n const m = init?.match(/\\bstatus\\s*:\\s*(\\d{3})\\b/)\n return m ? Number(m[1]) : undefined\n}\n\n/** Read a content-type header out of a `ResponseInit` literal. */\nfunction initContentType(init: string | undefined): string | undefined {\n const m = init?.match(/[\"']?content-type[\"']?\\s*:\\s*[\"']([^\"';]+)/i)\n return m ? m[1]!.trim() : undefined\n}\n\n/** Every `Response.json(…)` / `json(…)` / `new Response(…)` in a handler region. */\nfunction extractResponses(region: string): ApiResponseSpec[] {\n const re = /\\bnew\\s+Response\\s*\\(|\\bResponse\\s*\\.\\s*json\\s*\\(|(?<![.\\w$])json\\s*\\(/g\n const out: ApiResponseSpec[] = []\n const seen = new Set<string>()\n let m: RegExpExecArray | null\n while ((m = re.exec(region))) {\n const isJson = !m[0].startsWith(\"new\")\n const args = readBracketBody(region, m.index + m[0].length - 1)\n if (args == null) continue\n const [payload, init] = splitTopLevel(args)\n const contentType = isJson\n ? \"application/json\"\n : (initContentType(init) ??\n (payload?.startsWith(\"JSON.stringify\") ? \"application/json\" : undefined))\n const keys = isJson && payload ? objectKeys(payload) : undefined\n const spec: ApiResponseSpec = {\n status: initStatus(init) ?? 200,\n ...(contentType ? { contentType } : {}),\n ...(keys ? { keys } : {}),\n }\n const key = JSON.stringify(spec)\n if (seen.has(key)) continue\n seen.add(key)\n out.push(spec)\n }\n return out\n}\n\n/** Every `new HttpError(status, \"message\")` in a handler region. */\nfunction extractErrors(region: string): ApiErrorSpec[] {\n const re = /\\bHttpError\\s*\\(\\s*(\\d{3})\\s*(?:,\\s*(?:([\"'`])((?:\\\\.|(?!\\2)[\\s\\S])*)\\2))?/g\n const out: ApiErrorSpec[] = []\n const seen = new Set<string>()\n let m: RegExpExecArray | null\n while ((m = re.exec(region))) {\n const message = m[3]?.replace(/\\\\(.)/g, \"$1\")\n const spec: ApiErrorSpec = { status: Number(m[1]), ...(message ? { message } : {}) }\n const key = JSON.stringify(spec)\n if (seen.has(key)) continue\n seen.add(key)\n out.push(spec)\n }\n return out\n}\n\n/** Query keys read through `searchParams.get/getAll/has(\"x\")`. */\nfunction extractQuery(region: string): string[] {\n const re = /searchParams\\s*\\.\\s*(?:get|getAll|has)\\s*\\(\\s*[\"'`]([^\"'`]+)[\"'`]/g\n const out = new Set<string>()\n let m: RegExpExecArray | null\n while ((m = re.exec(region))) out.add(m[1]!)\n return [...out]\n}\n\n/** The request body a handler reads, plus destructured keys when present. */\nfunction extractBody(region: string): ApiBodySpec | undefined {\n const read = region.match(/\\b(?:request|req)\\s*\\.\\s*(json|formData|text|arrayBuffer|blob)\\s*\\(/)\n if (!read) return undefined\n const method = read[1]!\n const format: ApiBodySpec[\"format\"] =\n method === \"json\" || method === \"formData\" || method === \"text\" ? method : \"binary\"\n\n const destructured = region.match(\n /(?:const|let|var)\\s*(\\{[\\s\\S]*?\\})\\s*=\\s*(?:await\\s+)?[\\w.]*\\.json\\s*\\(/,\n )\n const keys = format === \"json\" && destructured ? objectKeys(destructured[1]!) : undefined\n return { format, ...(keys ? { keys } : {}) }\n}\n\nfunction toParams(segments: Segment[]): RouteParam[] {\n const out: RouteParam[] = []\n for (const s of segments) {\n if (s.kind === \"static\") continue\n out.push({ name: s.name, kind: s.kind })\n }\n return out\n}\n\n/** Static spec of a page: its path params and its `definePage` metadata. */\nexport function extractPageSpec(page: PageRoute): PageSpec {\n return { params: toParams(page.segments), ...extractPageMeta(page.absFile) }\n}\n\n/** Static spec of an API route: path params and a per-method request/response sketch. */\nexport function extractApiSpec(api: ApiRoute): ApiSpec {\n const params = toParams(api.segments)\n let src: string\n try {\n src = stripComments(fs.readFileSync(api.absFile, \"utf8\"))\n } catch {\n return { params, methods: [] }\n }\n const regions = methodRegions(src)\n const methods = API_METHODS.filter((m) => regions.has(m)).map((method) => {\n const region = regions.get(method)!\n const body = extractBody(region)\n return {\n method,\n query: extractQuery(region),\n ...(body ? { body } : {}),\n responses: extractResponses(region),\n errors: extractErrors(region),\n }\n })\n return { params, methods }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/constants.ts"],"names":[],"mappings":";;;AAMO,IAAM,WAAA,GAAc;AASpB,IAAM,OAAA,GAAU;AAGhB,IAAM,cAAA,uBAAqB,GAAA,CAAI;AAAA,EACpC,YAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAC;AASM,IAAM,qBAAA,uBAA4B,GAAA,CAAI;AAAA,EAC3C,OAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,eAAA,GAAkB,CAAC,MAAA,EAAQ,MAAM;AAGvC,IAAM,cAAA,GAAiB,CAAC,KAAA,EAAO,MAAA,EAAQ,KAAK;AAG5C,IAAM,mBAAA,GAAsB;AAAA,EACjC,MAAA,EAAQ,SAAA;AAAA,EACR,QAAA,EAAU,MAAA;AAAA,EACV,KAAA,EAAO;AACT;AAGO,IAAM,cAAc,CAAC,KAAA,EAAO,QAAQ,KAAA,EAAO,OAAA,EAAS,UAAU,SAAS;AAOvE,IAAM,eAAA,GAAwE;AAAA,EACnF;AAAA,IACE,OAAA,EAAS,yCAAA;AAAA,IACT,KAAA,EAAO,aAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AAAA,EACA;AAAA,IACE,OAAA,EAAS,qCAAA;AAAA,IACT,KAAA,EAAO,iBAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AAAA,EACA;AAAA,IACE,OAAA,EAAS,oCAAA;AAAA,IACT,KAAA,EAAO,gBAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AAAA,EACA;AAAA,IACE,OAAA,EAAS,oBAAA;AAAA,IACT,KAAA,EAAO,wBAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AAAA,EACA;AAAA,IACE,OAAA,EAAS,oCAAA;AAAA,IACT,KAAA,EAAO,gBAAA;AAAA,IACP,MAAA,EAAQ;AAAA;AAEZ;AAGO,IAAM,iBAAA,GAAoB;AAG1B,IAAM,uBAAA,GAA0B","file":"chunk-C4MQRWXS.js","sourcesContent":["/**\n * Framework-wide constants: naming conventions and reserved paths.\n * These encode the \"convention over configuration\" contract from the design.\n */\n\n/** Directory that holds all generated, hidden artifacts. */\nexport const VANTAGE_DIR = \".vantage\"\n\n/**\n * Optional directory holding the app's client source. When it exists, it is the\n * one and only page-scan root; when it does not, pages live at the project root.\n * Both layouts are supported so a single-file app stays a single file, while an\n * app (or a coding agent) that reaches for the near-universal `src/` convention\n * gets routes that make sense instead of a `/src/...` prefix on every page.\n */\nexport const SRC_DIR = \"src\"\n\n/** Directories that are never scanned for pages. */\nexport const NON_ROUTE_DIRS = new Set([\n \"components\",\n \"hooks\",\n \"lib\",\n \"server\",\n \"public\",\n \"node_modules\",\n VANTAGE_DIR,\n \"dist\",\n])\n\n/**\n * Directory names that almost always mean \"framework scaffolding\", not \"a URL\n * segment\". Page scanning is a denylist (`NON_ROUTE_DIRS`), so any directory\n * not on it becomes part of the route path — `pages/sales.tsx` silently serves\n * `/pages/sales`. These names are still scanned (they may be deliberate), but\n * routes coming out of them raise `SUSPICIOUS_ROUTE_DIR`.\n */\nexport const SUSPICIOUS_ROUTE_DIRS = new Set([\n SRC_DIR,\n \"app\",\n \"pages\",\n \"routes\",\n \"views\",\n \"screens\",\n \"features\",\n \"containers\",\n \"modules\",\n \"layouts\",\n \"utils\",\n \"types\",\n \"styles\",\n \"assets\",\n \"store\",\n \"stores\",\n \"context\",\n \"contexts\",\n \"providers\",\n \"api\",\n \"test\",\n \"tests\",\n \"__tests__\",\n])\n\n/** File extensions treated as page modules. */\nexport const PAGE_EXTENSIONS = [\".tsx\", \".jsx\"]\n\n/** File extensions treated as server API modules. */\nexport const API_EXTENSIONS = [\".ts\", \".tsx\", \".js\"]\n\n/** Reserved page filenames (without extension). */\nexport const RESERVED_PAGE_NAMES = {\n layout: \"_layout\",\n notFound: \"_404\",\n error: \"_error\",\n} as const\n\n/** HTTP methods that an API module may export. */\nexport const API_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"] as const\nexport type ApiMethod = (typeof API_METHODS)[number]\n\n/**\n * Files that must not exist in a Vantage project. Their presence means the\n * user is trying to escape the managed stack, which v1 forbids.\n */\nexport const FORBIDDEN_FILES: { pattern: RegExp; label: string; remedy: string }[] = [\n {\n pattern: /^vite\\.config\\.(ts|js|mjs|cjs|mts|cts)$/,\n label: \"Vite config\",\n remedy: \"Vantage owns the Vite configuration. Remove this file.\",\n },\n {\n pattern: /^tailwind\\.config\\.(ts|js|mjs|cjs)$/,\n label: \"Tailwind config\",\n remedy: \"Customize theme tokens in styles.css instead.\",\n },\n {\n pattern: /^postcss\\.config\\.(ts|js|mjs|cjs)$/,\n label: \"PostCSS config\",\n remedy: \"Vantage manages PostCSS/Tailwind. Remove this file.\",\n },\n {\n pattern: /^components\\.json$/,\n label: \"shadcn components.json\",\n remedy: \"Use `vantage add ui <name>` to copy UI source instead.\",\n },\n {\n pattern: /^vantage\\.config\\.(ts|js|mjs|cjs)$/,\n label: \"Vantage config\",\n remedy: \"v1 has no vantage.config. Configuration lives in file conventions.\",\n },\n]\n\n/** Public env var prefix exposed to the client bundle. */\nexport const PUBLIC_ENV_PREFIX = \"PUBLIC_\"\n\n/** Manifest schema version emitted into build output. */\nexport const MANIFEST_SCHEMA_VERSION = 1\n"]}