@squadbase/vantage-template 0.0.2 → 0.1.0-dev.c40b771

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 (32) hide show
  1. package/README.md +2 -0
  2. package/base-template/.squadbase/skills/project-storage/SKILL.md +157 -0
  3. package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +22 -0
  4. package/base-template/AGENTS.md +49 -2
  5. package/base-template/package-lock.json +5767 -0
  6. package/base-template/package.json +3 -3
  7. package/base-template/src/_error.tsx +7 -11
  8. package/base-template/src/_layout.tsx +8 -1
  9. package/base-template/src/components/build-recovery.tsx +219 -0
  10. package/base-template/src/styles.css +19 -0
  11. package/dist/index.js +1 -1
  12. package/package.json +18 -12
  13. package/ui-templates/chart-grid/preview-square.png +0 -0
  14. package/ui-templates/chart-grid/preview-wide.png +0 -0
  15. package/ui-templates/chart-grid-ja/preview-square.png +0 -0
  16. package/ui-templates/chart-grid-ja/preview-wide.png +0 -0
  17. package/ui-templates/funnel/preview-square.png +0 -0
  18. package/ui-templates/funnel/preview-wide.png +0 -0
  19. package/ui-templates/funnel-ja/preview-square.png +0 -0
  20. package/ui-templates/funnel-ja/preview-wide.png +0 -0
  21. package/ui-templates/kpi-chart-advanced/preview-square.png +0 -0
  22. package/ui-templates/kpi-chart-advanced/preview-wide.png +0 -0
  23. package/ui-templates/kpi-chart-advanced-ja/preview-square.png +0 -0
  24. package/ui-templates/kpi-chart-advanced-ja/preview-wide.png +0 -0
  25. package/ui-templates/kpi-chart-simple/preview-square.png +0 -0
  26. package/ui-templates/kpi-chart-simple/preview-wide.png +0 -0
  27. package/ui-templates/kpi-chart-simple-ja/preview-square.png +0 -0
  28. package/ui-templates/kpi-chart-simple-ja/preview-wide.png +0 -0
  29. package/ui-templates/table-focus/preview-square.png +0 -0
  30. package/ui-templates/table-focus/preview-wide.png +0 -0
  31. package/ui-templates/table-focus-ja/preview-square.png +0 -0
  32. package/ui-templates/table-focus-ja/preview-wide.png +0 -0
@@ -11,9 +11,9 @@
11
11
  "routes": "vantage routes"
12
12
  },
