@tscircuit/fake-snippets 0.0.106 → 0.0.108

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 (43) hide show
  1. package/CLAUDE.md +92 -0
  2. package/api/generated-index.js +84 -25
  3. package/biome.json +7 -1
  4. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
  5. package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
  6. package/dist/bundle.js +360 -434
  7. package/dist/index.d.ts +26 -15
  8. package/dist/index.js +40 -21
  9. package/dist/schema.d.ts +32 -24
  10. package/dist/schema.js +7 -5
  11. package/fake-snippets-api/lib/db/db-client.ts +19 -1
  12. package/fake-snippets-api/lib/db/schema.ts +6 -3
  13. package/fake-snippets-api/lib/db/seed.ts +23 -12
  14. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
  15. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
  16. package/fake-snippets-api/routes/api/package_builds/list.ts +0 -1
  17. package/package.json +3 -2
  18. package/src/App.tsx +27 -9
  19. package/src/components/FileSidebar.tsx +14 -159
  20. package/src/components/PackageBreadcrumb.tsx +111 -0
  21. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
  22. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +18 -2
  23. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +18 -8
  24. package/src/components/preview/BuildsList.tsx +84 -167
  25. package/src/components/preview/ConnectedPackagesList.tsx +92 -62
  26. package/src/components/preview/ConnectedRepoOverview.tsx +171 -155
  27. package/src/components/preview/{ConnectedRepoDashboard.tsx → PackageReleasesDashboard.tsx} +31 -69
  28. package/src/components/preview/index.tsx +20 -154
  29. package/src/hooks/use-package-builds.ts +0 -48
  30. package/src/hooks/use-package-release-by-id-or-version.ts +36 -0
  31. package/src/hooks/use-package-release.ts +32 -0
  32. package/src/index.css +24 -0
  33. package/src/lib/utils/isUuid.ts +5 -0
  34. package/src/lib/utils/transformFilesToTreeData.tsx +195 -0
  35. package/src/pages/404.tsx +3 -5
  36. package/src/pages/preview-release.tsx +269 -0
  37. package/src/pages/release-builds.tsx +99 -0
  38. package/src/pages/release-detail.tsx +120 -0
  39. package/src/pages/releases.tsx +55 -0
  40. package/fake-snippets-api/routes/api/package_builds/latest.ts +0 -109
  41. package/src/hooks/use-snippets-base-api-url.ts +0 -3
  42. package/src/pages/preview-build.tsx +0 -380
  43. package/src/pages/view-connected-repo.tsx +0 -49
