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,3247 @@
1
+ const F = "semaphor-data-app-browser-sandbox-files:v1", x = "browser://semaphor-data-app", R = "src/main.tsx", u = [
2
+ {
3
+ path: "src/App.tsx",
4
+ contents: `import { DataApp } from "./data-app"
5
+
6
+ export default function App() {
7
+ return <DataApp />
8
+ }
9
+ `
10
+ },
11
+ {
12
+ path: "src/main.tsx",
13
+ contents: `import { StrictMode } from "react"
14
+ import { createRoot } from "react-dom/client"
15
+ import App from "./App"
16
+ import "./index.css"
17
+
18
+ createRoot(document.getElementById("root")!).render(
19
+ <StrictMode>
20
+ <App />
21
+ </StrictMode>,
22
+ )
23
+ `
24
+ },
25
+ {
26
+ path: "src/index.css",
27
+ contents: `@import "tailwindcss";
28
+
29
+ @theme {
30
+ --color-background: #ffffff;
31
+ --color-foreground: #09090b;
32
+ --color-card: #ffffff;
33
+ --color-card-foreground: #09090b;
34
+ --color-muted: #f4f4f5;
35
+ --color-muted-foreground: #71717a;
36
+ --color-border: #e4e4e7;
37
+ --color-input: #e4e4e7;
38
+ --color-ring: #18181b;
39
+ }
40
+
41
+ body {
42
+ margin: 0;
43
+ min-width: 320px;
44
+ min-height: 100vh;
45
+ background: #f6f7f8;
46
+ }
47
+ `
48
+ },
49
+ {
50
+ path: "src/data-app/index.tsx",
51
+ contents: `import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
52
+ import { Activity, BarChart3, RefreshCw, Users } from "lucide-react"
53
+ import {
54
+ useSemaphorInput,
55
+ useSemaphorInputOptions,
56
+ useSemaphorMetric,
57
+ useSemaphorRecords,
58
+ } from "react-semaphor/data-app-sdk"
59
+
60
+ import { Badge } from "@/components/ui/badge"
61
+ import { Button } from "@/components/ui/button"
62
+ import {
63
+ Card,
64
+ CardContent,
65
+ CardDescription,
66
+ CardHeader,
67
+ CardTitle,
68
+ } from "@/components/ui/card"
69
+ import {
70
+ ChartContainer,
71
+ ChartTooltip,
72
+ ChartTooltipContent,
73
+ type ChartConfig,
74
+ } from "@/components/ui/chart"
75
+ import {
76
+ Select,
77
+ SelectContent,
78
+ SelectItem,
79
+ SelectTrigger,
80
+ SelectValue,
81
+ } from "@/components/ui/select"
82
+ import {
83
+ Table,
84
+ TableBody,
85
+ TableCell,
86
+ TableHead,
87
+ TableHeader,
88
+ TableRow,
89
+ } from "@/components/ui/table"
90
+
91
+ export function DataApp() {
92
+ const segmentOptions = useSemaphorInputOptions({
93
+ id: "segment-options",
94
+ dataset: "customers",
95
+ field: "customer_segment",
96
+ })
97
+ const regionOptions = useSemaphorInputOptions({
98
+ id: "region-options",
99
+ dataset: "customers",
100
+ field: "region",
101
+ })
102
+ const segment = useSemaphorInput({
103
+ id: "segment",
104
+ kind: "filter",
105
+ field: "customer_segment",
106
+ value: "",
107
+ options: [{ value: "", label: "All segments" }, ...segmentOptions.options],
108
+ })
109
+ const region = useSemaphorInput({
110
+ id: "region",
111
+ kind: "filter",
112
+ field: "region",
113
+ value: "",
114
+ options: [{ value: "", label: "All regions" }, ...regionOptions.options],
115
+ })
116
+ const revenue = useSemaphorMetric({
117
+ id: "revenue",
118
+ dataset: "orders",
119
+ metric: "revenue",
120
+ comparison: "previous_period",
121
+ inputs: [segment, region],
122
+ })
123
+ const arr = useSemaphorMetric({
124
+ id: "arr",
125
+ dataset: "customers",
126
+ metric: "arr",
127
+ inputs: [segment, region],
128
+ })
129
+ const tickets = useSemaphorMetric({
130
+ id: "tickets",
131
+ dataset: "customers",
132
+ metric: "open_tickets",
133
+ inputs: [segment, region],
134
+ })
135
+ const customerHealth = useSemaphorRecords({
136
+ id: "customer-health",
137
+ dataset: "customers",
138
+ dimensions: ["customer_name", "customer_segment", "region"],
139
+ measures: ["health_score", "open_tickets", "arr"],
140
+ inputs: [segment, region],
141
+ limit: 8,
142
+ })
143
+ const chartData = customerHealth.records.map((row) => ({
144
+ customer: String(row.customer_name).split(" ")[0],
145
+ arr: Number(row.arr || 0),
146
+ }))
147
+ const chartConfig = {
148
+ arr: {
149
+ label: "ARR",
150
+ color: "#2563eb",
151
+ },
152
+ } satisfies ChartConfig
153
+
154
+ const resetFilters = () => {
155
+ segment.setValue("")
156
+ region.setValue("")
157
+ }
158
+
159
+ return (
160
+ <main className="min-h-screen bg-zinc-50 p-4 text-zinc-950 sm:p-6">
161
+ <section className="mx-auto flex max-w-6xl flex-col gap-4">
162
+ <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">
163
+ <div className="min-w-0">
164
+ <Badge variant="secondary" className="mb-2 gap-1.5">
165
+ <BarChart3 className="size-3.5" />
166
+ Approved Browser Sandbox template
167
+ </Badge>
168
+ <h1 className="text-2xl font-semibold tracking-tight">Customer Revenue Console</h1>
169
+ <p className="mt-1 text-sm text-zinc-500">
170
+ Semaphor metrics, filters, shadcn source components, Tailwind, and Recharts in one browser-native starter.
171
+ </p>
172
+ </div>
173
+ <div className="grid gap-2 sm:grid-cols-[1fr_1fr_auto] lg:w-[520px]">
174
+ <label className="text-xs font-medium text-zinc-600">
175
+ Segment
176
+ <Select value={String(segment.value ?? "")} onValueChange={segment.setValue}>
177
+ <SelectTrigger className="mt-1 w-full">
178
+ <SelectValue placeholder="All segments" />
179
+ </SelectTrigger>
180
+ <SelectContent>
181
+ {segment.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
+ <label className="text-xs font-medium text-zinc-600">
190
+ Region
191
+ <Select value={String(region.value ?? "")} onValueChange={region.setValue}>
192
+ <SelectTrigger className="mt-1 w-full">
193
+ <SelectValue placeholder="All regions" />
194
+ </SelectTrigger>
195
+ <SelectContent>
196
+ {region.options.map((option) => (
197
+ <SelectItem key={option.value} value={String(option.value)}>
198
+ {option.label}
199
+ </SelectItem>
200
+ ))}
201
+ </SelectContent>
202
+ </Select>
203
+ </label>
204
+ <Button type="button" variant="outline" className="self-end" onClick={resetFilters}>
205
+ <RefreshCw className="size-4" />
206
+ Reset
207
+ </Button>
208
+ </div>
209
+ </div>
210
+
211
+ <div className="grid gap-3 md:grid-cols-3">
212
+ <Card>
213
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
214
+ <div>
215
+ <CardDescription>Total revenue</CardDescription>
216
+ <CardTitle className="mt-1 text-2xl text-zinc-950">
217
+ {revenue.value?.toLocaleString() ?? "0"}
218
+ </CardTitle>
219
+ </div>
220
+ <Activity className="size-4 text-zinc-400" />
221
+ </CardHeader>
222
+ </Card>
223
+ <Card>
224
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
225
+ <div>
226
+ <CardDescription>Customer ARR</CardDescription>
227
+ <CardTitle className="mt-1 text-2xl text-zinc-950">
228
+ {arr.value?.toLocaleString() ?? "0"}
229
+ </CardTitle>
230
+ </div>
231
+ <Users className="size-4 text-zinc-400" />
232
+ </CardHeader>
233
+ </Card>
234
+ <Card>
235
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
236
+ <div>
237
+ <CardDescription>Open tickets</CardDescription>
238
+ <CardTitle className="mt-1 text-2xl text-zinc-950">
239
+ {tickets.value?.toLocaleString() ?? "0"}
240
+ </CardTitle>
241
+ </div>
242
+ <Badge variant={tickets.value > 30 ? "destructive" : "secondary"}>
243
+ {tickets.value > 30 ? "Watch" : "Stable"}
244
+ </Badge>
245
+ </CardHeader>
246
+ </Card>
247
+ </div>
248
+
249
+ <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_420px]">
250
+ <Card>
251
+ <CardHeader>
252
+ <CardTitle>ARR by account</CardTitle>
253
+ <CardDescription>Real Recharts rendered through the shadcn chart wrapper.</CardDescription>
254
+ </CardHeader>
255
+ <CardContent>
256
+ <ChartContainer config={chartConfig} className="h-[300px] w-full">
257
+ <BarChart data={chartData} margin={{ left: 0, right: 12 }}>
258
+ <CartesianGrid vertical={false} />
259
+ <XAxis dataKey="customer" tickLine={false} axisLine={false} tickMargin={8} />
260
+ <YAxis tickLine={false} axisLine={false} tickMargin={8} width={52} />
261
+ <ChartTooltip content={<ChartTooltipContent />} />
262
+ <Bar dataKey="arr" fill="var(--color-arr)" radius={[4, 4, 0, 0]} />
263
+ </BarChart>
264
+ </ChartContainer>
265
+ </CardContent>
266
+ </Card>
267
+
268
+ <Card>
269
+ <CardHeader>
270
+ <CardTitle>Template coverage</CardTitle>
271
+ <CardDescription>Bundled source files the model can edit in-browser.</CardDescription>
272
+ </CardHeader>
273
+ <CardContent className="flex flex-col gap-2 text-sm">
274
+ {["shadcn card/table/button/badge/chart", "Tailwind v4 compiler", "Recharts SVG charts", "Semaphor data hooks"].map((item) => (
275
+ <div key={item} className="flex items-center justify-between gap-3 rounded-md border border-zinc-200 px-3 py-2">
276
+ <span className="text-zinc-700">{item}</span>
277
+ <Badge variant="outline">Ready</Badge>
278
+ </div>
279
+ ))}
280
+ </CardContent>
281
+ </Card>
282
+ </div>
283
+
284
+ <Card>
285
+ <CardHeader>
286
+ <CardTitle>Customer health watchlist</CardTitle>
287
+ <CardDescription>Filtered fixture rows shaped through the Semaphor data app SDK.</CardDescription>
288
+ </CardHeader>
289
+ <CardContent>
290
+ <div className="overflow-x-auto rounded-lg border border-zinc-200">
291
+ <Table>
292
+ <TableHeader>
293
+ <TableRow>
294
+ <TableHead>Customer</TableHead>
295
+ <TableHead>Segment</TableHead>
296
+ <TableHead>Region</TableHead>
297
+ <TableHead className="text-right">Health</TableHead>
298
+ <TableHead className="text-right">Tickets</TableHead>
299
+ <TableHead className="text-right">ARR</TableHead>
300
+ </TableRow>
301
+ </TableHeader>
302
+ <TableBody>
303
+ {customerHealth.records.map((row) => (
304
+ <TableRow key={row.customer_name}>
305
+ <TableCell className="font-medium text-zinc-900">{row.customer_name}</TableCell>
306
+ <TableCell>{row.customer_segment}</TableCell>
307
+ <TableCell>{row.region}</TableCell>
308
+ <TableCell className="text-right">{row.health_score}</TableCell>
309
+ <TableCell className="text-right">{row.open_tickets}</TableCell>
310
+ <TableCell className="text-right">
311
+ {Number(row.arr || 0).toLocaleString()}
312
+ </TableCell>
313
+ </TableRow>
314
+ ))}
315
+ </TableBody>
316
+ </Table>
317
+ </div>
318
+ </CardContent>
319
+ </Card>
320
+ </section>
321
+ </main>
322
+ )
323
+ }
324
+ `
325
+ },
326
+ {
327
+ path: "src/lib/utils.ts",
328
+ contents: `import { clsx, type ClassValue } from "clsx"
329
+ import { twMerge } from "tailwind-merge"
330
+
331
+ export function cn(...inputs: ClassValue[]) {
332
+ return twMerge(clsx(inputs))
333
+ }
334
+ `
335
+ },
336
+ {
337
+ path: "src/components/ui/badge.tsx",
338
+ contents: `import type { HTMLAttributes } from "react"
339
+
340
+ import { cn } from "@/lib/utils"
341
+
342
+ type BadgeVariant = "default" | "secondary" | "outline" | "destructive"
343
+
344
+ const variantClasses: Record<BadgeVariant, string> = {
345
+ default: "border-transparent bg-zinc-900 text-white",
346
+ secondary: "border-transparent bg-zinc-100 text-zinc-900",
347
+ outline: "border-zinc-200 bg-white text-zinc-700",
348
+ destructive: "border-transparent bg-red-100 text-red-700",
349
+ }
350
+
351
+ export function Badge({
352
+ className,
353
+ variant = "default",
354
+ ...props
355
+ }: HTMLAttributes<HTMLSpanElement> & { variant?: BadgeVariant }) {
356
+ return (
357
+ <span
358
+ {...props}
359
+ className={cn(
360
+ "inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium",
361
+ variantClasses[variant],
362
+ className,
363
+ )}
364
+ />
365
+ )
366
+ }
367
+ `
368
+ },
369
+ {
370
+ path: "src/components/ui/button.tsx",
371
+ contents: `import type { ButtonHTMLAttributes } from "react"
372
+
373
+ import { cn } from "@/lib/utils"
374
+
375
+ type ButtonVariant = "default" | "outline" | "ghost"
376
+ type ButtonSize = "default" | "sm"
377
+
378
+ const variantClasses: Record<ButtonVariant, string> = {
379
+ default: "bg-zinc-900 text-white hover:bg-zinc-700",
380
+ outline: "border border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50",
381
+ ghost: "text-zinc-700 hover:bg-zinc-100",
382
+ }
383
+
384
+ const sizeClasses: Record<ButtonSize, string> = {
385
+ default: "h-9 px-3",
386
+ sm: "h-8 px-2.5 text-xs",
387
+ }
388
+
389
+ export function Button({
390
+ className,
391
+ variant = "default",
392
+ size = "default",
393
+ ...props
394
+ }: ButtonHTMLAttributes<HTMLButtonElement> & {
395
+ variant?: ButtonVariant
396
+ size?: ButtonSize
397
+ }) {
398
+ return (
399
+ <button
400
+ {...props}
401
+ className={cn(
402
+ "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",
403
+ variantClasses[variant],
404
+ sizeClasses[size],
405
+ className,
406
+ )}
407
+ />
408
+ )
409
+ }
410
+ `
411
+ },
412
+ {
413
+ path: "src/components/ui/card.tsx",
414
+ contents: `import type { HTMLAttributes } from "react"
415
+
416
+ import { cn } from "@/lib/utils"
417
+
418
+ export function Card(props: HTMLAttributes<HTMLDivElement>) {
419
+ return <div {...props} className={cn("rounded-lg border border-zinc-200 bg-white shadow-sm", props.className)} />
420
+ }
421
+
422
+ export function CardHeader(props: HTMLAttributes<HTMLDivElement>) {
423
+ return <div {...props} className={cn("p-4 pb-2", props.className)} />
424
+ }
425
+
426
+ export function CardTitle(props: HTMLAttributes<HTMLHeadingElement>) {
427
+ return <h2 {...props} className={cn("text-sm font-medium text-zinc-600", props.className)} />
428
+ }
429
+
430
+ export function CardDescription(props: HTMLAttributes<HTMLParagraphElement>) {
431
+ return <p {...props} className={cn("text-xs text-zinc-500", props.className)} />
432
+ }
433
+
434
+ export function CardContent(props: HTMLAttributes<HTMLDivElement>) {
435
+ return <div {...props} className={cn("p-4 pt-2", props.className)} />
436
+ }
437
+ `
438
+ },
439
+ {
440
+ path: "src/components/ui/select.tsx",
441
+ contents: `import * as React from "react"
442
+
443
+ import { cn } from "@/lib/utils"
444
+
445
+ type SelectContextValue = {
446
+ value?: string
447
+ selectedLabel?: React.ReactNode
448
+ open: boolean
449
+ setOpen: (open: boolean) => void
450
+ setValue: (value: string, label: React.ReactNode) => void
451
+ }
452
+
453
+ const SelectContext = React.createContext<SelectContextValue | null>(null)
454
+
455
+ function useSelectContext(component: string) {
456
+ const context = React.useContext(SelectContext)
457
+ if (!context) {
458
+ throw new Error(component + " must be used within Select")
459
+ }
460
+ return context
461
+ }
462
+
463
+ function Select({
464
+ value,
465
+ defaultValue,
466
+ onValueChange,
467
+ children,
468
+ }: {
469
+ value?: string
470
+ defaultValue?: string
471
+ onValueChange?: (value: string) => void
472
+ children: React.ReactNode
473
+ }) {
474
+ const isControlled = Object.prototype.hasOwnProperty.call(
475
+ arguments[0] || {},
476
+ "value",
477
+ )
478
+ const [localValue, setLocalValue] = React.useState(defaultValue)
479
+ const [selectedLabel, setSelectedLabel] = React.useState<React.ReactNode>()
480
+ const [open, setOpen] = React.useState(false)
481
+ const currentValue = isControlled ? value : localValue
482
+
483
+ const setNextValue = React.useCallback(
484
+ (nextValue: string, label: React.ReactNode) => {
485
+ if (!isControlled) setLocalValue(nextValue)
486
+ setSelectedLabel(label)
487
+ onValueChange?.(nextValue)
488
+ setOpen(false)
489
+ },
490
+ [isControlled, onValueChange],
491
+ )
492
+
493
+ return (
494
+ <SelectContext.Provider
495
+ value={{
496
+ value: currentValue,
497
+ selectedLabel,
498
+ open,
499
+ setOpen,
500
+ setValue: setNextValue,
501
+ }}
502
+ >
503
+ <div className="relative inline-block min-w-0">{children}</div>
504
+ </SelectContext.Provider>
505
+ )
506
+ }
507
+
508
+ function SelectTrigger({
509
+ className,
510
+ children,
511
+ ...props
512
+ }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
513
+ const context = useSelectContext("SelectTrigger")
514
+
515
+ return (
516
+ <button
517
+ type="button"
518
+ data-slot="select-trigger"
519
+ aria-expanded={context.open}
520
+ onClick={() => context.setOpen(!context.open)}
521
+ className={cn(
522
+ "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",
523
+ className,
524
+ )}
525
+ {...props}
526
+ >
527
+ <span className="min-w-0 flex-1 truncate text-left">{children}</span>
528
+ <span className="text-xs text-zinc-400">▾</span>
529
+ </button>
530
+ )
531
+ }
532
+
533
+ function SelectValue({ placeholder }: { placeholder?: string }) {
534
+ const context = useSelectContext("SelectValue")
535
+ return (
536
+ <span
537
+ data-slot="select-value"
538
+ className={cn(!context.selectedLabel && "text-zinc-400")}
539
+ >
540
+ {context.selectedLabel || context.value || placeholder || "Select"}
541
+ </span>
542
+ )
543
+ }
544
+
545
+ function SelectContent({
546
+ className,
547
+ children,
548
+ }: {
549
+ className?: string
550
+ children: React.ReactNode
551
+ }) {
552
+ const context = useSelectContext("SelectContent")
553
+ if (!context.open) return null
554
+
555
+ return (
556
+ <div
557
+ data-slot="select-content"
558
+ className={cn(
559
+ "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",
560
+ className,
561
+ )}
562
+ >
563
+ {children}
564
+ </div>
565
+ )
566
+ }
567
+
568
+ function SelectItem({
569
+ value,
570
+ className,
571
+ children,
572
+ }: {
573
+ value: string
574
+ className?: string
575
+ children: React.ReactNode
576
+ }) {
577
+ const context = useSelectContext("SelectItem")
578
+ const selected = context.value === value
579
+
580
+ return (
581
+ <button
582
+ type="button"
583
+ data-slot="select-item"
584
+ data-state={selected ? "checked" : "unchecked"}
585
+ onClick={() => context.setValue(value, children)}
586
+ className={cn(
587
+ "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",
588
+ selected && "bg-zinc-100 font-medium",
589
+ className,
590
+ )}
591
+ >
592
+ <span className="min-w-0 truncate">{children}</span>
593
+ {selected ? <span className="text-xs text-zinc-500">✓</span> : null}
594
+ </button>
595
+ )
596
+ }
597
+
598
+ function SelectGroup({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
599
+ return <div data-slot="select-group" className={cn("p-1", className)} {...props} />
600
+ }
601
+
602
+ function SelectLabel({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
603
+ return <div data-slot="select-label" className={cn("px-2 py-1.5 text-xs font-medium text-zinc-500", className)} {...props} />
604
+ }
605
+
606
+ function SelectSeparator({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
607
+ return <div data-slot="select-separator" className={cn("-mx-1 my-1 h-px bg-zinc-200", className)} {...props} />
608
+ }
609
+
610
+ function SelectScrollUpButton() {
611
+ return null
612
+ }
613
+
614
+ function SelectScrollDownButton() {
615
+ return null
616
+ }
617
+
618
+ export {
619
+ Select,
620
+ SelectContent,
621
+ SelectGroup,
622
+ SelectItem,
623
+ SelectLabel,
624
+ SelectScrollDownButton,
625
+ SelectScrollUpButton,
626
+ SelectSeparator,
627
+ SelectTrigger,
628
+ SelectValue,
629
+ }
630
+ `
631
+ },
632
+ {
633
+ path: "src/components/ui/progress.tsx",
634
+ contents: `import type { HTMLAttributes } from "react"
635
+
636
+ import { cn } from "@/lib/utils"
637
+
638
+ function clampProgress(value: number | null | undefined) {
639
+ if (typeof value !== "number" || Number.isNaN(value)) return 0
640
+ return Math.max(0, Math.min(100, value))
641
+ }
642
+
643
+ function Progress({
644
+ className,
645
+ value,
646
+ ...props
647
+ }: HTMLAttributes<HTMLDivElement> & { value?: number | null }) {
648
+ const safeValue = clampProgress(value)
649
+
650
+ return (
651
+ <div
652
+ data-slot="progress"
653
+ role="progressbar"
654
+ aria-valuemin={0}
655
+ aria-valuemax={100}
656
+ aria-valuenow={safeValue}
657
+ className={cn(
658
+ "relative h-2 w-full overflow-hidden rounded-full bg-zinc-100",
659
+ className,
660
+ )}
661
+ {...props}
662
+ >
663
+ <div
664
+ data-slot="progress-indicator"
665
+ className="h-full w-full flex-1 rounded-full bg-zinc-900 transition-transform"
666
+ style={{ transform: "translateX(-" + (100 - safeValue) + "%)" }}
667
+ />
668
+ </div>
669
+ )
670
+ }
671
+
672
+ export { Progress }
673
+ `
674
+ },
675
+ {
676
+ path: "src/components/ui/calendar.tsx",
677
+ contents: `import * as React from "react"
678
+
679
+ import { cn } from "@/lib/utils"
680
+
681
+ type CalendarMode = "single" | "range"
682
+ type DateRange = { from?: Date; to?: Date }
683
+
684
+ function startOfMonth(date: Date) {
685
+ return new Date(date.getFullYear(), date.getMonth(), 1)
686
+ }
687
+
688
+ function addMonths(date: Date, count: number) {
689
+ return new Date(date.getFullYear(), date.getMonth() + count, 1)
690
+ }
691
+
692
+ function isSameDay(a?: Date, b?: Date) {
693
+ return Boolean(
694
+ a &&
695
+ b &&
696
+ a.getFullYear() === b.getFullYear() &&
697
+ a.getMonth() === b.getMonth() &&
698
+ a.getDate() === b.getDate(),
699
+ )
700
+ }
701
+
702
+ function isBetween(date: Date, from?: Date, to?: Date) {
703
+ if (!from || !to) return false
704
+ const value = date.getTime()
705
+ return value >= from.getTime() && value <= to.getTime()
706
+ }
707
+
708
+ function monthDays(month: Date) {
709
+ const first = startOfMonth(month)
710
+ const start = new Date(first)
711
+ start.setDate(first.getDate() - first.getDay())
712
+
713
+ return Array.from({ length: 42 }, (_, index) => {
714
+ const date = new Date(start)
715
+ date.setDate(start.getDate() + index)
716
+ return date
717
+ })
718
+ }
719
+
720
+ function formatDate(value?: Date) {
721
+ return value
722
+ ? value.toLocaleDateString(undefined, {
723
+ month: "short",
724
+ day: "numeric",
725
+ year: "numeric",
726
+ })
727
+ : ""
728
+ }
729
+
730
+ function Calendar({
731
+ className,
732
+ mode = "single",
733
+ selected,
734
+ defaultMonth,
735
+ month,
736
+ onMonthChange,
737
+ onSelect,
738
+ }: {
739
+ className?: string
740
+ mode?: CalendarMode
741
+ selected?: Date | DateRange
742
+ defaultMonth?: Date
743
+ month?: Date
744
+ onMonthChange?: (month: Date) => void
745
+ onSelect?: (value: Date | DateRange | undefined) => void
746
+ }) {
747
+ const [localMonth, setLocalMonth] = React.useState(
748
+ startOfMonth(month || defaultMonth || new Date()),
749
+ )
750
+ const currentMonth = startOfMonth(month || localMonth)
751
+ const range = mode === "range" && selected && !(selected instanceof Date)
752
+ ? selected
753
+ : undefined
754
+ const selectedDate = selected instanceof Date ? selected : undefined
755
+
756
+ const setMonth = React.useCallback(
757
+ (nextMonth: Date) => {
758
+ const normalized = startOfMonth(nextMonth)
759
+ if (!month) setLocalMonth(normalized)
760
+ onMonthChange?.(normalized)
761
+ },
762
+ [month, onMonthChange],
763
+ )
764
+
765
+ const selectDate = React.useCallback(
766
+ (date: Date) => {
767
+ if (mode === "range") {
768
+ const currentRange =
769
+ selected && !(selected instanceof Date) ? selected : undefined
770
+ if (!currentRange?.from || currentRange.to) {
771
+ onSelect?.({ from: date, to: undefined })
772
+ return
773
+ }
774
+ if (date.getTime() < currentRange.from.getTime()) {
775
+ onSelect?.({ from: date, to: currentRange.from })
776
+ return
777
+ }
778
+ onSelect?.({ from: currentRange.from, to: date })
779
+ return
780
+ }
781
+
782
+ onSelect?.(date)
783
+ },
784
+ [mode, onSelect, selected],
785
+ )
786
+
787
+ return (
788
+ <div
789
+ data-slot="calendar"
790
+ className={cn(
791
+ "w-fit rounded-md border border-zinc-200 bg-white p-3 text-zinc-900 shadow-sm",
792
+ className,
793
+ )}
794
+ >
795
+ <div className="mb-3 flex items-center justify-between gap-3">
796
+ <button
797
+ type="button"
798
+ className="inline-flex h-7 w-7 items-center justify-center rounded-md text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"
799
+ onClick={() => setMonth(addMonths(currentMonth, -1))}
800
+ aria-label="Previous month"
801
+ >
802
+ {"<"}
803
+ </button>
804
+ <div className="min-w-32 text-center text-sm font-medium">
805
+ {currentMonth.toLocaleDateString(undefined, {
806
+ month: "long",
807
+ year: "numeric",
808
+ })}
809
+ </div>
810
+ <button
811
+ type="button"
812
+ className="inline-flex h-7 w-7 items-center justify-center rounded-md text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"
813
+ onClick={() => setMonth(addMonths(currentMonth, 1))}
814
+ aria-label="Next month"
815
+ >
816
+ {">"}
817
+ </button>
818
+ </div>
819
+
820
+ <div className="grid grid-cols-7 gap-1 text-center text-[11px] font-medium uppercase tracking-[0.08em] text-zinc-500">
821
+ {["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => (
822
+ <div key={day} className="h-6 leading-6">
823
+ {day}
824
+ </div>
825
+ ))}
826
+ </div>
827
+ <div className="mt-1 grid grid-cols-7 gap-1">
828
+ {monthDays(currentMonth).map((date) => {
829
+ const outside = date.getMonth() !== currentMonth.getMonth()
830
+ const selectedSingle = isSameDay(date, selectedDate)
831
+ const selectedRangeStart = isSameDay(date, range?.from)
832
+ const selectedRangeEnd = isSameDay(date, range?.to)
833
+ const selectedRangeMiddle = isBetween(date, range?.from, range?.to)
834
+ const selectedState =
835
+ selectedSingle || selectedRangeStart || selectedRangeEnd
836
+ const inRange = selectedRangeMiddle && !selectedState
837
+
838
+ return (
839
+ <button
840
+ key={date.toISOString()}
841
+ type="button"
842
+ data-slot="calendar-day"
843
+ data-selected={selectedState || undefined}
844
+ data-range-middle={inRange || undefined}
845
+ onClick={() => selectDate(date)}
846
+ className={cn(
847
+ "h-8 w-8 rounded-md text-center text-sm tabular-nums outline-none transition-colors hover:bg-zinc-100 focus:bg-zinc-100",
848
+ outside && "text-zinc-300",
849
+ inRange && "bg-zinc-100 text-zinc-900",
850
+ selectedState && "bg-zinc-900 text-white hover:bg-zinc-800 focus:bg-zinc-800",
851
+ )}
852
+ >
853
+ {date.getDate()}
854
+ </button>
855
+ )
856
+ })}
857
+ </div>
858
+ </div>
859
+ )
860
+ }
861
+
862
+ function CalendarDayButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {
863
+ return <button type="button" {...props} />
864
+ }
865
+
866
+ function DatePicker({
867
+ value,
868
+ onValueChange,
869
+ placeholder = "Pick a date",
870
+ className,
871
+ }: {
872
+ value?: Date
873
+ onValueChange?: (value: Date | undefined) => void
874
+ placeholder?: string
875
+ className?: string
876
+ }) {
877
+ const [open, setOpen] = React.useState(false)
878
+
879
+ return (
880
+ <div className={cn("relative inline-block", className)}>
881
+ <button
882
+ type="button"
883
+ 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"
884
+ onClick={() => setOpen((next) => !next)}
885
+ >
886
+ <span className={cn(!value && "text-zinc-400")}>
887
+ {value ? formatDate(value) : placeholder}
888
+ </span>
889
+ <span className="text-xs text-zinc-400">▾</span>
890
+ </button>
891
+ {open ? (
892
+ <div className="absolute left-0 top-[calc(100%+4px)] z-50">
893
+ <Calendar
894
+ selected={value}
895
+ defaultMonth={value}
896
+ onSelect={(nextValue) => {
897
+ onValueChange?.(nextValue instanceof Date ? nextValue : undefined)
898
+ setOpen(false)
899
+ }}
900
+ />
901
+ </div>
902
+ ) : null}
903
+ </div>
904
+ )
905
+ }
906
+
907
+ export { Calendar, CalendarDayButton, DatePicker }
908
+ `
909
+ },
910
+ {
911
+ path: "src/components/ui/chart.tsx",
912
+ contents: `"use client"
913
+
914
+ import * as React from "react"
915
+ import * as RechartsPrimitive from "recharts"
916
+
917
+ import { cn } from "@/lib/utils"
918
+
919
+ export type ChartConfig = {
920
+ [key: string]: {
921
+ label?: React.ReactNode
922
+ color?: string
923
+ theme?: {
924
+ light?: string
925
+ dark?: string
926
+ }
927
+ }
928
+ }
929
+
930
+ type ChartContextProps = {
931
+ config: ChartConfig
932
+ }
933
+
934
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
935
+
936
+ function useChart() {
937
+ const context = React.useContext(ChartContext)
938
+ if (!context) {
939
+ throw new Error("useChart must be used within a ChartContainer")
940
+ }
941
+ return context
942
+ }
943
+
944
+ export function ChartContainer({
945
+ id,
946
+ className,
947
+ children,
948
+ config,
949
+ ...props
950
+ }: React.ComponentProps<"div"> & {
951
+ config: ChartConfig
952
+ children: React.ReactNode
953
+ }) {
954
+ const uniqueId = React.useId().replace(/:/g, "")
955
+ const chartId = "chart-" + (id || uniqueId)
956
+
957
+ return (
958
+ <ChartContext.Provider value={{ config }}>
959
+ <div
960
+ data-chart={chartId}
961
+ className={cn(
962
+ "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",
963
+ className,
964
+ )}
965
+ {...props}
966
+ >
967
+ <ChartStyle id={chartId} config={config} />
968
+ <RechartsPrimitive.ResponsiveContainer>
969
+ {children}
970
+ </RechartsPrimitive.ResponsiveContainer>
971
+ </div>
972
+ </ChartContext.Provider>
973
+ )
974
+ }
975
+
976
+ function ChartStyle({ id, config }: { id: string; config: ChartConfig }) {
977
+ const variables = Object.entries(config)
978
+ .map(([key, item]) => {
979
+ const color = item.theme?.light || item.color
980
+ return color ? " --color-" + key + ": " + color + ";" : null
981
+ })
982
+ .filter(Boolean)
983
+
984
+ if (!variables.length) return null
985
+
986
+ return (
987
+ <style
988
+ dangerouslySetInnerHTML={{
989
+ __html: "[data-chart=\\"" + id + "\\"] {\\n" + variables.join("\\n") + "\\n}",
990
+ }}
991
+ />
992
+ )
993
+ }
994
+
995
+ export const ChartTooltip = RechartsPrimitive.Tooltip
996
+ export const ChartLegend = RechartsPrimitive.Legend
997
+
998
+ export function ChartTooltipContent({
999
+ active,
1000
+ payload,
1001
+ label,
1002
+ className,
1003
+ hideLabel = false,
1004
+ hideIndicator = false,
1005
+ }: {
1006
+ active?: boolean
1007
+ payload?: Array<{
1008
+ color?: string
1009
+ dataKey?: string | number
1010
+ name?: string | number
1011
+ value?: React.ReactNode
1012
+ }>
1013
+ label?: React.ReactNode
1014
+ className?: string
1015
+ hideLabel?: boolean
1016
+ hideIndicator?: boolean
1017
+ }) {
1018
+ const { config } = useChart()
1019
+
1020
+ if (!active || !payload?.length) return null
1021
+
1022
+ return (
1023
+ <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)}>
1024
+ {!hideLabel && label ? <div className="font-medium text-foreground">{label}</div> : null}
1025
+ <div className="grid gap-1.5">
1026
+ {payload.map((item) => {
1027
+ const key = String(item.dataKey || item.name || "")
1028
+ const chartItem = config[key]
1029
+ const color = item.color || chartItem?.color || "var(--color-" + key + ")"
1030
+
1031
+ return (
1032
+ <div key={key} className="flex min-w-0 items-center gap-2">
1033
+ {!hideIndicator ? (
1034
+ <span
1035
+ className="h-2.5 w-2.5 shrink-0 rounded-[2px]"
1036
+ style={{ backgroundColor: color }}
1037
+ />
1038
+ ) : null}
1039
+ <span className="min-w-0 flex-1 truncate text-muted-foreground">
1040
+ {chartItem?.label || item.name || key}
1041
+ </span>
1042
+ <span className="font-mono font-medium tabular-nums text-foreground">
1043
+ {typeof item.value === "number" ? item.value.toLocaleString() : item.value}
1044
+ </span>
1045
+ </div>
1046
+ )
1047
+ })}
1048
+ </div>
1049
+ </div>
1050
+ )
1051
+ }
1052
+
1053
+ export function ChartLegendContent({
1054
+ payload,
1055
+ className,
1056
+ }: {
1057
+ payload?: Array<{ value?: string | number; color?: string }>
1058
+ className?: string
1059
+ }) {
1060
+ const { config } = useChart()
1061
+
1062
+ if (!payload?.length) return null
1063
+
1064
+ return (
1065
+ <div className={cn("flex flex-wrap items-center justify-center gap-4 text-xs", className)}>
1066
+ {payload.map((item) => {
1067
+ const key = String(item.value || "")
1068
+ const chartItem = config[key]
1069
+ return (
1070
+ <div key={key} className="flex items-center gap-1.5">
1071
+ <span
1072
+ className="h-2.5 w-2.5 rounded-[2px]"
1073
+ style={{ backgroundColor: item.color || chartItem?.color || "var(--color-" + key + ")" }}
1074
+ />
1075
+ <span className="text-muted-foreground">{chartItem?.label || key}</span>
1076
+ </div>
1077
+ )
1078
+ })}
1079
+ </div>
1080
+ )
1081
+ }
1082
+ `
1083
+ },
1084
+ {
1085
+ path: "src/components/ui/table.tsx",
1086
+ contents: `import type { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from "react"
1087
+
1088
+ import { cn } from "@/lib/utils"
1089
+
1090
+ export function Table(props: HTMLAttributes<HTMLTableElement>) {
1091
+ return <table {...props} className={cn("w-full caption-bottom text-sm", props.className)} />
1092
+ }
1093
+
1094
+ export function TableHeader(props: HTMLAttributes<HTMLTableSectionElement>) {
1095
+ return <thead {...props} className={cn("bg-zinc-50 text-xs uppercase text-zinc-500", props.className)} />
1096
+ }
1097
+
1098
+ export function TableBody(props: HTMLAttributes<HTMLTableSectionElement>) {
1099
+ return <tbody {...props} className={cn("divide-y divide-zinc-200 bg-white", props.className)} />
1100
+ }
1101
+
1102
+ export function TableRow(props: HTMLAttributes<HTMLTableRowElement>) {
1103
+ return <tr {...props} className={cn("border-b border-zinc-200 last:border-0", props.className)} />
1104
+ }
1105
+
1106
+ export function TableHead(props: ThHTMLAttributes<HTMLTableCellElement>) {
1107
+ return <th {...props} className={cn("h-10 whitespace-nowrap px-4 text-left align-middle font-medium", props.className)} />
1108
+ }
1109
+
1110
+ export function TableCell(props: TdHTMLAttributes<HTMLTableCellElement>) {
1111
+ return <td {...props} className={cn("whitespace-nowrap px-4 py-3 align-middle text-zinc-600", props.className)} />
1112
+ }
1113
+ `
1114
+ }
1115
+ ], C = [
1116
+ {
1117
+ id: "semaphor-analytics",
1118
+ name: "Semaphor Analytics Starter",
1119
+ description: "Approved client-side React starter with Semaphor data hooks, shadcn source components, Tailwind v4, and Recharts.",
1120
+ files: u
1121
+ }
1122
+ ], M = C[0].id;
1123
+ function p(e) {
1124
+ return e.map((t) => ({
1125
+ path: m(t.path),
1126
+ contents: t.contents
1127
+ })).sort((t, r) => t.path.localeCompare(r.path));
1128
+ }
1129
+ function W(e = M) {
1130
+ return C.find((t) => t.id === e) || C[0];
1131
+ }
1132
+ const Z = {
1133
+ id: "semaphor-browser-runtime-v1",
1134
+ name: "Semaphor Browser Runtime",
1135
+ execution: "browser-sandbox",
1136
+ moduleSystem: "virtual-tsx-commonjs",
1137
+ cssPipeline: {
1138
+ mode: "tailwind-v4-or-fallback",
1139
+ status: "The runtime tries Tailwind v4 candidate compilation and reports when it falls back to bundled utility CSS. Unsupported package CSS requires Local Bridge."
1140
+ },
1141
+ packages: [
1142
+ { name: "react", source: "host", status: "supported" },
1143
+ { name: "react-dom/client", source: "host", status: "supported" },
1144
+ { name: "react-semaphor/data-app-sdk", source: "runtime", status: "supported" },
1145
+ { name: "@semaphor/data-app-sdk", source: "runtime", status: "supported" },
1146
+ { name: "lucide-react", source: "shim", status: "partial" },
1147
+ { name: "recharts", source: "host", status: "partial" },
1148
+ { name: "clsx", source: "runtime", status: "supported" },
1149
+ { name: "tailwind-merge", source: "runtime", status: "supported" },
1150
+ { name: "class-variance-authority", source: "shim", status: "partial" },
1151
+ { name: "tailwindcss", source: "runtime", status: "partial" }
1152
+ ],
1153
+ components: [
1154
+ { name: "badge", importPath: "@/components/ui/badge", sourceFile: "src/components/ui/badge.tsx", implementation: "source", dependencies: [] },
1155
+ { name: "button", importPath: "@/components/ui/button", sourceFile: "src/components/ui/button.tsx", implementation: "source", dependencies: [] },
1156
+ { name: "card", importPath: "@/components/ui/card", sourceFile: "src/components/ui/card.tsx", implementation: "source", dependencies: [] },
1157
+ { name: "chart", importPath: "@/components/ui/chart", sourceFile: "src/components/ui/chart.tsx", implementation: "source", dependencies: ["recharts"] },
1158
+ { name: "select", importPath: "@/components/ui/select", sourceFile: "src/components/ui/select.tsx", implementation: "source", dependencies: [] },
1159
+ { name: "progress", importPath: "@/components/ui/progress", sourceFile: "src/components/ui/progress.tsx", implementation: "source", dependencies: [] },
1160
+ { name: "calendar", importPath: "@/components/ui/calendar", sourceFile: "src/components/ui/calendar.tsx", implementation: "source", dependencies: [] },
1161
+ { name: "table", importPath: "@/components/ui/table", sourceFile: "src/components/ui/table.tsx", implementation: "source", dependencies: [] }
1162
+ ],
1163
+ limits: {
1164
+ writeScope: ["src/**"],
1165
+ editableGlobs: ["src/**"],
1166
+ unsupported: [
1167
+ "package edits",
1168
+ "arbitrary npm installs",
1169
+ "server or Node APIs",
1170
+ "Vite plugin changes",
1171
+ "environment files"
1172
+ ]
1173
+ }
1174
+ }, y = {
1175
+ projectRoot: x,
1176
+ framework: "vite-react",
1177
+ packageManager: "browser-sandbox",
1178
+ runtimeManifest: Z,
1179
+ packageJson: {
1180
+ dependencies: {
1181
+ "@semaphor/data-app-sdk": "bundled",
1182
+ "class-variance-authority": "bundled",
1183
+ clsx: "bundled",
1184
+ "lucide-react": "bundled",
1185
+ react: "bundled",
1186
+ "react-dom": "bundled",
1187
+ "react-semaphor": "bundled",
1188
+ recharts: "bundled",
1189
+ "tailwind-merge": "bundled",
1190
+ tailwindcss: "bundled"
1191
+ },
1192
+ devDependencies: {
1193
+ "@vitejs/plugin-react": "browser-sandbox",
1194
+ typescript: "browser-sandbox",
1195
+ vite: "browser-sandbox"
1196
+ },
1197
+ scripts: {
1198
+ dev: "browser-sandbox dev",
1199
+ build: "browser-sandbox build",
1200
+ typecheck: "browser-sandbox validate"
1201
+ }
1202
+ },
1203
+ componentsJson: {
1204
+ aliases: {
1205
+ ui: "@/components/ui"
1206
+ }
1207
+ },
1208
+ tsconfig: {
1209
+ baseUrl: ".",
1210
+ paths: {
1211
+ "@/*": ["src/*"]
1212
+ },
1213
+ jsx: "react-jsx",
1214
+ strict: !0,
1215
+ noUnusedLocals: !0,
1216
+ noUnusedParameters: !0
1217
+ },
1218
+ files: {
1219
+ source: u.map((e) => e.path),
1220
+ root: ["package.json", "vite.config.ts", "tsconfig.json"],
1221
+ editable: u.map((e) => e.path),
1222
+ editableGlobs: ["src/**"],
1223
+ approvalRequiredGlobs: [
1224
+ "package.json",
1225
+ "package-lock.json",
1226
+ ".env*",
1227
+ "*.config.*",
1228
+ "vite.config.*",
1229
+ "tsconfig*.json"
1230
+ ],
1231
+ styleEntries: ["src/index.css"],
1232
+ localModules: u.map((e) => e.path).filter((e) => /\.(ts|tsx|js|jsx)$/.test(e))
1233
+ },
1234
+ validation: {
1235
+ typecheck: "browser sandbox static validation",
1236
+ build: "browser sandbox static export"
1237
+ },
1238
+ capabilities: {
1239
+ version: 1,
1240
+ source: "browser-sandbox-template",
1241
+ uiComponents: [
1242
+ {
1243
+ name: "badge",
1244
+ importPath: "@/components/ui/badge",
1245
+ exports: ["Badge"],
1246
+ example: {
1247
+ imports: 'import { Badge } from "@/components/ui/badge"',
1248
+ usage: '<Badge variant="secondary">Ready</Badge>'
1249
+ }
1250
+ },
1251
+ {
1252
+ name: "button",
1253
+ importPath: "@/components/ui/button",
1254
+ exports: ["Button"],
1255
+ example: {
1256
+ imports: 'import { Button } from "@/components/ui/button"',
1257
+ usage: '<Button variant="outline">Reset</Button>'
1258
+ }
1259
+ },
1260
+ {
1261
+ name: "card",
1262
+ importPath: "@/components/ui/card",
1263
+ exports: ["Card", "CardContent", "CardDescription", "CardHeader", "CardTitle"],
1264
+ example: {
1265
+ imports: 'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"',
1266
+ usage: "<Card><CardHeader><CardTitle>Title</CardTitle><CardDescription>Context</CardDescription></CardHeader><CardContent>Content</CardContent></Card>"
1267
+ }
1268
+ },
1269
+ {
1270
+ name: "chart",
1271
+ importPath: "@/components/ui/chart",
1272
+ exports: [
1273
+ "ChartConfig",
1274
+ "ChartContainer",
1275
+ "ChartLegend",
1276
+ "ChartLegendContent",
1277
+ "ChartTooltip",
1278
+ "ChartTooltipContent"
1279
+ ],
1280
+ example: {
1281
+ imports: 'import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"',
1282
+ usage: '<ChartContainer config={chartConfig} className="h-[280px] w-full"><BarChart data={data}><ChartTooltip content={<ChartTooltipContent />} /></BarChart></ChartContainer>'
1283
+ }
1284
+ },
1285
+ {
1286
+ name: "select",
1287
+ importPath: "@/components/ui/select",
1288
+ exports: [
1289
+ "Select",
1290
+ "SelectContent",
1291
+ "SelectGroup",
1292
+ "SelectItem",
1293
+ "SelectLabel",
1294
+ "SelectSeparator",
1295
+ "SelectTrigger",
1296
+ "SelectValue"
1297
+ ],
1298
+ example: {
1299
+ imports: 'import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"',
1300
+ usage: '<Select value={value} onValueChange={setValue}><SelectTrigger className="w-48"><SelectValue placeholder="Segment" /></SelectTrigger><SelectContent><SelectItem value="enterprise">Enterprise</SelectItem></SelectContent></Select>'
1301
+ }
1302
+ },
1303
+ {
1304
+ name: "progress",
1305
+ importPath: "@/components/ui/progress",
1306
+ exports: ["Progress"],
1307
+ example: {
1308
+ imports: 'import { Progress } from "@/components/ui/progress"',
1309
+ usage: '<Progress value={72} className="h-2" />'
1310
+ }
1311
+ },
1312
+ {
1313
+ name: "calendar",
1314
+ importPath: "@/components/ui/calendar",
1315
+ exports: ["Calendar", "CalendarDayButton", "DatePicker"],
1316
+ example: {
1317
+ imports: 'import { DatePicker } from "@/components/ui/calendar"',
1318
+ usage: '<DatePicker value={date} onValueChange={setDate} placeholder="Order date" />'
1319
+ }
1320
+ },
1321
+ {
1322
+ name: "table",
1323
+ importPath: "@/components/ui/table",
1324
+ exports: ["Table", "TableBody", "TableCell", "TableHead", "TableHeader", "TableRow"],
1325
+ example: {
1326
+ imports: 'import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"',
1327
+ usage: "<Table><TableHeader><TableRow><TableHead>Name</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>Northstar</TableCell></TableRow></TableBody></Table>"
1328
+ }
1329
+ }
1330
+ ],
1331
+ dataApis: [
1332
+ {
1333
+ package: "react-semaphor/data-app-sdk",
1334
+ source: "bundled",
1335
+ exports: [
1336
+ "SemaphorDataAppProvider",
1337
+ "useSemaphorInput",
1338
+ "useSemaphorInputOptions",
1339
+ "useSemaphorMetric",
1340
+ "useSemaphorRecords"
1341
+ ],
1342
+ datasets: [
1343
+ {
1344
+ name: "orders",
1345
+ fields: [
1346
+ "category",
1347
+ "customer_segment",
1348
+ "gross_margin",
1349
+ "order_date",
1350
+ "orders",
1351
+ "region",
1352
+ "revenue",
1353
+ "satisfaction_score"
1354
+ ]
1355
+ },
1356
+ {
1357
+ name: "customers",
1358
+ fields: [
1359
+ "arr",
1360
+ "customer_name",
1361
+ "customer_segment",
1362
+ "health_score",
1363
+ "last_active_days",
1364
+ "open_tickets",
1365
+ "region"
1366
+ ]
1367
+ }
1368
+ ],
1369
+ examples: [
1370
+ "const segmentInput = useSemaphorInput({ id: 'segment', kind: 'filter', field: 'customer_segment' })",
1371
+ "const revenue = useSemaphorMetric({ id: 'revenue', dataset: 'orders', metric: 'revenue', inputs: [segmentInput] })",
1372
+ "const rows = useSemaphorRecords({ id: 'trend', dataset: 'orders', dimensions: ['order_date'], measures: ['revenue'], inputs: [segmentInput] })"
1373
+ ],
1374
+ constraints: [
1375
+ "Semaphor input kind is only 'filter' or 'control'.",
1376
+ "Pass input objects into metrics/records via inputs: [input].",
1377
+ "useSemaphorMetric config uses metric, not measures.",
1378
+ "useSemaphorRecords returns { records }, and records are plain objects."
1379
+ ]
1380
+ }
1381
+ ]
1382
+ }
1383
+ };
1384
+ function U() {
1385
+ return typeof window < "u";
1386
+ }
1387
+ function m(e) {
1388
+ return e.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\.\//, "");
1389
+ }
1390
+ function Q(e) {
1391
+ const t = new Map(p(e).map((r) => [m(r.path), r]));
1392
+ for (const r of u) {
1393
+ const a = m(r.path);
1394
+ t.has(a) || t.set(a, {
1395
+ path: a,
1396
+ contents: r.contents
1397
+ });
1398
+ }
1399
+ return Array.from(t.values()).sort((r, a) => r.path.localeCompare(a.path));
1400
+ }
1401
+ function f() {
1402
+ if (!U())
1403
+ return p(u);
1404
+ try {
1405
+ const e = window.localStorage.getItem(F);
1406
+ if (!e)
1407
+ return p(u);
1408
+ const t = JSON.parse(e);
1409
+ if (!Array.isArray(t) || t.length === 0)
1410
+ return p(u);
1411
+ const r = Q(t);
1412
+ return r.length !== t.length && D(r), r;
1413
+ } catch {
1414
+ return p(u);
1415
+ }
1416
+ }
1417
+ function D(e) {
1418
+ U() && window.localStorage.setItem(F, JSON.stringify(e));
1419
+ }
1420
+ function G(e = f()) {
1421
+ return new Map(e.map((t) => [m(t.path), t.contents]));
1422
+ }
1423
+ function ee(e) {
1424
+ const t = e.map((r) => m(r.path)).sort();
1425
+ 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)) }) });
1426
+ }
1427
+ function _e() {
1428
+ return C.map((e) => ({
1429
+ id: e.id,
1430
+ name: e.name,
1431
+ description: e.description,
1432
+ fileCount: e.files.length
1433
+ }));
1434
+ }
1435
+ function ze(e = M) {
1436
+ D(p(W(e).files));
1437
+ }
1438
+ function Me() {
1439
+ return ee(f());
1440
+ }
1441
+ function De(e) {
1442
+ const t = G();
1443
+ return e.map((r) => ({
1444
+ path: r,
1445
+ contents: t.get(m(r)) || ""
1446
+ }));
1447
+ }
1448
+ function He() {
1449
+ return f().map((e) => ({
1450
+ path: m(e.path),
1451
+ size: e.contents.length
1452
+ })).sort((e, t) => e.path.localeCompare(t.path));
1453
+ }
1454
+ function $e(e = M) {
1455
+ const t = W(e);
1456
+ return {
1457
+ provider: "browser-sandbox-template",
1458
+ summary: `Seeded the ${t.name} template.`,
1459
+ changes: [
1460
+ {
1461
+ kind: "edit",
1462
+ label: `Replaced the virtual browser workspace with ${t.files.length} approved template files`
1463
+ }
1464
+ ],
1465
+ files: p(t.files),
1466
+ replaceExistingFiles: !0,
1467
+ generationMeta: {
1468
+ model: "approved-template",
1469
+ strategy: t.id,
1470
+ durationMs: 0,
1471
+ outputChars: t.files.reduce((r, a) => r + a.contents.length, 0)
1472
+ }
1473
+ };
1474
+ }
1475
+ function Be({ files: e, sourcePath: t }) {
1476
+ return {
1477
+ provider: "browser-sandbox-local-import",
1478
+ summary: `Imported approved browser files from ${t}.`,
1479
+ changes: [
1480
+ {
1481
+ kind: "edit",
1482
+ label: `Replaced the virtual browser workspace with ${e.length} local template files`
1483
+ }
1484
+ ],
1485
+ files: p(e),
1486
+ replaceExistingFiles: !0,
1487
+ generationMeta: {
1488
+ model: "local-template-import",
1489
+ strategy: "dev-local-import",
1490
+ durationMs: 0,
1491
+ outputChars: e.reduce((r, a) => r + a.contents.length, 0)
1492
+ }
1493
+ };
1494
+ }
1495
+ function v({ command: e, ok: t, stderr: r = "", stdout: a = "", startedAt: n }) {
1496
+ return {
1497
+ command: e,
1498
+ durationMs: Date.now() - n,
1499
+ exitCode: t ? 0 : 1,
1500
+ ok: t,
1501
+ stdout: a,
1502
+ stderr: r
1503
+ };
1504
+ }
1505
+ function te(e) {
1506
+ const t = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".css", ".json"]), r = [];
1507
+ for (const a of e) {
1508
+ const n = m(a.path), o = n.match(/\.[^.]+$/), i = (o == null ? void 0 : o[0]) || "";
1509
+ if (!a.path || n.startsWith("../") || n.startsWith("/")) {
1510
+ r.push(`${a.path}: generated file path must be a normalized relative project path.`);
1511
+ continue;
1512
+ }
1513
+ if (!n.startsWith("src/")) {
1514
+ r.push(`${a.path}: browser sandbox revisions may only write files under src/**.`);
1515
+ continue;
1516
+ }
1517
+ t.has(i) || r.push(`${a.path}: generated source files must use one of ${[...t].join(", ")}.`);
1518
+ }
1519
+ return {
1520
+ ok: r.length === 0,
1521
+ diagnostics: r
1522
+ };
1523
+ }
1524
+ function re(e) {
1525
+ const t = /* @__PURE__ */ new Set(), r = [
1526
+ /import\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,
1527
+ /export\s+(?:type\s+)?[^'"]+\s+from\s+['"]([^'"]+)['"]/g,
1528
+ /import\(\s*['"]([^'"]+)['"]\s*\)/g
1529
+ ];
1530
+ for (const a of r) {
1531
+ let n;
1532
+ for (; n = a.exec(e); )
1533
+ t.add(n[1]);
1534
+ }
1535
+ return [...t];
1536
+ }
1537
+ function ae(e) {
1538
+ if (e.startsWith("@")) {
1539
+ const [t, r] = e.split("/");
1540
+ return r ? `${t}/${r}` : e;
1541
+ }
1542
+ return e.split("/")[0];
1543
+ }
1544
+ function ne(e, t, r) {
1545
+ const a = [], n = (o) => {
1546
+ a.push(o);
1547
+ for (const i of [".tsx", ".ts", ".jsx", ".js", ".css", ".json"])
1548
+ a.push(`${o}${i}`);
1549
+ for (const i of ["index.tsx", "index.ts", "index.jsx", "index.js"])
1550
+ a.push(`${o}/${i}`);
1551
+ };
1552
+ if (t.startsWith(".")) {
1553
+ const o = m(e).split("/");
1554
+ o.pop();
1555
+ const i = [...o, ...t.split("/")], l = [];
1556
+ for (const s of i)
1557
+ if (!(!s || s === ".")) {
1558
+ if (s === "..") {
1559
+ l.pop();
1560
+ continue;
1561
+ }
1562
+ l.push(s);
1563
+ }
1564
+ n(l.join("/"));
1565
+ }
1566
+ return t.startsWith("@/") && n(`src/${t.slice(2)}`), a.some((o) => r.has(m(o)));
1567
+ }
1568
+ function oe(e, t = f()) {
1569
+ var r, a;
1570
+ 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 = /* @__PURE__ */ new Set([
1571
+ ...t.map((s) => m(s.path)),
1572
+ ...e.map((s) => m(s.path))
1573
+ ]), i = /* @__PURE__ */ new Set([
1574
+ "child_process",
1575
+ "crypto",
1576
+ "fs",
1577
+ "http",
1578
+ "https",
1579
+ "node:child_process",
1580
+ "node:crypto",
1581
+ "node:fs",
1582
+ "node:http",
1583
+ "node:https",
1584
+ "node:path",
1585
+ "path"
1586
+ ]), l = [];
1587
+ for (const s of e)
1588
+ for (const d of re(s.contents)) {
1589
+ const b = ae(d);
1590
+ if (i.has(d) || i.has(b)) {
1591
+ l.push(`${s.path}: blocked server/runtime import "${d}". Browser sandbox apps must run in the browser.`);
1592
+ continue;
1593
+ }
1594
+ if (d.startsWith(".") || d.startsWith("@/")) {
1595
+ ne(s.path, d, o) || l.push(`${s.path}: local import "${d}" does not resolve from the browser sandbox files.`);
1596
+ continue;
1597
+ }
1598
+ n[b] || l.push(`${s.path}: package import "${d}" is not available in Browser Sandbox. Switch to Local Bridge for arbitrary npm packages.`);
1599
+ }
1600
+ return {
1601
+ ok: l.length === 0,
1602
+ diagnostics: l
1603
+ };
1604
+ }
1605
+ function je(e) {
1606
+ const t = Date.now(), r = te(e.files), a = e.replaceExistingFiles ? [] : f();
1607
+ if (!r.ok) {
1608
+ const s = v({
1609
+ command: "browser sandbox write policy validation",
1610
+ ok: !1,
1611
+ stderr: r.diagnostics.join(`
1612
+ `),
1613
+ startedAt: t
1614
+ });
1615
+ return {
1616
+ ok: !1,
1617
+ projectRoot: x,
1618
+ provider: e.provider || "browser-sandbox",
1619
+ validation: "write-policy-validation",
1620
+ summary: e.summary,
1621
+ changes: e.changes,
1622
+ generationMeta: e.generationMeta,
1623
+ files: e.files.map((d) => ({ path: d.path, size: d.contents.length })),
1624
+ changedFiles: [],
1625
+ writePolicyValidation: r,
1626
+ attempts: [{ label: e.attemptLabel || "initial", ok: !1, command: s }],
1627
+ error: "Generated files failed Browser Sandbox write policy validation."
1628
+ };
1629
+ }
1630
+ const n = oe(e.files, a);
1631
+ if (!n.ok) {
1632
+ const s = v({
1633
+ command: "browser sandbox import validation",
1634
+ ok: !1,
1635
+ stderr: n.diagnostics.join(`
1636
+ `),
1637
+ startedAt: t
1638
+ });
1639
+ return {
1640
+ ok: !1,
1641
+ projectRoot: x,
1642
+ provider: e.provider || "browser-sandbox",
1643
+ validation: "import-validation",
1644
+ summary: e.summary,
1645
+ changes: e.changes,
1646
+ generationMeta: e.generationMeta,
1647
+ files: e.files.map((d) => ({ path: d.path, size: d.contents.length })),
1648
+ changedFiles: [],
1649
+ importValidation: n,
1650
+ attempts: [{ label: e.attemptLabel || "initial", ok: !1, command: s }],
1651
+ error: "Generated files failed Browser Sandbox import validation."
1652
+ };
1653
+ }
1654
+ const o = e.replaceExistingFiles ? /* @__PURE__ */ new Map() : G(), i = [];
1655
+ for (const s of e.files) {
1656
+ const d = m(s.path);
1657
+ o.set(d, s.contents), i.push(d);
1658
+ }
1659
+ D([...o.entries()].map(([s, d]) => ({ path: s, contents: d })).sort((s, d) => s.path.localeCompare(d.path)));
1660
+ const l = v({
1661
+ command: "browser sandbox static validation",
1662
+ ok: !0,
1663
+ stdout: "Write policy and import validation passed. Browser preview refresh is handled by app-builder.",
1664
+ startedAt: t
1665
+ });
1666
+ return {
1667
+ ok: !0,
1668
+ projectRoot: x,
1669
+ provider: e.provider || "browser-sandbox",
1670
+ validation: "typecheck",
1671
+ summary: e.summary,
1672
+ changes: e.changes,
1673
+ generationMeta: e.generationMeta,
1674
+ files: e.files.map((s) => ({ path: s.path, size: s.contents.length })),
1675
+ changedFiles: i,
1676
+ importValidation: n,
1677
+ attempts: [{ label: e.attemptLabel || "initial", ok: !0, command: l }],
1678
+ command: l
1679
+ };
1680
+ }
1681
+ function Pe() {
1682
+ const e = {
1683
+ path: "src/data-app/index.tsx",
1684
+ contents: `import { useEffect, useRef, useState } from "react"
1685
+ import { Activity, AlertTriangle, BarChart3, Users } from "lucide-react"
1686
+ import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
1687
+ import {
1688
+ useSemaphorInput,
1689
+ useSemaphorInputOptions,
1690
+ useSemaphorMetric,
1691
+ useSemaphorRecords,
1692
+ } from "react-semaphor/data-app-sdk"
1693
+
1694
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
1695
+ import {
1696
+ ChartContainer,
1697
+ ChartTooltip,
1698
+ ChartTooltipContent,
1699
+ type ChartConfig,
1700
+ } from "@/components/ui/chart"
1701
+ import {
1702
+ Select,
1703
+ SelectContent,
1704
+ SelectItem,
1705
+ SelectTrigger,
1706
+ SelectValue,
1707
+ } from "@/components/ui/select"
1708
+
1709
+ export function DataApp() {
1710
+ const statusRef = useRef<HTMLSpanElement | null>(null)
1711
+ const [runtimeStatus, setRuntimeStatus] = useState("mounting")
1712
+
1713
+ useEffect(() => {
1714
+ statusRef.current?.setAttribute("data-ready", "true")
1715
+ setRuntimeStatus("interactive")
1716
+ }, [])
1717
+
1718
+ const segmentOptions = useSemaphorInputOptions({
1719
+ id: "segment-options",
1720
+ dataset: "customers",
1721
+ field: "customer_segment",
1722
+ })
1723
+ const regionOptions = useSemaphorInputOptions({
1724
+ id: "region-options",
1725
+ dataset: "customers",
1726
+ field: "region",
1727
+ })
1728
+ const segment = useSemaphorInput({
1729
+ id: "segment",
1730
+ kind: "filter",
1731
+ field: "customer_segment",
1732
+ value: "",
1733
+ options: [{ value: "", label: "All segments" }, ...segmentOptions.options],
1734
+ })
1735
+ const region = useSemaphorInput({
1736
+ id: "region",
1737
+ kind: "filter",
1738
+ field: "region",
1739
+ value: "",
1740
+ options: [{ value: "", label: "All regions" }, ...regionOptions.options],
1741
+ })
1742
+ const revenue = useSemaphorMetric({
1743
+ id: "revenue",
1744
+ dataset: "orders",
1745
+ metric: "revenue",
1746
+ comparison: "previous_period",
1747
+ inputs: [segment, region],
1748
+ })
1749
+
1750
+ const orders = useSemaphorMetric({
1751
+ id: "orders",
1752
+ dataset: "orders",
1753
+ metric: "orders",
1754
+ comparison: "previous_period",
1755
+ inputs: [segment, region],
1756
+ })
1757
+ const arr = useSemaphorMetric({
1758
+ id: "arr",
1759
+ dataset: "customers",
1760
+ metric: "arr",
1761
+ inputs: [segment, region],
1762
+ })
1763
+ const tickets = useSemaphorMetric({
1764
+ id: "tickets",
1765
+ dataset: "customers",
1766
+ metric: "open_tickets",
1767
+ inputs: [segment, region],
1768
+ })
1769
+ const customerHealth = useSemaphorRecords({
1770
+ id: "customer-health",
1771
+ dataset: "customers",
1772
+ dimensions: ["customer_name", "customer_segment", "region"],
1773
+ measures: ["health_score", "open_tickets", "arr"],
1774
+ inputs: [segment, region],
1775
+ limit: 6,
1776
+ })
1777
+ const chartData = customerHealth.records.map((row) => ({
1778
+ customer: String(row.customer_name).split(" ")[0],
1779
+ arr: Number(row.arr || 0),
1780
+ open_tickets: Number(row.open_tickets || 0),
1781
+ }))
1782
+ const chartConfig = {
1783
+ arr: {
1784
+ label: "ARR",
1785
+ color: "#2563eb",
1786
+ },
1787
+ open_tickets: {
1788
+ label: "Open tickets",
1789
+ color: "#f97316",
1790
+ },
1791
+ } satisfies ChartConfig
1792
+
1793
+ return (
1794
+ <main className="min-h-screen bg-zinc-50 p-4 text-zinc-950 sm:p-6">
1795
+ <section className="mx-auto max-w-6xl space-y-4">
1796
+ <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">
1797
+ <div className="min-w-0">
1798
+ <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">
1799
+ <BarChart3 className="size-4" />
1800
+ <span ref={statusRef}>Real React runtime: {runtimeStatus}</span>
1801
+ </div>
1802
+ <h1 className="text-2xl font-semibold tracking-tight">Revenue Command Center</h1>
1803
+ <p className="mt-1 text-sm text-zinc-500">
1804
+ Responsive filters, KPI cards, and customer watchlist render in-browser.
1805
+ </p>
1806
+ </div>
1807
+ <div className="grid gap-2 sm:grid-cols-2 lg:w-[360px]">
1808
+ <label className="text-xs font-medium text-zinc-600">
1809
+ Segment
1810
+ <Select value={String(segment.value ?? "")} onValueChange={segment.setValue}>
1811
+ <SelectTrigger className="mt-1 w-full">
1812
+ <SelectValue placeholder="All segments" />
1813
+ </SelectTrigger>
1814
+ <SelectContent>
1815
+ {segment.options.map((option) => (
1816
+ <SelectItem key={option.value} value={String(option.value)}>
1817
+ {option.label}
1818
+ </SelectItem>
1819
+ ))}
1820
+ </SelectContent>
1821
+ </Select>
1822
+ </label>
1823
+ <label className="text-xs font-medium text-zinc-600">
1824
+ Region
1825
+ <Select value={String(region.value ?? "")} onValueChange={region.setValue}>
1826
+ <SelectTrigger className="mt-1 w-full">
1827
+ <SelectValue placeholder="All regions" />
1828
+ </SelectTrigger>
1829
+ <SelectContent>
1830
+ {region.options.map((option) => (
1831
+ <SelectItem key={option.value} value={String(option.value)}>
1832
+ {option.label}
1833
+ </SelectItem>
1834
+ ))}
1835
+ </SelectContent>
1836
+ </Select>
1837
+ </label>
1838
+ </div>
1839
+ </div>
1840
+
1841
+ <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
1842
+ <Card>
1843
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1844
+ <CardTitle>Revenue</CardTitle>
1845
+ <Activity className="size-4 text-zinc-400" />
1846
+ </CardHeader>
1847
+ <CardContent>
1848
+ <div className="text-3xl font-semibold">
1849
+ {revenue.value?.toLocaleString() ?? "0"}
1850
+ </div>
1851
+ <p className="mt-1 text-xs text-zinc-500">Current order fixture total</p>
1852
+ </CardContent>
1853
+ </Card>
1854
+ <Card>
1855
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1856
+ <CardTitle>Orders</CardTitle>
1857
+ <BarChart3 className="size-4 text-zinc-400" />
1858
+ </CardHeader>
1859
+ <CardContent>
1860
+ <div className="text-3xl font-semibold">
1861
+ {orders.value?.toLocaleString() ?? "0"}
1862
+ </div>
1863
+ <p className="mt-1 text-xs text-zinc-500">Filtered order volume</p>
1864
+ </CardContent>
1865
+ </Card>
1866
+ <Card>
1867
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1868
+ <CardTitle>Customer ARR</CardTitle>
1869
+ <Users className="size-4 text-zinc-400" />
1870
+ </CardHeader>
1871
+ <CardContent>
1872
+ <div className="text-3xl font-semibold">
1873
+ {arr.value?.toLocaleString() ?? "0"}
1874
+ </div>
1875
+ <p className="mt-1 text-xs text-zinc-500">Responds to segment and region</p>
1876
+ </CardContent>
1877
+ </Card>
1878
+ <Card>
1879
+ <CardHeader className="flex flex-row items-center justify-between gap-2">
1880
+ <CardTitle>Open Tickets</CardTitle>
1881
+ <AlertTriangle className="size-4 text-zinc-400" />
1882
+ </CardHeader>
1883
+ <CardContent>
1884
+ <div className="text-3xl font-semibold">
1885
+ {tickets.value?.toLocaleString() ?? "0"}
1886
+ </div>
1887
+ <p className="mt-1 text-xs text-zinc-500">Support pressure signal</p>
1888
+ </CardContent>
1889
+ </Card>
1890
+ </div>
1891
+
1892
+ <Card>
1893
+ <CardHeader>
1894
+ <CardTitle>Customer ARR by account</CardTitle>
1895
+ </CardHeader>
1896
+ <CardContent>
1897
+ <ChartContainer config={chartConfig} className="h-[280px] w-full">
1898
+ <BarChart data={chartData} margin={{ left: 0, right: 12 }}>
1899
+ <CartesianGrid vertical={false} />
1900
+ <XAxis
1901
+ dataKey="customer"
1902
+ tickLine={false}
1903
+ axisLine={false}
1904
+ tickMargin={8}
1905
+ />
1906
+ <YAxis
1907
+ tickLine={false}
1908
+ axisLine={false}
1909
+ tickMargin={8}
1910
+ width={52}
1911
+ />
1912
+ <ChartTooltip content={<ChartTooltipContent />} />
1913
+ <Bar dataKey="arr" fill="var(--color-arr)" radius={[4, 4, 0, 0]} />
1914
+ </BarChart>
1915
+ </ChartContainer>
1916
+ </CardContent>
1917
+ </Card>
1918
+
1919
+ <Card>
1920
+ <CardHeader>
1921
+ <CardTitle>Customer health watchlist</CardTitle>
1922
+ </CardHeader>
1923
+ <CardContent>
1924
+ <div className="overflow-x-auto rounded-lg border border-zinc-200">
1925
+ <table className="min-w-full text-left text-sm">
1926
+ <thead className="bg-zinc-50 text-xs uppercase text-zinc-500">
1927
+ <tr>
1928
+ <th className="px-4 py-3 font-medium">Customer</th>
1929
+ <th className="px-4 py-3 font-medium">Segment</th>
1930
+ <th className="px-4 py-3 font-medium">Region</th>
1931
+ <th className="px-4 py-3 text-right font-medium">Health</th>
1932
+ <th className="px-4 py-3 text-right font-medium">Tickets</th>
1933
+ <th className="px-4 py-3 text-right font-medium">ARR</th>
1934
+ </tr>
1935
+ </thead>
1936
+ <tbody className="divide-y divide-zinc-200 bg-white">
1937
+ {customerHealth.records.map((row) => (
1938
+ <tr key={row.customer_name}>
1939
+ <td className="whitespace-nowrap px-4 py-3 font-medium text-zinc-900">
1940
+ {row.customer_name}
1941
+ </td>
1942
+ <td className="whitespace-nowrap px-4 py-3 text-zinc-600">
1943
+ {row.customer_segment}
1944
+ </td>
1945
+ <td className="whitespace-nowrap px-4 py-3 text-zinc-600">{row.region}</td>
1946
+ <td className="whitespace-nowrap px-4 py-3 text-right font-medium">
1947
+ {row.health_score}
1948
+ </td>
1949
+ <td className="whitespace-nowrap px-4 py-3 text-right">
1950
+ {row.open_tickets}
1951
+ </td>
1952
+ <td className="whitespace-nowrap px-4 py-3 text-right">
1953
+ {Number(row.arr || 0).toLocaleString()}
1954
+ </td>
1955
+ </tr>
1956
+ ))}
1957
+ </tbody>
1958
+ </table>
1959
+ </div>
1960
+ </CardContent>
1961
+ </Card>
1962
+ </section>
1963
+ </main>
1964
+ )
1965
+ }
1966
+ `
1967
+ };
1968
+ return {
1969
+ provider: "browser-sandbox",
1970
+ summary: "Applied a deterministic Browser Sandbox smoke revision.",
1971
+ changes: [
1972
+ {
1973
+ kind: "edit",
1974
+ label: "Replaced the virtual browser workspace with a known-good smoke app"
1975
+ }
1976
+ ],
1977
+ files: u.map((t) => t.path === e.path ? e : t),
1978
+ replaceExistingFiles: !0,
1979
+ generationMeta: {
1980
+ model: "deterministic-smoke",
1981
+ strategy: "browser-sandbox-smoke",
1982
+ durationMs: 0,
1983
+ outputChars: 0
1984
+ }
1985
+ };
1986
+ }
1987
+ function Ae() {
1988
+ const e = Date.now(), t = v({
1989
+ command: "browser sandbox build",
1990
+ ok: !1,
1991
+ stderr: "Static export is not implemented yet. Next step: compile the virtual filesystem with the Browser Sandbox compiler worker, then upload dist/** to S3.",
1992
+ startedAt: e
1993
+ });
1994
+ return {
1995
+ ok: !1,
1996
+ projectRoot: x,
1997
+ provider: "browser-sandbox",
1998
+ validation: "build",
1999
+ changedFiles: [],
2000
+ attempts: [{ label: "publish", ok: !1, command: t }],
2001
+ command: t,
2002
+ error: t.stderr
2003
+ };
2004
+ }
2005
+ async function Le() {
2006
+ const e = f(), t = [];
2007
+ try {
2008
+ const r = await import("./typescript-H1EwZsOb.js").then((i) => i.t), a = {}, n = e.filter((i) => /\.(ts|tsx|js|jsx)$/.test(i.path));
2009
+ for (const i of n) {
2010
+ const l = m(i.path), s = r.transpileModule(i.contents, {
2011
+ compilerOptions: {
2012
+ esModuleInterop: !0,
2013
+ jsx: r.JsxEmit.ReactJSX,
2014
+ module: r.ModuleKind.CommonJS,
2015
+ target: r.ScriptTarget.ES2020
2016
+ },
2017
+ fileName: l,
2018
+ reportDiagnostics: !0
2019
+ });
2020
+ for (const d of s.diagnostics || [])
2021
+ t.push(se(r, d));
2022
+ a[l] = s.outputText;
2023
+ }
2024
+ if (a[R] || t.push(`${R}: browser sandbox entry file is missing.`), t.length > 0)
2025
+ return {
2026
+ html: E(t),
2027
+ diagnostics: t
2028
+ };
2029
+ const o = await ie(e);
2030
+ return {
2031
+ html: ke({
2032
+ css: o.css,
2033
+ diagnostics: t,
2034
+ entryPath: R,
2035
+ moduleSources: a
2036
+ }),
2037
+ diagnostics: t,
2038
+ warnings: o.warnings,
2039
+ cssMode: o.mode
2040
+ };
2041
+ } catch (r) {
2042
+ const a = r instanceof Error ? r.message : String(r);
2043
+ return {
2044
+ html: E([
2045
+ `Browser Sandbox compiler failed before rendering: ${a}`
2046
+ ]),
2047
+ diagnostics: [a]
2048
+ };
2049
+ }
2050
+ }
2051
+ function se(e, t) {
2052
+ const r = t.file && typeof t.start == "number" ? (() => {
2053
+ const n = t.file.getLineAndCharacterOfPosition(t.start);
2054
+ return `${t.file.fileName}:${n.line + 1}:${n.character + 1}`;
2055
+ })() : "browser-sandbox", a = e.flattenDiagnosticMessageText(t.messageText, `
2056
+ `);
2057
+ return `${r}: ${a}`;
2058
+ }
2059
+ async function ie(e) {
2060
+ const t = e.filter((r) => r.path.endsWith(".css")).map((r) => r.contents).join(`
2061
+ `);
2062
+ try {
2063
+ const { compile: r } = await import("./lib-DFvr9fM4.js"), a = le(t), n = await r(a, {
2064
+ from: void 0,
2065
+ loadStylesheet: ce
2066
+ });
2067
+ return {
2068
+ css: `${z}
2069
+ ${n.build([...K(e)])}`,
2070
+ mode: "tailwind",
2071
+ warnings: []
2072
+ };
2073
+ } catch (r) {
2074
+ const a = r instanceof Error ? r.message : String(r), n = t.replace(/@import\s+["']tailwindcss["'];?/g, "");
2075
+ return {
2076
+ css: [
2077
+ z,
2078
+ `/* Browser Sandbox Tailwind compiler failed; using fallback utility CSS. ${a} */`,
2079
+ he(e),
2080
+ n
2081
+ ].join(`
2082
+ `),
2083
+ mode: "fallback",
2084
+ warnings: [
2085
+ `Tailwind compiler was unavailable, so Browser Sandbox used fallback utility CSS. ${a}`
2086
+ ]
2087
+ };
2088
+ }
2089
+ }
2090
+ function le(e) {
2091
+ return /@import\s+["']tailwindcss(?:\/[^"']*)?["'];?/.test(e) || /@tailwind\s+/.test(e) ? e : `@import "tailwindcss";
2092
+ ${e}`;
2093
+ }
2094
+ const P = /* @__PURE__ */ new Map();
2095
+ async function ce(e, t) {
2096
+ 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;
2097
+ if (!a)
2098
+ throw new Error(`Unsupported Tailwind stylesheet import: ${e}`);
2099
+ let n = P.get(a);
2100
+ if (!n) {
2101
+ const o = await fetch(a);
2102
+ if (!o.ok)
2103
+ throw new Error(`Unable to load Tailwind stylesheet ${a}: ${o.status}`);
2104
+ n = await o.text(), P.set(a, n);
2105
+ }
2106
+ return {
2107
+ base: t || new URL("/browser-sandbox/tailwind/", window.location.origin).href,
2108
+ content: n,
2109
+ path: a
2110
+ };
2111
+ }
2112
+ const de = {
2113
+ sm: "640px",
2114
+ md: "768px",
2115
+ lg: "1024px",
2116
+ xl: "1280px",
2117
+ "2xl": "1536px"
2118
+ }, me = {
2119
+ hover: ":hover",
2120
+ focus: ":focus",
2121
+ active: ":active",
2122
+ disabled: ":disabled"
2123
+ }, q = {
2124
+ 0: "0",
2125
+ px: "1px",
2126
+ "0.5": "0.125rem",
2127
+ 1: "0.25rem",
2128
+ "1.5": "0.375rem",
2129
+ 2: "0.5rem",
2130
+ "2.5": "0.625rem",
2131
+ 3: "0.75rem",
2132
+ "3.5": "0.875rem",
2133
+ 4: "1rem",
2134
+ 5: "1.25rem",
2135
+ 6: "1.5rem",
2136
+ 7: "1.75rem",
2137
+ 8: "2rem",
2138
+ 9: "2.25rem",
2139
+ 10: "2.5rem",
2140
+ 11: "2.75rem",
2141
+ 12: "3rem",
2142
+ 14: "3.5rem",
2143
+ 16: "4rem",
2144
+ 20: "5rem",
2145
+ 24: "6rem",
2146
+ 28: "7rem",
2147
+ 32: "8rem",
2148
+ 36: "9rem",
2149
+ 40: "10rem",
2150
+ 44: "11rem",
2151
+ 48: "12rem",
2152
+ 56: "14rem",
2153
+ 64: "16rem",
2154
+ 72: "18rem",
2155
+ 80: "20rem",
2156
+ 96: "24rem"
2157
+ }, ue = {
2158
+ "1/2": "50%",
2159
+ "1/3": "33.333333%",
2160
+ "2/3": "66.666667%",
2161
+ "1/4": "25%",
2162
+ "2/4": "50%",
2163
+ "3/4": "75%",
2164
+ "1/5": "20%",
2165
+ "2/5": "40%",
2166
+ "3/5": "60%",
2167
+ "4/5": "80%",
2168
+ "1/6": "16.666667%",
2169
+ "5/6": "83.333333%",
2170
+ "1/12": "8.333333%",
2171
+ "2/12": "16.666667%",
2172
+ "3/12": "25%",
2173
+ "4/12": "33.333333%",
2174
+ "5/12": "41.666667%",
2175
+ "6/12": "50%",
2176
+ "7/12": "58.333333%",
2177
+ "8/12": "66.666667%",
2178
+ "9/12": "75%",
2179
+ "10/12": "83.333333%",
2180
+ "11/12": "91.666667%"
2181
+ }, A = {
2182
+ none: "none",
2183
+ xs: "20rem",
2184
+ sm: "24rem",
2185
+ md: "28rem",
2186
+ lg: "32rem",
2187
+ xl: "36rem",
2188
+ "2xl": "42rem",
2189
+ "3xl": "48rem",
2190
+ "4xl": "56rem",
2191
+ "5xl": "64rem",
2192
+ "6xl": "72rem",
2193
+ "7xl": "80rem",
2194
+ full: "100%",
2195
+ screen: "100vw"
2196
+ }, L = {
2197
+ xs: "font-size: 0.75rem; line-height: 1rem;",
2198
+ sm: "font-size: 0.875rem; line-height: 1.25rem;",
2199
+ base: "font-size: 1rem; line-height: 1.5rem;",
2200
+ lg: "font-size: 1.125rem; line-height: 1.75rem;",
2201
+ xl: "font-size: 1.25rem; line-height: 1.75rem;",
2202
+ "2xl": "font-size: 1.5rem; line-height: 2rem;",
2203
+ "3xl": "font-size: 1.875rem; line-height: 2.25rem;",
2204
+ "4xl": "font-size: 2.25rem; line-height: 2.5rem;",
2205
+ "5xl": "font-size: 3rem; line-height: 1;",
2206
+ "6xl": "font-size: 3.75rem; line-height: 1;"
2207
+ }, pe = {
2208
+ none: "0",
2209
+ sm: "0.125rem",
2210
+ DEFAULT: "0.25rem",
2211
+ md: "0.375rem",
2212
+ lg: "0.5rem",
2213
+ xl: "0.75rem",
2214
+ "2xl": "1rem",
2215
+ "3xl": "1.5rem",
2216
+ full: "9999px"
2217
+ }, fe = {
2218
+ sm: "0 1px 2px rgba(24, 24, 27, 0.06)",
2219
+ DEFAULT: "0 1px 3px rgba(24, 24, 27, 0.1), 0 1px 2px rgba(24, 24, 27, 0.06)",
2220
+ md: "0 4px 6px -1px rgba(24, 24, 27, 0.1), 0 2px 4px -2px rgba(24, 24, 27, 0.1)",
2221
+ lg: "0 10px 15px -3px rgba(24, 24, 27, 0.1), 0 4px 6px -4px rgba(24, 24, 27, 0.1)",
2222
+ none: "none"
2223
+ }, ge = {
2224
+ slate: {
2225
+ 50: "#f8fafc",
2226
+ 100: "#f1f5f9",
2227
+ 200: "#e2e8f0",
2228
+ 300: "#cbd5e1",
2229
+ 400: "#94a3b8",
2230
+ 500: "#64748b",
2231
+ 600: "#475569",
2232
+ 700: "#334155",
2233
+ 800: "#1e293b",
2234
+ 900: "#0f172a",
2235
+ 950: "#020617"
2236
+ },
2237
+ zinc: {
2238
+ 50: "#fafafa",
2239
+ 100: "#f4f4f5",
2240
+ 200: "#e4e4e7",
2241
+ 300: "#d4d4d8",
2242
+ 400: "#a1a1aa",
2243
+ 500: "#71717a",
2244
+ 600: "#52525b",
2245
+ 700: "#3f3f46",
2246
+ 800: "#27272a",
2247
+ 900: "#18181b",
2248
+ 950: "#09090b"
2249
+ },
2250
+ neutral: {
2251
+ 50: "#fafafa",
2252
+ 100: "#f5f5f5",
2253
+ 200: "#e5e5e5",
2254
+ 300: "#d4d4d4",
2255
+ 400: "#a3a3a3",
2256
+ 500: "#737373",
2257
+ 600: "#525252",
2258
+ 700: "#404040",
2259
+ 800: "#262626",
2260
+ 900: "#171717",
2261
+ 950: "#0a0a0a"
2262
+ },
2263
+ red: { 50: "#fef2f2", 100: "#fee2e2", 200: "#fecaca", 500: "#ef4444", 600: "#dc2626", 700: "#b91c1c", 900: "#7f1d1d" },
2264
+ amber: { 50: "#fffbeb", 100: "#fef3c7", 200: "#fde68a", 500: "#f59e0b", 600: "#d97706", 700: "#b45309", 900: "#78350f" },
2265
+ yellow: { 50: "#fefce8", 100: "#fef9c3", 200: "#fef08a", 500: "#eab308", 600: "#ca8a04", 700: "#a16207", 900: "#713f12" },
2266
+ green: { 50: "#f0fdf4", 100: "#dcfce7", 200: "#bbf7d0", 500: "#22c55e", 600: "#16a34a", 700: "#15803d", 900: "#14532d" },
2267
+ emerald: { 50: "#ecfdf5", 100: "#d1fae5", 200: "#a7f3d0", 500: "#10b981", 600: "#059669", 700: "#047857", 900: "#064e3b" },
2268
+ blue: { 50: "#eff6ff", 100: "#dbeafe", 200: "#bfdbfe", 500: "#3b82f6", 600: "#2563eb", 700: "#1d4ed8", 900: "#1e3a8a" },
2269
+ indigo: { 50: "#eef2ff", 100: "#e0e7ff", 200: "#c7d2fe", 500: "#6366f1", 600: "#4f46e5", 700: "#4338ca", 900: "#312e81" },
2270
+ purple: { 50: "#faf5ff", 100: "#f3e8ff", 200: "#e9d5ff", 500: "#a855f7", 600: "#9333ea", 700: "#7e22ce", 900: "#581c87" },
2271
+ rose: { 50: "#fff1f2", 100: "#ffe4e6", 200: "#fecdd3", 500: "#f43f5e", 600: "#e11d48", 700: "#be123c", 900: "#881337" }
2272
+ }, V = {
2273
+ background: "#ffffff",
2274
+ foreground: "#09090b",
2275
+ card: "#ffffff",
2276
+ "card-foreground": "#09090b",
2277
+ muted: "#f4f4f5",
2278
+ "muted-foreground": "#71717a",
2279
+ primary: "#18181b",
2280
+ "primary-foreground": "#ffffff",
2281
+ secondary: "#f4f4f5",
2282
+ "secondary-foreground": "#18181b",
2283
+ accent: "#f4f4f5",
2284
+ "accent-foreground": "#18181b",
2285
+ destructive: "#dc2626",
2286
+ "destructive-foreground": "#ffffff",
2287
+ border: "#e4e4e7",
2288
+ input: "#e4e4e7",
2289
+ ring: "#18181b",
2290
+ white: "#ffffff",
2291
+ black: "#000000",
2292
+ transparent: "transparent"
2293
+ }, J = {
2294
+ block: "display: block;",
2295
+ "inline-block": "display: inline-block;",
2296
+ inline: "display: inline;",
2297
+ flex: "display: flex;",
2298
+ "inline-flex": "display: inline-flex;",
2299
+ grid: "display: grid;",
2300
+ hidden: "display: none;",
2301
+ table: "display: table;",
2302
+ "table-row": "display: table-row;",
2303
+ "table-cell": "display: table-cell;",
2304
+ relative: "position: relative;",
2305
+ absolute: "position: absolute;",
2306
+ fixed: "position: fixed;",
2307
+ sticky: "position: sticky;",
2308
+ "flex-row": "flex-direction: row;",
2309
+ "flex-col": "flex-direction: column;",
2310
+ "flex-wrap": "flex-wrap: wrap;",
2311
+ "flex-nowrap": "flex-wrap: nowrap;",
2312
+ "flex-1": "flex: 1 1 0%;",
2313
+ "flex-auto": "flex: 1 1 auto;",
2314
+ "flex-none": "flex: none;",
2315
+ grow: "flex-grow: 1;",
2316
+ "grow-0": "flex-grow: 0;",
2317
+ shrink: "flex-shrink: 1;",
2318
+ "shrink-0": "flex-shrink: 0;",
2319
+ "items-start": "align-items: flex-start;",
2320
+ "items-center": "align-items: center;",
2321
+ "items-end": "align-items: flex-end;",
2322
+ "items-stretch": "align-items: stretch;",
2323
+ "justify-start": "justify-content: flex-start;",
2324
+ "justify-center": "justify-content: center;",
2325
+ "justify-end": "justify-content: flex-end;",
2326
+ "justify-between": "justify-content: space-between;",
2327
+ "justify-around": "justify-content: space-around;",
2328
+ "content-start": "align-content: flex-start;",
2329
+ "content-center": "align-content: center;",
2330
+ "self-start": "align-self: flex-start;",
2331
+ "self-center": "align-self: center;",
2332
+ "self-stretch": "align-self: stretch;",
2333
+ "overflow-hidden": "overflow: hidden;",
2334
+ "overflow-auto": "overflow: auto;",
2335
+ "overflow-x-auto": "overflow-x: auto;",
2336
+ "overflow-y-auto": "overflow-y: auto;",
2337
+ "overflow-x-hidden": "overflow-x: hidden;",
2338
+ "overflow-y-hidden": "overflow-y: hidden;",
2339
+ truncate: "overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
2340
+ "whitespace-nowrap": "white-space: nowrap;",
2341
+ "whitespace-normal": "white-space: normal;",
2342
+ "text-left": "text-align: left;",
2343
+ "text-center": "text-align: center;",
2344
+ "text-right": "text-align: right;",
2345
+ "align-middle": "vertical-align: middle;",
2346
+ "font-normal": "font-weight: 400;",
2347
+ "font-medium": "font-weight: 500;",
2348
+ "font-semibold": "font-weight: 600;",
2349
+ "font-bold": "font-weight: 700;",
2350
+ uppercase: "text-transform: uppercase;",
2351
+ lowercase: "text-transform: lowercase;",
2352
+ capitalize: "text-transform: capitalize;",
2353
+ "tracking-tight": "letter-spacing: 0;",
2354
+ "tracking-normal": "letter-spacing: 0;",
2355
+ "tracking-wide": "letter-spacing: 0.025em;",
2356
+ "tracking-wider": "letter-spacing: 0.05em;",
2357
+ "leading-none": "line-height: 1;",
2358
+ "leading-tight": "line-height: 1.25;",
2359
+ "leading-snug": "line-height: 1.375;",
2360
+ "leading-normal": "line-height: 1.5;",
2361
+ "leading-relaxed": "line-height: 1.625;",
2362
+ "min-h-screen": "min-height: 100vh;",
2363
+ "h-screen": "height: 100vh;",
2364
+ "w-screen": "width: 100vw;",
2365
+ "w-full": "width: 100%;",
2366
+ "h-full": "height: 100%;",
2367
+ "min-w-0": "min-width: 0;",
2368
+ "min-h-0": "min-height: 0;",
2369
+ "min-w-full": "min-width: 100%;",
2370
+ "border-collapse": "border-collapse: collapse;",
2371
+ "border-separate": "border-collapse: separate;",
2372
+ "object-cover": "object-fit: cover;",
2373
+ "object-contain": "object-fit: contain;",
2374
+ "aspect-square": "aspect-ratio: 1 / 1;",
2375
+ "aspect-video": "aspect-ratio: 16 / 9;",
2376
+ "cursor-pointer": "cursor: pointer;",
2377
+ "cursor-default": "cursor: default;",
2378
+ "select-none": "user-select: none;",
2379
+ "pointer-events-none": "pointer-events: none;"
2380
+ };
2381
+ function he(e) {
2382
+ const t = /* @__PURE__ */ new Set();
2383
+ for (const r of K(e)) {
2384
+ const a = xe(r);
2385
+ a && t.add(a);
2386
+ }
2387
+ return [...t].join(`
2388
+ `);
2389
+ }
2390
+ function K(e) {
2391
+ const t = /* @__PURE__ */ new Set(), r = (a) => {
2392
+ a.split(/\s+/).map((n) => n.trim().replace(/[;,]+$/g, "")).filter(Boolean).filter((n) => !n.includes("\\") && !n.includes('"')).filter(be).forEach((n) => t.add(n));
2393
+ };
2394
+ for (const a of e) {
2395
+ if (!/\.(ts|tsx|js|jsx)$/.test(a.path))
2396
+ continue;
2397
+ const n = /\bclassName\s*=\s*(["'`])([^"'`]+)\1/g;
2398
+ let o;
2399
+ for (; o = n.exec(a.contents); )
2400
+ r(o[2]);
2401
+ const i = /(["'`])([^"'`]*(?:-|:|\[)[^"'`]*)\1/g;
2402
+ let l;
2403
+ for (; l = i.exec(a.contents); )
2404
+ r(l[2]);
2405
+ }
2406
+ return t;
2407
+ }
2408
+ function be(e) {
2409
+ if (!e || e.includes("${") || e.startsWith("@"))
2410
+ return !1;
2411
+ const t = X(e).base;
2412
+ 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;
2413
+ }
2414
+ function X(e) {
2415
+ const t = e.split(":");
2416
+ return {
2417
+ variants: t.slice(0, -1),
2418
+ base: t[t.length - 1]
2419
+ };
2420
+ }
2421
+ function xe(e) {
2422
+ const { variants: t, base: r } = X(e), a = we(r);
2423
+ if (!a)
2424
+ return null;
2425
+ const n = t.map((l) => me[l]).filter(Boolean).join(""), o = `.${Ne(e)}${n}`;
2426
+ let i = a.includes("&") ? a.replace(/&/g, o) : `${o} { ${a} }`;
2427
+ for (const l of t.slice().reverse()) {
2428
+ const s = de[l];
2429
+ s && (i = `@media (min-width: ${s}) { ${i} }`);
2430
+ }
2431
+ return i;
2432
+ }
2433
+ function we(e) {
2434
+ const t = J[e];
2435
+ if (t)
2436
+ return t;
2437
+ 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)-(.+)$/);
2438
+ if (n) {
2439
+ const c = _(n[2]);
2440
+ return c ? ve(n[1], r ? `-${c}` : c) : null;
2441
+ }
2442
+ const o = a.match(/^(size|w|h|min-w|max-w|min-h|max-h)-(.+)$/);
2443
+ if (o) {
2444
+ const c = Se(o[2], o[1]);
2445
+ if (!c)
2446
+ return null;
2447
+ const j = {
2448
+ size: "size",
2449
+ w: "width",
2450
+ h: "height",
2451
+ "min-w": "min-width",
2452
+ "max-w": "max-width",
2453
+ "min-h": "min-height",
2454
+ "max-h": "max-height"
2455
+ }[o[1]];
2456
+ return j === "size" ? `width: ${c}; height: ${c};` : `${j}: ${c};`;
2457
+ }
2458
+ const i = a.match(/^grid-cols-(.+)$/);
2459
+ if (i) {
2460
+ const c = I(i[1]);
2461
+ return c ? `grid-template-columns: ${c};` : null;
2462
+ }
2463
+ const l = a.match(/^grid-rows-(.+)$/);
2464
+ if (l) {
2465
+ const c = I(l[1]);
2466
+ return c ? `grid-template-rows: ${c};` : null;
2467
+ }
2468
+ const s = a.match(/^col-span-(\d+)$/);
2469
+ if (s)
2470
+ return `grid-column: span ${s[1]} / span ${s[1]};`;
2471
+ const d = a.match(/^row-span-(\d+)$/);
2472
+ if (d)
2473
+ return `grid-row: span ${d[1]} / span ${d[1]};`;
2474
+ const b = Ce(a);
2475
+ if (b)
2476
+ return b;
2477
+ const S = a.match(/^rounded(?:-(.+))?$/);
2478
+ if (S) {
2479
+ const c = pe[S[1] || "DEFAULT"] || h(S[1] || "");
2480
+ return c ? `border-radius: ${c};` : null;
2481
+ }
2482
+ const H = a.match(/^shadow(?:-(.+))?$/);
2483
+ if (H) {
2484
+ const c = fe[H[1] || "DEFAULT"];
2485
+ return c ? `box-shadow: ${c};` : null;
2486
+ }
2487
+ const $ = ye(a);
2488
+ if ($)
2489
+ return $;
2490
+ const N = a.match(/^text-(.+)$/);
2491
+ if (N && L[N[1]])
2492
+ return L[N[1]];
2493
+ const k = a.match(/^leading-(.+)$/);
2494
+ if (k) {
2495
+ const c = q[k[1]] || h(k[1]);
2496
+ return c ? `line-height: ${c};` : null;
2497
+ }
2498
+ const B = a.match(/^opacity-(\d+)$/);
2499
+ if (B)
2500
+ return `opacity: ${Number(B[1]) / 100};`;
2501
+ const w = a.match(/^z-(.+)$/);
2502
+ if (w)
2503
+ return `z-index: ${w[1] === "auto" ? "auto" : h(w[1]) || w[1]};`;
2504
+ const T = a.match(/^(inset|top|right|bottom|left)-(.+)$/);
2505
+ if (T) {
2506
+ const c = _(T[2]);
2507
+ return c ? `${T[1]}: ${r ? `-${c}` : c};` : null;
2508
+ }
2509
+ return null;
2510
+ }
2511
+ function ve(e, t) {
2512
+ switch (e) {
2513
+ case "p":
2514
+ return `padding: ${t};`;
2515
+ case "px":
2516
+ return `padding-left: ${t}; padding-right: ${t};`;
2517
+ case "py":
2518
+ return `padding-top: ${t}; padding-bottom: ${t};`;
2519
+ case "pt":
2520
+ return `padding-top: ${t};`;
2521
+ case "pr":
2522
+ return `padding-right: ${t};`;
2523
+ case "pb":
2524
+ return `padding-bottom: ${t};`;
2525
+ case "pl":
2526
+ return `padding-left: ${t};`;
2527
+ case "m":
2528
+ return `margin: ${t};`;
2529
+ case "mx":
2530
+ return `margin-left: ${t}; margin-right: ${t};`;
2531
+ case "my":
2532
+ return `margin-top: ${t}; margin-bottom: ${t};`;
2533
+ case "mt":
2534
+ return `margin-top: ${t};`;
2535
+ case "mr":
2536
+ return `margin-right: ${t};`;
2537
+ case "mb":
2538
+ return `margin-bottom: ${t};`;
2539
+ case "ml":
2540
+ return `margin-left: ${t};`;
2541
+ case "gap":
2542
+ return `gap: ${t};`;
2543
+ case "gap-x":
2544
+ return `column-gap: ${t};`;
2545
+ case "gap-y":
2546
+ return `row-gap: ${t};`;
2547
+ case "space-y":
2548
+ return `& > * + * { margin-top: ${t}; }`;
2549
+ case "space-x":
2550
+ return `& > * + * { margin-left: ${t}; }`;
2551
+ default:
2552
+ return "";
2553
+ }
2554
+ }
2555
+ function Ce(e) {
2556
+ if (e === "border")
2557
+ return "border-width: 1px; border-style: solid; border-color: #e4e4e7;";
2558
+ if (e === "border-0")
2559
+ return "border-width: 0;";
2560
+ if (e === "border-t")
2561
+ return "border-top-width: 1px; border-top-style: solid; border-top-color: #e4e4e7;";
2562
+ if (e === "border-r")
2563
+ return "border-right-width: 1px; border-right-style: solid; border-right-color: #e4e4e7;";
2564
+ if (e === "border-b")
2565
+ return "border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #e4e4e7;";
2566
+ if (e === "border-l")
2567
+ return "border-left-width: 1px; border-left-style: solid; border-left-color: #e4e4e7;";
2568
+ const t = e.match(/^border-(\d+)$/);
2569
+ if (t)
2570
+ return `border-width: ${t[1]}px; border-style: solid;`;
2571
+ if (e === "divide-y")
2572
+ return "& > * + * { border-top-width: 1px; border-top-style: solid; border-color: #e4e4e7; }";
2573
+ if (e === "divide-x")
2574
+ return "& > * + * { border-left-width: 1px; border-left-style: solid; border-color: #e4e4e7; }";
2575
+ const r = g(e, "divide-");
2576
+ if (r)
2577
+ return `& > * + * { border-color: ${r}; }`;
2578
+ const a = g(e, "border-");
2579
+ return a ? `border-color: ${a};` : null;
2580
+ }
2581
+ function ye(e) {
2582
+ const t = g(e, "bg-");
2583
+ if (t)
2584
+ return `background-color: ${t};`;
2585
+ const r = g(e, "text-");
2586
+ if (r)
2587
+ return `color: ${r};`;
2588
+ const a = g(e, "fill-");
2589
+ if (a)
2590
+ return `fill: ${a};`;
2591
+ const n = g(e, "stroke-");
2592
+ return n ? `stroke: ${n};` : null;
2593
+ }
2594
+ function g(e, t) {
2595
+ var r;
2596
+ if (!e.startsWith(t))
2597
+ return null;
2598
+ const a = e.slice(t.length), n = h(a);
2599
+ if (n)
2600
+ return n;
2601
+ if (V[a])
2602
+ return V[a];
2603
+ const o = a.split("-"), i = o.pop(), l = o.join("-");
2604
+ return !i || !l ? null : ((r = ge[l]) === null || r === void 0 ? void 0 : r[i]) || null;
2605
+ }
2606
+ function Se(e, t) {
2607
+ 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);
2608
+ }
2609
+ function _(e) {
2610
+ return q[e] || ue[e] || h(e);
2611
+ }
2612
+ function I(e) {
2613
+ const t = h(e);
2614
+ if (t)
2615
+ return t;
2616
+ const r = Number(e);
2617
+ return Number.isInteger(r) && r > 0 ? `repeat(${r}, minmax(0, 1fr))` : e === "none" ? "none" : null;
2618
+ }
2619
+ function h(e) {
2620
+ if (!(e != null && e.startsWith("[")) || !e.endsWith("]"))
2621
+ return null;
2622
+ const t = e.slice(1, -1).replace(/_/g, " ");
2623
+ return /[;{}<>]/.test(t) ? null : t;
2624
+ }
2625
+ function Ne(e) {
2626
+ return e.replace(/([^a-zA-Z0-9_-])/g, "\\$1");
2627
+ }
2628
+ const z = `
2629
+ * { box-sizing: border-box; }
2630
+ html, body, #root { margin: 0; min-width: 320px; min-height: 100%; }
2631
+ body { font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f6f7f8; color: #18181b; }
2632
+ button, input, select, textarea { font: inherit; }
2633
+ button, select { cursor: pointer; }
2634
+ table { border-collapse: collapse; width: 100%; }
2635
+ th, td { text-align: inherit; vertical-align: middle; }
2636
+ svg { display: block; flex-shrink: 0; }
2637
+ .min-h-screen { min-height: 100vh; }
2638
+ .bg-zinc-50 { background: #fafafa; }
2639
+ .bg-white { background: #ffffff; }
2640
+ .bg-zinc-900 { background: #18181b; }
2641
+ .bg-blue-50 { background: #eff6ff; }
2642
+ .bg-emerald-50 { background: #ecfdf5; }
2643
+ .bg-red-50 { background: #fef2f2; }
2644
+ .text-zinc-950 { color: #09090b; }
2645
+ .text-zinc-900 { color: #18181b; }
2646
+ .text-zinc-800 { color: #27272a; }
2647
+ .text-zinc-700 { color: #3f3f46; }
2648
+ .text-zinc-600 { color: #52525b; }
2649
+ .text-zinc-500 { color: #71717a; }
2650
+ .text-white { color: #ffffff; }
2651
+ .text-blue-700 { color: #1d4ed8; }
2652
+ .text-emerald-700 { color: #047857; }
2653
+ .text-red-700 { color: #b91c1c; }
2654
+ .mx-auto { margin-left: auto; margin-right: auto; }
2655
+ .mt-1 { margin-top: 0.25rem; }
2656
+ .mt-2 { margin-top: 0.5rem; }
2657
+ .mt-3 { margin-top: 0.75rem; }
2658
+ .mt-4 { margin-top: 1rem; }
2659
+ .mb-2 { margin-bottom: 0.5rem; }
2660
+ .mb-3 { margin-bottom: 0.75rem; }
2661
+ .mb-4 { margin-bottom: 1rem; }
2662
+ .ml-auto { margin-left: auto; }
2663
+ .flex { display: flex; }
2664
+ .inline-flex { display: inline-flex; }
2665
+ .grid { display: grid; }
2666
+ .hidden { display: none; }
2667
+ .block { display: block; }
2668
+ .items-center { align-items: center; }
2669
+ .items-start { align-items: flex-start; }
2670
+ .justify-between { justify-content: space-between; }
2671
+ .justify-center { justify-content: center; }
2672
+ .gap-1 { gap: 0.25rem; }
2673
+ .gap-2 { gap: 0.5rem; }
2674
+ .gap-3 { gap: 0.75rem; }
2675
+ .gap-4 { gap: 1rem; }
2676
+ .space-y-1 > * + * { margin-top: 0.25rem; }
2677
+ .space-y-2 > * + * { margin-top: 0.5rem; }
2678
+ .space-y-3 > * + * { margin-top: 0.75rem; }
2679
+ .max-w-3xl { max-width: 48rem; }
2680
+ .max-w-4xl { max-width: 56rem; }
2681
+ .max-w-5xl { max-width: 64rem; }
2682
+ .w-full { width: 100%; }
2683
+ .h-full { height: 100%; }
2684
+ .size-4 { width: 1rem; height: 1rem; }
2685
+ .size-5 { width: 1.25rem; height: 1.25rem; }
2686
+ .p-2 { padding: 0.5rem; }
2687
+ .p-3 { padding: 0.75rem; }
2688
+ .p-4 { padding: 1rem; }
2689
+ .p-6 { padding: 1.5rem; }
2690
+ .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
2691
+ .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
2692
+ .px-4 { padding-left: 1rem; padding-right: 1rem; }
2693
+ .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
2694
+ .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
2695
+ .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
2696
+ .pt-2 { padding-top: 0.5rem; }
2697
+ .pb-2 { padding-bottom: 0.5rem; }
2698
+ .rounded { border-radius: 0.25rem; }
2699
+ .rounded-md { border-radius: 0.375rem; }
2700
+ .rounded-lg { border-radius: 0.5rem; }
2701
+ .border { border-width: 1px; border-style: solid; border-color: #e4e4e7; }
2702
+ .border-zinc-100 { border-color: #f4f4f5; }
2703
+ .border-zinc-200 { border-color: #e4e4e7; }
2704
+ .border-blue-100 { border-color: #dbeafe; }
2705
+ .shadow-sm { box-shadow: 0 1px 2px rgba(24, 24, 27, 0.06); }
2706
+ .text-xs { font-size: 0.75rem; line-height: 1rem; }
2707
+ .text-sm { font-size: 0.875rem; line-height: 1.25rem; }
2708
+ .text-lg { font-size: 1.125rem; line-height: 1.75rem; }
2709
+ .text-xl { font-size: 1.25rem; line-height: 1.75rem; }
2710
+ .text-2xl { font-size: 1.5rem; line-height: 2rem; }
2711
+ .text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
2712
+ .font-medium { font-weight: 500; }
2713
+ .font-semibold { font-weight: 600; }
2714
+ .font-bold { font-weight: 700; }
2715
+ .uppercase { text-transform: uppercase; }
2716
+ .tracking-tight { letter-spacing: 0; }
2717
+ .leading-5 { line-height: 1.25rem; }
2718
+ .leading-6 { line-height: 1.5rem; }
2719
+ .overflow-hidden { overflow: hidden; }
2720
+ .overflow-auto { overflow: auto; }
2721
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2722
+ .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2723
+ .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
2724
+ @media (min-width: 640px) {
2725
+ .sm\\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2726
+ .sm\\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
2727
+ }
2728
+ `;
2729
+ function ke({ css: e, diagnostics: t, entryPath: r, moduleSources: a }) {
2730
+ return `<!doctype html>
2731
+ <html>
2732
+ <head>
2733
+ <meta charset="utf-8" />
2734
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2735
+ <style>${Y(e)}</style>
2736
+ </head>
2737
+ <body>
2738
+ <div id="root"></div>
2739
+ <script>
2740
+ window.__SEMAPHOR_SANDBOX__ = {
2741
+ entryPath: ${JSON.stringify(r)},
2742
+ moduleSources: ${O(a)},
2743
+ diagnostics: ${O(t)}
2744
+ };
2745
+ <\/script>
2746
+ <script>${Re()}<\/script>
2747
+ </body>
2748
+ </html>`;
2749
+ }
2750
+ function E(e) {
2751
+ return `<!doctype html>
2752
+ <html>
2753
+ <head>
2754
+ <meta charset="utf-8" />
2755
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2756
+ <style>${Y(z)}</style>
2757
+ </head>
2758
+ <body>
2759
+ <main class="min-h-screen bg-zinc-50 p-6 text-zinc-950">
2760
+ <section class="mx-auto max-w-4xl rounded-lg border border-zinc-200 bg-white p-4 shadow-sm">
2761
+ <h1 class="text-lg font-semibold">Browser Sandbox compile failed</h1>
2762
+ <pre class="mt-3 overflow-auto rounded-md bg-zinc-900 p-3 text-xs text-white">${Te(e.join(`
2763
+ `))}</pre>
2764
+ </section>
2765
+ </main>
2766
+ </body>
2767
+ </html>`;
2768
+ }
2769
+ function O(e) {
2770
+ return JSON.stringify(e).replace(/</g, "\\u003c");
2771
+ }
2772
+ function Y(e) {
2773
+ return e.replace(/<\/style/gi, "<\\/style");
2774
+ }
2775
+ function Te(e) {
2776
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2777
+ }
2778
+ function Re() {
2779
+ return `
2780
+ (() => {
2781
+ const sandbox = window.__SEMAPHOR_SANDBOX__;
2782
+ const moduleSources = sandbox.moduleSources || {};
2783
+ const moduleCache = {};
2784
+ const datasets = createDatasets();
2785
+ const vendor = window.parent?.__SEMAPHOR_BROWSER_SANDBOX_VENDOR__;
2786
+ if (!vendor?.React || !vendor?.ReactDOMClient) {
2787
+ throw new Error("Browser Sandbox vendor runtime is not available from the app-builder host.");
2788
+ }
2789
+ const React = vendor.React;
2790
+ const ReactDOMClient = vendor.ReactDOMClient;
2791
+ const recharts = vendor.Recharts || createRechartsFallback(React);
2792
+ const jsxRuntime = {
2793
+ Fragment: React.Fragment,
2794
+ jsx(type, props, key) {
2795
+ return React.createElement(type, key === undefined ? props : { ...(props || {}), key });
2796
+ },
2797
+ jsxs(type, props, key) {
2798
+ return React.createElement(type, key === undefined ? props : { ...(props || {}), key });
2799
+ },
2800
+ };
2801
+ const reactPackage = { ...React, default: React, __esModule: true };
2802
+ const reactDomClientPackage = { ...ReactDOMClient, default: ReactDOMClient, __esModule: true };
2803
+
2804
+ function createRechartsFallback(React) {
2805
+ function ChartShell({ children, className = "", ...props }) {
2806
+ return React.createElement(
2807
+ "div",
2808
+ {
2809
+ ...props,
2810
+ 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]
2811
+ .filter(Boolean)
2812
+ .join(" "),
2813
+ },
2814
+ React.createElement("div", { className: "space-y-2 text-center" }, [
2815
+ React.createElement("div", { key: "label", className: "font-medium text-zinc-700" }, "Chart preview"),
2816
+ React.createElement("div", { key: "meta" }, "Recharts is not bundled in this host; generated chart code is still import-valid."),
2817
+ React.createElement("div", { key: "children", className: "hidden" }, children),
2818
+ ]),
2819
+ );
2820
+ }
2821
+
2822
+ function NullPart({ children }) {
2823
+ return React.createElement(React.Fragment, null, children || null);
2824
+ }
2825
+
2826
+ return {
2827
+ Area: NullPart,
2828
+ AreaChart: ChartShell,
2829
+ Bar: NullPart,
2830
+ BarChart: ChartShell,
2831
+ CartesianGrid: NullPart,
2832
+ Cell: NullPart,
2833
+ Legend: NullPart,
2834
+ Line: NullPart,
2835
+ LineChart: ChartShell,
2836
+ Pie: NullPart,
2837
+ PieChart: ChartShell,
2838
+ ResponsiveContainer: ({ children, className = "", ...props }) =>
2839
+ React.createElement("div", { ...props, className: ["h-full w-full", className].filter(Boolean).join(" ") }, children),
2840
+ Tooltip: NullPart,
2841
+ XAxis: NullPart,
2842
+ YAxis: NullPart,
2843
+ };
2844
+ }
2845
+
2846
+ function createIcon(name) {
2847
+ return function Icon(props = {}) {
2848
+ return React.createElement(
2849
+ "span",
2850
+ {
2851
+ ...props,
2852
+ className: ["inline-flex size-5 items-center justify-center", props.className]
2853
+ .filter(Boolean)
2854
+ .join(" "),
2855
+ title: props.title || name,
2856
+ },
2857
+ "◇",
2858
+ );
2859
+ };
2860
+ }
2861
+
2862
+ const lucideReact = new Proxy({}, { get: (_target, key) => createIcon(String(key)) });
2863
+
2864
+ function useSemaphorRuntime() {
2865
+ return { token: "browser-sandbox", apiBaseUrl: "/api" };
2866
+ }
2867
+
2868
+ function SemaphorDataAppProvider({ children }) {
2869
+ return children;
2870
+ }
2871
+
2872
+ function useSemaphorInput(config) {
2873
+ const [value, setValue] = React.useState(config.value ?? config.defaultValue);
2874
+ const options = (config.options || []).map((option) =>
2875
+ typeof option === "object" ? option : { value: option, label: String(option) },
2876
+ );
2877
+ const input = {
2878
+ id: config.id,
2879
+ kind: config.kind,
2880
+ value,
2881
+ setValue(nextValue) {
2882
+ config.onValueChange?.(nextValue);
2883
+ setValue(nextValue);
2884
+ },
2885
+ options,
2886
+ isActive: hasInputValue(value),
2887
+ };
2888
+ if (config.kind === "filter") {
2889
+ input.filter = {
2890
+ field: config.field,
2891
+ operator: config.operator || "=",
2892
+ value,
2893
+ };
2894
+ } else {
2895
+ input.control = {
2896
+ role: config.role,
2897
+ value,
2898
+ };
2899
+ }
2900
+ return input;
2901
+ }
2902
+
2903
+ function useSemaphorInputOptions(config) {
2904
+ const rows = readDataset(config.dataset);
2905
+ const values = [...new Set(rows.map((row) => row[config.field]).filter((value) => typeof value === "string" || typeof value === "number"))];
2906
+ return {
2907
+ id: config.id,
2908
+ options: values.slice(0, config.limit || 50).map((value) => ({ value, label: String(value) })),
2909
+ isLoading: false,
2910
+ error: null,
2911
+ };
2912
+ }
2913
+
2914
+ function useSemaphorMetric(config) {
2915
+ useSemaphorRuntime();
2916
+ const rows = applyInputs(readDataset(config.dataset), config.inputs);
2917
+ const value = sumField(rows, config.metric);
2918
+ const comparisonValue =
2919
+ config.comparison === "target"
2920
+ ? config.targetValue ?? null
2921
+ : config.comparison && config.comparison !== "none"
2922
+ ? Math.round(value * 0.91)
2923
+ : null;
2924
+ const delta = comparisonValue === null ? null : value - comparisonValue;
2925
+ const deltaPercent = comparisonValue && comparisonValue !== 0 ? delta / comparisonValue : null;
2926
+ return {
2927
+ id: config.id,
2928
+ value,
2929
+ comparisonValue,
2930
+ delta,
2931
+ deltaPercent,
2932
+ trendline: buildTrendline(rows, config.dateField, config.metric),
2933
+ isLoading: false,
2934
+ error: null,
2935
+ };
2936
+ }
2937
+
2938
+ function useSemaphorRecords(config) {
2939
+ useSemaphorRuntime();
2940
+ const rows = applyInputs(readDataset(config.dataset), config.inputs);
2941
+ const records = shapeRecords({
2942
+ rows,
2943
+ measures: config.measures || [],
2944
+ dimensions: config.dimensions || [],
2945
+ inputs: config.inputs || [],
2946
+ limit: config.limit,
2947
+ });
2948
+ return {
2949
+ id: config.id,
2950
+ records,
2951
+ isLoading: false,
2952
+ error: null,
2953
+ metadata: {
2954
+ dataset: config.dataset,
2955
+ rowCount: records.length,
2956
+ source: "fixture",
2957
+ },
2958
+ };
2959
+ }
2960
+
2961
+ const semaphorSdk = {
2962
+ SemaphorDataAppProvider,
2963
+ useSemaphorRuntime,
2964
+ useSemaphorInput,
2965
+ useSemaphorInputOptions,
2966
+ useSemaphorMetric,
2967
+ useSemaphorRecords,
2968
+ };
2969
+
2970
+ function cx(...values) {
2971
+ const classes = [];
2972
+ const visit = (value) => {
2973
+ if (!value) return;
2974
+ if (typeof value === "string" || typeof value === "number") {
2975
+ classes.push(String(value));
2976
+ return;
2977
+ }
2978
+ if (Array.isArray(value)) {
2979
+ value.forEach(visit);
2980
+ return;
2981
+ }
2982
+ if (typeof value === "object") {
2983
+ Object.entries(value).forEach(([key, enabled]) => {
2984
+ if (enabled) classes.push(key);
2985
+ });
2986
+ }
2987
+ };
2988
+ values.forEach(visit);
2989
+ return classes.join(" ");
2990
+ }
2991
+
2992
+ const packages = {
2993
+ react: reactPackage,
2994
+ "react/jsx-runtime": jsxRuntime,
2995
+ "react-dom/client": reactDomClientPackage,
2996
+ "lucide-react": lucideReact,
2997
+ recharts,
2998
+ "@semaphor/data-app-sdk": semaphorSdk,
2999
+ "react-semaphor/data-app-sdk": semaphorSdk,
3000
+ clsx: { clsx: cx, default: cx },
3001
+ "tailwind-merge": { twMerge: cx },
3002
+ "class-variance-authority": { cva: () => () => "" },
3003
+ };
3004
+
3005
+ function executeModule(moduleId) {
3006
+ if (moduleCache[moduleId]) return moduleCache[moduleId].exports;
3007
+ const code = moduleSources[moduleId];
3008
+ if (!code) throw new Error("Missing sandbox module: " + moduleId);
3009
+ const module = { exports: {} };
3010
+ moduleCache[moduleId] = module;
3011
+ const localRequire = (specifier) => requireFrom(moduleId, specifier);
3012
+ new Function("require", "exports", "module", code)(
3013
+ localRequire,
3014
+ module.exports,
3015
+ module,
3016
+ );
3017
+ return module.exports;
3018
+ }
3019
+
3020
+ function requireFrom(importerPath, specifier) {
3021
+ if (specifier.endsWith(".css")) return {};
3022
+ if (packages[specifier]) return packages[specifier];
3023
+ const resolved = resolveSpecifier(importerPath, specifier);
3024
+ return executeModule(resolved);
3025
+ }
3026
+
3027
+ function resolveSpecifier(importerPath, specifier) {
3028
+ const candidates = [];
3029
+ const addCandidates = (basePath) => {
3030
+ candidates.push(basePath);
3031
+ [".tsx", ".ts", ".jsx", ".js"].forEach((extension) => candidates.push(basePath + extension));
3032
+ ["index.tsx", "index.ts", "index.jsx", "index.js"].forEach((indexFile) => candidates.push(basePath + "/" + indexFile));
3033
+ };
3034
+ if (specifier.startsWith("@/")) {
3035
+ addCandidates("src/" + specifier.slice(2));
3036
+ } else if (specifier.startsWith(".")) {
3037
+ const importerParts = importerPath.split("/");
3038
+ importerParts.pop();
3039
+ const parts = [...importerParts, ...specifier.split("/")];
3040
+ const resolvedParts = [];
3041
+ for (const part of parts) {
3042
+ if (!part || part === ".") continue;
3043
+ if (part === "..") resolvedParts.pop();
3044
+ else resolvedParts.push(part);
3045
+ }
3046
+ addCandidates(resolvedParts.join("/"));
3047
+ } else {
3048
+ throw new Error("Unsupported package in Browser Sandbox runtime: " + specifier);
3049
+ }
3050
+ const found = candidates.find((candidate) => moduleSources[candidate]);
3051
+ if (!found) throw new Error("Cannot resolve " + specifier + " from " + importerPath);
3052
+ return found;
3053
+ }
3054
+
3055
+ function readDataset(dataset) {
3056
+ return datasets[dataset] || datasets.orders;
3057
+ }
3058
+
3059
+ function hasInputValue(value) {
3060
+ if (value === undefined || value === null) return false;
3061
+ if (Array.isArray(value)) return value.length > 0;
3062
+ if (typeof value === "string") return value.trim().length > 0;
3063
+ return true;
3064
+ }
3065
+
3066
+ function applyInputs(rows, inputs) {
3067
+ return rows.filter((row) =>
3068
+ (inputs || []).every((input) => {
3069
+ if (input.kind !== "filter" || !input.filter || !hasInputValue(input.value)) return true;
3070
+ const candidate = row[input.filter.field];
3071
+ const value = input.value;
3072
+ switch (input.filter.operator) {
3073
+ case "in":
3074
+ return Array.isArray(value) ? value.includes(candidate) : candidate === value;
3075
+ case "not in":
3076
+ return Array.isArray(value) ? !value.includes(candidate) : candidate !== value;
3077
+ case "!=":
3078
+ return candidate !== value;
3079
+ case ">":
3080
+ return Number(candidate) > Number(value);
3081
+ case ">=":
3082
+ return Number(candidate) >= Number(value);
3083
+ case "<":
3084
+ return Number(candidate) < Number(value);
3085
+ case "<=":
3086
+ return Number(candidate) <= Number(value);
3087
+ case "between":
3088
+ return Array.isArray(value) && value.length >= 2
3089
+ ? String(candidate) >= String(value[0]) && String(candidate) <= String(value[1])
3090
+ : true;
3091
+ case "=":
3092
+ default:
3093
+ return candidate === value;
3094
+ }
3095
+ }),
3096
+ );
3097
+ }
3098
+
3099
+ function sumField(rows, field) {
3100
+ return rows.reduce((sum, row) => sum + Number(row[field] || 0), 0);
3101
+ }
3102
+
3103
+ function buildTrendline(rows, dateField, metric) {
3104
+ if (!dateField) return [];
3105
+ return groupRows(rows, [dateField], [metric], []).map((row) => ({
3106
+ date: String(row[dateField] || ""),
3107
+ value: Number(row[metric] || 0),
3108
+ }));
3109
+ }
3110
+
3111
+ function shapeRecords(params) {
3112
+ const grain = params.inputs.find((input) => input.kind === "control" && input.control?.role === "grain")?.value;
3113
+ const dimensions = params.dimensions.map((dimension) =>
3114
+ grain && /date/i.test(dimension) ? dimension + ":" + grain : dimension,
3115
+ );
3116
+ const records =
3117
+ params.measures.length > 0 && dimensions.length > 0
3118
+ ? groupRows(params.rows, dimensions, params.measures, params.inputs)
3119
+ : params.rows;
3120
+ return records.slice(0, params.limit || 500);
3121
+ }
3122
+
3123
+ function groupRows(rows, dimensions, measures, inputs) {
3124
+ const groups = new Map();
3125
+ for (const row of rows) {
3126
+ const dimensionValues = dimensions.map((dimension) => readDimensionValue(row, dimension, inputs));
3127
+ const key = JSON.stringify(dimensionValues);
3128
+ const existing =
3129
+ groups.get(key) ||
3130
+ Object.fromEntries(dimensions.map((dimension, index) => [cleanDimensionName(dimension), dimensionValues[index]]));
3131
+ for (const measure of measures) {
3132
+ existing[measure] = Number(existing[measure] || 0) + Number(row[measure] || 0);
3133
+ }
3134
+ groups.set(key, existing);
3135
+ }
3136
+ return [...groups.values()];
3137
+ }
3138
+
3139
+ function readDimensionValue(row, dimension) {
3140
+ const [field, grain] = dimension.split(":");
3141
+ const value = row[field];
3142
+ if (!grain || typeof value !== "string") return value;
3143
+ if (grain === "month") return value.slice(0, 7);
3144
+ if (grain === "year") return value.slice(0, 4);
3145
+ return value;
3146
+ }
3147
+
3148
+ function cleanDimensionName(dimension) {
3149
+ return dimension.split(":")[0];
3150
+ }
3151
+
3152
+ function createDatasets() {
3153
+ const orders = [
3154
+ { 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 },
3155
+ { 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 },
3156
+ { 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 },
3157
+ { 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 },
3158
+ { 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 },
3159
+ { 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 },
3160
+ { 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 },
3161
+ { 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 },
3162
+ { 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 },
3163
+ { 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 },
3164
+ ];
3165
+ const customers = [
3166
+ { 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 },
3167
+ { 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 },
3168
+ { 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 },
3169
+ { 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 },
3170
+ { 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 },
3171
+ { 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 },
3172
+ ];
3173
+ return { orders, customers };
3174
+ }
3175
+
3176
+ function showRuntimeError(error) {
3177
+ const root = document.getElementById("root");
3178
+ root.innerHTML = "";
3179
+ const main = document.createElement("main");
3180
+ main.className = "min-h-screen bg-zinc-50 p-6 text-zinc-950";
3181
+ 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>';
3182
+ main.querySelector("pre").textContent = error?.stack || error?.message || String(error);
3183
+ root.appendChild(main);
3184
+ }
3185
+
3186
+ try {
3187
+ executeModule(sandbox.entryPath);
3188
+ } catch (error) {
3189
+ showRuntimeError(error);
3190
+ }
3191
+ })();
3192
+ `;
3193
+ }
3194
+ function Ve() {
3195
+ 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;");
3196
+ return `<!doctype html>
3197
+ <html>
3198
+ <head>
3199
+ <meta charset="utf-8" />
3200
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
3201
+ <style>
3202
+ body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f6f7f8; color: #18181b; }
3203
+ main { min-height: 100vh; padding: 24px; box-sizing: border-box; }
3204
+ .shell { max-width: 980px; margin: 0 auto; }
3205
+ .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; }
3206
+ .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); }
3207
+ .panel header { padding: 14px 16px; border-bottom: 1px solid #e4e4e7; }
3208
+ .panel h1 { margin: 0; font-size: 16px; }
3209
+ .panel p { margin: 6px 0 0; color: #71717a; font-size: 13px; line-height: 1.5; }
3210
+ pre { margin: 0; max-height: 520px; overflow: auto; padding: 16px; background: #09090b; color: #e4e4e7; font-size: 12px; line-height: 1.6; }
3211
+ .files { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; }
3212
+ .file { border-radius: 5px; background: #f4f4f5; color: #52525b; padding: 3px 7px; font-size: 11px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
3213
+ </style>
3214
+ </head>
3215
+ <body>
3216
+ <main>
3217
+ <div class="shell">
3218
+ <span class="badge">Browser Sandbox alpha</span>
3219
+ <section class="panel">
3220
+ <header>
3221
+ <h1>Virtual app workspace</h1>
3222
+ <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>
3223
+ <div class="files">
3224
+ ${e.map((a) => `<span class="file">${a.path}</span>`).join("")}
3225
+ </div>
3226
+ </header>
3227
+ <pre>${r}</pre>
3228
+ </section>
3229
+ </div>
3230
+ </main>
3231
+ </body>
3232
+ </html>`;
3233
+ }
3234
+ export {
3235
+ je as applyBrowserSandboxRevision,
3236
+ Ve as browserSandboxPreviewHtml,
3237
+ Le as compileBrowserSandboxPreviewHtml,
3238
+ Be as createBrowserSandboxImportedTemplateRevision,
3239
+ Pe as createBrowserSandboxSmokeRevision,
3240
+ $e as createBrowserSandboxTemplateRevision,
3241
+ He as listBrowserSandboxFiles,
3242
+ _e as listBrowserSandboxTemplates,
3243
+ De as readBrowserSandboxFiles,
3244
+ Me as readBrowserSandboxWorkspaceContext,
3245
+ ze as resetBrowserSandbox,
3246
+ Ae as validateBrowserSandboxPublish
3247
+ };