13
13
  "dependencies": {
14
- "@squadbase/react": "^0.0.1",
15
- "@squadbase/vantage": "^0.5.0",
16
- "@squadbase/vantage-sdk": "^0.1.0-dev.0",
14
+ "@squadbase/react": "^0.1.0",
15
+ "@squadbase/vantage": "^0.5.3",
16
+ "@squadbase/vantage-sdk": "^0.1.0",
17
17
  "date-fns": "^4.1.0",
18
18
  "lucide-react": "^0.468.0",
19
19
  "react": "^19.2.7",
@@ -1,14 +1,10 @@
1
- import { Button, ErrorState } from "@squadbase/vantage/ui"
1
+ import { RecoveringState } from "./components/build-recovery"
2
2
 
3
+ /**
4
+ * ルートルート(= `_layout.tsx`)が throw したときの画面。ページのレンダリングエラーは
5
+ * ここには来ない — フレームワークの既定エラー画面が受け止め、その上を `BuildRecovery` が
6
+ * 覆う(→ `components/build-recovery.tsx`)。どちらも見た目と復帰の仕方は揃えてある。
7
+ */
3
8
  export default function RouteError({ error }: { error: unknown }) {
4
- const message = error instanceof Error ? error.message : String(error)
5
- return (
6
- <div className="flex min-h-[50vh] items-center justify-center">
7
- <ErrorState
8
- title="This page failed to render"
9
- message={message}
10
- action={<Button onClick={() => window.location.reload()}>Reload</Button>}
11
- />
12
- </div>
13
- )
9
+ return <RecoveringState error={error} />
14
10
  }
@@ -4,6 +4,7 @@ import { Link, Outlet, useCurrentRoute, useRoutes } from "@squadbase/vantage/rou
4
4
  import { AppShell } from "@squadbase/vantage/components"
5
5
  import type { NavGroup } from "@squadbase/vantage/components"
6
6
 
7
+ import { BuildRecovery } from "./components/build-recovery"
7
8
  import { UserCard } from "./components/user-card"
8
9
 
9
10
  /**
@@ -43,6 +44,10 @@ function useNavGroups(): NavGroup[] {
43
44
  * the React root, so there is no `main.tsx` to wrap. `SquadbaseProvider` supplies
44
45
  * the signed-in user to `useUser()`; the QueryClient provider is already
45
46
  * installed by the runtime.
47
+ *
48
+ * `BuildRecovery` は構築中の一時的なエラーを「進行中」表示に見せて自動復帰させる
49
+ * (→ `components/build-recovery.tsx`)。外すと、書き込み途中の一瞬のエラーが
50
+ * 「Something went wrong」のままリロードするまで残る。
46
51
  */
47
52
  export default function RootLayout() {
48
53
  return (
@@ -53,7 +58,9 @@ export default function RootLayout() {
53
58
  actions={<UserCard />}
54
59
  linkComponent={RouterLink}
55
60
  >
56
- <Outlet />
61
+ <BuildRecovery>
62
+ <Outlet />
63
+ </BuildRecovery>
57
64
  </AppShell>
58
65
  </SquadbaseProvider>
59
66
  )
@@ -0,0 +1,219 @@
1
+ import { useEffect, useRef, useState, type ReactNode } from "react"
2
+ import { Check, Copy } from "lucide-react"
3
+ import { Button, Spinner } from "@squadbase/vantage/ui"
4
+
5
+ /**
6
+ * ダッシュボードを組み立てている最中は、ファイルが書き終わる順番の都合で一瞬だけ
7
+ * ページが throw することがある(存在しないモジュールの import、まだ書き終わっていない
8
+ * コンポーネント)。それ自体は通り過ぎる状態なのに、ユーザーには 2 つの形で残ってしまう。
9
+ *
10
+ * 1. **見た目が「失敗」に見える。** ページのレンダリングエラーを受け止めるのは
11
+ * Vantage の既定エラー画面("Something went wrong")で、これはテンプレートからは
12
+ * 差し替えられない — `_error.tsx` はルートルート専用で、ページごとの
13
+ * `errorComponent` は現行プロトタイプではまだ結線されていない。
14
+ * 2. **直しても戻らない。** エラーを掴んでいるのは TanStack の CatchBoundary で、
15
+ * そのリセットはルートの再ロードに紐づいている。HMR でソースが差し替わっても
16
+ * リセットされないので、リロードするまでエラー画面が貼り付いたままになる。
17
+ *
18
+ * そこで、既定エラー画面が出たことを DOM から検知して「進行中」表示を被せ、dev では
19
+ * 短い待ち時間のあと自動でリロードして復帰させる(回数は上限つき)。旧 Vite テンプレートの
20
+ * `_router.tsx` と同じ考え方で、一時的なエラーを失敗ではなく更新中として見せる。
21
+ *
22
+ * `_layout.tsx` が `<Outlet />` を包む形で 1 度だけ使う。**外さないこと** — 外すと
23
+ * 構築中のエラーがそのまま「Something went wrong」として残る。
24
+ */
25
+
26
+ /**
27
+ * 既定エラー画面の目印。`role="alert"` の中の `h2` は Vantage の `DefaultError` だけが
28
+ * 持つ(`@squadbase/vantage/ui` の `ErrorState` は見出しを `<p>` で描くので当たらない)。
29
+ */
30
+ const FRAMEWORK_ERROR_SELECTOR = '[role="alert"] h2'
31
+
32
+ /** 書き込みの連鎖が落ち着くのを待ってからリロードする。 */
33
+ const RELOAD_DELAY_MS = 1200
34
+
35
+ /** 直らないエラーで無限リロードしないための上限。使い切ったら手動の案内に降りる。 */
36
+ const MAX_RELOADS = 3
37
+
38
+ /** これだけ待っても復帰しなければ、「進行中」表示から控えめな再読み込み案内へ降格する。 */
39
+ const DEGRADE_AFTER_MS = 6000
40
+
41
+ /** エラーなしでこれだけ描画できたら、自動リロードの回数を使い切っていない扱いに戻す。 */
42
+ const SETTLE_MS = 5000
43
+
44
+ const RELOAD_COUNT_KEY = "vantage:build-recovery-reloads"
45
+
46
+ /** コピーしたことが伝わればいいので、確認表示はすぐ元に戻す。 */
47
+ const COPIED_FEEDBACK_MS = 2000
48
+
49
+ function errorMessage(error: unknown): string {
50
+ if (error == null) return ""
51
+ return error instanceof Error ? (error.stack ?? error.message) : String(error)
52
+ }
53
+
54
+ function readReloadCount(): number {
55
+ try {
56
+ return Number(sessionStorage.getItem(RELOAD_COUNT_KEY)) || 0
57
+ } catch {
58
+ // sessionStorage が使えない環境では自動リロードを諦める(上限を超えた扱い)。
59
+ return MAX_RELOADS
60
+ }
61
+ }
62
+
63
+ function writeReloadCount(count: number): void {
64
+ try {
65
+ sessionStorage.setItem(RELOAD_COUNT_KEY, String(count))
66
+ } catch {
67
+ // 書けなくても致命ではない。次の判定で MAX_RELOADS 扱いになるだけ。
68
+ }
69
+ }
70
+
71
+ function clearReloadCount(): void {
72
+ try {
73
+ sessionStorage.removeItem(RELOAD_COUNT_KEY)
74
+ } catch {
75
+ // 同上。
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 「進行中」表示。最初はローディングと見分けのつかない穏やかな見た目で、一定時間
81
+ * 復帰しなければ控えめな再読み込み案内に降格する。dev では降格の前に自動リロードを試す。
82
+ */
83
+ export function RecoveringState({ error }: { error?: unknown }) {
84
+ const [degraded, setDegraded] = useState(false)
85
+
86
+ useEffect(() => {
87
+ // dev の構築中は、少し待てばファイルが揃って直っていることがほとんど。
88
+ // リロードすれば CatchBoundary も lazy import のキャッシュもまとめて捨てられる。
89
+ if (import.meta.env.DEV) {
90
+ const attempts = readReloadCount()
91
+ if (attempts < MAX_RELOADS) {
92
+ const timer = setTimeout(() => {
93
+ writeReloadCount(attempts + 1)
94
+ window.location.reload()
95
+ }, RELOAD_DELAY_MS)
96
+ return () => clearTimeout(timer)
97
+ }
98
+ }
99
+ const timer = setTimeout(() => setDegraded(true), DEGRADE_AFTER_MS)
100
+ return () => clearTimeout(timer)
101
+ }, [])
102
+
103
+ if (!degraded) {
104
+ return (
105
+ <div
106
+ className="flex min-h-[50vh] flex-col items-center justify-center gap-3"
107
+ role="status"
108
+ aria-live="polite"
109
+ >
110
+ <Spinner className="size-6 text-muted-foreground" />
111
+ <p className="text-base text-muted-foreground">Working on it…</p>
112
+ </div>
113
+ )
114
+ }
115
+
116
+ const message = errorMessage(error)
117
+
118
+ return (
119
+ <div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 p-8">
120
+ <p className="text-center text-base text-muted-foreground">
121
+ This view is taking longer than expected to update.
122
+ </p>
123
+ <div className="flex items-center gap-2">
124
+ <Button onClick={() => window.location.reload()}>Reload</Button>
125
+ {message ? <CopyLogButton message={message} /> : null}
126
+ </div>
127
+ {/*
128
+ ここまで来た人はエラー本文を AI に貼り戻して直してもらう。畳んでおくと辿り着けない
129
+ ので開いたまま出す。フレームワークの既定エラー画面も同じ本文を隠さず出しているので、
130
+ これで新しく何かが露出するわけではない。
131
+ */}
132
+ {message ? (
133
+ <pre className="max-h-64 w-full max-w-2xl select-text overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-muted p-4 text-left text-xs text-muted-foreground">
134
+ {message}
135
+ </pre>
136
+ ) : null}
137
+ </div>
138
+ )
139
+ }
140
+
141
+ /**
142
+ * エラー本文をクリップボードへ。`<pre>` は選択できるので、これが失敗しても手で
143
+ * コピーする道は残る — 失敗しても黙って何もしない。
144
+ */
145
+ function CopyLogButton({ message }: { message: string }) {
146
+ const [copied, setCopied] = useState(false)
147
+
148
+ useEffect(() => {
149
+ if (!copied) return
150
+ const timer = setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS)
151
+ return () => clearTimeout(timer)
152
+ }, [copied])
153
+
154
+ return (
155
+ <Button
156
+ variant="outline"
157
+ onClick={() => {
158
+ navigator.clipboard?.writeText(message).then(
159
+ () => setCopied(true),
160
+ () => {},
161
+ )
162
+ }}
163
+ >
164
+ {copied ? <Check className="size-4" /> : <Copy className="size-4" />}
165
+ {copied ? "Copied" : "Copy log"}
166
+ </Button>
167
+ )
168
+ }
169
+
170
+ /**
171
+ * 子の中に既定エラー画面が現れたら、その上に「進行中」表示を被せる。エラー画面自体は
172
+ * DOM に残したまま覆うだけなので、復帰したかどうかは同じ MutationObserver で追える。
173
+ */
174
+ export function BuildRecovery({ children }: { children: ReactNode }) {
175
+ const hostRef = useRef<HTMLDivElement>(null)
176
+ // エラー本文。`null` は「壊れていない」を兼ねる。既定エラー画面は throw された値を
177
+ // 保持していないので、DOM に描かれた本文を読み取るのが唯一の入手経路になる。
178
+ const [message, setMessage] = useState<string | null>(null)
179
+
180
+ useEffect(() => {
181
+ const host = hostRef.current
182
+ if (!host) return
183
+ const sync = () => {
184
+ const heading = host.querySelector(FRAMEWORK_ERROR_SELECTOR)
185
+ // 覆って見えなくする以上、支援技術にも読ませない。`role="alert"` のままだと
186
+ // 「Something went wrong」がそのまま読み上げられてしまう。属性は observe の
187
+ // 対象外(childList / subtree のみ)なので、この書き換えでは再入しない。
188
+ heading?.closest('[role="alert"]')?.setAttribute("aria-hidden", "true")
189
+ // 既定エラー画面は見出しの直後の段落に本文を描く。取れなくても覆う判断は変えない。
190
+ setMessage(heading === null ? null : (heading.nextElementSibling?.textContent ?? ""))
191
+ }
192
+ sync()
193
+ const observer = new MutationObserver(sync)
194
+ observer.observe(host, { childList: true, subtree: true })
195
+ return () => observer.disconnect()
196
+ }, [])
197
+
198
+ const crashed = message !== null
199
+
200
+ useEffect(() => {
201
+ if (crashed) return
202
+ // 無事に描けている状態がしばらく続いたら、自動リロードの残数を戻す。
203
+ const timer = setTimeout(clearReloadCount, SETTLE_MS)
204
+ return () => clearTimeout(timer)
205
+ }, [crashed])
206
+
207
+ return (
208
+ // 通常時は `relative` を足すだけでレイアウトに影響させない。覆うときだけ、
209
+ // 被せる「進行中」表示が収まる高さを確保する。
210
+ <div ref={hostRef} className={crashed ? "relative min-h-[50vh]" : "relative"}>
211
+ {children}
212
+ {crashed ? (
213
+ <div className="absolute inset-0 z-10 overflow-auto bg-background">
214
+ <RecoveringState error={message} />
215
+ </div>
216
+ ) : null}
217
+ </div>
218
+ )
219
+ }
@@ -16,3 +16,22 @@
16
16
  /* --primary: hsl(221 83% 53%); */
17
17
  /* --radius: 0.625rem; */
18
18
  }
19
+
20
+ /* vantage-template:chart-preset — regenerated by `chart`; edits here are lost */
21
+ :root {
22
+ --chart-1: var(--color-blue-600);
23
+ --chart-2: var(--color-teal-500);
24
+ --chart-3: var(--color-fuchsia-500);
25
+ --chart-4: var(--color-violet-500);
26
+ --chart-5: var(--color-gray-400);
27
+ }
28
+
29
+ .dark,
30
+ [data-theme="dark"] {
31
+ --chart-1: var(--color-blue-400);
32
+ --chart-2: var(--color-teal-400);
33
+ --chart-3: var(--color-fuchsia-400);
34
+ --chart-4: var(--color-violet-400);
35
+ --chart-5: var(--color-gray-500);
36
+ }
37
+ /* vantage-template:chart-preset:end */
package/dist/index.js CHANGED
@@ -391,7 +391,7 @@ async function relabelFile(generateObject, jsonSchema, model, intent, path, cont
391
391
  system: SYSTEM_PROMPT,
392
392
  prompt: userPrompt,
393
393
  temperature: 0.3,
394
- maxTokens: 8e3
394
+ maxOutputTokens: 8e3
395
395
  });
396
396
  const object = result.object;
397
397
  if (process.env.SQUADBASE_AI_DEBUG) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squadbase/vantage-template",
3
- "version": "0.0.2",
3
+ "version": "0.1.0-dev.c40b771",
4
4
  "description": "CLI tool for initializing and customizing Squadbase Vantage projects.",
5
5
  "keywords": [
6
6
  "squadbase",
@@ -12,12 +12,12 @@
12
12
  ],
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/squadbase/squadbase-templates.git",
16
- "directory": "vantage-template"
15
+ "url": "git+https://github.com/squadbase/squadbase-monorepo.git",
16
+ "directory": "packages/vantage-template"
17
17
  },
18
- "homepage": "https://github.com/squadbase/squadbase-templates/tree/main/vantage-template#readme",
18
+ "homepage": "https://github.com/squadbase/squadbase-monorepo/tree/main/packages/vantage-template#readme",
19
19
  "bugs": {
20
- "url": "https://github.com/squadbase/squadbase-templates/issues"
20
+ "url": "https://github.com/squadbase/squadbase-monorepo/issues"
21
21
  },
22
22
  "type": "module",
23
23
  "engines": {
@@ -33,12 +33,18 @@
33
33
  "chart-presets"
34
34
  ],
35
35
  "publishConfig": {
36
- "access": "public"
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org"
37
38
  },
38
39
  "scripts": {
39
- "sync-base": "rsync -a --delete --exclude='node_modules' --exclude='dist' --exclude='.vantage' --exclude='package-lock.json' --exclude='*.tsbuildinfo' --exclude='*.log' ../vantage/ base-template/ && mv base-template/.gitignore base-template/_gitignore",
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",
40
41
  "build": "npm run sync-base && tsup",
41
- "prepublishOnly": "npm --prefix .. run agents:check && npm run build",
42
+ "type-check": "tsc",
43
+ "agents": "node scripts/build-agents.cjs",
44
+ "agents:check": "node scripts/build-agents.cjs --check",
45
+ "template:install": "npm --prefix template ci",
46
+ "template:build": "npm --prefix template run build",
47
+ "prepublishOnly": "npm run agents:check && npm run build",
42
48
  "release": "npm publish --@squadbase:registry=https://registry.npmjs.org",
43
49
  "dev": "node scripts/dev.mjs",
44
50
  "screenshot": "node scripts/screenshot.mjs"
@@ -50,9 +56,9 @@
50
56
  "typescript": "~5.7.0"
51
57
  },
52
58
  "optionalDependencies": {
53
- "@ai-sdk/anthropic": "^1.0.0",
54
- "@ai-sdk/google": "^1.0.0",
55
- "@ai-sdk/openai": "^1.0.0",
56
- "ai": "^4.0.0"
59
+ "@ai-sdk/anthropic": "^2.0.0",
60
+ "@ai-sdk/google": "^2.0.0",
61
+ "@ai-sdk/openai": "^2.0.0",
62
+ "ai": "^5.0.52"
57
63
  }
58
64
  }