@@ -0,0 +1,120 @@
1
+ import { useParams } from "wouter"
2
+ import NotFoundPage from "./404"
3
+ import { usePackageByName } from "@/hooks/use-package-by-package-name"
4
+ import { usePackageReleaseByIdOrVersion } from "@/hooks/use-package-release-by-id-or-version"
5
+ import { usePackageBuild } from "@/hooks/use-package-builds"
6
+ import { ConnectedRepoOverview } from "@/components/preview/ConnectedRepoOverview"
7
+ import Header from "@/components/Header"
8
+ import { Badge } from "@/components/ui/badge"
9
+ import { Calendar, GitBranch } from "lucide-react"
10
+ import { useState } from "react"
11
+ import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
12
+ import { PackageBreadcrumb } from "@/components/PackageBreadcrumb"
13
+
14
+ export default function ReleaseDetailPage() {
15
+ const params = useParams<{
16
+ author: string
17
+ packageName: string
18
+ releaseId?: string
19
+ packageReleaseId?: string
20
+ }>()
21
+
22
+ const packageName =
23
+ params?.author && params?.packageName
24
+ ? `${params.author}/${params.packageName}`
25
+ : null
26
+
27
+ const [copied, setCopied] = useState(false)
28
+
29
+ const {
30
+ data: pkg,
31
+ isLoading: isLoadingPackage,
32
+ error: packageError,
33
+ } = usePackageByName(packageName)
34
+
35
+ const releaseIdOrVersion =
36
+ params?.releaseId ?? params?.packageReleaseId ?? null
37
+
38
+ const {
39
+ data: packageRelease,
40
+ isLoading: isLoadingRelease,
41
+ error: releaseError,
42
+ } = usePackageReleaseByIdOrVersion(releaseIdOrVersion, packageName)
43
+
44
+ const {
45
+ data: latestBuild,
46
+ isLoading: isLoadingBuild,
47
+ error: buildError,
48
+ } = usePackageBuild(packageRelease?.latest_package_build_id ?? null)
49
+
50
+ if (isLoadingPackage || isLoadingRelease) {
51
+ return null
52
+ }
53
+
54
+ if (packageError?.status === 404 || !pkg) {
55
+ return <NotFoundPage heading="Package Not Found" />
56
+ }
57
+
58
+ if (releaseError?.status === 404 || !packageRelease) {
59
+ return <NotFoundPage heading="Release Not Found" />
60
+ }
61
+
62
+ return (
63
+ <>
64
+ <Header />
65
+ <div className="min-h-screen bg-white">
66
+ {/* Page Header */}
67
+ <div className="bg-gray-50 border-b py-6">
68
+ <div className="max-w-7xl lg:flex lg:justify-between mx-auto px-4 sm:px-6 lg:px-8">
69
+ {/* Breadcrumb */}
70
+ <PackageBreadcrumb
71
+ author={pkg.owner_github_username || ""}
72
+ packageName={pkg.name}
73
+ unscopedName={pkg.unscoped_name}
74
+ releaseVersion={
75
+ packageRelease.version ||
76
+ `v${packageRelease.package_release_id.slice(-6)}`
77
+ }
78
+ />
79
+
80
+ {/* Header Content */}
81
+ <div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-2">
82
+ <div className="flex-1">
83
+ <div className="flex items-center gap-4 text-sm text-gray-600">
84
+ {packageRelease.is_pr_preview && (
85
+ <a
86
+ href={`https://github.com/${pkg.github_repo_full_name}/pull/${packageRelease.github_pr_number}`}
87
+ target="_blank"
88
+ rel="noopener noreferrer"
89
+ >
90
+ <div className="flex items-center gap-1">
91
+ <GitBranch className="w-4 h-4" />
92
+ <Badge variant="outline" className="text-xs">
93
+ PR #{packageRelease.github_pr_number}
94
+ </Badge>
95
+ </div>
96
+ </a>
97
+ )}
98
+ <div className="flex items-center gap-1">
99
+ <Calendar className="w-4 h-4" />
100
+ <span>
101
+ Created {formatTimeAgo(packageRelease.created_at)}
102
+ </span>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+
110
+ {/* Main Content */}
111
+ <ConnectedRepoOverview
112
+ packageBuild={latestBuild ?? null}
113
+ isLoadingBuild={isLoadingBuild}
114
+ pkg={pkg}
115
+ packageRelease={packageRelease}
116
+ />
117
+ </div>
118
+ </>
119
+ )
120
+ }
@@ -0,0 +1,55 @@
1
+ import { useParams } from "wouter"
2
+ import NotFoundPage from "./404"
3
+ import { useLatestPackageRelease } from "@/hooks/use-package-release"
4
+ import { usePackageBuild } from "@/hooks/use-package-builds"
5
+ import { usePackageByName } from "@/hooks/use-package-by-package-name"
6
+ import { PackageReleasesDashboard } from "@/components/preview/PackageReleasesDashboard"
7
+
8
+ export default function ReleasesPage() {
9
+ const params = useParams<{ author: string; packageName: string }>()
10
+ const packageName =
11
+ params?.author && params?.packageName
12
+ ? `${params.author}/${params.packageName}`
13
+ : null
14
+
15
+ const {
16
+ data: pkg,
17
+ isLoading: isLoadingPackage,
18
+ error: packageError,
19
+ } = usePackageByName(packageName)
20
+
21
+ const {
22
+ data: latestRelease,
23
+ isLoading: isLoadingRelease,
24
+ error: releaseError,
25
+ } = useLatestPackageRelease(pkg?.package_id ?? null)
26
+
27
+ // Get the latest build for the latest release to show status and metadata
28
+ const {
29
+ data: latestBuild,
30
+ isLoading: isLoadingBuild,
31
+ error: buildError,
32
+ } = usePackageBuild(latestRelease?.latest_package_build_id ?? null)
33
+
34
+ if (isLoadingPackage || isLoadingRelease || isLoadingBuild) {
35
+ return null
36
+ }
37
+
38
+ if (packageError?.status === 404 || !pkg) {
39
+ return <NotFoundPage heading="Package Not Found" />
40
+ }
41
+
42
+ if (releaseError?.status === 404 || !latestRelease) {
43
+ return <NotFoundPage heading="No Releases Found" />
44
+ }
45
+
46
+ // If there's no build, we still want to show the releases page
47
+ // The PackageReleasesDashboard will handle the case where latestBuild is null
48
+ return (
49
+ <PackageReleasesDashboard
50
+ latestRelease={latestRelease}
51
+ latestBuild={latestBuild ?? null}
52
+ pkg={pkg}
53
+ />
54
+ )
55
+ }
@@ -1,109 +0,0 @@
1
- import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
- import { z } from "zod"
3
- import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
4
-
5
- export default withRouteSpec({
6
- methods: ["GET"],
7
- auth: "session",
8
- queryParams: z.object({
9
- package_id: z.string().optional(),
10
- package_release_id: z.string().optional(),
11
- }),
12
- jsonResponse: z.object({
13
- package_build: z.any().nullable(),
14
- }),
15
- })(async (req, ctx) => {
16
- const { package_id, package_release_id } = req.query
17
-
18
- if (!package_id && !package_release_id) {
19
- return ctx.error(400, {
20
- error_code: "invalid_request",
21
- message: "Either package_id or package_release_id must be provided",
22
- })
23
- }
24
-
25
- let targetPackageId = package_id
26
-
27
- if (package_release_id) {
28
- const packageRelease = ctx.db.packageReleases.find(
29
- (pr) => pr.package_release_id === package_release_id,
30
- )
31
- if (!packageRelease) {
32
- return ctx.error(404, {
33
- error_code: "package_release_not_found",
34
- message: "Package release not found",
35
- })
36
- }
37
- targetPackageId = packageRelease.package_id
38
- }
39
-
40
- if (targetPackageId) {
41
- const pkg = ctx.db.packages.find((p) => p.package_id === targetPackageId)
42
- if (!pkg) {
43
- return ctx.error(404, {
44
- error_code: "package_not_found",
45
- message: "Package not found",
46
- })
47
- }
48
- if (pkg.creator_account_id !== ctx.auth.account_id) {
49
- console.log(
50
- pkg.creator_account_id !== ctx.auth.account_id,
51
- pkg.creator_account_id,
52
- ctx.auth.account_id,
53
- )
54
- return ctx.error(403, {
55
- error_code: "unauthorized",
56
- message: "You are not authorized to access this package",
57
- })
58
- }
59
- }
60
-
61
- let builds = ctx.db.packageBuilds
62
-
63
- if (package_id) {
64
- const packageReleases = ctx.db.packageReleases.filter(
65
- (x) => x.package_id === package_id,
66
- )
67
- if (packageReleases.length === 0) {
68
- return ctx.error(404, {
69
- error_code: "package_not_found",
70
- message: "Package not found",
71
- })
72
- }
73
-
74
- const packageReleaseIds = packageReleases
75
- .filter((pr) => pr.package_id === package_id)
76
- .map((pr) => pr.package_release_id)
77
-
78
- builds = builds.filter((build) =>
79
- packageReleaseIds.includes(build.package_release_id),
80
- )
81
- }
82
-
83
- if (package_release_id) {
84
- builds = builds.filter(
85
- (build) => build.package_release_id === package_release_id,
86
- )
87
- }
88
-
89
- builds = builds.sort(
90
- (a, b) =>
91
- new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
92
- )
93
-
94
- const latestBuild = builds[0] || null
95
-
96
- if (!latestBuild) {
97
- return ctx.json({
98
- package_build: null,
99
- })
100
- }
101
-
102
- const publicBuild = publicMapPackageBuild(latestBuild, {
103
- include_logs: true,
104
- })
105
-
106
- return ctx.json({
107
- package_build: publicBuild,
108
- })
109
- })
@@ -1,3 +0,0 @@
1
- export const useSnippetsBaseApiUrl = () => {
2
- return import.meta.env.VITE_SNIPPETS_API_URL ?? "/api"
3
- }
@@ -1,380 +0,0 @@
1
- import { useState } from "react"
2
- import { useParams } from "wouter"
3
- import { Loader2, ChevronLeft, ChevronRight, File, Folder } from "lucide-react"
4
- import Header from "@/components/Header"
5
- import { SuspenseRunFrame } from "@/components/SuspenseRunFrame"
6
- import { TreeView, TreeDataItem } from "@/components/ui/tree-view"
7
- import { cn } from "@/lib/utils"
8
- import { PrefetchPageLink } from "@/components/PrefetchPageLink"
9
- import NotFoundPage from "./404"
10
- import { getBuildStatus, MOCK_DEPLOYMENTS } from "@/components/preview"
11
-
12
- const MOCK_DEPLOYMENT_FILES: Record<
13
- string,
14
- Array<{ path: string; content: string }>
15
- > = {
16
- pb_1a2b3c4d: [
17
- {
18
- path: "index.tsx",
19
- content: `export default () => (
20
- <board width="10mm" height="10mm">
21
- <resistor
22
- resistance="1k"
23
- footprint="0402"
24
- name="R1"
25
- schX={3}
26
- pcbX={3}
27
- />
28
- <capacitor
29
- capacitance="1000pF"
30
- footprint="0402"
31
- name="C1"
32
- schX={-3}
33
- pcbX={-3}
34
- />
35
- <trace from=".R1 > .pin1" to=".C1 > .pin1" />
36
- </board>
37
- )`,
38
- },
39
- {
40
- path: "components/Button.tsx",
41
- content: `export default () => (
42
- <board width="10mm" height="10mm">
43
- <resistor
44
- resistance="1k"
45
- footprint="0402"
46
- name="R1"
47
- schX={3}
48
- pcbX={3}
49
- />
50
- <capacitor
51
- capacitance="1000pF"
52
- footprint="0402"
53
- name="C1"
54
- schX={-3}
55
- pcbX={-3}
56
- />
57
- <trace from=".R1 > .pin1" to=".C1 > .pin1" />
58
- </board>
59
- )`,
60
- },
61
- ],
62
- pb_9i8j7k6l: [
63
- {
64
- path: "index.tsx",
65
- content: `export default () => (
66
- <board width="10mm" height="10mm">
67
- <resistor
68
- resistance="1k"
69
- footprint="0402"
70
- name="R1"
71
- schX={3}
72
- pcbX={3}
73
- />
74
- <capacitor
75
- capacitance="1000pF"
76
- footprint="0402"
77
- name="C1"
78
- schX={-3}
79
- pcbX={-3}
80
- />
81
- <trace from=".R1 > .pin1" to=".C1 > .pin1" />
82
- </board>
83
- )`,
84
- },
85
- {
86
- path: "utils/helpers.ts",
87
- content: `export default () => (
88
- <board width="10mm" height="10mm">
89
- <resistor
90
- resistance="1k"
91
- footprint="0402"
92
- name="R1"
93
- schX={3}
94
- pcbX={3}
95
- />
96
- <capacitor
97
- capacitance="1000pF"
98
- footprint="0402"
99
- name="C1"
100
- schX={-3}
101
- pcbX={-3}
102
- />
103
- <trace from=".R1 > .pin1" to=".C1 > .pin1" />
104
- </board>
105
- )`,
106
- },
107
- ],
108
- pb_1q2w3e4r: [
109
- {
110
- path: "index.tsx",
111
- content: `export default () => (
112
- <board width="10mm" height="10mm">
113
- <resistor
114
- resistance="1k"
115
- footprint="0402"
116
- name="R1"
117
- schX={3}
118
- pcbX={3}
119
- />
120
- <capacitor
121
- capacitance="1000pF"
122
- footprint="0402"
123
- name="C1"
124
- schX={-3}
125
- pcbX={-3}
126
- />
127
- <trace from=".R1 > .pin1" to=".C1 > .pin1" />
128
- </board>
129
- )`,
130
- },
131
- ],
132
- pb_9o8i7u6y: [
133
- {
134
- path: "index.tsx",
135
- content: `export default () => (
136
- <board width="10mm" height="10mm">
137
- <resistor
138
- resistance="1k"
139
- footprint="0402"
140
- name="R1"
141
- schX={3}
142
- pcbX={3}
143
- />
144
- <capacitor
145
- capacitance="1000pF"
146
- footprint="0402"
147
- name="C1"
148
- schX={-3}
149
- pcbX={-3}
150
- />
151
- <trace from=".R1 > .pin1" to=".C1 > .pin1" />
152
- </board>
153
- )`,
154
- },
155
- ],
156
- }
157
-
158
- const getBuildFiles = (buildId: string | null) => {
159
- if (!buildId) return []
160
- return MOCK_DEPLOYMENT_FILES[buildId] || []
161
- }
162
-
163
- const getBuildFsMap = (buildId: string | null) => {
164
- const files = getBuildFiles(buildId)
165
- return Object.fromEntries(files.map((f) => [f.path, f.content]))
166
- }
167
-
168
- const StatusPill = ({ status }: { status: string }) => {
169
- const color =
170
- status === "success"
171
- ? "bg-emerald-600"
172
- : status === "failed"
173
- ? "bg-red-600"
174
- : status === "building"
175
- ? "bg-blue-600 animate-pulse"
176
- : "bg-gray-500"
177
- return <span className={cn("inline-block w-2 h-2 rounded-full", color)} />
178
- }
179
-
180
- export default function PreviewBuildPage() {
181
- const params = useParams<{ buildId: string }>()
182
- const buildId = params?.buildId || null
183
-
184
- const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
185
- const [selectedFile, setSelectedFile] = useState<string | null>("index.tsx")
186
- const [selectedItemId, setSelectedItemId] = useState<string>("")
187
-
188
- const buildFiles = getBuildFiles(buildId)
189
- const buildFsMap = getBuildFsMap(buildId)
190
-
191
- const build = buildId
192
- ? MOCK_DEPLOYMENTS.find((d) => d.package_build_id === buildId)
193
- : MOCK_DEPLOYMENTS[0]
194
-
195
- if (!build) {
196
- return <NotFoundPage heading="Build Not Found" />
197
- }
198
- const { status, label } = getBuildStatus(build)
199
- const convertFilesToTreeData = (
200
- files: Array<{ path: string; content: string }>,
201
- ): TreeDataItem[] => {
202
- const tree: TreeDataItem[] = []
203
- const pathMap = new Map<string, TreeDataItem>()
204
-
205
- files.forEach((file) => {
206
- const parts = file.path.split("/")
207
- let currentPath = ""
208
-
209
- parts.forEach((part, index) => {
210
- const isLast = index === parts.length - 1
211
- currentPath = currentPath ? `${currentPath}/${part}` : part
212
-
213
- if (!pathMap.has(currentPath)) {
214
- const item: TreeDataItem = {
215
- id: currentPath,
216
- name: part,
217
- icon: isLast ? File : Folder,
218
- children: isLast ? undefined : [],
219
- }
220
-
221
- pathMap.set(currentPath, item)
222
-
223
- if (index === 0) {
224
- tree.push(item)
225
- } else {
226
- const parentPath = parts.slice(0, index).join("/")
227
- const parent = pathMap.get(parentPath)
228
- if (parent && parent.children) {
229
- parent.children.push(item)
230
- }
231
- }
232
- }
233
- })
234
- })
235
-
236
- return tree
237
- }
238
-
239
- const treeData = convertFilesToTreeData(buildFiles)
240
-
241
- return (
242
- <>
243
- <Header />
244
- <div className="flex flex-col h-screen overflow-hidden !-mt-1">
245
- <div className="flex flex-1 overflow-hidden">
246
- <aside
247
- className={cn(
248
- "relative border-r border-gray-200 rounded-r-lg z-[5] h-full transition-all duration-300 ease-in-out bg-white",
249
- sidebarCollapsed ? "w-3" : "w-80",
250
- )}
251
- >
252
- <button
253
- onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
254
- className="absolute top-4 -right-3 z-10 bg-white border border-gray-200 rounded-full p-1 hover:bg-gray-50"
255
- >
256
- {sidebarCollapsed ? (
257
- <ChevronRight size={20} />
258
- ) : (
259
- <ChevronLeft size={20} />
260
- )}
261
- </button>
262
-
263
- {!sidebarCollapsed && (
264
- <>
265
- <div className="p-4 border-b border-gray-200">
266
- <div className="space-y-3">
267
- <div className="flex items-center justify-between">
268
- <h2 className="text-lg font-semibold text-gray-900">
269
- Deployment
270
- </h2>
271
- <StatusPill status={status} />
272
- </div>
273
-
274
- <div className="space-y-2">
275
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
276
- <span className="text-xs text-gray-500 uppercase tracking-wide">
277
- ID
278
- </span>
279
- <PrefetchPageLink
280
- href={`/build/${build.package_build_id}`}
281
- className="font-mono text-sm text-gray-900 bg-gray-100 px-2 py-1 rounded"
282
- >
283
- {build.package_build_id}
284
- </PrefetchPageLink>
285
- </div>
286
-
287
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
288
- <span className="text-xs text-gray-500 uppercase tracking-wide">
289
- Commit
290
- </span>
291
- <PrefetchPageLink
292
- href={`https://github.com/${build.commit_author}/tscircuit.com/commit/${build.commit_message}`}
293
- className="font-mono text-xs text-gray-600 bg-gray-50 px-2 text-right py-1 rounded line-clamp-1"
294
- >
295
- {build.commit_message}
296
- </PrefetchPageLink>
297
- </div>
298
-
299
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
300
- <span className="text-xs text-gray-500 uppercase tracking-wide">
301
- Status
302
- </span>
303
- <span
304
- className={`text-xs font-medium px-2 py-1 rounded-full capitalize ${
305
- status === "success"
306
- ? "bg-emerald-100 text-emerald-800"
307
- : status === "failed"
308
- ? "bg-red-100 text-red-800"
309
- : status === "building"
310
- ? "bg-blue-100 text-blue-800"
311
- : "bg-gray-100 text-gray-800"
312
- }`}
313
- >
314
- {status}
315
- </span>
316
- </div>
317
- </div>
318
- </div>
319
- </div>
320
-
321
- <div className="flex-1 overflow-hidden">
322
- <div className="px-4 py-3 border-b border-gray-200">
323
- <h3 className="text-sm font-semibold text-gray-900">
324
- Files
325
- </h3>
326
- <p className="text-xs text-gray-500 mt-1">
327
- {buildFiles.length} file
328
- {buildFiles.length !== 1 ? "s" : ""}
329
- </p>
330
- </div>
331
- <div className="px-2 py-2 overflow-y-auto select-none">
332
- <TreeView
333
- selectedItemId={selectedItemId || ""}
334
- setSelectedItemId={(v) => setSelectedItemId(v || "")}
335
- data={treeData}
336
- className="w-full"
337
- onSelectChange={(item) => {
338
- if (item && !item.children) {
339
- setSelectedFile(item.id)
340
- }
341
- }}
342
- />
343
- </div>
344
- </div>
345
- </>
346
- )}
347
- </aside>
348
-
349
- <main className="flex-1 overflow-y-auto">
350
- <div className="flex flex-col h-full overflow-h-hidden">
351
- {status === "success" ? (
352
- <SuspenseRunFrame
353
- fsMap={buildFsMap}
354
- mainComponentPath={selectedFile ?? "index.tsx"}
355
- showRunButton={false}
356
- className="[&>div]:overflow-y-hidden"
357
- />
358
- ) : (
359
- <div className="flex-1 flex items-center justify-center">
360
- {status === "building" ? (
361
- <div className="flex flex-col items-center gap-3 text-gray-500">
362
- <Loader2 className="w-6 h-6 animate-spin" />
363
- <p>Building…</p>
364
- </div>
365
- ) : status === "failed" ? (
366
- <div className="text-center">
367
- <p className="text-red-600 font-medium mb-2">
368
- Build Failed
369
- </p>
370
- </div>
371
- ) : null}
372
- </div>
373
- )}
374
- </div>
375
- </main>
376
- </div>
377
- </div>
378
- </>
379
- )
380
- }