react-semaphor 0.1.327 → 0.1.328

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 (60) hide show
  1. package/dist/brand-studio/index.cjs +5 -5
  2. package/dist/brand-studio/index.js +23 -24
  3. package/dist/chunks/_commonjsHelpers-BVfed4GL.js +28 -0
  4. package/dist/chunks/_commonjsHelpers-DwTZ_eVU.js +1 -0
  5. package/dist/chunks/{braces-BGWZEnQJ.js → braces-CG8viaD2.js} +1 -1
  6. package/dist/chunks/{braces-B6qRDu1H.js → braces-DSaa_4Oc.js} +1 -1
  7. package/dist/chunks/{calendar-preferences-dialog-fkLUMJyR.js → calendar-preferences-dialog-DD_qAthL.js} +1 -1
  8. package/dist/chunks/{calendar-preferences-dialog-CjwbE_82.js → calendar-preferences-dialog-JRmNJptJ.js} +3 -3
  9. package/dist/chunks/{chevrons-up-down-xG-bVFD9.js → chevrons-up-down-CBa0uh0X.js} +1 -1
  10. package/dist/chunks/{chevrons-up-down-BpsogQvv.js → chevrons-up-down-ChDNqVP7.js} +1 -1
  11. package/dist/chunks/dashboard-briefing-launcher-BKdJFwH5.js +106 -0
  12. package/dist/chunks/{dashboard-briefing-launcher-Cy1nWZRW.js → dashboard-briefing-launcher-BfyNkd8i.js} +1141 -1137
  13. package/dist/chunks/{dashboard-controls-BWnVEFJq.js → dashboard-controls-0pZDLvFL.js} +218 -218
  14. package/dist/chunks/{dashboard-controls-C7rOGZO-.js → dashboard-controls-C0xm1QMR.js} +10 -10
  15. package/dist/chunks/dashboard-json-BW5OVZ6m.js +1 -0
  16. package/dist/chunks/{dashboard-json-BpRNSsF3.js → dashboard-json-DsruhRPD.js} +14 -13
  17. package/dist/chunks/{edit-dashboard-visual-B2vkIKEa.js → edit-dashboard-visual-DQyJ7SSv.js} +1 -1
  18. package/dist/chunks/{edit-dashboard-visual-CYf26co_.js → edit-dashboard-visual-g5SZZahJ.js} +9 -8
  19. package/dist/chunks/index-1JWDPCun.js +1935 -0
  20. package/dist/chunks/index-BdjXTQt4.js +1444 -0
  21. package/dist/chunks/{index-DTlbYpxd.js → index-BmKr-K7J.js} +116971 -113296
  22. package/dist/chunks/index-C1l78BIx.js +3247 -0
  23. package/dist/chunks/lib-Ce3zosXY.js +38 -0
  24. package/dist/chunks/lib-DFvr9fM4.js +5500 -0
  25. package/dist/chunks/{palette-CSF7IVJn.js → palette-D0YmAqBS.js} +1 -1
  26. package/dist/chunks/{palette-CWgEPBoG.js → palette-DQgq3edH.js} +1 -1
  27. package/dist/chunks/{resource-management-panel-D6nbfJY3.js → resource-management-panel-CIfBh46E.js} +1 -1
  28. package/dist/chunks/{resource-management-panel-D893Onv8.js → resource-management-panel-Cj19pw9M.js} +145 -145
  29. package/dist/chunks/{switch-DJJJD_g1.js → switch-BvTzw2AW.js} +39 -34
  30. package/dist/chunks/{switch-DKf6vHfP.js → switch-De31SR3q.js} +878 -885
  31. package/dist/chunks/typescript-Cmizj1hi.js +446 -0
  32. package/dist/chunks/typescript-H1EwZsOb.js +159820 -0
  33. package/dist/chunks/use-create-flow-overlay-state-BTQiKRD1.js +26 -0
  34. package/dist/chunks/{use-create-flow-overlay-state-p21zs2p6.js → use-create-flow-overlay-state-IcHP0l39.js} +150 -151
  35. package/dist/chunks/{use-visual-utils-BKBua6o4.js → use-visual-utils-DQ5zGYD2.js} +13 -13
  36. package/dist/chunks/{use-visual-utils-BqWm0QeW.js → use-visual-utils-XF-AqV8o.js} +1 -1
  37. package/dist/dashboard/index.cjs +1 -1
  38. package/dist/dashboard/index.js +1 -1
  39. package/dist/data-app-builder/index.cjs +1 -0
  40. package/dist/data-app-builder/index.js +4 -0
  41. package/dist/data-app-builder-browser-runtime/index.cjs +1 -0
  42. package/dist/data-app-builder-browser-runtime/index.js +10 -0
  43. package/dist/data-app-sdk/index.cjs +1 -1
  44. package/dist/data-app-sdk/index.js +208 -196
  45. package/dist/index.cjs +1 -1
  46. package/dist/index.js +14 -14
  47. package/dist/style.css +1 -1
  48. package/dist/surfboard/index.cjs +1 -1
  49. package/dist/surfboard/index.js +2 -2
  50. package/dist/types/data-app-builder-browser-runtime.d.ts +185 -0
  51. package/dist/types/data-app-builder.d.ts +630 -0
  52. package/dist/types/data-app-sdk.d.ts +14 -1
  53. package/dist/types/main.d.ts +2 -0
  54. package/package.json +11 -1
  55. package/dist/chunks/dashboard-briefing-launcher-Co57xBfS.js +0 -106
  56. package/dist/chunks/dashboard-json-DBPMknGo.js +0 -1
  57. package/dist/chunks/index-BD90s-wf.js +0 -1309
  58. package/dist/chunks/save-CtQbSub2.js +0 -6
  59. package/dist/chunks/save-DRdFKF57.js +0 -21
  60. package/dist/chunks/use-create-flow-overlay-state-C4LgoK8q.js +0 -26
@@ -0,0 +1,1935 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const O="semaphor-data-app-browser-sandbox-files:v1",x="browser://semaphor-data-app",R="src/main.tsx",u=[{path:"src/App.tsx",contents:`import { DataApp } from "./data-app"
2
+
3
+ export default function App() {
4
+ return <DataApp />
5
+ }
6
+ `},{path:"src/main.tsx",contents:`import { StrictMode } from "react"
7
+ import { createRoot } from "react-dom/client"
8
+ import App from "./App"
9
+ import "./index.css"
10
+
11
+ createRoot(document.getElementById("root")!).render(
12
+ <StrictMode>
13
+ <App />
14
+ </StrictMode>,
15
+ )
16
+ `},{path:"src/index.css",contents:`@import "tailwindcss";
17
+
18
+ @theme {
19
+ --color-background: #ffffff;
20
+ --color-foreground: #09090b;
21
+ --color-card: #ffffff;
22
+ --color-card-foreground: #09090b;
23
+ --color-muted: #f4f4f5;
24
+ --color-muted-foreground: #71717a;
25
+ --color-border: #e4e4e7;
26
+ --color-input: #e4e4e7;
27
+ --color-ring: #18181b;
28
+ }
29
+
30
+ body {
31
+ margin: 0;
32
+ min-width: 320px;
33
+ min-height: 100vh;
34
+ background: #f6f7f8;
35
+ }
36
+ `},{path:"src/data-app/index.tsx",contents:`import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
37
+ import { Activity, BarChart3, RefreshCw, Users } from "lucide-react"
38
+ import {
39
+ useSemaphorInput,
40
+ useSemaphorInputOptions,
41
+ useSemaphorMetric,
42
+ useSemaphorRecords,
43
+ } from "react-semaphor/data-app-sdk"
44
+
45
+ import { Badge } from "@/components/ui/badge"
46
+ import { Button } from "@/components/ui/button"
47
+ import {
48
+ Card,
49
+ CardContent,
50
+ CardDescription,
51
+ CardHeader,
52
+ CardTitle,
53
+ } from "@/components/ui/card"
54
+ import {
55
+ ChartContainer,
56
+ ChartTooltip,
57
+ ChartTooltipContent,
58
+ type ChartConfig,
59
+ } from "@/components/ui/chart"
60
+ import {
61
+ Select,
62
+ SelectContent,
63
+ SelectItem,
64
+ SelectTrigger,
65
+ SelectValue,
66
+ } from "@/components/ui/select"
67
+ import {
68
+ Table,
69
+ TableBody,
70
+ TableCell,
71
+ TableHead,
72
+ TableHeader,
73
+ TableRow,
74
+ } from "@/components/ui/table"
75
+
76
+ export function DataApp() {
77
+ const segmentOptions = useSemaphorInputOptions({
78
+ id: "segment-options",
79
+ dataset: "customers",
80
+ field: "customer_segment",
81
+ })
82
+ const regionOptions = useSemaphorInputOptions({
83
+ id: "region-options",
84
+ dataset: "customers",
85
+ field: "region",
86
+ })
87
+ const segment = useSemaphorInput({
88
+ id: "segment",
89
+ kind: "filter",
90
+ field: "customer_segment",
91
+ value: "",
92
+ options: [{ value: "", label: "All segments" }, ...segmentOptions.options],
93
+ })
94
+ const region = useSemaphorInput({
95
+ id: "region",
96
+ kind: "filter",
97
+ field: "region",
98
+ value: "",
99
+ options: [{ value: "", label: "All regions" }, ...regionOptions.options],
100
+ })
101
+ const revenue = useSemaphorMetric({
102
+ id: "revenue",
103
+ dataset: "orders",
104
+ metric: "revenue",
105
+ comparison: "previous_period",
106
+ inputs: [segment, region],
107
+ })
108
+ const arr = useSemaphorMetric({
109
+ id: "arr",
110
+ dataset: "customers",
111
+ metric: "arr",
112
+ inputs: [segment, region],
113
+ })
114
+ const tickets = useSemaphorMetric({
115
+ id: "tickets",
116
+ dataset: "customers",
117
+ metric: "open_tickets",
118
+ inputs: [segment, region],
119
+ })
120
+ const customerHealth = useSemaphorRecords({
121
+ id: "customer-health",
122
+ dataset: "customers",
123
+ dimensions: ["customer_name", "customer_segment", "region"],
124
+ measures: ["health_score", "open_tickets", "arr"],
125
+ inputs: [segment, region],
126
+ limit: 8,
127
+ })
128
+ const chartData = customerHealth.records.map((row) => ({
129
+ customer: String(row.customer_name).split(" ")[0],
130
+ arr: Number(row.arr || 0),
131
+ }))
132
+ const chartConfig = {
133
+ arr: {
134
+ label: "ARR",
135
+ color: "#2563eb",
136
+ },
137
+ } satisfies ChartConfig
138
+
139
+ const resetFilters = () => {
140
+ segment.setValue("")
141
+ region.setValue("")
142
+ }
143
+
144
+ return (
145
+ <main className="min-h-screen bg-zinc-50 p-4 text-zinc-950 sm:p-6">
146
+ <section className="mx-auto flex max-w-6xl flex-col gap-4">
147
+ <div className="flex flex-col gap-3 rounded-lg border border-zinc-200 bg-white p-4 shadow-sm lg:flex-row lg:items-center lg:justify-between">
148
+ <div className="min-w-0">
149
+ <Badge variant="secondary" className="mb-2 gap-1.5">
150
+ <BarChart3 className="size-3.5" />
151
+ Approved Browser Sandbox template
152
+ </Badge>
153
+ <h1 className="text-2xl font-semibold tracking-tight">Customer Revenue Console</h1>
154
+ <p className="mt-1 text-sm text-zinc-500">
155
+ Semaphor metrics, filters, shadcn source components, Tailwind, and Recharts in one browser-native starter.
156
+ </p>
157
+ </div>
158
+ <div className="grid gap-2 sm:grid-cols-[1fr_1fr_auto] lg:w-[520px]">
159
+ <label className="text-xs font-medium text-zinc-600">
160
+ Segment
161
+ <Select value={String(segment.value ?? "")} onValueChange={segment.setValue}>
162
+ <SelectTrigger className="mt-1 w-full">
163
+ <SelectValue placeholder="All segments" />
164
+ </SelectTrigger>
165
+ <SelectContent>
166
+ {segment.options.map((option) => (
167
+ <SelectItem key={option.value} value={String(option.value)}>
168
+ {option.label}
169
+ </SelectItem>
170
+ ))}
171
+ </SelectContent>
172
+ </Select>
173
+ </label>
174
+ <label className="text-xs font-medium text-zinc-600">
175
+ Region
176
+ <Select value={String(region.value ?? "")} onValueChange={region.setValue}>
177
+ <SelectTrigger className="mt-1 w-full">
178
+ <SelectValue placeholder="All regions" />
179
+ </SelectTrigger>
180
+ <SelectContent>
181
+ {region.options.map((option) => (
182
+ <SelectItem key={option.value} value={String(option.value)}>
183
+ {option.label}
184
+ </SelectItem>
185
+ ))}
186
+ </SelectContent>
187
+ </Select>
188
+ </label>
189
+ <Button type="button" variant="outline" className="self-end" onClick={resetFilters}>
190
+ <RefreshCw className="size-4" />
191
+ Reset
192
+ </Button>
193
+ </div>
194
+ </div>
195
+
196
+ <div className="grid gap-3 md:grid-cols-3">
197
+ <Card>
198
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
199
+ <div>
200
+ <CardDescription>Total revenue</CardDescription>
201
+ <CardTitle className="mt-1 text-2xl text-zinc-950">
202
+ {revenue.value?.toLocaleString() ?? "0"}
203
+ </CardTitle>
204
+ </div>
205
+ <Activity className="size-4 text-zinc-400" />
206
+ </CardHeader>
207
+ </Card>
208
+ <Card>
209
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
210
+ <div>
211
+ <CardDescription>Customer ARR</CardDescription>
212
+ <CardTitle className="mt-1 text-2xl text-zinc-950">
213
+ {arr.value?.toLocaleString() ?? "0"}
214
+ </CardTitle>
215
+ </div>
216
+ <Users className="size-4 text-zinc-400" />
217
+ </CardHeader>
218
+ </Card>
219
+ <Card>
220
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
221
+ <div>
222
+ <CardDescription>Open tickets</CardDescription>
223
+ <CardTitle className="mt-1 text-2xl text-zinc-950">
224
+ {tickets.value?.toLocaleString() ?? "0"}
225
+ </CardTitle>
226
+ </div>
227
+ <Badge variant={tickets.value > 30 ? "destructive" : "secondary"}>
228
+ {tickets.value > 30 ? "Watch" : "Stable"}
229
+ </Badge>
230
+ </CardHeader>
231
+ </Card>
232
+ </div>
233
+
234
+ <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_420px]">
235
+ <Card>
236
+ <CardHeader>
237
+ <CardTitle>ARR by account</CardTitle>
238
+ <CardDescription>Real Recharts rendered through the shadcn chart wrapper.</CardDescription>
239
+ </CardHeader>
240
+ <CardContent>
241
+ <ChartContainer config={chartConfig} className="h-[300px] w-full">
242
+ <BarChart data={chartData} margin={{ left: 0, right: 12 }}>
243
+ <CartesianGrid vertical={false} />
244
+ <XAxis dataKey="customer" tickLine={false} axisLine={false} tickMargin={8} />
245
+ <YAxis tickLine={false} axisLine={false} tickMargin={8} width={52} />
246
+ <ChartTooltip content={<ChartTooltipContent />} />
247
+ <Bar dataKey="arr" fill="var(--color-arr)" radius={[4, 4, 0, 0]} />
248
+ </BarChart>
249
+ </ChartContainer>
250
+ </CardContent>
251
+ </Card>
252
+
253
+ <Card>
254
+ <CardHeader>
255
+ <CardTitle>Template coverage</CardTitle>
256
+ <CardDescription>Bundled source files the model can edit in-browser.</CardDescription>
257
+ </CardHeader>
258
+ <CardContent className="flex flex-col gap-2 text-sm">
259
+ {["shadcn card/table/button/badge/chart", "Tailwind v4 compiler", "Recharts SVG charts", "Semaphor data hooks"].map((item) => (
260
+ <div key={item} className="flex items-center justify-between gap-3 rounded-md border border-zinc-200 px-3 py-2">
261
+ <span className="text-zinc-700">{item}</span>
262
+ <Badge variant="outline">Ready</Badge>
263
+ </div>
264
+ ))}
265
+ </CardContent>
266
+ </Card>
267
+ </div>
268
+
269
+ <Card>
270
+ <CardHeader>
271
+ <CardTitle>Customer health watchlist</CardTitle>
272
+ <CardDescription>Filtered fixture rows shaped through the Semaphor data app SDK.</CardDescription>
273
+ </CardHeader>
274
+ <CardContent>
275
+ <div className="overflow-x-auto rounded-lg border border-zinc-200">
276
+ <Table>
277
+ <TableHeader>
278
+ <TableRow>
279
+ <TableHead>Customer</TableHead>
280
+ <TableHead>Segment</TableHead>
281
+ <TableHead>Region</TableHead>
282
+ <TableHead className="text-right">Health</TableHead>
283
+ <TableHead className="text-right">Tickets</TableHead>
284
+ <TableHead className="text-right">ARR</TableHead>
285
+ </TableRow>
286
+ </TableHeader>
287
+ <TableBody>
288
+ {customerHealth.records.map((row) => (
289
+ <TableRow key={row.customer_name}>
290
+ <TableCell className="font-medium text-zinc-900">{row.customer_name}</TableCell>
291
+ <TableCell>{row.customer_segment}</TableCell>
292
+ <TableCell>{row.region}</TableCell>
293
+ <TableCell className="text-right">{row.health_score}</TableCell>
294
+ <TableCell className="text-right">{row.open_tickets}</TableCell>
295
+ <TableCell className="text-right">
296
+ {Number(row.arr || 0).toLocaleString()}
297
+ </TableCell>
298
+ </TableRow>
299
+ ))}
300
+ </TableBody>
301
+ </Table>
302
+ </div>
303
+ </CardContent>
304
+ </Card>
305
+ </section>
306
+ </main>
307
+ )
308
+ }
309
+ `},{path:"src/lib/utils.ts",contents:`import { clsx, type ClassValue } from "clsx"
310
+ import { twMerge } from "tailwind-merge"
311
+
312
+ export function cn(...inputs: ClassValue[]) {
313
+ return twMerge(clsx(inputs))
314
+ }
315
+ `},{path:"src/components/ui/badge.tsx",contents:`import type { HTMLAttributes } from "react"
316
+
317
+ import { cn } from "@/lib/utils"
318
+
319
+ type BadgeVariant = "default" | "secondary" | "outline" | "destructive"
320
+
321
+ const variantClasses: Record<BadgeVariant, string> = {
322
+ default: "border-transparent bg-zinc-900 text-white",
323
+ secondary: "border-transparent bg-zinc-100 text-zinc-900",
324
+ outline: "border-zinc-200 bg-white text-zinc-700",
325
+ destructive: "border-transparent bg-red-100 text-red-700",
326
+ }
327
+
328
+ export function Badge({
329
+ className,
330
+ variant = "default",
331
+ ...props
332
+ }: HTMLAttributes<HTMLSpanElement> & { variant?: BadgeVariant }) {
333
+ return (
334
+ <span
335
+ {...props}
336
+ className={cn(
337
+ "inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium",
338
+ variantClasses[variant],
339
+ className,
340
+ )}
341
+ />
342
+ )
343
+ }
344
+ `},{path:"src/components/ui/button.tsx",contents:`import type { ButtonHTMLAttributes } from "react"
345
+
346
+ import { cn } from "@/lib/utils"
347
+
348
+ type ButtonVariant = "default" | "outline" | "ghost"
349
+ type ButtonSize = "default" | "sm"
350
+
351
+ const variantClasses: Record<ButtonVariant, string> = {
352
+ default: "bg-zinc-900 text-white hover:bg-zinc-700",
353
+ outline: "border border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50",
354
+ ghost: "text-zinc-700 hover:bg-zinc-100",
355
+ }
356
+
357
+ const sizeClasses: Record<ButtonSize, string> = {
358
+ default: "h-9 px-3",
359
+ sm: "h-8 px-2.5 text-xs",
360
+ }
361
+
362
+ export function Button({
363
+ className,
364
+ variant = "default",
365
+ size = "default",
366
+ ...props
367
+ }: ButtonHTMLAttributes<HTMLButtonElement> & {
368
+ variant?: ButtonVariant
369
+ size?: ButtonSize
370
+ }) {
371
+ return (
372
+ <button
373
+ {...props}
374
+ className={cn(
375
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium outline-none transition-colors disabled:pointer-events-none disabled:opacity-50",
376
+ variantClasses[variant],
377
+ sizeClasses[size],
378
+ className,
379
+ )}
380
+ />
381
+ )
382
+ }
383
+ `},{path:"src/components/ui/card.tsx",contents:`import type { HTMLAttributes } from "react"
384
+
385
+ import { cn } from "@/lib/utils"
386
+
387
+ export function Card(props: HTMLAttributes<HTMLDivElement>) {
388
+ return <div {...props} className={cn("rounded-lg border border-zinc-200 bg-white shadow-sm", props.className)} />
389
+ }
390
+
391
+ export function CardHeader(props: HTMLAttributes<HTMLDivElement>) {
392
+ return <div {...props} className={cn("p-4 pb-2", props.className)} />
393
+ }
394
+
395
+ export function CardTitle(props: HTMLAttributes<HTMLHeadingElement>) {
396
+ return <h2 {...props} className={cn("text-sm font-medium text-zinc-600", props.className)} />
397
+ }
398
+
399
+ export function CardDescription(props: HTMLAttributes<HTMLParagraphElement>) {
400
+ return <p {...props} className={cn("text-xs text-zinc-500", props.className)} />
401
+ }
402
+
403
+ export function CardContent(props: HTMLAttributes<HTMLDivElement>) {
404
+ return <div {...props} className={cn("p-4 pt-2", props.className)} />
405
+ }
406
+ `},{path:"src/components/ui/select.tsx",contents:`import * as React from "react"
407
+
408
+ import { cn } from "@/lib/utils"
409
+
410
+ type SelectContextValue = {
411
+ value?: string
412
+ selectedLabel?: React.ReactNode
413
+ open: boolean
414
+ setOpen: (open: boolean) => void
415
+ setValue: (value: string, label: React.ReactNode) => void
416
+ }
417
+
418
+ const SelectContext = React.createContext<SelectContextValue | null>(null)
419
+
420
+ function useSelectContext(component: string) {
421
+ const context = React.useContext(SelectContext)
422
+ if (!context) {
423
+ throw new Error(component + " must be used within Select")
424
+ }
425
+ return context
426
+ }
427
+
428
+ function Select({
429
+ value,
430
+ defaultValue,
431
+ onValueChange,
432
+ children,
433
+ }: {
434
+ value?: string
435
+ defaultValue?: string
436
+ onValueChange?: (value: string) => void
437
+ children: React.ReactNode
438
+ }) {
439
+ const isControlled = Object.prototype.hasOwnProperty.call(
440
+ arguments[0] || {},
441
+ "value",
442
+ )
443
+ const [localValue, setLocalValue] = React.useState(defaultValue)
444
+ const [selectedLabel, setSelectedLabel] = React.useState<React.ReactNode>()
445
+ const [open, setOpen] = React.useState(false)
446
+ const currentValue = isControlled ? value : localValue
447
+
448
+ const setNextValue = React.useCallback(
449
+ (nextValue: string, label: React.ReactNode) => {
450
+ if (!isControlled) setLocalValue(nextValue)
451
+ setSelectedLabel(label)
452
+ onValueChange?.(nextValue)
453
+ setOpen(false)
454
+ },
455
+ [isControlled, onValueChange],
456
+ )
457
+
458
+ return (
459
+ <SelectContext.Provider
460
+ value={{
461
+ value: currentValue,
462
+ selectedLabel,
463
+ open,
464
+ setOpen,
465
+ setValue: setNextValue,
466
+ }}
467
+ >
468
+ <div className="relative inline-block min-w-0">{children}</div>
469
+ </SelectContext.Provider>
470
+ )
471
+ }
472
+
473
+ function SelectTrigger({
474
+ className,
475
+ children,
476
+ ...props
477
+ }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
478
+ const context = useSelectContext("SelectTrigger")
479
+
480
+ return (
481
+ <button
482
+ type="button"
483
+ data-slot="select-trigger"
484
+ aria-expanded={context.open}
485
+ onClick={() => context.setOpen(!context.open)}
486
+ className={cn(
487
+ "flex h-9 min-w-32 items-center justify-between gap-2 rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm outline-none transition-colors hover:bg-zinc-50 focus:border-zinc-400 disabled:pointer-events-none disabled:opacity-50",
488
+ className,
489
+ )}
490
+ {...props}
491
+ >
492
+ <span className="min-w-0 flex-1 truncate text-left">{children}</span>
493
+ <span className="text-xs text-zinc-400">▾</span>
494
+ </button>
495
+ )
496
+ }
497
+
498
+ function SelectValue({ placeholder }: { placeholder?: string }) {
499
+ const context = useSelectContext("SelectValue")
500
+ return (
501
+ <span
502
+ data-slot="select-value"
503
+ className={cn(!context.selectedLabel && "text-zinc-400")}
504
+ >
505
+ {context.selectedLabel || context.value || placeholder || "Select"}
506
+ </span>
507
+ )
508
+ }
509
+
510
+ function SelectContent({
511
+ className,
512
+ children,
513
+ }: {
514
+ className?: string
515
+ children: React.ReactNode
516
+ }) {
517
+ const context = useSelectContext("SelectContent")
518
+ if (!context.open) return null
519
+
520
+ return (
521
+ <div
522
+ data-slot="select-content"
523
+ className={cn(
524
+ "absolute left-0 top-[calc(100%+4px)] z-50 max-h-64 min-w-full overflow-auto rounded-md border border-zinc-200 bg-white p-1 text-zinc-900 shadow-lg",
525
+ className,
526
+ )}
527
+ >
528
+ {children}
529
+ </div>
530
+ )
531
+ }
532
+
533
+ function SelectItem({
534
+ value,
535
+ className,
536
+ children,
537
+ }: {
538
+ value: string
539
+ className?: string
540
+ children: React.ReactNode
541
+ }) {
542
+ const context = useSelectContext("SelectItem")
543
+ const selected = context.value === value
544
+
545
+ return (
546
+ <button
547
+ type="button"
548
+ data-slot="select-item"
549
+ data-state={selected ? "checked" : "unchecked"}
550
+ onClick={() => context.setValue(value, children)}
551
+ className={cn(
552
+ "flex w-full items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-zinc-100 focus:bg-zinc-100",
553
+ selected && "bg-zinc-100 font-medium",
554
+ className,
555
+ )}
556
+ >
557
+ <span className="min-w-0 truncate">{children}</span>
558
+ {selected ? <span className="text-xs text-zinc-500">✓</span> : null}
559
+ </button>
560
+ )
561
+ }
562
+
563
+ function SelectGroup({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
564
+ return <div data-slot="select-group" className={cn("p-1", className)} {...props} />
565
+ }
566
+
567
+ function SelectLabel({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
568
+ return <div data-slot="select-label" className={cn("px-2 py-1.5 text-xs font-medium text-zinc-500", className)} {...props} />
569
+ }
570
+
571
+ function SelectSeparator({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
572
+ return <div data-slot="select-separator" className={cn("-mx-1 my-1 h-px bg-zinc-200", className)} {...props} />
573
+ }
574
+
575
+ function SelectScrollUpButton() {
576
+ return null
577
+ }
578
+
579
+ function SelectScrollDownButton() {
580
+ return null
581
+ }
582
+
583
+ export {
584
+ Select,
585
+ SelectContent,
586
+ SelectGroup,
587
+ SelectItem,
588
+ SelectLabel,
589
+ SelectScrollDownButton,
590
+ SelectScrollUpButton,
591
+ SelectSeparator,
592
+ SelectTrigger,
593
+ SelectValue,
594
+ }
595
+ `},{path:"src/components/ui/progress.tsx",contents:`import type { HTMLAttributes } from "react"
596
+
597
+ import { cn } from "@/lib/utils"
598
+
599
+ function clampProgress(value: number | null | undefined) {
600
+ if (typeof value !== "number" || Number.isNaN(value)) return 0
601
+ return Math.max(0, Math.min(100, value))
602
+ }
603
+
604
+ function Progress({
605
+ className,
606
+ value,
607
+ ...props
608
+ }: HTMLAttributes<HTMLDivElement> & { value?: number | null }) {
609
+ const safeValue = clampProgress(value)
610
+
611
+ return (
612
+ <div
613
+ data-slot="progress"
614
+ role="progressbar"
615
+ aria-valuemin={0}
616
+ aria-valuemax={100}
617
+ aria-valuenow={safeValue}
618
+ className={cn(
619
+ "relative h-2 w-full overflow-hidden rounded-full bg-zinc-100",
620
+ className,
621
+ )}
622
+ {...props}
623
+ >
624
+ <div
625
+ data-slot="progress-indicator"
626
+ className="h-full w-full flex-1 rounded-full bg-zinc-900 transition-transform"
627
+ style={{ transform: "translateX(-" + (100 - safeValue) + "%)" }}
628
+ />
629
+ </div>
630
+ )
631
+ }
632
+
633
+ export { Progress }
634
+ `},{path:"src/components/ui/calendar.tsx",contents:`import * as React from "react"
635
+
636
+ import { cn } from "@/lib/utils"
637
+
638
+ type CalendarMode = "single" | "range"
639
+ type DateRange = { from?: Date; to?: Date }
640
+
641
+ function startOfMonth(date: Date) {
642
+ return new Date(date.getFullYear(), date.getMonth(), 1)
643
+ }
644
+
645
+ function addMonths(date: Date, count: number) {
646
+ return new Date(date.getFullYear(), date.getMonth() + count, 1)
647
+ }
648
+
649
+ function isSameDay(a?: Date, b?: Date) {
650
+ return Boolean(
651
+ a &&
652
+ b &&
653
+ a.getFullYear() === b.getFullYear() &&
654
+ a.getMonth() === b.getMonth() &&
655
+ a.getDate() === b.getDate(),
656
+ )
657
+ }
658
+
659
+ function isBetween(date: Date, from?: Date, to?: Date) {
660
+ if (!from || !to) return false
661
+ const value = date.getTime()
662
+ return value >= from.getTime() && value <= to.getTime()
663
+ }
664
+
665
+ function monthDays(month: Date) {
666
+ const first = startOfMonth(month)
667
+ const start = new Date(first)
668
+ start.setDate(first.getDate() - first.getDay())
669
+
670
+ return Array.from({ length: 42 }, (_, index) => {
671
+ const date = new Date(start)
672
+ date.setDate(start.getDate() + index)
673
+ return date
674
+ })
675
+ }
676
+
677
+ function formatDate(value?: Date) {
678
+ return value
679
+ ? value.toLocaleDateString(undefined, {
680
+ month: "short",
681
+ day: "numeric",
682
+ year: "numeric",
683
+ })
684
+ : ""
685
+ }
686
+
687
+ function Calendar({
688
+ className,
689
+ mode = "single",
690
+ selected,
691
+ defaultMonth,
692
+ month,
693
+ onMonthChange,
694
+ onSelect,
695
+ }: {
696
+ className?: string
697
+ mode?: CalendarMode
698
+ selected?: Date | DateRange
699
+ defaultMonth?: Date
700
+ month?: Date
701
+ onMonthChange?: (month: Date) => void
702
+ onSelect?: (value: Date | DateRange | undefined) => void
703
+ }) {
704
+ const [localMonth, setLocalMonth] = React.useState(
705
+ startOfMonth(month || defaultMonth || new Date()),
706
+ )
707
+ const currentMonth = startOfMonth(month || localMonth)
708
+ const range = mode === "range" && selected && !(selected instanceof Date)
709
+ ? selected
710
+ : undefined
711
+ const selectedDate = selected instanceof Date ? selected : undefined
712
+
713
+ const setMonth = React.useCallback(
714
+ (nextMonth: Date) => {
715
+ const normalized = startOfMonth(nextMonth)
716
+ if (!month) setLocalMonth(normalized)
717
+ onMonthChange?.(normalized)
718
+ },
719
+ [month, onMonthChange],
720
+ )
721
+
722
+ const selectDate = React.useCallback(
723
+ (date: Date) => {
724
+ if (mode === "range") {
725
+ const currentRange =
726
+ selected && !(selected instanceof Date) ? selected : undefined
727
+ if (!currentRange?.from || currentRange.to) {
728
+ onSelect?.({ from: date, to: undefined })
729
+ return
730
+ }
731
+ if (date.getTime() < currentRange.from.getTime()) {
732
+ onSelect?.({ from: date, to: currentRange.from })
733
+ return
734
+ }
735
+ onSelect?.({ from: currentRange.from, to: date })
736
+ return
737
+ }
738
+
739
+ onSelect?.(date)
740
+ },
741
+ [mode, onSelect, selected],
742
+ )
743
+
744
+ return (
745
+ <div
746
+ data-slot="calendar"
747
+ className={cn(
748
+ "w-fit rounded-md border border-zinc-200 bg-white p-3 text-zinc-900 shadow-sm",
749
+ className,
750
+ )}
751
+ >
752
+ <div className="mb-3 flex items-center justify-between gap-3">
753
+ <button
754
+ type="button"
755
+ className="inline-flex h-7 w-7 items-center justify-center rounded-md text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"
756
+ onClick={() => setMonth(addMonths(currentMonth, -1))}
757
+ aria-label="Previous month"
758
+ >
759
+ {"<"}
760
+ </button>
761
+ <div className="min-w-32 text-center text-sm font-medium">
762
+ {currentMonth.toLocaleDateString(undefined, {
763
+ month: "long",
764
+ year: "numeric",
765
+ })}
766
+ </div>
767
+ <button
768
+ type="button"
769
+ className="inline-flex h-7 w-7 items-center justify-center rounded-md text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"
770
+ onClick={() => setMonth(addMonths(currentMonth, 1))}
771
+ aria-label="Next month"
772
+ >
773
+ {">"}
774
+ </button>
775
+ </div>
776
+
777
+ <div className="grid grid-cols-7 gap-1 text-center text-[11px] font-medium uppercase tracking-[0.08em] text-zinc-500">
778
+ {["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => (
779
+ <div key={day} className="h-6 leading-6">
780
+ {day}
781
+ </div>
782
+ ))}
783
+ </div>
784
+ <div className="mt-1 grid grid-cols-7 gap-1">
785
+ {monthDays(currentMonth).map((date) => {
786
+ const outside = date.getMonth() !== currentMonth.getMonth()
787
+ const selectedSingle = isSameDay(date, selectedDate)
788
+ const selectedRangeStart = isSameDay(date, range?.from)
789
+ const selectedRangeEnd = isSameDay(date, range?.to)
790
+ const selectedRangeMiddle = isBetween(date, range?.from, range?.to)
791
+ const selectedState =
792
+ selectedSingle || selectedRangeStart || selectedRangeEnd
793
+ const inRange = selectedRangeMiddle && !selectedState
794
+
795
+ return (
796
+ <button
797
+ key={date.toISOString()}
798
+ type="button"
799
+ data-slot="calendar-day"
800
+ data-selected={selectedState || undefined}
801
+ data-range-middle={inRange || undefined}
802
+ onClick={() => selectDate(date)}
803
+ className={cn(
804
+ "h-8 w-8 rounded-md text-center text-sm tabular-nums outline-none transition-colors hover:bg-zinc-100 focus:bg-zinc-100",
805
+ outside && "text-zinc-300",
806
+ inRange && "bg-zinc-100 text-zinc-900",
807
+ selectedState && "bg-zinc-900 text-white hover:bg-zinc-800 focus:bg-zinc-800",
808
+ )}
809
+ >
810
+ {date.getDate()}
811
+ </button>
812
+ )
813
+ })}
814
+ </div>
815
+ </div>
816
+ )
817
+ }
818
+
819
+ function CalendarDayButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {
820
+ return <button type="button" {...props} />
821
+ }
822
+
823
+ function DatePicker({
824
+ value,
825
+ onValueChange,
826
+ placeholder = "Pick a date",
827
+ className,
828
+ }: {
829
+ value?: Date
830
+ onValueChange?: (value: Date | undefined) => void
831
+ placeholder?: string
832
+ className?: string
833
+ }) {
834
+ const [open, setOpen] = React.useState(false)
835
+
836
+ return (
837
+ <div className={cn("relative inline-block", className)}>
838
+ <button
839
+ type="button"
840
+ className="inline-flex h-9 min-w-40 items-center justify-between gap-2 rounded-md border border-zinc-200 bg-white px-3 text-sm text-zinc-900 shadow-sm hover:bg-zinc-50"
841
+ onClick={() => setOpen((next) => !next)}
842
+ >
843
+ <span className={cn(!value && "text-zinc-400")}>
844
+ {value ? formatDate(value) : placeholder}
845
+ </span>
846
+ <span className="text-xs text-zinc-400">▾</span>
847
+ </button>
848
+ {open ? (
849
+ <div className="absolute left-0 top-[calc(100%+4px)] z-50">
850
+ <Calendar
851
+ selected={value}
852
+ defaultMonth={value}
853
+ onSelect={(nextValue) => {
854
+ onValueChange?.(nextValue instanceof Date ? nextValue : undefined)
855
+ setOpen(false)
856
+ }}
857
+ />
858
+ </div>
859
+ ) : null}
860
+ </div>
861
+ )
862
+ }
863
+
864
+ export { Calendar, CalendarDayButton, DatePicker }
865
+ `},{path:"src/components/ui/chart.tsx",contents:`"use client"
866
+
867
+ import * as React from "react"
868
+ import * as RechartsPrimitive from "recharts"
869
+
870
+ import { cn } from "@/lib/utils"
871
+
872
+ export type ChartConfig = {
873
+ [key: string]: {
874
+ label?: React.ReactNode
875
+ color?: string
876
+ theme?: {
877
+ light?: string
878
+ dark?: string
879
+ }
880
+ }
881
+ }
882
+
883
+ type ChartContextProps = {
884
+ config: ChartConfig
885
+ }
886
+
887
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
888
+
889
+ function useChart() {
890
+ const context = React.useContext(ChartContext)
891
+ if (!context) {
892
+ throw new Error("useChart must be used within a ChartContainer")
893
+ }
894
+ return context
895
+ }
896
+
897
+ export function ChartContainer({
898
+ id,
899
+ className,
900
+ children,
901
+ config,
902
+ ...props
903
+ }: React.ComponentProps<"div"> & {
904
+ config: ChartConfig
905
+ children: React.ReactNode
906
+ }) {
907
+ const uniqueId = React.useId().replace(/:/g, "")
908
+ const chartId = "chart-" + (id || uniqueId)
909
+
910
+ return (
911
+ <ChartContext.Provider value={{ config }}>
912
+ <div
913
+ data-chart={chartId}
914
+ className={cn(
915
+ "flex aspect-video justify-center text-xs text-muted-foreground [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-none",
916
+ className,
917
+ )}
918
+ {...props}
919
+ >
920
+ <ChartStyle id={chartId} config={config} />
921
+ <RechartsPrimitive.ResponsiveContainer>
922
+ {children}
923
+ </RechartsPrimitive.ResponsiveContainer>
924
+ </div>
925
+ </ChartContext.Provider>
926
+ )
927
+ }
928
+
929
+ function ChartStyle({ id, config }: { id: string; config: ChartConfig }) {
930
+ const variables = Object.entries(config)
931
+ .map(([key, item]) => {
932
+ const color = item.theme?.light || item.color
933
+ return color ? " --color-" + key + ": " + color + ";" : null
934
+ })
935
+ .filter(Boolean)
936
+
937
+ if (!variables.length) return null
938
+
939
+ return (
940
+ <style
941
+ dangerouslySetInnerHTML={{
942
+ __html: "[data-chart=\\"" + id + "\\"] {\\n" + variables.join("\\n") + "\\n}",
943
+ }}
944
+ />
945
+ )
946
+ }
947
+
948
+ export const ChartTooltip = RechartsPrimitive.Tooltip
949
+ export const ChartLegend = RechartsPrimitive.Legend
950
+
951
+ export function ChartTooltipContent({
952
+ active,
953
+ payload,
954
+ label,
955
+ className,
956
+ hideLabel = false,
957
+ hideIndicator = false,
958
+ }: {
959
+ active?: boolean
960
+ payload?: Array<{
961
+ color?: string
962
+ dataKey?: string | number
963
+ name?: string | number
964
+ value?: React.ReactNode
965
+ }>
966
+ label?: React.ReactNode
967
+ className?: string
968
+ hideLabel?: boolean
969
+ hideIndicator?: boolean
970
+ }) {
971
+ const { config } = useChart()
972
+
973
+ if (!active || !payload?.length) return null
974
+
975
+ return (
976
+ <div className={cn("grid min-w-[8rem] gap-1.5 rounded-lg border border-border bg-background px-2.5 py-1.5 text-xs shadow-xl", className)}>
977
+ {!hideLabel && label ? <div className="font-medium text-foreground">{label}</div> : null}
978
+ <div className="grid gap-1.5">
979
+ {payload.map((item) => {
980
+ const key = String(item.dataKey || item.name || "")
981
+ const chartItem = config[key]
982
+ const color = item.color || chartItem?.color || "var(--color-" + key + ")"
983
+
984
+ return (
985
+ <div key={key} className="flex min-w-0 items-center gap-2">
986
+ {!hideIndicator ? (
987
+ <span
988
+ className="h-2.5 w-2.5 shrink-0 rounded-[2px]"
989
+ style={{ backgroundColor: color }}
990
+ />
991
+ ) : null}
992
+ <span className="min-w-0 flex-1 truncate text-muted-foreground">
993
+ {chartItem?.label || item.name || key}
994
+ </span>
995
+ <span className="font-mono font-medium tabular-nums text-foreground">
996
+ {typeof item.value === "number" ? item.value.toLocaleString() : item.value}
997
+ </span>
998
+ </div>
999
+ )
1000
+ })}
1001
+ </div>
1002
+ </div>
1003
+ )
1004
+ }
1005
+
1006
+ export function ChartLegendContent({
1007
+ payload,
1008
+ className,
1009
+ }: {
1010
+ payload?: Array<{ value?: string | number; color?: string }>
1011
+ className?: string
1012
+ }) {
1013
+ const { config } = useChart()
1014
+
1015
+ if (!payload?.length) return null
1016
+
1017
+ return (
1018
+ <div className={cn("flex flex-wrap items-center justify-center gap-4 text-xs", className)}>
1019
+ {payload.map((item) => {
1020
+ const key = String(item.value || "")
1021
+ const chartItem = config[key]
1022
+ return (
1023
+ <div key={key} className="flex items-center gap-1.5">
1024
+ <span
1025
+ className="h-2.5 w-2.5 rounded-[2px]"
1026
+ style={{ backgroundColor: item.color || chartItem?.color || "var(--color-" + key + ")" }}
1027
+ />
1028
+ <span className="text-muted-foreground">{chartItem?.label || key}</span>
1029
+ </div>
1030
+ )
1031
+ })}
1032
+ </div>
1033
+ )
1034
+ }
1035
+ `},{path:"src/components/ui/table.tsx",contents:`import type { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from "react"
1036
+
1037
+ import { cn } from "@/lib/utils"
1038
+
1039
+ export function Table(props: HTMLAttributes<HTMLTableElement>) {
1040
+ return <table {...props} className={cn("w-full caption-bottom text-sm", props.className)} />
1041
+ }
1042
+
1043
+ export function TableHeader(props: HTMLAttributes<HTMLTableSectionElement>) {
1044
+ return <thead {...props} className={cn("bg-zinc-50 text-xs uppercase text-zinc-500", props.className)} />
1045
+ }
1046
+
1047
+ export function TableBody(props: HTMLAttributes<HTMLTableSectionElement>) {
1048
+ return <tbody {...props} className={cn("divide-y divide-zinc-200 bg-white", props.className)} />
1049
+ }
1050
+
1051
+ export function TableRow(props: HTMLAttributes<HTMLTableRowElement>) {
1052
+ return <tr {...props} className={cn("border-b border-zinc-200 last:border-0", props.className)} />
1053
+ }
1054
+
1055
+ export function TableHead(props: ThHTMLAttributes<HTMLTableCellElement>) {
1056
+ return <th {...props} className={cn("h-10 whitespace-nowrap px-4 text-left align-middle font-medium", props.className)} />
1057
+ }
1058
+
1059
+ export function TableCell(props: TdHTMLAttributes<HTMLTableCellElement>) {
1060
+ return <td {...props} className={cn("whitespace-nowrap px-4 py-3 align-middle text-zinc-600", props.className)} />
1061
+ }
1062
+ `}],C=[{id:"semaphor-analytics",name:"Semaphor Analytics Starter",description:"Approved client-side React starter with Semaphor data hooks, shadcn source components, Tailwind v4, and Recharts.",files:u}],M=C[0].id;function p(e){return e.map(t=>({path:m(t.path),contents:t.contents})).sort((t,r)=>t.path.localeCompare(r.path))}function W(e=M){return C.find(t=>t.id===e)||C[0]}const Z={id:"semaphor-browser-runtime-v1",name:"Semaphor Browser Runtime",execution:"browser-sandbox",moduleSystem:"virtual-tsx-commonjs",cssPipeline:{mode:"tailwind-v4-or-fallback",status:"The runtime tries Tailwind v4 candidate compilation and reports when it falls back to bundled utility CSS. Unsupported package CSS requires Local Bridge."},packages:[{name:"react",source:"host",status:"supported"},{name:"react-dom/client",source:"host",status:"supported"},{name:"react-semaphor/data-app-sdk",source:"runtime",status:"supported"},{name:"@semaphor/data-app-sdk",source:"runtime",status:"supported"},{name:"lucide-react",source:"shim",status:"partial"},{name:"recharts",source:"host",status:"partial"},{name:"clsx",source:"runtime",status:"supported"},{name:"tailwind-merge",source:"runtime",status:"supported"},{name:"class-variance-authority",source:"shim",status:"partial"},{name:"tailwindcss",source:"runtime",status:"partial"}],components:[{name:"badge",importPath:"@/components/ui/badge",sourceFile:"src/components/ui/badge.tsx",implementation:"source",dependencies:[]},{name:"button",importPath:"@/components/ui/button",sourceFile:"src/components/ui/button.tsx",implementation:"source",dependencies:[]},{name:"card",importPath:"@/components/ui/card",sourceFile:"src/components/ui/card.tsx",implementation:"source",dependencies:[]},{name:"chart",importPath:"@/components/ui/chart",sourceFile:"src/components/ui/chart.tsx",implementation:"source",dependencies:["recharts"]},{name:"select",importPath:"@/components/ui/select",sourceFile:"src/components/ui/select.tsx",implementation:"source",dependencies:[]},{name:"progress",importPath:"@/components/ui/progress",sourceFile:"src/components/ui/progress.tsx",implementation:"source",dependencies:[]},{name:"calendar",importPath:"@/components/ui/calendar",sourceFile:"src/components/ui/calendar.tsx",implementation:"source",dependencies:[]},{name:"table",importPath:"@/components/ui/table",sourceFile:"src/components/ui/table.tsx",implementation:"source",dependencies:[]}],limits:{writeScope:["src/**"],editableGlobs:["src/**"],unsupported:["package edits","arbitrary npm installs","server or Node APIs","Vite plugin changes","environment files"]}},y={projectRoot:x,framework:"vite-react",packageManager:"browser-sandbox",runtimeManifest:Z,packageJson:{dependencies:{"@semaphor/data-app-sdk":"bundled","class-variance-authority":"bundled",clsx:"bundled","lucide-react":"bundled",react:"bundled","react-dom":"bundled","react-semaphor":"bundled",recharts:"bundled","tailwind-merge":"bundled",tailwindcss:"bundled"},devDependencies:{"@vitejs/plugin-react":"browser-sandbox",typescript:"browser-sandbox",vite:"browser-sandbox"},scripts:{dev:"browser-sandbox dev",build:"browser-sandbox build",typecheck:"browser-sandbox validate"}},componentsJson:{aliases:{ui:"@/components/ui"}},tsconfig:{baseUrl:".",paths:{"@/*":["src/*"]},jsx:"react-jsx",strict:!0,noUnusedLocals:!0,noUnusedParameters:!0},files:{source:u.map(e=>e.path),root:["package.json","vite.config.ts","tsconfig.json"],editable:u.map(e=>e.path),editableGlobs:["src/**"],approvalRequiredGlobs:["package.json","package-lock.json",".env*","*.config.*","vite.config.*","tsconfig*.json"],styleEntries:["src/index.css"],localModules:u.map(e=>e.path).filter(e=>/\.(ts|tsx|js|jsx)$/.test(e))},validation:{typecheck:"browser sandbox static validation",build:"browser sandbox static export"},capabilities:{version:1,source:"browser-sandbox-template",uiComponents:[{name:"badge",importPath:"@/components/ui/badge",exports:["Badge"],example:{imports:'import { Badge } from "@/components/ui/badge"',usage:'<Badge variant="secondary">Ready</Badge>'}},{name:"button",importPath:"@/components/ui/button",exports:["Button"],example:{imports:'import { Button } from "@/components/ui/button"',usage:'<Button variant="outline">Reset</Button>'}},{name:"card",importPath:"@/components/ui/card",exports:["Card","CardContent","CardDescription","CardHeader","CardTitle"],example:{imports:'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"',usage:"<Card><CardHeader><CardTitle>Title</CardTitle><CardDescription>Context</CardDescription></CardHeader><CardContent>Content</CardContent></Card>"}},{name:"chart",importPath:"@/components/ui/chart",exports:["ChartConfig","ChartContainer","ChartLegend","ChartLegendContent","ChartTooltip","ChartTooltipContent"],example:{imports:'import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"',usage:'<ChartContainer config={chartConfig} className="h-[280px] w-full"><BarChart data={data}><ChartTooltip content={<ChartTooltipContent />} /></BarChart></ChartContainer>'}},{name:"select",importPath:"@/components/ui/select",exports:["Select","SelectContent","SelectGroup","SelectItem","SelectLabel","SelectSeparator","SelectTrigger","SelectValue"],example:{imports:'import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"',usage:'<Select value={value} onValueChange={setValue}><SelectTrigger className="w-48"><SelectValue placeholder="Segment" /></SelectTrigger><SelectContent><SelectItem value="enterprise">Enterprise</SelectItem></SelectContent></Select>'}},{name:"progress",importPath:"@/components/ui/progress",exports:["Progress"],example:{imports:'import { Progress } from "@/components/ui/progress"',usage:'<Progress value={72} className="h-2" />'}},{name:"calendar",importPath:"@/components/ui/calendar",exports:["Calendar","CalendarDayButton","DatePicker"],example:{imports:'import { DatePicker } from "@/components/ui/calendar"',usage:'<DatePicker value={date} onValueChange={setDate} placeholder="Order date" />'}},{name:"table",importPath:"@/components/ui/table",exports:["Table","TableBody","TableCell","TableHead","TableHeader","TableRow"],example:{imports:'import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"',usage:"<Table><TableHeader><TableRow><TableHead>Name</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>Northstar</TableCell></TableRow></TableBody></Table>"}}],dataApis:[{package:"react-semaphor/data-app-sdk",source:"bundled",exports:["SemaphorDataAppProvider","useSemaphorInput","useSemaphorInputOptions","useSemaphorMetric","useSemaphorRecords"],datasets:[{name:"orders",fields:["category","customer_segment","gross_margin","order_date","orders","region","revenue","satisfaction_score"]},{name:"customers",fields:["arr","customer_name","customer_segment","health_score","last_active_days","open_tickets","region"]}],examples:["const segmentInput = useSemaphorInput({ id: 'segment', kind: 'filter', field: 'customer_segment' })","const revenue = useSemaphorMetric({ id: 'revenue', dataset: 'orders', metric: 'revenue', inputs: [segmentInput] })","const rows = useSemaphorRecords({ id: 'trend', dataset: 'orders', dimensions: ['order_date'], measures: ['revenue'], inputs: [segmentInput] })"],constraints:["Semaphor input kind is only 'filter' or 'control'.","Pass input objects into metrics/records via inputs: [input].","useSemaphorMetric config uses metric, not measures.","useSemaphorRecords returns { records }, and records are plain objects."]}]}};function U(){return typeof window<"u"}function m(e){return e.replace(/\\/g,"/").replace(/\/+/g,"/").replace(/^\.\//,"")}function Q(e){const t=new Map(p(e).map(r=>[m(r.path),r]));for(const r of u){const a=m(r.path);t.has(a)||t.set(a,{path:a,contents:r.contents})}return Array.from(t.values()).sort((r,a)=>r.path.localeCompare(a.path))}function f(){if(!U())return p(u);try{const e=window.localStorage.getItem(O);if(!e)return p(u);const t=JSON.parse(e);if(!Array.isArray(t)||t.length===0)return p(u);const r=Q(t);return r.length!==t.length&&D(r),r}catch{return p(u)}}function D(e){U()&&window.localStorage.setItem(O,JSON.stringify(e))}function q(e=f()){return new Map(e.map(t=>[m(t.path),t.contents]))}function ee(e){const t=e.map(r=>m(r.path)).sort();return Object.assign(Object.assign({},y),{files:Object.assign(Object.assign({},y.files),{source:t,editable:t.filter(r=>/^src\/.+\.(ts|tsx|js|jsx|css|json)$/.test(r)),styleEntries:t.filter(r=>/(^|\/)(index|app|globals)\.css$/i.test(r)),localModules:t.filter(r=>/\.(ts|tsx|js|jsx)$/.test(r))})})}function te(){return C.map(e=>({id:e.id,name:e.name,description:e.description,fileCount:e.files.length}))}function re(e=M){D(p(W(e).files))}function ae(){return ee(f())}function ne(e){const t=q();return e.map(r=>({path:r,contents:t.get(m(r))||""}))}function oe(){return f().map(e=>({path:m(e.path),size:e.contents.length})).sort((e,t)=>e.path.localeCompare(t.path))}function se(e=M){const t=W(e);return{provider:"browser-sandbox-template",summary:`Seeded the ${t.name} template.`,changes:[{kind:"edit",label:`Replaced the virtual browser workspace with ${t.files.length} approved template files`}],files:p(t.files),replaceExistingFiles:!0,generationMeta:{model:"approved-template",strategy:t.id,durationMs:0,outputChars:t.files.reduce((r,a)=>r+a.contents.length,0)}}}function ie({files:e,sourcePath:t}){return{provider:"browser-sandbox-local-import",summary:`Imported approved browser files from ${t}.`,changes:[{kind:"edit",label:`Replaced the virtual browser workspace with ${e.length} local template files`}],files:p(e),replaceExistingFiles:!0,generationMeta:{model:"local-template-import",strategy:"dev-local-import",durationMs:0,outputChars:e.reduce((r,a)=>r+a.contents.length,0)}}}function v({command:e,ok:t,stderr:r="",stdout:a="",startedAt:n}){return{command:e,durationMs:Date.now()-n,exitCode:t?0:1,ok:t,stdout:a,stderr:r}}function le(e){const t=new Set([".ts",".tsx",".js",".jsx",".css",".json"]),r=[];for(const a of e){const n=m(a.path),o=n.match(/\.[^.]+$/),i=(o==null?void 0:o[0])||"";if(!a.path||n.startsWith("../")||n.startsWith("/")){r.push(`${a.path}: generated file path must be a normalized relative project path.`);continue}if(!n.startsWith("src/")){r.push(`${a.path}: browser sandbox revisions may only write files under src/**.`);continue}t.has(i)||r.push(`${a.path}: generated source files must use one of ${[...t].join(", ")}.`)}return{ok:r.length===0,diagnostics:r}}function ce(e){const t=new Set,r=[/import\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,/export\s+(?:type\s+)?[^'"]+\s+from\s+['"]([^'"]+)['"]/g,/import\(\s*['"]([^'"]+)['"]\s*\)/g];for(const a of r){let n;for(;n=a.exec(e);)t.add(n[1])}return[...t]}function de(e){if(e.startsWith("@")){const[t,r]=e.split("/");return r?`${t}/${r}`:e}return e.split("/")[0]}function me(e,t,r){const a=[],n=o=>{a.push(o);for(const i of[".tsx",".ts",".jsx",".js",".css",".json"])a.push(`${o}${i}`);for(const i of["index.tsx","index.ts","index.jsx","index.js"])a.push(`${o}/${i}`)};if(t.startsWith(".")){const o=m(e).split("/");o.pop();const i=[...o,...t.split("/")],l=[];for(const s of i)if(!(!s||s===".")){if(s===".."){l.pop();continue}l.push(s)}n(l.join("/"))}return t.startsWith("@/")&&n(`src/${t.slice(2)}`),a.some(o=>r.has(m(o)))}function ue(e,t=f()){var r,a;const n=Object.assign(Object.assign({},(r=y.packageJson)===null||r===void 0?void 0:r.dependencies),(a=y.packageJson)===null||a===void 0?void 0:a.devDependencies),o=new Set([...t.map(s=>m(s.path)),...e.map(s=>m(s.path))]),i=new Set(["child_process","crypto","fs","http","https","node:child_process","node:crypto","node:fs","node:http","node:https","node:path","path"]),l=[];for(const s of e)for(const d of ce(s.contents)){const b=de(d);if(i.has(d)||i.has(b)){l.push(`${s.path}: blocked server/runtime import "${d}". Browser sandbox apps must run in the browser.`);continue}if(d.startsWith(".")||d.startsWith("@/")){me(s.path,d,o)||l.push(`${s.path}: local import "${d}" does not resolve from the browser sandbox files.`);continue}n[b]||l.push(`${s.path}: package import "${d}" is not available in Browser Sandbox. Switch to Local Bridge for arbitrary npm packages.`)}return{ok:l.length===0,diagnostics:l}}function pe(e){const t=Date.now(),r=le(e.files),a=e.replaceExistingFiles?[]:f();if(!r.ok){const s=v({command:"browser sandbox write policy validation",ok:!1,stderr:r.diagnostics.join(`
1063
+ `),startedAt:t});return{ok:!1,projectRoot:x,provider:e.provider||"browser-sandbox",validation:"write-policy-validation",summary:e.summary,changes:e.changes,generationMeta:e.generationMeta,files:e.files.map(d=>({path:d.path,size:d.contents.length})),changedFiles:[],writePolicyValidation:r,attempts:[{label:e.attemptLabel||"initial",ok:!1,command:s}],error:"Generated files failed Browser Sandbox write policy validation."}}const n=ue(e.files,a);if(!n.ok){const s=v({command:"browser sandbox import validation",ok:!1,stderr:n.diagnostics.join(`
1064
+ `),startedAt:t});return{ok:!1,projectRoot:x,provider:e.provider||"browser-sandbox",validation:"import-validation",summary:e.summary,changes:e.changes,generationMeta:e.generationMeta,files:e.files.map(d=>({path:d.path,size:d.contents.length})),changedFiles:[],importValidation:n,attempts:[{label:e.attemptLabel||"initial",ok:!1,command:s}],error:"Generated files failed Browser Sandbox import validation."}}const o=e.replaceExistingFiles?new Map:q(),i=[];for(const s of e.files){const d=m(s.path);o.set(d,s.contents),i.push(d)}D([...o.entries()].map(([s,d])=>({path:s,contents:d})).sort((s,d)=>s.path.localeCompare(d.path)));const l=v({command:"browser sandbox static validation",ok:!0,stdout:"Write policy and import validation passed. Browser preview refresh is handled by app-builder.",startedAt:t});return{ok:!0,projectRoot:x,provider:e.provider||"browser-sandbox",validation:"typecheck",summary:e.summary,changes:e.changes,generationMeta:e.generationMeta,files:e.files.map(s=>({path:s.path,size:s.contents.length})),changedFiles:i,importValidation:n,attempts:[{label:e.attemptLabel||"initial",ok:!0,command:l}],command:l}}function fe(){const e={path:"src/data-app/index.tsx",contents:`import { useEffect, useRef, useState } from "react"
1065
+ import { Activity, AlertTriangle, BarChart3, Users } from "lucide-react"
1066
+ import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
1067
+ import {
1068
+ useSemaphorInput,
1069
+ useSemaphorInputOptions,
1070
+ useSemaphorMetric,
1071
+ useSemaphorRecords,
1072
+ } from "react-semaphor/data-app-sdk"
1073
+
1074
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
1075
+ import {
1076
+ ChartContainer,
1077
+ ChartTooltip,
1078
+ ChartTooltipContent,
1079
+ type ChartConfig,
1080
+ } from "@/components/ui/chart"
1081
+ import {
1082
+ Select,
1083
+ SelectContent,
1084
+ SelectItem,
1085
+ SelectTrigger,
1086
+ SelectValue,
1087
+ } from "@/components/ui/select"
1088
+
1089
+ export function DataApp() {
1090
+ const statusRef = useRef<HTMLSpanElement | null>(null)
1091
+ const [runtimeStatus, setRuntimeStatus] = useState("mounting")
1092
+
1093
+ useEffect(() => {
1094
+ statusRef.current?.setAttribute("data-ready", "true")
1095
+ setRuntimeStatus("interactive")
1096
+ }, [])
1097
+
1098
+ const segmentOptions = useSemaphorInputOptions({
1099
+ id: "segment-options",
1100
+ dataset: "customers",
1101
+ field: "customer_segment",
1102
+ })
1103
+ const regionOptions = useSemaphorInputOptions({
1104
+ id: "region-options",
1105
+ dataset: "customers",
1106
+ field: "region",
1107
+ })
1108
+ const segment = useSemaphorInput({
1109
+ id: "segment",
1110
+ kind: "filter",
1111
+ field: "customer_segment",
1112
+ value: "",
1113
+ options: [{ value: "", label: "All segments" }, ...segmentOptions.options],
1114
+ })
1115
+ const region = useSemaphorInput({
1116
+ id: "region",
1117
+ kind: "filter",
1118
+ field: "region",
1119
+ value: "",
1120
+ options: [{ value: "", label: "All regions" }, ...regionOptions.options],
1121
+ })
1122
+ const revenue = useSemaphorMetric({
1123
+ id: "revenue",
1124
+ dataset: "orders",
1125
+ metric: "revenue",
1126
+ comparison: "previous_period",
1127
+ inputs: [segment, region],
1128
+ })
1129
+
1130
+ const orders = useSemaphorMetric({
1131
+ id: "orders",
1132
+ dataset: "orders",
1133
+ metric: "orders",
1134
+ comparison: "previous_period",
1135
+ inputs: [segment, region],
1136
+ })
1137
+ const arr = useSemaphorMetric({
1138
+ id: "arr",
1139
+ dataset: "customers",
1140
+ metric: "arr",
1141
+ inputs: [segment, region],
1142
+ })
1143
+ const tickets = useSemaphorMetric({
1144
+ id: "tickets",
1145
+ dataset: "customers",
1146
+ metric: "open_tickets",
1147
+ inputs: [segment, region],
1148
+ })
1149
+ const customerHealth = useSemaphorRecords({
1150
+ id: "customer-health",
1151
+ dataset: "customers",
1152
+ dimensions: ["customer_name", "customer_segment", "region"],
1153
+ measures: ["health_score", "open_tickets", "arr"],
1154
+ inputs: [segment, region],
1155
+ limit: 6,
1156
+ })
1157
+ const chartData = customerHealth.records.map((row) => ({
1158
+ customer: String(row.customer_name).split(" ")[0],
1159
+ arr: Number(row.arr || 0),
1160
+ open_tickets: Number(row.open_tickets || 0),
1161
+ }))
1162
+ const chartConfig = {
1163
+ arr: {
1164
+ label: "ARR",
1165
+ color: "#2563eb",
1166
+ },
1167
+ open_tickets: {
1168
+ label: "Open tickets",
1169
+ color: "#f97316",
1170
+ },
1171
+ } satisfies ChartConfig
1172
+
1173
+ return (
1174
+ <main className="min-h-screen bg-zinc-50 p-4 text-zinc-950 sm:p-6">
1175
+ <section className="mx-auto max-w-6xl space-y-4">
1176
+ <div className="flex flex-col gap-3 rounded-xl border border-zinc-200 bg-white p-4 shadow-sm lg:flex-row lg:items-center lg:justify-between">
1177
+ <div className="min-w-0">
1178
+ <div className="mb-2 inline-flex items-center gap-2 rounded-full bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700">
1179
+ <BarChart3 className="size-4" />
1180
+ <span ref={statusRef}>Real React runtime: {runtimeStatus}</span>
1181
+ </div>
1182
+ <h1 className="text-2xl font-semibold tracking-tight">Revenue Command Center</h1>
1183
+ <p className="mt-1 text-sm text-zinc-500">
1184
+ Responsive filters, KPI cards, and customer watchlist render in-browser.
1185
+ </p>
1186
+ </div>
1187
+ <div className="grid gap-2 sm:grid-cols-2 lg:w-[360px]">
1188
+ <label className="text-xs font-medium text-zinc-600">
1189
+ Segment
1190
+ <Select value={String(segment.value ?? "")} onValueChange={segment.setValue}>
1191
+ <SelectTrigger className="mt-1 w-full">
1192
+ <SelectValue placeholder="All segments" />
1193
+ </SelectTrigger>
1194
+ <SelectContent>
1195
+ {segment.options.map((option) => (
1196
+ <SelectItem key={option.value} value={String(option.value)}>
1197
+ {option.label}
1198
+ </SelectItem>
1199
+ ))}
1200
+ </SelectContent>
1201
+ </Select>
1202
+ </label>
1203
+ <label className="text-xs font-medium text-zinc-600">
1204
+ Region
1205
+ <Select value={String(region.value ?? "")} onValueChange={region.setValue}>
1206
+ <SelectTrigger className="mt-1 w-full">
1207
+ <SelectValue placeholder="All regions" />
1208
+ </SelectTrigger>
1209
+ <SelectContent>
1210
+ {region.options.map((option) => (
1211
+ <SelectItem key={option.value} value={String(option.value)}>
1212
+ {option.label}
1213
+ </SelectItem>
1214
+ ))}
1215
+ </SelectContent>
1216
+ </Select>
1217
+ </label>
1218
+ </div>
1219
+ </div>
1220
+
1221
+ <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
1222
+ <Card>
1223
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1224
+ <CardTitle>Revenue</CardTitle>
1225
+ <Activity className="size-4 text-zinc-400" />
1226
+ </CardHeader>
1227
+ <CardContent>
1228
+ <div className="text-3xl font-semibold">
1229
+ {revenue.value?.toLocaleString() ?? "0"}
1230
+ </div>
1231
+ <p className="mt-1 text-xs text-zinc-500">Current order fixture total</p>
1232
+ </CardContent>
1233
+ </Card>
1234
+ <Card>
1235
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1236
+ <CardTitle>Orders</CardTitle>
1237
+ <BarChart3 className="size-4 text-zinc-400" />
1238
+ </CardHeader>
1239
+ <CardContent>
1240
+ <div className="text-3xl font-semibold">
1241
+ {orders.value?.toLocaleString() ?? "0"}
1242
+ </div>
1243
+ <p className="mt-1 text-xs text-zinc-500">Filtered order volume</p>
1244
+ </CardContent>
1245
+ </Card>
1246
+ <Card>
1247
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1248
+ <CardTitle>Customer ARR</CardTitle>
1249
+ <Users className="size-4 text-zinc-400" />
1250
+ </CardHeader>
1251
+ <CardContent>
1252
+ <div className="text-3xl font-semibold">
1253
+ {arr.value?.toLocaleString() ?? "0"}
1254
+ </div>
1255
+ <p className="mt-1 text-xs text-zinc-500">Responds to segment and region</p>
1256
+ </CardContent>
1257
+ </Card>
1258
+ <Card>
1259
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1260
+ <CardTitle>Open Tickets</CardTitle>
1261
+ <AlertTriangle className="size-4 text-zinc-400" />
1262
+ </CardHeader>
1263
+ <CardContent>
1264
+ <div className="text-3xl font-semibold">
1265
+ {tickets.value?.toLocaleString() ?? "0"}
1266
+ </div>
1267
+ <p className="mt-1 text-xs text-zinc-500">Support pressure signal</p>
1268
+ </CardContent>
1269
+ </Card>
1270
+ </div>
1271
+
1272
+ <Card>
1273
+ <CardHeader>
1274
+ <CardTitle>Customer ARR by account</CardTitle>
1275
+ </CardHeader>
1276
+ <CardContent>
1277
+ <ChartContainer config={chartConfig} className="h-[280px] w-full">
1278
+ <BarChart data={chartData} margin={{ left: 0, right: 12 }}>
1279
+ <CartesianGrid vertical={false} />
1280
+ <XAxis
1281
+ dataKey="customer"
1282
+ tickLine={false}
1283
+ axisLine={false}
1284
+ tickMargin={8}
1285
+ />
1286
+ <YAxis
1287
+ tickLine={false}
1288
+ axisLine={false}
1289
+ tickMargin={8}
1290
+ width={52}
1291
+ />
1292
+ <ChartTooltip content={<ChartTooltipContent />} />
1293
+ <Bar dataKey="arr" fill="var(--color-arr)" radius={[4, 4, 0, 0]} />
1294
+ </BarChart>
1295
+ </ChartContainer>
1296
+ </CardContent>
1297
+ </Card>
1298
+
1299
+ <Card>
1300
+ <CardHeader>
1301
+ <CardTitle>Customer health watchlist</CardTitle>
1302
+ </CardHeader>
1303
+ <CardContent>
1304
+ <div className="overflow-x-auto rounded-lg border border-zinc-200">
1305
+ <table className="min-w-full text-left text-sm">
1306
+ <thead className="bg-zinc-50 text-xs uppercase text-zinc-500">
1307
+ <tr>
1308
+ <th className="px-4 py-3 font-medium">Customer</th>
1309
+ <th className="px-4 py-3 font-medium">Segment</th>
1310
+ <th className="px-4 py-3 font-medium">Region</th>
1311
+ <th className="px-4 py-3 text-right font-medium">Health</th>
1312
+ <th className="px-4 py-3 text-right font-medium">Tickets</th>
1313
+ <th className="px-4 py-3 text-right font-medium">ARR</th>
1314
+ </tr>
1315
+ </thead>
1316
+ <tbody className="divide-y divide-zinc-200 bg-white">
1317
+ {customerHealth.records.map((row) => (
1318
+ <tr key={row.customer_name}>
1319
+ <td className="whitespace-nowrap px-4 py-3 font-medium text-zinc-900">
1320
+ {row.customer_name}
1321
+ </td>
1322
+ <td className="whitespace-nowrap px-4 py-3 text-zinc-600">
1323
+ {row.customer_segment}
1324
+ </td>
1325
+ <td className="whitespace-nowrap px-4 py-3 text-zinc-600">{row.region}</td>
1326
+ <td className="whitespace-nowrap px-4 py-3 text-right font-medium">
1327
+ {row.health_score}
1328
+ </td>
1329
+ <td className="whitespace-nowrap px-4 py-3 text-right">
1330
+ {row.open_tickets}
1331
+ </td>
1332
+ <td className="whitespace-nowrap px-4 py-3 text-right">
1333
+ {Number(row.arr || 0).toLocaleString()}
1334
+ </td>
1335
+ </tr>
1336
+ ))}
1337
+ </tbody>
1338
+ </table>
1339
+ </div>
1340
+ </CardContent>
1341
+ </Card>
1342
+ </section>
1343
+ </main>
1344
+ )
1345
+ }
1346
+ `};return{provider:"browser-sandbox",summary:"Applied a deterministic Browser Sandbox smoke revision.",changes:[{kind:"edit",label:"Replaced the virtual browser workspace with a known-good smoke app"}],files:u.map(t=>t.path===e.path?e:t),replaceExistingFiles:!0,generationMeta:{model:"deterministic-smoke",strategy:"browser-sandbox-smoke",durationMs:0,outputChars:0}}}function ge(){const e=Date.now(),t=v({command:"browser sandbox build",ok:!1,stderr:"Static export is not implemented yet. Next step: compile the virtual filesystem with the Browser Sandbox compiler worker, then upload dist/** to S3.",startedAt:e});return{ok:!1,projectRoot:x,provider:"browser-sandbox",validation:"build",changedFiles:[],attempts:[{label:"publish",ok:!1,command:t}],command:t,error:t.stderr}}async function he(){const e=f(),t=[];try{const r=await Promise.resolve().then(()=>require("./typescript-Cmizj1hi.js")).then(i=>i.typescript),a={},n=e.filter(i=>/\.(ts|tsx|js|jsx)$/.test(i.path));for(const i of n){const l=m(i.path),s=r.transpileModule(i.contents,{compilerOptions:{esModuleInterop:!0,jsx:r.JsxEmit.ReactJSX,module:r.ModuleKind.CommonJS,target:r.ScriptTarget.ES2020},fileName:l,reportDiagnostics:!0});for(const d of s.diagnostics||[])t.push(be(r,d));a[l]=s.outputText}if(a[R]||t.push(`${R}: browser sandbox entry file is missing.`),t.length>0)return{html:E(t),diagnostics:t};const o=await xe(e);return{html:je({css:o.css,diagnostics:t,entryPath:R,moduleSources:a}),diagnostics:t,warnings:o.warnings,cssMode:o.mode}}catch(r){const a=r instanceof Error?r.message:String(r);return{html:E([`Browser Sandbox compiler failed before rendering: ${a}`]),diagnostics:[a]}}}function be(e,t){const r=t.file&&typeof t.start=="number"?(()=>{const n=t.file.getLineAndCharacterOfPosition(t.start);return`${t.file.fileName}:${n.line+1}:${n.character+1}`})():"browser-sandbox",a=e.flattenDiagnosticMessageText(t.messageText,`
1347
+ `);return`${r}: ${a}`}async function xe(e){const t=e.filter(r=>r.path.endsWith(".css")).map(r=>r.contents).join(`
1348
+ `);try{const{compile:r}=await Promise.resolve().then(()=>require("./lib-Ce3zosXY.js")),a=we(t),n=await r(a,{from:void 0,loadStylesheet:ve});return{css:`${z}
1349
+ ${n.build([...K(e)])}`,mode:"tailwind",warnings:[]}}catch(r){const a=r instanceof Error?r.message:String(r),n=t.replace(/@import\s+["']tailwindcss["'];?/g,"");return{css:[z,`/* Browser Sandbox Tailwind compiler failed; using fallback utility CSS. ${a} */`,Re(e),n].join(`
1350
+ `),mode:"fallback",warnings:[`Tailwind compiler was unavailable, so Browser Sandbox used fallback utility CSS. ${a}`]}}}function we(e){return/@import\s+["']tailwindcss(?:\/[^"']*)?["'];?/.test(e)||/@tailwind\s+/.test(e)?e:`@import "tailwindcss";
1351
+ ${e}`}const j=new Map;async function ve(e,t){const r=e.replace(/^\.\//,"tailwindcss/"),a=r==="tailwindcss"?"/browser-sandbox/tailwind/index.css":r==="tailwindcss/theme"?"/browser-sandbox/tailwind/theme.css":r==="tailwindcss/preflight"?"/browser-sandbox/tailwind/preflight.css":r==="tailwindcss/utilities"?"/browser-sandbox/tailwind/utilities.css":null;if(!a)throw new Error(`Unsupported Tailwind stylesheet import: ${e}`);let n=j.get(a);if(!n){const o=await fetch(a);if(!o.ok)throw new Error(`Unable to load Tailwind stylesheet ${a}: ${o.status}`);n=await o.text(),j.set(a,n)}return{base:t||new URL("/browser-sandbox/tailwind/",window.location.origin).href,content:n,path:a}}const Ce={sm:"640px",md:"768px",lg:"1024px",xl:"1280px","2xl":"1536px"},ye={hover:":hover",focus:":focus",active:":active",disabled:":disabled"},G={0:"0",px:"1px","0.5":"0.125rem",1:"0.25rem","1.5":"0.375rem",2:"0.5rem","2.5":"0.625rem",3:"0.75rem","3.5":"0.875rem",4:"1rem",5:"1.25rem",6:"1.5rem",7:"1.75rem",8:"2rem",9:"2.25rem",10:"2.5rem",11:"2.75rem",12:"3rem",14:"3.5rem",16:"4rem",20:"5rem",24:"6rem",28:"7rem",32:"8rem",36:"9rem",40:"10rem",44:"11rem",48:"12rem",56:"14rem",64:"16rem",72:"18rem",80:"20rem",96:"24rem"},Se={"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%"},A={none:"none",xs:"20rem",sm:"24rem",md:"28rem",lg:"32rem",xl:"36rem","2xl":"42rem","3xl":"48rem","4xl":"56rem","5xl":"64rem","6xl":"72rem","7xl":"80rem",full:"100%",screen:"100vw"},L={xs:"font-size: 0.75rem; line-height: 1rem;",sm:"font-size: 0.875rem; line-height: 1.25rem;",base:"font-size: 1rem; line-height: 1.5rem;",lg:"font-size: 1.125rem; line-height: 1.75rem;",xl:"font-size: 1.25rem; line-height: 1.75rem;","2xl":"font-size: 1.5rem; line-height: 2rem;","3xl":"font-size: 1.875rem; line-height: 2.25rem;","4xl":"font-size: 2.25rem; line-height: 2.5rem;","5xl":"font-size: 3rem; line-height: 1;","6xl":"font-size: 3.75rem; line-height: 1;"},Ne={none:"0",sm:"0.125rem",DEFAULT:"0.25rem",md:"0.375rem",lg:"0.5rem",xl:"0.75rem","2xl":"1rem","3xl":"1.5rem",full:"9999px"},ke={sm:"0 1px 2px rgba(24, 24, 27, 0.06)",DEFAULT:"0 1px 3px rgba(24, 24, 27, 0.1), 0 1px 2px rgba(24, 24, 27, 0.06)",md:"0 4px 6px -1px rgba(24, 24, 27, 0.1), 0 2px 4px -2px rgba(24, 24, 27, 0.1)",lg:"0 10px 15px -3px rgba(24, 24, 27, 0.1), 0 4px 6px -4px rgba(24, 24, 27, 0.1)",none:"none"},Te={slate:{50:"#f8fafc",100:"#f1f5f9",200:"#e2e8f0",300:"#cbd5e1",400:"#94a3b8",500:"#64748b",600:"#475569",700:"#334155",800:"#1e293b",900:"#0f172a",950:"#020617"},zinc:{50:"#fafafa",100:"#f4f4f5",200:"#e4e4e7",300:"#d4d4d8",400:"#a1a1aa",500:"#71717a",600:"#52525b",700:"#3f3f46",800:"#27272a",900:"#18181b",950:"#09090b"},neutral:{50:"#fafafa",100:"#f5f5f5",200:"#e5e5e5",300:"#d4d4d4",400:"#a3a3a3",500:"#737373",600:"#525252",700:"#404040",800:"#262626",900:"#171717",950:"#0a0a0a"},red:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",500:"#ef4444",600:"#dc2626",700:"#b91c1c",900:"#7f1d1d"},amber:{50:"#fffbeb",100:"#fef3c7",200:"#fde68a",500:"#f59e0b",600:"#d97706",700:"#b45309",900:"#78350f"},yellow:{50:"#fefce8",100:"#fef9c3",200:"#fef08a",500:"#eab308",600:"#ca8a04",700:"#a16207",900:"#713f12"},green:{50:"#f0fdf4",100:"#dcfce7",200:"#bbf7d0",500:"#22c55e",600:"#16a34a",700:"#15803d",900:"#14532d"},emerald:{50:"#ecfdf5",100:"#d1fae5",200:"#a7f3d0",500:"#10b981",600:"#059669",700:"#047857",900:"#064e3b"},blue:{50:"#eff6ff",100:"#dbeafe",200:"#bfdbfe",500:"#3b82f6",600:"#2563eb",700:"#1d4ed8",900:"#1e3a8a"},indigo:{50:"#eef2ff",100:"#e0e7ff",200:"#c7d2fe",500:"#6366f1",600:"#4f46e5",700:"#4338ca",900:"#312e81"},purple:{50:"#faf5ff",100:"#f3e8ff",200:"#e9d5ff",500:"#a855f7",600:"#9333ea",700:"#7e22ce",900:"#581c87"},rose:{50:"#fff1f2",100:"#ffe4e6",200:"#fecdd3",500:"#f43f5e",600:"#e11d48",700:"#be123c",900:"#881337"}},V={background:"#ffffff",foreground:"#09090b",card:"#ffffff","card-foreground":"#09090b",muted:"#f4f4f5","muted-foreground":"#71717a",primary:"#18181b","primary-foreground":"#ffffff",secondary:"#f4f4f5","secondary-foreground":"#18181b",accent:"#f4f4f5","accent-foreground":"#18181b",destructive:"#dc2626","destructive-foreground":"#ffffff",border:"#e4e4e7",input:"#e4e4e7",ring:"#18181b",white:"#ffffff",black:"#000000",transparent:"transparent"},J={block:"display: block;","inline-block":"display: inline-block;",inline:"display: inline;",flex:"display: flex;","inline-flex":"display: inline-flex;",grid:"display: grid;",hidden:"display: none;",table:"display: table;","table-row":"display: table-row;","table-cell":"display: table-cell;",relative:"position: relative;",absolute:"position: absolute;",fixed:"position: fixed;",sticky:"position: sticky;","flex-row":"flex-direction: row;","flex-col":"flex-direction: column;","flex-wrap":"flex-wrap: wrap;","flex-nowrap":"flex-wrap: nowrap;","flex-1":"flex: 1 1 0%;","flex-auto":"flex: 1 1 auto;","flex-none":"flex: none;",grow:"flex-grow: 1;","grow-0":"flex-grow: 0;",shrink:"flex-shrink: 1;","shrink-0":"flex-shrink: 0;","items-start":"align-items: flex-start;","items-center":"align-items: center;","items-end":"align-items: flex-end;","items-stretch":"align-items: stretch;","justify-start":"justify-content: flex-start;","justify-center":"justify-content: center;","justify-end":"justify-content: flex-end;","justify-between":"justify-content: space-between;","justify-around":"justify-content: space-around;","content-start":"align-content: flex-start;","content-center":"align-content: center;","self-start":"align-self: flex-start;","self-center":"align-self: center;","self-stretch":"align-self: stretch;","overflow-hidden":"overflow: hidden;","overflow-auto":"overflow: auto;","overflow-x-auto":"overflow-x: auto;","overflow-y-auto":"overflow-y: auto;","overflow-x-hidden":"overflow-x: hidden;","overflow-y-hidden":"overflow-y: hidden;",truncate:"overflow: hidden; text-overflow: ellipsis; white-space: nowrap;","whitespace-nowrap":"white-space: nowrap;","whitespace-normal":"white-space: normal;","text-left":"text-align: left;","text-center":"text-align: center;","text-right":"text-align: right;","align-middle":"vertical-align: middle;","font-normal":"font-weight: 400;","font-medium":"font-weight: 500;","font-semibold":"font-weight: 600;","font-bold":"font-weight: 700;",uppercase:"text-transform: uppercase;",lowercase:"text-transform: lowercase;",capitalize:"text-transform: capitalize;","tracking-tight":"letter-spacing: 0;","tracking-normal":"letter-spacing: 0;","tracking-wide":"letter-spacing: 0.025em;","tracking-wider":"letter-spacing: 0.05em;","leading-none":"line-height: 1;","leading-tight":"line-height: 1.25;","leading-snug":"line-height: 1.375;","leading-normal":"line-height: 1.5;","leading-relaxed":"line-height: 1.625;","min-h-screen":"min-height: 100vh;","h-screen":"height: 100vh;","w-screen":"width: 100vw;","w-full":"width: 100%;","h-full":"height: 100%;","min-w-0":"min-width: 0;","min-h-0":"min-height: 0;","min-w-full":"min-width: 100%;","border-collapse":"border-collapse: collapse;","border-separate":"border-collapse: separate;","object-cover":"object-fit: cover;","object-contain":"object-fit: contain;","aspect-square":"aspect-ratio: 1 / 1;","aspect-video":"aspect-ratio: 16 / 9;","cursor-pointer":"cursor: pointer;","cursor-default":"cursor: default;","select-none":"user-select: none;","pointer-events-none":"pointer-events: none;"};function Re(e){const t=new Set;for(const r of K(e)){const a=ze(r);a&&t.add(a)}return[...t].join(`
1352
+ `)}function K(e){const t=new Set,r=a=>{a.split(/\s+/).map(n=>n.trim().replace(/[;,]+$/g,"")).filter(Boolean).filter(n=>!n.includes("\\")&&!n.includes('"')).filter(_e).forEach(n=>t.add(n))};for(const a of e){if(!/\.(ts|tsx|js|jsx)$/.test(a.path))continue;const n=/\bclassName\s*=\s*(["'`])([^"'`]+)\1/g;let o;for(;o=n.exec(a.contents);)r(o[2]);const i=/(["'`])([^"'`]*(?:-|:|\[)[^"'`]*)\1/g;let l;for(;l=i.exec(a.contents);)r(l[2])}return t}function _e(e){if(!e||e.includes("${")||e.startsWith("@"))return!1;const t=X(e).base;return t?e.includes("[")||e.includes(":")||t in J||/^(accent|basis|bg|border|caret|col-span|content|decoration|delay|divide-y|divide-x|divide|duration|ease|fill|font|from|gap|gap-x|gap-y|grid-cols|grid-rows|h|inset|items|justify|leading|m|mb|ml|mr|mt|mx|my|max-h|max-w|min-h|min-w|object|opacity|order|outline|overflow|overscroll|p|pb|pl|pr|pt|px|py|ring|rotate|rounded|row-span|scale|scroll|self|shadow|shrink|size|skew|space-y|space-x|stroke|text|to|top|right|bottom|left|tracking|transition|translate|via|w|whitespace|z)-/.test(t)||/^(tabular-nums|sr-only|not-sr-only|truncate|container|grow|isolate|invisible|visible)$/.test(t)||/^-?(mt|mr|mb|ml|mx|my|m|top|right|bottom|left)-/.test(t):!1}function X(e){const t=e.split(":");return{variants:t.slice(0,-1),base:t[t.length-1]}}function ze(e){const{variants:t,base:r}=X(e),a=Me(r);if(!a)return null;const n=t.map(l=>ye[l]).filter(Boolean).join(""),o=`.${Pe(e)}${n}`;let i=a.includes("&")?a.replace(/&/g,o):`${o} { ${a} }`;for(const l of t.slice().reverse()){const s=Ce[l];s&&(i=`@media (min-width: ${s}) { ${i} }`)}return i}function Me(e){const t=J[e];if(t)return t;const r=e.startsWith("-"),a=r?e.slice(1):e,n=a.match(/^(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y|space-y|space-x)-(.+)$/);if(n){const c=_(n[2]);return c?De(n[1],r?`-${c}`:c):null}const o=a.match(/^(size|w|h|min-w|max-w|min-h|max-h)-(.+)$/);if(o){const c=$e(o[2],o[1]);if(!c)return null;const P={size:"size",w:"width",h:"height","min-w":"min-width","max-w":"max-width","min-h":"min-height","max-h":"max-height"}[o[1]];return P==="size"?`width: ${c}; height: ${c};`:`${P}: ${c};`}const i=a.match(/^grid-cols-(.+)$/);if(i){const c=I(i[1]);return c?`grid-template-columns: ${c};`:null}const l=a.match(/^grid-rows-(.+)$/);if(l){const c=I(l[1]);return c?`grid-template-rows: ${c};`:null}const s=a.match(/^col-span-(\d+)$/);if(s)return`grid-column: span ${s[1]} / span ${s[1]};`;const d=a.match(/^row-span-(\d+)$/);if(d)return`grid-row: span ${d[1]} / span ${d[1]};`;const b=Be(a);if(b)return b;const S=a.match(/^rounded(?:-(.+))?$/);if(S){const c=Ne[S[1]||"DEFAULT"]||h(S[1]||"");return c?`border-radius: ${c};`:null}const B=a.match(/^shadow(?:-(.+))?$/);if(B){const c=ke[B[1]||"DEFAULT"];return c?`box-shadow: ${c};`:null}const H=He(a);if(H)return H;const N=a.match(/^text-(.+)$/);if(N&&L[N[1]])return L[N[1]];const k=a.match(/^leading-(.+)$/);if(k){const c=G[k[1]]||h(k[1]);return c?`line-height: ${c};`:null}const $=a.match(/^opacity-(\d+)$/);if($)return`opacity: ${Number($[1])/100};`;const w=a.match(/^z-(.+)$/);if(w)return`z-index: ${w[1]==="auto"?"auto":h(w[1])||w[1]};`;const T=a.match(/^(inset|top|right|bottom|left)-(.+)$/);if(T){const c=_(T[2]);return c?`${T[1]}: ${r?`-${c}`:c};`:null}return null}function De(e,t){switch(e){case"p":return`padding: ${t};`;case"px":return`padding-left: ${t}; padding-right: ${t};`;case"py":return`padding-top: ${t}; padding-bottom: ${t};`;case"pt":return`padding-top: ${t};`;case"pr":return`padding-right: ${t};`;case"pb":return`padding-bottom: ${t};`;case"pl":return`padding-left: ${t};`;case"m":return`margin: ${t};`;case"mx":return`margin-left: ${t}; margin-right: ${t};`;case"my":return`margin-top: ${t}; margin-bottom: ${t};`;case"mt":return`margin-top: ${t};`;case"mr":return`margin-right: ${t};`;case"mb":return`margin-bottom: ${t};`;case"ml":return`margin-left: ${t};`;case"gap":return`gap: ${t};`;case"gap-x":return`column-gap: ${t};`;case"gap-y":return`row-gap: ${t};`;case"space-y":return`& > * + * { margin-top: ${t}; }`;case"space-x":return`& > * + * { margin-left: ${t}; }`;default:return""}}function Be(e){if(e==="border")return"border-width: 1px; border-style: solid; border-color: #e4e4e7;";if(e==="border-0")return"border-width: 0;";if(e==="border-t")return"border-top-width: 1px; border-top-style: solid; border-top-color: #e4e4e7;";if(e==="border-r")return"border-right-width: 1px; border-right-style: solid; border-right-color: #e4e4e7;";if(e==="border-b")return"border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #e4e4e7;";if(e==="border-l")return"border-left-width: 1px; border-left-style: solid; border-left-color: #e4e4e7;";const t=e.match(/^border-(\d+)$/);if(t)return`border-width: ${t[1]}px; border-style: solid;`;if(e==="divide-y")return"& > * + * { border-top-width: 1px; border-top-style: solid; border-color: #e4e4e7; }";if(e==="divide-x")return"& > * + * { border-left-width: 1px; border-left-style: solid; border-color: #e4e4e7; }";const r=g(e,"divide-");if(r)return`& > * + * { border-color: ${r}; }`;const a=g(e,"border-");return a?`border-color: ${a};`:null}function He(e){const t=g(e,"bg-");if(t)return`background-color: ${t};`;const r=g(e,"text-");if(r)return`color: ${r};`;const a=g(e,"fill-");if(a)return`fill: ${a};`;const n=g(e,"stroke-");return n?`stroke: ${n};`:null}function g(e,t){var r;if(!e.startsWith(t))return null;const a=e.slice(t.length),n=h(a);if(n)return n;if(V[a])return V[a];const o=a.split("-"),i=o.pop(),l=o.join("-");return!i||!l?null:((r=Te[l])===null||r===void 0?void 0:r[i])||null}function $e(e,t){return t==="max-w"&&A[e]?A[e]:e==="auto"?"auto":e==="full"?"100%":e==="screen"?t.includes("h")?"100vh":"100vw":e==="min"?"min-content":e==="max"?"max-content":e==="fit"?"fit-content":_(e)}function _(e){return G[e]||Se[e]||h(e)}function I(e){const t=h(e);if(t)return t;const r=Number(e);return Number.isInteger(r)&&r>0?`repeat(${r}, minmax(0, 1fr))`:e==="none"?"none":null}function h(e){if(!(e!=null&&e.startsWith("["))||!e.endsWith("]"))return null;const t=e.slice(1,-1).replace(/_/g," ");return/[;{}<>]/.test(t)?null:t}function Pe(e){return e.replace(/([^a-zA-Z0-9_-])/g,"\\$1")}const z=`
1353
+ * { box-sizing: border-box; }
1354
+ html, body, #root { margin: 0; min-width: 320px; min-height: 100%; }
1355
+ body { font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f6f7f8; color: #18181b; }
1356
+ button, input, select, textarea { font: inherit; }
1357
+ button, select { cursor: pointer; }
1358
+ table { border-collapse: collapse; width: 100%; }
1359
+ th, td { text-align: inherit; vertical-align: middle; }
1360
+ svg { display: block; flex-shrink: 0; }
1361
+ .min-h-screen { min-height: 100vh; }
1362
+ .bg-zinc-50 { background: #fafafa; }
1363
+ .bg-white { background: #ffffff; }
1364
+ .bg-zinc-900 { background: #18181b; }
1365
+ .bg-blue-50 { background: #eff6ff; }
1366
+ .bg-emerald-50 { background: #ecfdf5; }
1367
+ .bg-red-50 { background: #fef2f2; }
1368
+ .text-zinc-950 { color: #09090b; }
1369
+ .text-zinc-900 { color: #18181b; }
1370
+ .text-zinc-800 { color: #27272a; }
1371
+ .text-zinc-700 { color: #3f3f46; }
1372
+ .text-zinc-600 { color: #52525b; }
1373
+ .text-zinc-500 { color: #71717a; }
1374
+ .text-white { color: #ffffff; }
1375
+ .text-blue-700 { color: #1d4ed8; }
1376
+ .text-emerald-700 { color: #047857; }
1377
+ .text-red-700 { color: #b91c1c; }
1378
+ .mx-auto { margin-left: auto; margin-right: auto; }
1379
+ .mt-1 { margin-top: 0.25rem; }
1380
+ .mt-2 { margin-top: 0.5rem; }
1381
+ .mt-3 { margin-top: 0.75rem; }
1382
+ .mt-4 { margin-top: 1rem; }
1383
+ .mb-2 { margin-bottom: 0.5rem; }
1384
+ .mb-3 { margin-bottom: 0.75rem; }
1385
+ .mb-4 { margin-bottom: 1rem; }
1386
+ .ml-auto { margin-left: auto; }
1387
+ .flex { display: flex; }
1388
+ .inline-flex { display: inline-flex; }
1389
+ .grid { display: grid; }
1390
+ .hidden { display: none; }
1391
+ .block { display: block; }
1392
+ .items-center { align-items: center; }
1393
+ .items-start { align-items: flex-start; }
1394
+ .justify-between { justify-content: space-between; }
1395
+ .justify-center { justify-content: center; }
1396
+ .gap-1 { gap: 0.25rem; }
1397
+ .gap-2 { gap: 0.5rem; }
1398
+ .gap-3 { gap: 0.75rem; }
1399
+ .gap-4 { gap: 1rem; }
1400
+ .space-y-1 > * + * { margin-top: 0.25rem; }
1401
+ .space-y-2 > * + * { margin-top: 0.5rem; }
1402
+ .space-y-3 > * + * { margin-top: 0.75rem; }
1403
+ .max-w-3xl { max-width: 48rem; }
1404
+ .max-w-4xl { max-width: 56rem; }
1405
+ .max-w-5xl { max-width: 64rem; }
1406
+ .w-full { width: 100%; }
1407
+ .h-full { height: 100%; }
1408
+ .size-4 { width: 1rem; height: 1rem; }
1409
+ .size-5 { width: 1.25rem; height: 1.25rem; }
1410
+ .p-2 { padding: 0.5rem; }
1411
+ .p-3 { padding: 0.75rem; }
1412
+ .p-4 { padding: 1rem; }
1413
+ .p-6 { padding: 1.5rem; }
1414
+ .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
1415
+ .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
1416
+ .px-4 { padding-left: 1rem; padding-right: 1rem; }
1417
+ .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
1418
+ .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
1419
+ .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
1420
+ .pt-2 { padding-top: 0.5rem; }
1421
+ .pb-2 { padding-bottom: 0.5rem; }
1422
+ .rounded { border-radius: 0.25rem; }
1423
+ .rounded-md { border-radius: 0.375rem; }
1424
+ .rounded-lg { border-radius: 0.5rem; }
1425
+ .border { border-width: 1px; border-style: solid; border-color: #e4e4e7; }
1426
+ .border-zinc-100 { border-color: #f4f4f5; }
1427
+ .border-zinc-200 { border-color: #e4e4e7; }
1428
+ .border-blue-100 { border-color: #dbeafe; }
1429
+ .shadow-sm { box-shadow: 0 1px 2px rgba(24, 24, 27, 0.06); }
1430
+ .text-xs { font-size: 0.75rem; line-height: 1rem; }
1431
+ .text-sm { font-size: 0.875rem; line-height: 1.25rem; }
1432
+ .text-lg { font-size: 1.125rem; line-height: 1.75rem; }
1433
+ .text-xl { font-size: 1.25rem; line-height: 1.75rem; }
1434
+ .text-2xl { font-size: 1.5rem; line-height: 2rem; }
1435
+ .text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
1436
+ .font-medium { font-weight: 500; }
1437
+ .font-semibold { font-weight: 600; }
1438
+ .font-bold { font-weight: 700; }
1439
+ .uppercase { text-transform: uppercase; }
1440
+ .tracking-tight { letter-spacing: 0; }
1441
+ .leading-5 { line-height: 1.25rem; }
1442
+ .leading-6 { line-height: 1.5rem; }
1443
+ .overflow-hidden { overflow: hidden; }
1444
+ .overflow-auto { overflow: auto; }
1445
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1446
+ .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1447
+ .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1448
+ @media (min-width: 640px) {
1449
+ .sm\\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1450
+ .sm\\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1451
+ }
1452
+ `;function je({css:e,diagnostics:t,entryPath:r,moduleSources:a}){return`<!doctype html>
1453
+ <html>
1454
+ <head>
1455
+ <meta charset="utf-8" />
1456
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1457
+ <style>${Y(e)}</style>
1458
+ </head>
1459
+ <body>
1460
+ <div id="root"></div>
1461
+ <script>
1462
+ window.__SEMAPHOR_SANDBOX__ = {
1463
+ entryPath: ${JSON.stringify(r)},
1464
+ moduleSources: ${F(a)},
1465
+ diagnostics: ${F(t)}
1466
+ };
1467
+ <\/script>
1468
+ <script>${Le()}<\/script>
1469
+ </body>
1470
+ </html>`}function E(e){return`<!doctype html>
1471
+ <html>
1472
+ <head>
1473
+ <meta charset="utf-8" />
1474
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1475
+ <style>${Y(z)}</style>
1476
+ </head>
1477
+ <body>
1478
+ <main class="min-h-screen bg-zinc-50 p-6 text-zinc-950">
1479
+ <section class="mx-auto max-w-4xl rounded-lg border border-zinc-200 bg-white p-4 shadow-sm">
1480
+ <h1 class="text-lg font-semibold">Browser Sandbox compile failed</h1>
1481
+ <pre class="mt-3 overflow-auto rounded-md bg-zinc-900 p-3 text-xs text-white">${Ae(e.join(`
1482
+ `))}</pre>
1483
+ </section>
1484
+ </main>
1485
+ </body>
1486
+ </html>`}function F(e){return JSON.stringify(e).replace(/</g,"\\u003c")}function Y(e){return e.replace(/<\/style/gi,"<\\/style")}function Ae(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Le(){return`
1487
+ (() => {
1488
+ const sandbox = window.__SEMAPHOR_SANDBOX__;
1489
+ const moduleSources = sandbox.moduleSources || {};
1490
+ const moduleCache = {};
1491
+ const datasets = createDatasets();
1492
+ const vendor = window.parent?.__SEMAPHOR_BROWSER_SANDBOX_VENDOR__;
1493
+ if (!vendor?.React || !vendor?.ReactDOMClient) {
1494
+ throw new Error("Browser Sandbox vendor runtime is not available from the app-builder host.");
1495
+ }
1496
+ const React = vendor.React;
1497
+ const ReactDOMClient = vendor.ReactDOMClient;
1498
+ const recharts = vendor.Recharts || createRechartsFallback(React);
1499
+ const jsxRuntime = {
1500
+ Fragment: React.Fragment,
1501
+ jsx(type, props, key) {
1502
+ return React.createElement(type, key === undefined ? props : { ...(props || {}), key });
1503
+ },
1504
+ jsxs(type, props, key) {
1505
+ return React.createElement(type, key === undefined ? props : { ...(props || {}), key });
1506
+ },
1507
+ };
1508
+ const reactPackage = { ...React, default: React, __esModule: true };
1509
+ const reactDomClientPackage = { ...ReactDOMClient, default: ReactDOMClient, __esModule: true };
1510
+
1511
+ function createRechartsFallback(React) {
1512
+ function ChartShell({ children, className = "", ...props }) {
1513
+ return React.createElement(
1514
+ "div",
1515
+ {
1516
+ ...props,
1517
+ className: ["flex min-h-[180px] items-center justify-center rounded-md border border-dashed border-zinc-200 bg-zinc-50 text-xs text-zinc-500", className]
1518
+ .filter(Boolean)
1519
+ .join(" "),
1520
+ },
1521
+ React.createElement("div", { className: "space-y-2 text-center" }, [
1522
+ React.createElement("div", { key: "label", className: "font-medium text-zinc-700" }, "Chart preview"),
1523
+ React.createElement("div", { key: "meta" }, "Recharts is not bundled in this host; generated chart code is still import-valid."),
1524
+ React.createElement("div", { key: "children", className: "hidden" }, children),
1525
+ ]),
1526
+ );
1527
+ }
1528
+
1529
+ function NullPart({ children }) {
1530
+ return React.createElement(React.Fragment, null, children || null);
1531
+ }
1532
+
1533
+ return {
1534
+ Area: NullPart,
1535
+ AreaChart: ChartShell,
1536
+ Bar: NullPart,
1537
+ BarChart: ChartShell,
1538
+ CartesianGrid: NullPart,
1539
+ Cell: NullPart,
1540
+ Legend: NullPart,
1541
+ Line: NullPart,
1542
+ LineChart: ChartShell,
1543
+ Pie: NullPart,
1544
+ PieChart: ChartShell,
1545
+ ResponsiveContainer: ({ children, className = "", ...props }) =>
1546
+ React.createElement("div", { ...props, className: ["h-full w-full", className].filter(Boolean).join(" ") }, children),
1547
+ Tooltip: NullPart,
1548
+ XAxis: NullPart,
1549
+ YAxis: NullPart,
1550
+ };
1551
+ }
1552
+
1553
+ function createIcon(name) {
1554
+ return function Icon(props = {}) {
1555
+ return React.createElement(
1556
+ "span",
1557
+ {
1558
+ ...props,
1559
+ className: ["inline-flex size-5 items-center justify-center", props.className]
1560
+ .filter(Boolean)
1561
+ .join(" "),
1562
+ title: props.title || name,
1563
+ },
1564
+ "◇",
1565
+ );
1566
+ };
1567
+ }
1568
+
1569
+ const lucideReact = new Proxy({}, { get: (_target, key) => createIcon(String(key)) });
1570
+
1571
+ function useSemaphorRuntime() {
1572
+ return { token: "browser-sandbox", apiBaseUrl: "/api" };
1573
+ }
1574
+
1575
+ function SemaphorDataAppProvider({ children }) {
1576
+ return children;
1577
+ }
1578
+
1579
+ function useSemaphorInput(config) {
1580
+ const [value, setValue] = React.useState(config.value ?? config.defaultValue);
1581
+ const options = (config.options || []).map((option) =>
1582
+ typeof option === "object" ? option : { value: option, label: String(option) },
1583
+ );
1584
+ const input = {
1585
+ id: config.id,
1586
+ kind: config.kind,
1587
+ value,
1588
+ setValue(nextValue) {
1589
+ config.onValueChange?.(nextValue);
1590
+ setValue(nextValue);
1591
+ },
1592
+ options,
1593
+ isActive: hasInputValue(value),
1594
+ };
1595
+ if (config.kind === "filter") {
1596
+ input.filter = {
1597
+ field: config.field,
1598
+ operator: config.operator || "=",
1599
+ value,
1600
+ };
1601
+ } else {
1602
+ input.control = {
1603
+ role: config.role,
1604
+ value,
1605
+ };
1606
+ }
1607
+ return input;
1608
+ }
1609
+
1610
+ function useSemaphorInputOptions(config) {
1611
+ const rows = readDataset(config.dataset);
1612
+ const values = [...new Set(rows.map((row) => row[config.field]).filter((value) => typeof value === "string" || typeof value === "number"))];
1613
+ return {
1614
+ id: config.id,
1615
+ options: values.slice(0, config.limit || 50).map((value) => ({ value, label: String(value) })),
1616
+ isLoading: false,
1617
+ error: null,
1618
+ };
1619
+ }
1620
+
1621
+ function useSemaphorMetric(config) {
1622
+ useSemaphorRuntime();
1623
+ const rows = applyInputs(readDataset(config.dataset), config.inputs);
1624
+ const value = sumField(rows, config.metric);
1625
+ const comparisonValue =
1626
+ config.comparison === "target"
1627
+ ? config.targetValue ?? null
1628
+ : config.comparison && config.comparison !== "none"
1629
+ ? Math.round(value * 0.91)
1630
+ : null;
1631
+ const delta = comparisonValue === null ? null : value - comparisonValue;
1632
+ const deltaPercent = comparisonValue && comparisonValue !== 0 ? delta / comparisonValue : null;
1633
+ return {
1634
+ id: config.id,
1635
+ value,
1636
+ comparisonValue,
1637
+ delta,
1638
+ deltaPercent,
1639
+ trendline: buildTrendline(rows, config.dateField, config.metric),
1640
+ isLoading: false,
1641
+ error: null,
1642
+ };
1643
+ }
1644
+
1645
+ function useSemaphorRecords(config) {
1646
+ useSemaphorRuntime();
1647
+ const rows = applyInputs(readDataset(config.dataset), config.inputs);
1648
+ const records = shapeRecords({
1649
+ rows,
1650
+ measures: config.measures || [],
1651
+ dimensions: config.dimensions || [],
1652
+ inputs: config.inputs || [],
1653
+ limit: config.limit,
1654
+ });
1655
+ return {
1656
+ id: config.id,
1657
+ records,
1658
+ isLoading: false,
1659
+ error: null,
1660
+ metadata: {
1661
+ dataset: config.dataset,
1662
+ rowCount: records.length,
1663
+ source: "fixture",
1664
+ },
1665
+ };
1666
+ }
1667
+
1668
+ const semaphorSdk = {
1669
+ SemaphorDataAppProvider,
1670
+ useSemaphorRuntime,
1671
+ useSemaphorInput,
1672
+ useSemaphorInputOptions,
1673
+ useSemaphorMetric,
1674
+ useSemaphorRecords,
1675
+ };
1676
+
1677
+ function cx(...values) {
1678
+ const classes = [];
1679
+ const visit = (value) => {
1680
+ if (!value) return;
1681
+ if (typeof value === "string" || typeof value === "number") {
1682
+ classes.push(String(value));
1683
+ return;
1684
+ }
1685
+ if (Array.isArray(value)) {
1686
+ value.forEach(visit);
1687
+ return;
1688
+ }
1689
+ if (typeof value === "object") {
1690
+ Object.entries(value).forEach(([key, enabled]) => {
1691
+ if (enabled) classes.push(key);
1692
+ });
1693
+ }
1694
+ };
1695
+ values.forEach(visit);
1696
+ return classes.join(" ");
1697
+ }
1698
+
1699
+ const packages = {
1700
+ react: reactPackage,
1701
+ "react/jsx-runtime": jsxRuntime,
1702
+ "react-dom/client": reactDomClientPackage,
1703
+ "lucide-react": lucideReact,
1704
+ recharts,
1705
+ "@semaphor/data-app-sdk": semaphorSdk,
1706
+ "react-semaphor/data-app-sdk": semaphorSdk,
1707
+ clsx: { clsx: cx, default: cx },
1708
+ "tailwind-merge": { twMerge: cx },
1709
+ "class-variance-authority": { cva: () => () => "" },
1710
+ };
1711
+
1712
+ function executeModule(moduleId) {
1713
+ if (moduleCache[moduleId]) return moduleCache[moduleId].exports;
1714
+ const code = moduleSources[moduleId];
1715
+ if (!code) throw new Error("Missing sandbox module: " + moduleId);
1716
+ const module = { exports: {} };
1717
+ moduleCache[moduleId] = module;
1718
+ const localRequire = (specifier) => requireFrom(moduleId, specifier);
1719
+ new Function("require", "exports", "module", code)(
1720
+ localRequire,
1721
+ module.exports,
1722
+ module,
1723
+ );
1724
+ return module.exports;
1725
+ }
1726
+
1727
+ function requireFrom(importerPath, specifier) {
1728
+ if (specifier.endsWith(".css")) return {};
1729
+ if (packages[specifier]) return packages[specifier];
1730
+ const resolved = resolveSpecifier(importerPath, specifier);
1731
+ return executeModule(resolved);
1732
+ }
1733
+
1734
+ function resolveSpecifier(importerPath, specifier) {
1735
+ const candidates = [];
1736
+ const addCandidates = (basePath) => {
1737
+ candidates.push(basePath);
1738
+ [".tsx", ".ts", ".jsx", ".js"].forEach((extension) => candidates.push(basePath + extension));
1739
+ ["index.tsx", "index.ts", "index.jsx", "index.js"].forEach((indexFile) => candidates.push(basePath + "/" + indexFile));
1740
+ };
1741
+ if (specifier.startsWith("@/")) {
1742
+ addCandidates("src/" + specifier.slice(2));
1743
+ } else if (specifier.startsWith(".")) {
1744
+ const importerParts = importerPath.split("/");
1745
+ importerParts.pop();
1746
+ const parts = [...importerParts, ...specifier.split("/")];
1747
+ const resolvedParts = [];
1748
+ for (const part of parts) {
1749
+ if (!part || part === ".") continue;
1750
+ if (part === "..") resolvedParts.pop();
1751
+ else resolvedParts.push(part);
1752
+ }
1753
+ addCandidates(resolvedParts.join("/"));
1754
+ } else {
1755
+ throw new Error("Unsupported package in Browser Sandbox runtime: " + specifier);
1756
+ }
1757
+ const found = candidates.find((candidate) => moduleSources[candidate]);
1758
+ if (!found) throw new Error("Cannot resolve " + specifier + " from " + importerPath);
1759
+ return found;
1760
+ }
1761
+
1762
+ function readDataset(dataset) {
1763
+ return datasets[dataset] || datasets.orders;
1764
+ }
1765
+
1766
+ function hasInputValue(value) {
1767
+ if (value === undefined || value === null) return false;
1768
+ if (Array.isArray(value)) return value.length > 0;
1769
+ if (typeof value === "string") return value.trim().length > 0;
1770
+ return true;
1771
+ }
1772
+
1773
+ function applyInputs(rows, inputs) {
1774
+ return rows.filter((row) =>
1775
+ (inputs || []).every((input) => {
1776
+ if (input.kind !== "filter" || !input.filter || !hasInputValue(input.value)) return true;
1777
+ const candidate = row[input.filter.field];
1778
+ const value = input.value;
1779
+ switch (input.filter.operator) {
1780
+ case "in":
1781
+ return Array.isArray(value) ? value.includes(candidate) : candidate === value;
1782
+ case "not in":
1783
+ return Array.isArray(value) ? !value.includes(candidate) : candidate !== value;
1784
+ case "!=":
1785
+ return candidate !== value;
1786
+ case ">":
1787
+ return Number(candidate) > Number(value);
1788
+ case ">=":
1789
+ return Number(candidate) >= Number(value);
1790
+ case "<":
1791
+ return Number(candidate) < Number(value);
1792
+ case "<=":
1793
+ return Number(candidate) <= Number(value);
1794
+ case "between":
1795
+ return Array.isArray(value) && value.length >= 2
1796
+ ? String(candidate) >= String(value[0]) && String(candidate) <= String(value[1])
1797
+ : true;
1798
+ case "=":
1799
+ default:
1800
+ return candidate === value;
1801
+ }
1802
+ }),
1803
+ );
1804
+ }
1805
+
1806
+ function sumField(rows, field) {
1807
+ return rows.reduce((sum, row) => sum + Number(row[field] || 0), 0);
1808
+ }
1809
+
1810
+ function buildTrendline(rows, dateField, metric) {
1811
+ if (!dateField) return [];
1812
+ return groupRows(rows, [dateField], [metric], []).map((row) => ({
1813
+ date: String(row[dateField] || ""),
1814
+ value: Number(row[metric] || 0),
1815
+ }));
1816
+ }
1817
+
1818
+ function shapeRecords(params) {
1819
+ const grain = params.inputs.find((input) => input.kind === "control" && input.control?.role === "grain")?.value;
1820
+ const dimensions = params.dimensions.map((dimension) =>
1821
+ grain && /date/i.test(dimension) ? dimension + ":" + grain : dimension,
1822
+ );
1823
+ const records =
1824
+ params.measures.length > 0 && dimensions.length > 0
1825
+ ? groupRows(params.rows, dimensions, params.measures, params.inputs)
1826
+ : params.rows;
1827
+ return records.slice(0, params.limit || 500);
1828
+ }
1829
+
1830
+ function groupRows(rows, dimensions, measures, inputs) {
1831
+ const groups = new Map();
1832
+ for (const row of rows) {
1833
+ const dimensionValues = dimensions.map((dimension) => readDimensionValue(row, dimension, inputs));
1834
+ const key = JSON.stringify(dimensionValues);
1835
+ const existing =
1836
+ groups.get(key) ||
1837
+ Object.fromEntries(dimensions.map((dimension, index) => [cleanDimensionName(dimension), dimensionValues[index]]));
1838
+ for (const measure of measures) {
1839
+ existing[measure] = Number(existing[measure] || 0) + Number(row[measure] || 0);
1840
+ }
1841
+ groups.set(key, existing);
1842
+ }
1843
+ return [...groups.values()];
1844
+ }
1845
+
1846
+ function readDimensionValue(row, dimension) {
1847
+ const [field, grain] = dimension.split(":");
1848
+ const value = row[field];
1849
+ if (!grain || typeof value !== "string") return value;
1850
+ if (grain === "month") return value.slice(0, 7);
1851
+ if (grain === "year") return value.slice(0, 4);
1852
+ return value;
1853
+ }
1854
+
1855
+ function cleanDimensionName(dimension) {
1856
+ return dimension.split(":")[0];
1857
+ }
1858
+
1859
+ function createDatasets() {
1860
+ const orders = [
1861
+ { order_id: "ord_1001", order_date: "2026-01-08", customer_segment: "Technology", region: "West", category: "Software", revenue: 128400, gross_margin: 74200, orders: 164, satisfaction_score: 91 },
1862
+ { order_id: "ord_1002", order_date: "2026-01-19", customer_segment: "Healthcare", region: "Northeast", category: "Services", revenue: 84600, gross_margin: 41800, orders: 93, satisfaction_score: 87 },
1863
+ { order_id: "ord_1003", order_date: "2026-02-04", customer_segment: "Technology", region: "West", category: "Hardware", revenue: 151200, gross_margin: 81600, orders: 188, satisfaction_score: 93 },
1864
+ { order_id: "ord_1004", order_date: "2026-02-21", customer_segment: "Retail", region: "South", category: "Software", revenue: 97300, gross_margin: 52200, orders: 121, satisfaction_score: 82 },
1865
+ { order_id: "ord_1005", order_date: "2026-03-03", customer_segment: "Technology", region: "Central", category: "Services", revenue: 173900, gross_margin: 93100, orders: 206, satisfaction_score: 94 },
1866
+ { order_id: "ord_1006", order_date: "2026-03-14", customer_segment: "Healthcare", region: "West", category: "Software", revenue: 112700, gross_margin: 65800, orders: 138, satisfaction_score: 89 },
1867
+ { order_id: "ord_1007", order_date: "2026-04-02", customer_segment: "Retail", region: "Northeast", category: "Hardware", revenue: 134500, gross_margin: 62300, orders: 149, satisfaction_score: 84 },
1868
+ { order_id: "ord_1008", order_date: "2026-04-20", customer_segment: "Technology", region: "South", category: "Software", revenue: 196800, gross_margin: 111900, orders: 232, satisfaction_score: 95 },
1869
+ { order_id: "ord_1009", order_date: "2026-05-07", customer_segment: "Healthcare", region: "Central", category: "Services", revenue: 126300, gross_margin: 69100, orders: 157, satisfaction_score: 90 },
1870
+ { order_id: "ord_1010", order_date: "2026-05-24", customer_segment: "Technology", region: "West", category: "Software", revenue: 218600, gross_margin: 128300, orders: 251, satisfaction_score: 96 },
1871
+ ];
1872
+ const customers = [
1873
+ { customer_id: "cus_001", customer_name: "Northstar Systems", customer_segment: "Technology", region: "West", health_score: 94, arr: 420000, open_tickets: 2, last_active_days: 1 },
1874
+ { customer_id: "cus_002", customer_name: "Meridian Health", customer_segment: "Healthcare", region: "Northeast", health_score: 86, arr: 280000, open_tickets: 4, last_active_days: 3 },
1875
+ { customer_id: "cus_003", customer_name: "Atlas Retail Group", customer_segment: "Retail", region: "South", health_score: 72, arr: 190000, open_tickets: 9, last_active_days: 8 },
1876
+ { customer_id: "cus_004", customer_name: "Helio Cloud", customer_segment: "Technology", region: "Central", health_score: 88, arr: 335000, open_tickets: 3, last_active_days: 2 },
1877
+ { customer_id: "cus_005", customer_name: "Cedar Care", customer_segment: "Healthcare", region: "West", health_score: 81, arr: 245000, open_tickets: 5, last_active_days: 4 },
1878
+ { customer_id: "cus_006", customer_name: "Urban Outfit Co", customer_segment: "Retail", region: "Northeast", health_score: 67, arr: 160000, open_tickets: 12, last_active_days: 11 },
1879
+ ];
1880
+ return { orders, customers };
1881
+ }
1882
+
1883
+ function showRuntimeError(error) {
1884
+ const root = document.getElementById("root");
1885
+ root.innerHTML = "";
1886
+ const main = document.createElement("main");
1887
+ main.className = "min-h-screen bg-zinc-50 p-6 text-zinc-950";
1888
+ main.innerHTML = '<section class="mx-auto max-w-4xl rounded-lg border border-zinc-200 bg-white p-4 shadow-sm"><h1 class="text-lg font-semibold">Browser Sandbox runtime failed</h1><pre class="mt-3 overflow-auto rounded-md bg-zinc-900 p-3 text-xs text-white"></pre></section>';
1889
+ main.querySelector("pre").textContent = error?.stack || error?.message || String(error);
1890
+ root.appendChild(main);
1891
+ }
1892
+
1893
+ try {
1894
+ executeModule(sandbox.entryPath);
1895
+ } catch (error) {
1896
+ showRuntimeError(error);
1897
+ }
1898
+ })();
1899
+ `}function Ve(){const e=f(),t=e.find(a=>a.path==="src/data-app/index.tsx"),r=((t==null?void 0:t.contents)||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");return`<!doctype html>
1900
+ <html>
1901
+ <head>
1902
+ <meta charset="utf-8" />
1903
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1904
+ <style>
1905
+ body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f6f7f8; color: #18181b; }
1906
+ main { min-height: 100vh; padding: 24px; box-sizing: border-box; }
1907
+ .shell { max-width: 980px; margin: 0 auto; }
1908
+ .badge { display: inline-flex; align-items: center; border: 1px solid #bfdbfe; background: #eff6ff; color: #1d4ed8; border-radius: 6px; padding: 4px 8px; font-size: 12px; font-weight: 600; }
1909
+ .panel { margin-top: 16px; border: 1px solid #e4e4e7; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 2px rgba(24, 24, 27, 0.04); }
1910
+ .panel header { padding: 14px 16px; border-bottom: 1px solid #e4e4e7; }
1911
+ .panel h1 { margin: 0; font-size: 16px; }
1912
+ .panel p { margin: 6px 0 0; color: #71717a; font-size: 13px; line-height: 1.5; }
1913
+ pre { margin: 0; max-height: 520px; overflow: auto; padding: 16px; background: #09090b; color: #e4e4e7; font-size: 12px; line-height: 1.6; }
1914
+ .files { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; }
1915
+ .file { border-radius: 5px; background: #f4f4f5; color: #52525b; padding: 3px 7px; font-size: 11px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
1916
+ </style>
1917
+ </head>
1918
+ <body>
1919
+ <main>
1920
+ <div class="shell">
1921
+ <span class="badge">Browser Sandbox alpha</span>
1922
+ <section class="panel">
1923
+ <header>
1924
+ <h1>Virtual app workspace</h1>
1925
+ <p>The direct browser runtime is storing generated files in localStorage and validating write/import policy. The compiler worker and hot React preview are the next layer behind this mode.</p>
1926
+ <div class="files">
1927
+ ${e.map(a=>`<span class="file">${a.path}</span>`).join("")}
1928
+ </div>
1929
+ </header>
1930
+ <pre>${r}</pre>
1931
+ </section>
1932
+ </div>
1933
+ </main>
1934
+ </body>
1935
+ </html>`}exports.applyBrowserSandboxRevision=pe;exports.browserSandboxPreviewHtml=Ve;exports.compileBrowserSandboxPreviewHtml=he;exports.createBrowserSandboxImportedTemplateRevision=ie;exports.createBrowserSandboxSmokeRevision=fe;exports.createBrowserSandboxTemplateRevision=se;exports.listBrowserSandboxFiles=oe;exports.listBrowserSandboxTemplates=te;exports.readBrowserSandboxFiles=ne;exports.readBrowserSandboxWorkspaceContext=ae;exports.resetBrowserSandbox=re;exports.validateBrowserSandboxPublish=ge;