@rebasepro/studio 0.23.0 → 0.24.0

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 (57) hide show
  1. package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-CCtczj3F.js} +15 -28
  2. package/dist/ApiExplorer-CCtczj3F.js.map +1 -0
  3. package/dist/ApiKeysView-B66TdzhH.js +1738 -0
  4. package/dist/ApiKeysView-B66TdzhH.js.map +1 -0
  5. package/dist/BackupsView-DmSMzAbT.js +368 -0
  6. package/dist/BackupsView-DmSMzAbT.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-DP1a87E-.js} +12 -8
  8. package/dist/BranchesView-DP1a87E-.js.map +1 -0
  9. package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-TDErHTuK.js} +126 -60
  10. package/dist/CronJobsView-TDErHTuK.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-B59pwg4c.js} +120 -74
  12. package/dist/JSEditor-B59pwg4c.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-BKM4yfP8.js} +48 -22
  14. package/dist/LogsExplorer-BKM4yfP8.js.map +1 -0
  15. package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-BlLLV6xs.js} +237 -77
  16. package/dist/RLSEditor-BlLLV6xs.js.map +1 -0
  17. package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-1An7w0j6.js} +369 -294
  18. package/dist/SQLEditor-1An7w0j6.js.map +1 -0
  19. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-DCLVfA5t.js} +20 -24
  20. package/dist/SchemaVisualizer-DCLVfA5t.js.map +1 -0
  21. package/dist/{StorageView-CMG1DOVX.js → StorageView-CbsHuCBf.js} +355 -225
  22. package/dist/StorageView-CbsHuCBf.js.map +1 -0
  23. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  24. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  25. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  26. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  27. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  28. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  29. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  30. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -1
  31. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +6 -0
  32. package/dist/components/load-failure.d.ts +1 -1
  33. package/dist/components/useRunAsUsers.d.ts +25 -0
  34. package/dist/index.es.js +11 -11
  35. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  36. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  37. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  38. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  39. package/dist/utils/sql_utils.d.ts +90 -49
  40. package/package.json +9 -9
  41. package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
  42. package/dist/ApiKeysView-qLSs47tw.js +0 -1239
  43. package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
  44. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  45. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  46. package/dist/BackupsView-DYTAD6aW.js +0 -253
  47. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  48. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  49. package/dist/CronJobsView-7hjgSYId.js.map +0 -1
  50. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  51. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  52. package/dist/RLSEditor-Djh43lBE.js.map +0 -1
  53. package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
  54. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  55. package/dist/StorageView-CMG1DOVX.js.map +0 -1
  56. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  57. package/dist/load-failure-C6OMJSGC.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"BranchesView-D6Sz1a5f.js","names":[],"sources":["../src/components/Branches/BranchesView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n CopyIcon,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n GitBranchIcon,\n IconButton,\n iconSize,\n Paper,\n PlusIcon,\n RefreshCwIcon,\n Select,\n SelectItem,\n TextField,\n Trash2Icon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseContext, useSnackbarController, ConfirmationDialog, useTranslation } from \"@rebasepro/app\";\nimport { isBranchAdmin } from \"@rebasepro/types\";\nimport type { BranchInfo } from \"@rebasepro/types\";\nimport { formatRelativeTime } from \"@rebasepro/utils\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\n\n/**\n * The prefix the driver puts on every branch database.\n *\n * Stated here rather than derived from the row, because `BranchInfo` carries\n * the *name* a person typed and not the database it became — and the detail\n * pane used to print the name under \"Database:\", which is a connection string\n * to nothing. Kept in step with `BranchService.BRANCH_DB_PREFIX` and the CLI's\n * `branch-pointer.ts`, which say the same thing for the same reason.\n */\nconst BRANCH_DB_PREFIX = \"rb_\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatRelative(date: Date | string | undefined): string {\n if (!date) return \"—\";\n const d = date instanceof Date ? date : new Date(date);\n if (isNaN(d.getTime())) return \"—\";\n return formatRelativeTime(d) ?? d.toLocaleDateString();\n}\n\nexport function BranchesView() {\n const { databaseAdmin } = useRebaseContext();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n\n const [branches, setBranches] = useState<BranchInfo[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedBranch, setSelectedBranch] = useState<string | null>(null);\n\n // Create dialog\n const [createOpen, setCreateOpen] = useState(false);\n const [newBranchName, setNewBranchName] = useState(\"\");\n const [sourceBranch, setSourceBranch] = useState<string | undefined>(undefined);\n const [creating, setCreating] = useState(false);\n /**\n * Why the last create attempt was refused, kept on screen.\n *\n * The server refuses branching outright on the managed development\n * database — PGlite serves one database, so the \"branch\" would be the\n * parent. That refusal is the whole answer to what the reader just tried to\n * do, and a snackbar is gone in four seconds; the same argument\n * `load-failure.ts` makes for listings.\n */\n const [createError, setCreateError] = useState<string | null>(null);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n /** Why the branch listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Refs\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const branchAdmin = isBranchAdmin(databaseAdmin) ? databaseAdmin : undefined;\n\n const loadBranches = useCallback(async () => {\n if (!branchAdmin) {\n setLoading(false);\n return;\n }\n try {\n const result = await branchAdmin.listBranches();\n setBranches(result);\n setFailure(null);\n } catch (e: unknown) {\n // \"No branches yet. Create one…\" over a refused listing invites the\n // reader to create a branch they are not allowed to see, and then\n // hides the one they already have.\n setFailure(classifyLoadFailure(e));\n } finally {\n setLoading(false);\n }\n }, [branchAdmin]);\n\n useEffect(() => {\n loadBranches();\n }, [loadBranches]);\n\n const handleCreate = async () => {\n if (!branchAdmin || !newBranchName.trim()) return;\n setCreating(true);\n setCreateError(null);\n try {\n await branchAdmin.createBranch(newBranchName.trim(), sourceBranch ? { source: sourceBranch } : undefined);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${newBranchName.trim()}\" created successfully`\n });\n setCreateOpen(false);\n setNewBranchName(\"\");\n setSourceBranch(undefined);\n await loadBranches();\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n setCreateError(message);\n snackbarRef.current.open({ type: \"error\",\n message });\n } finally {\n setCreating(false);\n }\n };\n\n const handleDelete = async () => {\n if (!branchAdmin || !deleteTarget) return;\n setDeleting(true);\n try {\n await branchAdmin.deleteBranch(deleteTarget);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${deleteTarget}\" deleted`\n });\n if (selectedBranch === deleteTarget) setSelectedBranch(null);\n setDeleteTarget(null);\n await loadBranches();\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDeleting(false);\n }\n };\n\n // Not supported\n if (!branchAdmin) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_branches_unavailable_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_branches_unavailable_body\")}\n </Typography>\n <a\n href=\"https://rebase.pro/docs/backend/branching\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary text-sm underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n const selected = branches.find(b => b.name === selectedBranch);\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-surface-card\">\n {/* ── Branch List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <GitBranchIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_branches\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{branches.length}</Chip>\n </div>\n <div className=\"flex items-center gap-1\">\n <IconButton size=\"small\" onClick={loadBranches} title=\"Refresh\">\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n <IconButton size=\"small\" onClick={() => setCreateOpen(true)} title=\"Create branch\" className=\"text-primary\">\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_branches_read_failed\")}\n deniedTitle={t(\"studio_branches_denied_title\")}\n deniedHint={t(\"studio_branches_denied_hint\")}\n onRetry={loadBranches}\n />\n ) : branches.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center px-4\">\n <CopyIcon size={iconSize.small} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"body2\" color=\"disabled\" className=\"text-[13px]\">\n No branches yet. Create one to start working with an isolated database copy.\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => setCreateOpen(true)}\n startIcon={<PlusIcon size={iconSize.smallest}/>}\n >\n Create Branch\n </Button>\n </div>\n ) : (\n branches.map(branch => (\n <div\n key={branch.name}\n onClick={() => setSelectedBranch(branch.name)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedBranch === branch.name\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-hover\"\n )}\n >\n <div className=\"w-2 h-2 rounded-full shrink-0 bg-emerald-500\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{branch.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px]\">\n from {branch.parentDatabase} · {formatRelative(branch.createdAt)}\n </Typography>\n </div>\n {branch.sizeBytes !== undefined && (\n <Typography variant=\"caption\" color=\"disabled\" className=\"font-mono text-[10px] shrink-0\">\n {formatSize(branch.sizeBytes)}\n </Typography>\n )}\n </div>\n ))\n )}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selected ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n {branches.length === 0 ? \"Create a branch to get started\" : \"Select a branch to view details\"}\n </Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-surface-card min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-emerald-500\"/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selected.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">\n Created from <span className=\"font-mono text-[11px]\">{selected.parentDatabase}</span>\n </Typography>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n onClick={() => setDeleteTarget(selected.name)}\n startIcon={<Trash2Icon size={iconSize.smallest}/>}\n >\n Delete\n </Button>\n </div>\n </div>\n\n {/* Info Cards */}\n <div className=\"px-5 py-4 bg-surface-sheet\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Branch Name\" value={selected.name} mono/>\n <StatCard label=\"Source Database\" value={selected.parentDatabase} mono/>\n <StatCard label=\"Created\" value={formatRelative(selected.createdAt)}/>\n <StatCard label=\"Size\" value={formatSize(selected.sizeBytes)}/>\n </div>\n </div>\n\n {/* Usage Info */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>How to use this branch:</strong> point this checkout at it with\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]\">rebase db branch switch {selected.name}</code>\n — every later command uses it, and\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]\">rebase db branch switch --off</code>\n goes back to the main database. Changes made here don&apos;t affect it.\n </Typography>\n </Alert>\n <div className=\"mt-4 p-4 rounded-lg border bg-surface-sheet border-hairline-strong\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-2 block font-medium\">Connection Details</Typography>\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n {/* The *database*, not the branch name. They are not the\n same string — the driver prefixes every branch database\n with `rb_` — and a connection string built from the name\n on this pane pointed at a database that does not\n exist. */}\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Database:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{`${BRANCH_DB_PREFIX}${selected.name}`}</Typography>\n </div>\n <div className=\"flex items-start gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Connect with:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px] break-all\">\n {`DATABASE_URL=…/${BRANCH_DB_PREFIX}${selected.name}`}\n </Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Branched from:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{selected.parentDatabase}</Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Created at:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">\n {selected.createdAt instanceof Date\n ? selected.createdAt.toLocaleString()\n : new Date(selected.createdAt).toLocaleString()}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n </>\n )}\n </div>\n\n {/* ── Create Dialog ── */}\n <Dialog open={createOpen} onOpenChange={(open) => {\n setCreateOpen(open);\n if (!open) setCreateError(null);\n }}>\n <DialogTitle>Create New Branch</DialogTitle>\n <DialogContent className=\"space-y-4 min-w-[400px]\">\n <Typography variant=\"body2\" color=\"secondary\" className=\"text-[13px]\">\n Create an isolated database copy. The branch will be a full clone of the source database at this point in time.\n </Typography>\n {createError && (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"text-[13px]\">{createError}</Typography>\n </Alert>\n )}\n <div>\n <TextField\n label=\"Branch Name\"\n value={newBranchName}\n onChange={(e) => setNewBranchName(e.target.value)}\n placeholder=\"e.g. feature-auth, staging, preview-pr-42\"\n size=\"small\"\n autoFocus\n />\n </div>\n {branches.length > 0 && (\n <div>\n <Select\n label=\"Source Database\"\n value={sourceBranch ?? \"__main__\"}\n onValueChange={(v) => setSourceBranch(v === \"__main__\" ? undefined : v)}\n placeholder=\"Default (main database)\"\n size=\"small\"\n >\n <SelectItem value=\"__main__\">Default (main database)</SelectItem>\n {branches.map(b => (\n <SelectItem key={b.name} value={b.name}>{b.name}</SelectItem>\n ))}\n </Select>\n </div>\n )}\n </DialogContent>\n <DialogActions>\n <Button variant=\"text\" onClick={() => setCreateOpen(false)} disabled={creating}>\n Cancel\n </Button>\n <Button\n color=\"primary\"\n onClick={handleCreate}\n disabled={!newBranchName.trim() || creating}\n startIcon={creating ? <CircularProgress size=\"smallest\"/> : <PlusIcon size={iconSize.smallest}/>}\n >\n {creating ? \"Creating...\" : \"Create Branch\"}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* ── Delete Confirm ── */}\n <ConfirmationDialog\n open={!!deleteTarget}\n onAccept={handleDelete}\n onCancel={() => setDeleteTarget(null)}\n title=\"Delete Branch\"\n body={\n <Typography variant=\"body2\">\n Are you sure you want to permanently delete the branch <strong>{deleteTarget}</strong>?\n This action cannot be undone and all data in this branch will be lost.\n </Typography>\n }\n loading={deleting}\n />\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono }: {\n label: string; value: string; mono?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-surface-card\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px] truncate\",\n mono && \"font-mono\"\n )}>{value}</Typography>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"CronJobsView-7hjgSYId.js","names":[],"sources":["../src/components/CronJobs/CronJobsView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef } from \"react\";\nimport {\n AlertCircleIcon,\n Button,\n CalendarIcon,\n Card,\n CheckCircleIcon,\n Chip,\n CircularProgress,\n cls,\n defaultBorderMixin,\n HistoryIcon,\n IconButton,\n iconSize,\n Paper,\n PauseIcon,\n PlayIcon,\n RefreshCwIcon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController, useTranslation } from \"@rebasepro/app\";\nimport type { CronJobStatus, CronJobLogEntry } from \"@rebasepro/types\";\nimport type { RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${(ms / 60000).toFixed(1)}m`;\n}\n\nfunction formatRelative(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n const now = Date.now();\n const diff = d.getTime() - now;\n const abs = Math.abs(diff);\n if (abs < 60000) return diff > 0 ? \"in <1m\" : \"<1m ago\";\n if (abs < 3600000) { const m = Math.round(abs / 60000); return diff > 0 ? `in ${m}m` : `${m}m ago`; }\n if (abs < 86400000) { const h = Math.round(abs / 3600000); return diff > 0 ? `in ${h}h` : `${h}h ago`; }\n return d.toLocaleString();\n}\n\nconst stateColors: Record<string, string> = {\n idle: \"bg-emerald-500\",\nrunning: \"bg-blue-500\",\nsuccess: \"bg-emerald-500\",\n error: \"bg-red-500\",\ndisabled: \"bg-surface-400\"\n};\n\nexport function CronJobsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n const [jobs, setJobs] = useState<CronJobStatus[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [logs, setLogs] = useState<CronJobLogEntry[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n /**\n * Why the selected job's executions could not be read. Shown where the\n * list would be: an empty list reads as \"this job has never run\", and the\n * snackbar that used to carry the reason was gone in four seconds.\n */\n const [logsFailure, setLogsFailure] = useState<LoadFailure | null>(null);\n const [triggering, setTriggering] = useState<string | null>(null);\n /** Why the job listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Refs so effects never re-fire due to identity changes\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n // ── Fetch jobs on mount + poll every 15s ──\n useEffect(() => {\n let cancelled = false;\n\n async function load() {\n const c = clientRef.current;\n if (!c?.cron) {\n setLoading(false);\n return;\n }\n try {\n const res = await c.cron.listJobs();\n if (!cancelled) {\n setJobs(res.jobs);\n setFailure(null);\n }\n } catch (e: unknown) {\n // A snackbar over an empty list said \"No Cron Jobs Registered\"\n // about a project whose crons the caller may not read. This\n // view polls every 15s, so the toast is also gone long before\n // anyone looks.\n if (!cancelled) setFailure(classifyLoadFailure(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n load();\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const scheduleNext = () => {\n if (cancelled) return;\n timeoutId = setTimeout(async () => {\n if (document.visibilityState === \"visible\") {\n await load();\n }\n scheduleNext();\n }, 15_000);\n };\n\n scheduleNext();\n\n const handleVisibility = () => {\n if (document.visibilityState === \"visible\") {\n load();\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibility);\n\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n document.removeEventListener(\"visibilitychange\", handleVisibility);\n };\n }, []); // runs once\n\n // ── Fetch logs when selection changes ──\n useEffect(() => {\n if (!selectedId) {\n setLogs([]);\n setLogsFailure(null);\n return;\n }\n let cancelled = false;\n const c = clientRef.current;\n if (!c?.cron) return;\n\n setLogsLoading(true);\n setLogsFailure(null);\n c.cron.getJobLogs(selectedId, { limit: 25 })\n .then(res => { if (!cancelled) setLogs(res.logs); })\n .catch((e: unknown) => {\n if (cancelled) return;\n // Same reasoning as `refreshLogs`: an empty log list is a claim.\n setLogs([]);\n setLogsFailure(classifyLoadFailure(e));\n })\n .finally(() => { if (!cancelled) setLogsLoading(false); });\n\n return () => { cancelled = true; };\n }, [selectedId]);\n\n // ── Imperative helpers (not in any dep array) ──\n async function refreshJobs() {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n const res = await c.cron.listJobs();\n setJobs(res.jobs);\n setFailure(null);\n } catch (e: unknown) {\n // Swallowed before, which left the list showing whatever it last\n // held — or nothing — after a failed refresh. \"No cron jobs\" and\n // \"could not read the cron jobs\" are not the same statement, so the\n // reason stays on screen rather than passing through a snackbar.\n setFailure(classifyLoadFailure(e));\n }\n }\n\n async function refreshLogs(id: string) {\n const c = clientRef.current;\n if (!c?.cron) return;\n setLogsLoading(true);\n setLogsFailure(null);\n try {\n const res = await c.cron.getJobLogs(id, { limit: 25 });\n setLogs(res.logs);\n } catch (e: unknown) {\n // Clearing the list silently reads as \"this job has never run\".\n setLogs([]);\n setLogsFailure(classifyLoadFailure(e));\n }\n finally { setLogsLoading(false); }\n }\n\n const handleTrigger = async (id: string) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n setTriggering(id);\n try {\n await c.cron.triggerJob(id);\n snackbarRef.current.open({ type: \"success\",\nmessage: \"Job triggered\" });\n await refreshJobs();\n if (selectedId === id) refreshLogs(id);\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally { setTriggering(null); }\n };\n\n const handleToggle = async (id: string, enabled: boolean) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n await c.cron.toggleJob(id, enabled);\n snackbarRef.current.open({ type: \"success\",\nmessage: enabled ? \"Job enabled\" : \"Job paused\" });\n await refreshJobs();\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n }\n };\n\n const selectedJob = jobs.find(j => j.id === selectedId);\n\n if (loading) return <div className=\"flex items-center justify-center h-full\"><CircularProgress/></div>;\n\n if (failure) return (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_cron_read_failed\")}\n deniedTitle={t(\"studio_cron_denied_title\")}\n deniedHint={t(\"studio_cron_denied_hint\")}\n onRetry={refreshJobs}\n />\n );\n\n if (jobs.length === 0) return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <CalendarIcon size={iconSize.medium} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_cron_empty_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_cron_empty_body\")}\n </Typography>\n <a\n href=\"https://rebase.pro/docs/backend/cron-jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary text-sm underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </div>\n );\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-surface-card\">\n {/* ── Job List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <CalendarIcon size={iconSize.smallest} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_cron\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{jobs.length}</Chip>\n </div>\n <IconButton size=\"small\" onClick={refreshJobs} title=\"Refresh\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {jobs.map(job => (\n <div\n key={job.id}\n onClick={() => setSelectedId(job.id)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedId === job.id\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-hover\"\n )}\n >\n <div className={cls(\"w-2 h-2 rounded-full shrink-0\", stateColors[job.state] || \"bg-surface-400\")}/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{job.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px] font-mono\">{job.schedule}</Typography>\n </div>\n {job.state === \"running\" && <CircularProgress size=\"smallest\"/>}\n </div>\n ))}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selectedJob ? (\n <div className=\"flex items-center justify-center h-full\">\n <Typography variant=\"body2\" color=\"disabled\">Select a cron job to view details</Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-surface-card min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className={cls(\"w-2.5 h-2.5 rounded-full\", stateColors[selectedJob.state])}/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selectedJob.name}</Typography>\n {selectedJob.description && <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">{selectedJob.description}</Typography>}\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <IconButton title={selectedJob.enabled ? \"Pause job\" : \"Enable job\"} size=\"small\" onClick={() => handleToggle(selectedJob.id, !selectedJob.enabled)}>\n {selectedJob.enabled ? <PauseIcon size={iconSize.small}/> : <PlayIcon size={iconSize.smallest}/>}\n </IconButton>\n <Button\n size=\"small\"\n color=\"primary\"\n onClick={() => handleTrigger(selectedJob.id)}\n disabled={triggering === selectedJob.id}\n startIcon={triggering === selectedJob.id ? <CircularProgress size=\"smallest\"/> : <PlayIcon size={iconSize.smallest}/>}\n >\n Run Now\n </Button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"px-5 py-4 bg-surface-sheet\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Schedule\" value={selectedJob.schedule} mono/>\n <StatCard label=\"Last Run\" value={formatRelative(selectedJob.lastRunAt)}/>\n <StatCard label=\"Next Run\" value={selectedJob.enabled ? formatRelative(selectedJob.nextRunAt) : \"Paused\"}/>\n <StatCard label=\"Duration\" value={selectedJob.lastDurationMs !== undefined ? formatDuration(selectedJob.lastDurationMs) : \"—\"}/>\n </div>\n <div className=\"grid grid-cols-3 gap-3 mt-3\">\n <StatCard label=\"Status\" value={selectedJob.state.toUpperCase()} chipColor={selectedJob.state === \"error\" ? \"red\" : selectedJob.state === \"disabled\" ? \"gray\" : \"green\"}/>\n <StatCard label=\"Total Runs\" value={String(selectedJob.totalRuns)}/>\n <StatCard label=\"Failures\" value={String(selectedJob.totalFailures)} highlight={selectedJob.totalFailures > 0}/>\n </div>\n {selectedJob.lastError && (\n <div className=\"mt-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <div className=\"flex items-center gap-2 mb-1\">\n <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500\"/>\n <Typography variant=\"caption\" className=\"font-semibold text-red-700 dark:text-red-400\">Last Error</Typography>\n </div>\n <Typography variant=\"caption\" className=\"font-mono text-red-600 dark:text-red-300 text-[11px] break-all\">{selectedJob.lastError}</Typography>\n </div>\n )}\n </div>\n\n {/* Logs Section */}\n <div className={cls(\"flex items-center justify-between px-5 py-2 border-y bg-surface-card\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <HistoryIcon size={iconSize.smallest} className=\"text-surface-400\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold text-[13px]\">Execution History</Typography>\n </div>\n <IconButton size=\"small\" onClick={() => refreshLogs(selectedJob.id)} title=\"Refresh logs\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n {logsLoading ? (\n <div className=\"flex justify-center p-8\"><CircularProgress size=\"small\"/></div>\n ) : logsFailure ? (\n <LoadFailureView\n failure={logsFailure}\n title={t(\"studio_cron_logs_read_failed\")}\n deniedTitle={t(\"studio_cron_logs_denied_title\")}\n deniedHint={t(\"studio_cron_denied_hint\")}\n onRetry={() => refreshLogs(selectedJob.id)}\n />\n ) : logs.length === 0 ? (\n <div className=\"flex items-center justify-center h-32\">\n <Typography variant=\"body2\" color=\"disabled\">No executions yet</Typography>\n </div>\n ) : (\n <div className=\"divide-y divide-surface-100 dark:divide-surface-950\">\n {logs.map((log, idx) => (\n <LogRow key={idx} log={log}/>\n ))}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono, chipColor, highlight }: {\n label: string; value: string; mono?: boolean; chipColor?: string; highlight?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-surface-card\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px]\",\n mono && \"font-mono\",\n highlight && \"text-red-500 dark:text-red-400\",\n chipColor === \"red\" && \"text-red-500\",\n chipColor === \"green\" && \"text-emerald-500\",\n chipColor === \"gray\" && \"text-surface-400\"\n )}>{value}</Typography>\n </div>\n );\n}\n\nfunction LogRow({ log }: { log: CronJobLogEntry }) {\n const [expanded, setExpanded] = useState(false);\n return (\n <div className=\"px-5 py-2.5 hover:bg-surface-hover transition-colors\">\n <div className=\"flex items-center gap-3 cursor-pointer\" onClick={() => setExpanded(!expanded)}>\n {log.success\n ? <CheckCircleIcon size={iconSize.smallest} className=\"text-emerald-500 shrink-0\"/>\n : <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500 shrink-0\"/>}\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-surface-500\">{new Date(log.startedAt).toLocaleString()}</Typography>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {log.manual && <Chip size=\"smallest\" className=\"bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300\">manual</Chip>}\n <Typography variant=\"caption\" className=\"font-mono text-[11px]\">{formatDuration(log.durationMs)}</Typography>\n <svg className={cls(\"w-3 h-3 transition-transform text-surface-400\", expanded && \"rotate-180\")} fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"/></svg>\n </div>\n </div>\n {expanded && (\n <div className=\"mt-2 ml-6 space-y-2\">\n {log.error && (\n <div className=\"p-2 rounded bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-red-600 dark:text-red-300 break-all\">{log.error}</Typography>\n </div>\n )}\n {log.logs.length > 0 && (\n <div className=\"p-2 rounded bg-surface-sheet border border-hairline-strong max-h-40 overflow-auto\">\n {log.logs.map((line, i) => (\n <div key={i} className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 leading-relaxed\">{line}</div>\n ))}\n </div>\n )}\n {log.result !== undefined && (\n <div className=\"p-2 rounded bg-surface-sheet border border-hairline-strong\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-1 block\">Result</Typography>\n <pre className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 whitespace-pre-wrap break-all\">{JSON.stringify(log.result, null, 2)}</pre>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"JSEditor-qiuwmcvk.js","names":[],"sources":["../src/components/JSEditor/JSMonacoEditor.tsx","../src/components/JSEditor/JSEditorSidebar.tsx","../src/components/JSEditor/JSEditor.tsx"],"sourcesContent":["\nimport React, { useRef, useEffect } from \"react\";\nimport Editor, { Monaco, OnMount } from \"@monaco-editor/react\";\nimport type { editor } from \"monaco-editor\";\nimport { cls, defaultBorderMixin, FileIcon } from \"@rebasepro/ui\";\nimport { useModeController } from \"@rebasepro/app\";\nimport { ALL_WHERE_FILTER_OPS } from \"@rebasepro/types\";\n\n/**\n * Shape of `monaco.languages.typescript` at runtime.\n * The bundled type stubs mark this API as deprecated, but it is fully functional.\n */\ninterface MonacoTypeScriptApi {\n typescriptDefaults: {\n setCompilerOptions(options: Record<string, unknown>): void;\n setDiagnosticsOptions(options: Record<string, unknown>): void;\n addExtraLib(content: string, filePath: string): void;\n };\n ScriptTarget: Record<string, number>;\n ModuleKind: Record<string, number>;\n}\n\n/**\n * Ambient type definitions for the Rebase client SDK injected into Monaco.\n *\n * Hand-mirrored from `@rebasepro/client`, so it drifts: this block spent long\n * enough declaring the ten Firestore-era filter operators and a\n * `where?: Record<string, string>` that the editor was autocompleting a query\n * shape the server rejects. The operator union is interpolated from\n * {@link ALL_WHERE_FILTER_OPS} for that reason — it is the one part that cannot\n * fall behind. The rest still has to be kept in step by hand.\n */\nconst REBASE_CLIENT_TYPES = `\n// ─── Rebase Client SDK Type Definitions ─────────────────────────────\n\n/** A flat database row with the id merged at the top level. */\n\ninterface FindParams {\n limit?: number;\n offset?: number;\n page?: number;\n /** \\`{ status: [\"==\", \"active\"] }\\` — a tuple per field, or an array of tuples. */\n where?: Record<string, [WhereFilterOp, any] | [WhereFilterOp, any][]>;\n logical?: LogicalCondition;\n /** \\`[\"created_at\", \"desc\"]\\` */\n orderBy?: [string, \"asc\" | \"desc\"];\n include?: string[];\n searchString?: string;\n}\n\ninterface FindResult<M extends Record<string, any> = any> {\n data: M[];\n meta: {\n total: number;\n limit: number;\n offset: number;\n hasMore: boolean;\n };\n}\n\ntype WhereFilterOp = ${ALL_WHERE_FILTER_OPS.map(op => `\"${op}\"`).join(\" | \")};\n\ninterface FilterCondition {\n column: string;\n operator: WhereFilterOp;\n value: any;\n}\n\ninterface LogicalCondition {\n type: \"and\" | \"or\";\n conditions: (FilterCondition | LogicalCondition)[];\n}\n\ninterface QueryBuilder<M extends Record<string, any> = any> {\n where(column: keyof M & string, operator: WhereFilterOp, value: any): QueryBuilder<M>;\n orderBy(column: keyof M & string, direction?: \"asc\" | \"desc\"): QueryBuilder<M>;\n limit(count: number): QueryBuilder<M>;\n offset(count: number): QueryBuilder<M>;\n search(searchString: string): QueryBuilder<M>;\n find(): Promise<FindResult<M>>;\n count(): Promise<number>;\n}\n\ninterface CollectionClient<M extends Record<string, any> = any> {\n find(params?: FindParams): Promise<FindResult<M>>;\n findById(id: string | number): Promise<M | undefined>;\n create(data: Partial<M>, id?: string | number): Promise<M>;\n update(id: string | number, data: Partial<M>): Promise<M>;\n delete(id: string | number): Promise<void>;\n where(column: keyof M & string, operator: WhereFilterOp, value: any): QueryBuilder<M>;\n orderBy(column: keyof M & string, direction?: \"asc\" | \"desc\"): QueryBuilder<M>;\n limit(count: number): QueryBuilder<M>;\n offset(count: number): QueryBuilder<M>;\n search(searchString: string): QueryBuilder<M>;\n listen?(params: FindParams | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;\n listenById?(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void): () => void;\n count?(params?: FindParams): Promise<number>;\n}\n\ninterface User {\n uid: string;\n email: string | null;\n displayName: string | null;\n photoURL: string | null;\n providerId: string;\n isAnonymous: boolean;\n emailVerified?: boolean;\n roles?: string[];\n metadata?: Record<string, unknown>;\n}\n\ninterface RebaseSession {\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n user: User;\n}\n\ntype AuthChangeEvent = 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED';\n\ninterface RebaseAuth {\n signInWithEmail(email: string, password: string): Promise<{ user: User; accessToken: string; refreshToken: string }>;\n signUp(email: string, password: string, displayName?: string): Promise<{ user: User; accessToken: string; refreshToken: string }>;\n signInWithGoogle(idToken: string): Promise<{ user: User; accessToken: string; refreshToken: string }>;\n signOut(): Promise<void>;\n refreshSession(): Promise<RebaseSession>;\n getUser(): Promise<User>;\n updateUser(updates: { displayName?: string; photoURL?: string }): Promise<User>;\n resetPasswordForEmail(email: string): Promise<{ success: boolean; message: string }>;\n resetPassword(token: string, password: string): Promise<{ success: boolean; message: string }>;\n changePassword(oldPassword: string, newPassword: string): Promise<{ success: boolean; message: string }>;\n sendVerificationEmail(): Promise<{ success: boolean; message: string }>;\n verifyEmail(token: string): Promise<{ success: boolean; message: string }>;\n getSessions(): Promise<RebaseSession[]>;\n revokeSession(sessionId: string): Promise<{ success: boolean }>;\n revokeAllSessions(): Promise<{ success: boolean }>;\n getSession(): RebaseSession | null;\n onAuthStateChange(callback: (event: AuthChangeEvent, session: RebaseSession | null) => void): () => void;\n}\n\ninterface AdminUser {\n uid: string;\n email: string;\n displayName: string | null;\n photoURL: string | null;\n provider: string;\n roles: string[];\n createdAt: string;\n updatedAt: string;\n}\n\ninterface RebaseCMS {\n listUsers(): Promise<{ users: AdminUser[] }>;\n getUser(userId: string): Promise<{ user: AdminUser }>;\n createUser(data: { email: string; displayName?: string; password?: string; roles?: string[] }): Promise<{ user: AdminUser }>;\n updateUser(userId: string, data: { email?: string; displayName?: string; password?: string; roles?: string[] }): Promise<{ user: AdminUser }>;\n deleteUser(userId: string): Promise<{ success: boolean }>;\n bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;\n}\n\ninterface UploadFileProps {\n file: FileIcon;\n fileName?: string;\n path?: string;\n metadata?: Record<string, unknown>;\n bucket?: string;\n}\n\ninterface UploadFileResult {\n path: string;\n bucket?: string;\n downloadUrl?: string;\n}\n\ninterface DownloadConfig {\n url: string | null;\n fileNotFound?: boolean;\n metadata?: Record<string, unknown>;\n}\n\ninterface StorageSource {\n putObject(props: UploadFileProps): Promise<UploadFileResult>;\n getSignedUrl(pathOrUrl: string, bucket?: string): Promise<DownloadConfig>;\n getObject(path: string, bucket?: string): Promise<FileIcon | null>;\n deleteObject(path: string, bucket?: string): Promise<void>;\n listObjects(path: string, options?: { bucket?: string; maxResults?: number; pageToken?: string }): Promise<unknown>;\n}\n\ntype RebaseData = {\n /** Look up a collection by slug. */\n collection(slug: string): CollectionClient;\n} & {\n /** Dynamic collection access — e.g. client.data.authors */\n [collectionSlug: string]: CollectionClient;\n};\n\n/**\n * The Rebase client instance. Use \\`client.data\\`, \\`client.auth\\`, \\`client.admin\\`,\n * \\`client.storage\\`, and \\`client.call()\\` to interact with your Rebase backend.\n *\n * @example\n * // Query a collection\n * const result = await client.data.products.find({ limit: 10 });\n *\n * // Create a record\n * await client.data.products.create({ name: \"Camera\", price: 299 });\n *\n * // Fluent query\n * const expensive = await client.data.products.where(\"price\", \">\", 100).orderBy(\"price\", \"desc\").limit(5).find();\n *\n * // Auth\n * const session = client.auth.getSession();\n *\n * // Admin\n * const { users } = await client.admin.listUsers();\n *\n * // Custom endpoint\n * const result = await client.call(\"/my-endpoint\", { myData: 123 });\n */\ninterface RebaseClient {\n /** Data access — dynamic collection accessors */\n data: RebaseData;\n /** Authentication methods */\n auth: RebaseAuth;\n /** User/role admin methods */\n admin: RebaseCMS;\n /** Storage operations */\n storage?: StorageSource;\n /** Call a custom server-side endpoint */\n call<T = unknown>(endpoint: string, payload?: unknown): Promise<T>;\n /** Direct collection access (shorthand) */\n [collectionSlug: string]: unknown;\n}\n\n/** The pre-configured client instance. Already authenticated with the current user session. */\ndeclare const client: RebaseClient;\n\n/** Execution context with user and collection information. */\ninterface JSEditorContext {\n /** The user the script is running as. */\n user: {\n uid: string;\n displayName: string | null;\n email: string | null;\n roles?: string[];\n } | null;\n /** Registered collections with their property names. */\n collections: Array<{\n slug: string;\n name: string;\n properties: string[];\n }>;\n}\n\n/** Execution context — contains info about the selected user and registered collections. */\ndeclare const context: JSEditorContext;\n`;\n\nexport type CollectionInfo = {\n slug: string;\n name: string;\n properties: string[];\n};\n\nexport type JSMonacoEditorProps = {\n value: string;\n onChange: (value: string | undefined) => void;\n onRun?: (selectedText?: string) => void;\n className?: string;\n readOnly?: boolean;\n autoFocus?: boolean;\n /** Collection slugs for basic autocomplete */\n collectionSlugs?: string[];\n /** Full collection info with property names for richer type generation */\n collections?: CollectionInfo[];\n};\n\nexport const JSMonacoEditor = ({\n value,\n onChange,\n onRun,\n className,\n readOnly = false,\n autoFocus = true,\n collectionSlugs = [],\n collections = []\n}: JSMonacoEditorProps) => {\n const { mode } = useModeController();\n const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);\n const monacoRef = useRef<Monaco | null>(null);\n const onRunRef = useRef(onRun);\n onRunRef.current = onRun;\n const typesRegisteredRef = useRef(false);\n\n const handleEditorOnMount: OnMount = (editorInstance, monaco) => {\n editorRef.current = editorInstance;\n monacoRef.current = monaco;\n\n // Register Cmd/Ctrl+Enter to run\n editorInstance.addAction({\n id: \"run-script\",\n label: \"Run Script\",\n keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],\n contextMenuGroupId: \"operation\",\n contextMenuOrder: 0,\n run: () => {\n if (onRunRef.current) {\n const selection = editorInstance.getSelection();\n let selectedText: string | undefined;\n if (selection && !selection.isEmpty()) {\n selectedText = editorInstance.getModel()?.getValueInRange(selection)?.trim();\n }\n onRunRef.current(selectedText || undefined);\n }\n }\n });\n\n // Configure TypeScript/JavaScript defaults for richer completion\n if (!typesRegisteredRef.current) {\n typesRegisteredRef.current = true;\n\n // Configure TS compiler for modern JS with top-level await\n // Note: cast through `any` because the bundled type stubs mark\n // `monaco.languages.typescript` as deprecated while the runtime\n // API is fully functional.\n const ts = (monaco.languages as unknown as { typescript: MonacoTypeScriptApi }).typescript;\n ts.typescriptDefaults.setCompilerOptions({\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n allowJs: true,\n checkJs: false,\n strict: false,\n noEmit: true,\n allowNonTsExtensions: true\n });\n\n // Suppress diagnostics that don't apply to our script context.\n // Scripts are executed inside `new AsyncFunction(...)` at runtime,\n // so top-level `await` (1375) and `return` (1108) are valid.\n ts.typescriptDefaults.setDiagnosticsOptions({\n diagnosticCodesToIgnore: [1375, 1108]\n });\n\n // Inject the Rebase Client SDK type definitions\n ts.typescriptDefaults.addExtraLib(\n REBASE_CLIENT_TYPES,\n \"ts:rebase-client.d.ts\"\n );\n\n // Generate collection-specific types from registered collections\n if (collections.length > 0) {\n const lines: string[] = [];\n for (const col of collections) {\n // Generate a typed interface for each collection\n const ifaceName = col.slug.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const propsType = col.properties.length > 0\n ? `{ ${col.properties.map(p => `${p}: any`).join(\"; \")} }`\n : \"Record<string, any>\";\n lines.push(`/** Collection: ${col.name} (${col.slug}) */`);\n lines.push(`interface ${ifaceName}_Row ${propsType}`);\n lines.push(`declare namespace client.data { const ${col.slug}: CollectionClient<${ifaceName}_Row>; }`);\n }\n ts.typescriptDefaults.addExtraLib(\n lines.join(\"\\n\"),\n \"ts:rebase-collections.d.ts\"\n );\n } else if (collectionSlugs.length > 0) {\n // Fallback: just slug names without property info\n const collectionHints = collectionSlugs.map(slug =>\n `/** Collection: ${slug} */\\ndeclare namespace client.data { const ${slug}: CollectionClient; }`\n ).join(\"\\n\");\n ts.typescriptDefaults.addExtraLib(\n collectionHints,\n \"ts:rebase-collections.d.ts\"\n );\n }\n }\n\n if (autoFocus) {\n editorInstance.focus();\n }\n };\n\n return (\n <div className={cls(\"relative w-full h-full overflow-hidden\", className)}>\n <Editor\n height=\"100%\"\n defaultLanguage=\"typescript\"\n path=\"rebase-script.ts\"\n value={value}\n onChange={onChange}\n onMount={handleEditorOnMount}\n theme={mode === \"dark\" ? \"vs-dark\" : \"vs\"}\n options={{\n minimap: { enabled: false },\n fontSize: 14,\n lineNumbers: \"on\",\n scrollBeyondLastLine: false,\n automaticLayout: true,\n readOnly,\n tabSize: 2,\n wordWrap: \"on\",\n suggestOnTriggerCharacters: true,\n quickSuggestions: true,\n parameterHints: { enabled: true }\n }}\n />\n </div>\n );\n};\n","\nimport React, { useState } from \"react\";\nimport {\n cls,\n defaultBorderMixin,\n IconButton,\n iconSize,\n PlusIcon,\n Tab,\n Tabs,\n Tooltip,\n Trash2Icon,\n Typography\n} from \"@rebasepro/ui\";\n\nexport interface JSSnippet {\n id: string;\n name: string;\n code: string;\n createdAt: number;\n isFavorite?: boolean;\n}\n\ninterface CollectionInfo {\n slug: string;\n properties: string[];\n}\n\ninterface JSEditorSidebarProps {\n collections: CollectionInfo[];\n snippets: JSSnippet[];\n history: string[];\n onSelectSnippet: (code: string) => void;\n onDeleteSnippet: (id: string) => void;\n onInsertCode: (code: string) => void;\n}\n\nconst QUICK_REFERENCE: { label: string; code: string; description: string }[] = [\n {\n label: \"Find all\",\n code: \"const result = await client.data.collection(\\\"COLLECTION\\\").find({ limit: 20 });\\nreturn result;\",\n description: \"Fetch records with pagination\"\n },\n {\n label: \"Find by ID\",\n code: \"const item = await client.data.collection(\\\"COLLECTION\\\").findById(\\\"ID\\\");\\nreturn item;\",\n description: \"Fetch a single record\"\n },\n {\n label: \"Create\",\n code: \"const created = await client.data.collection(\\\"COLLECTION\\\").create({\\n // your fields here\\n});\\nreturn created;\",\n description: \"Insert a new record\"\n },\n {\n label: \"Update\",\n code: \"const updated = await client.data.collection(\\\"COLLECTION\\\").update(\\\"ID\\\", {\\n // fields to update\\n});\\nreturn updated;\",\n description: \"Update an existing record\"\n },\n {\n label: \"Delete\",\n code: \"await client.data.collection(\\\"COLLECTION\\\").delete(\\\"ID\\\");\\nreturn { success: true };\",\n description: \"Delete a record\"\n },\n {\n label: \"Fluent query\",\n code: \"const result = await client.data.collection(\\\"COLLECTION\\\")\\n .where(\\\"field\\\", \\\">\\\", value)\\n .orderBy(\\\"field\\\", \\\"desc\\\")\\n .limit(10)\\n .find();\\nreturn result;\",\n description: \"Chained query builder\"\n },\n {\n label: \"Auth: Current session\",\n code: \"const session = client.auth.getSession();\\nreturn session;\",\n description: \"Get current auth session\"\n },\n {\n label: \"Admin: List users\",\n code: \"const { users } = await client.admin.listUsers();\\nreturn users;\",\n description: \"List all registered users\"\n },\n {\n label: \"Custom endpoint\",\n code: \"const result = await client.call(\\\"/my-endpoint\\\", { key: \\\"value\\\" });\\nreturn result;\",\n description: \"Call a custom API endpoint\"\n }\n];\n\nexport const JSEditorSidebar = ({\n collections,\n snippets,\n history,\n onSelectSnippet,\n onDeleteSnippet,\n onInsertCode\n}: JSEditorSidebarProps) => {\n const [activeTab, setActiveTab] = useState<\"collections\" | \"snippets\" | \"history\" | \"reference\">(\"collections\");\n\n return (\n <div className={cls(\"flex flex-col h-full w-full bg-surface-card border-r\", defaultBorderMixin)}>\n <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as \"collections\" | \"snippets\" | \"history\" | \"reference\")} variant=\"boxy\" className=\"border-b border-hairline\">\n <Tab value=\"collections\">\n <Tooltip title=\"Browse collections\">\n <span className=\"text-xs\">Collections</span>\n </Tooltip>\n </Tab>\n <Tab value=\"reference\">\n <Tooltip title=\"SDK quick reference\">\n <span className=\"text-xs\">Reference</span>\n </Tooltip>\n </Tab>\n <Tab value=\"snippets\">\n <span className=\"text-xs\">Snippets</span>\n </Tab>\n <Tab value=\"history\">\n <span className=\"text-xs\">History</span>\n </Tab>\n </Tabs>\n\n <div className=\"flex-grow overflow-hidden relative\">\n {/* Collections browser */}\n {activeTab === \"collections\" && (\n <div className=\"flex flex-col h-full\">\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark\">\n Collections\n </Typography>\n </div>\n <div className=\"flex-grow overflow-y-auto p-1 space-y-1 no-scrollbar\">\n {collections.length === 0 ? (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark\">\n No collections found\n </Typography>\n </div>\n ) : (\n collections.map(col => (\n <CollectionItem\n key={col.slug}\n collection={col}\n onInsertCode={onInsertCode}\n />\n ))\n )}\n </div>\n </div>\n )}\n\n {/* Quick reference */}\n {activeTab === \"reference\" && (\n <div className=\"flex flex-col h-full\">\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark\">\n SDK Reference\n </Typography>\n </div>\n <div className=\"flex-grow overflow-y-auto p-2 space-y-2 no-scrollbar\">\n {QUICK_REFERENCE.map((ref, i) => (\n <div\n key={i}\n className={cls(\n \"group p-2.5 rounded-lg border hover:border-primary/40 dark:hover:border-primary/40\",\n \"bg-surface-card transition-all cursor-pointer\",\n defaultBorderMixin\n )}\n onClick={() => onInsertCode(ref.code)}\n >\n <div className=\"flex items-center justify-between mb-1\">\n <Typography variant=\"body2\" className=\"text-text-primary dark:text-text-primary-dark font-semibold text-[13px]\">\n {ref.label}\n </Typography>\n <svg className=\"w-3.5 h-3.5 text-text-disabled group-hover:text-primary transition-colors\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\"/>\n </svg>\n </div>\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark text-[10px] leading-tight\">\n {ref.description}\n </Typography>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Snippets */}\n {activeTab === \"snippets\" && (() => {\n const favorites = snippets.filter(s => s.isFavorite);\n const others = snippets.filter(s => !s.isFavorite);\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark\">Snippets</Typography>\n </div>\n <div className=\"flex-grow overflow-y-auto p-2 space-y-2 no-scrollbar\">\n {snippets.length === 0 ? (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark\">\n No saved snippets yet. Use the save button to store reusable code.\n </Typography>\n </div>\n ) : (\n <>\n {favorites.length > 0 && (\n <div className=\"mb-3\">\n <Typography variant=\"caption\" className=\"text-[10px] font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark mb-2 px-1 flex items-center\">\n <svg className=\"w-3 h-3 mr-1 text-amber-500\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z\"/></svg>\n Favorites\n </Typography>\n <div className=\"space-y-2\">\n {favorites.map(snippet => (\n <SnippetItem key={snippet.id} snippet={snippet} onSelect={onSelectSnippet} onDelete={onDeleteSnippet}/>\n ))}\n </div>\n </div>\n )}\n {others.length > 0 && (\n <div>\n {favorites.length > 0 && (\n <Typography variant=\"caption\" className=\"text-[10px] font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark mb-2 px-1\">\n Others\n </Typography>\n )}\n <div className=\"space-y-2\">\n {others.map(snippet => (\n <SnippetItem key={snippet.id} snippet={snippet} onSelect={onSelectSnippet} onDelete={onDeleteSnippet}/>\n ))}\n </div>\n </div>\n )}\n </>\n )}\n </div>\n </div>\n );\n })()}\n\n {/* History */}\n {activeTab === \"history\" && (\n <div className=\"flex flex-col h-full\">\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark\">History</Typography>\n </div>\n <div className=\"flex-grow overflow-y-auto p-1 space-y-1 no-scrollbar\">\n {history.length === 0 ? (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark\">\n No execution history yet\n </Typography>\n </div>\n ) : (\n [...history].reverse().map((code, i) => (\n <div\n key={i}\n className=\"p-2 py-1.5 rounded hover:bg-surface-hover cursor-pointer group transition-colors flex items-start\"\n onClick={() => onSelectSnippet(code)}\n >\n <svg className=\"w-3 h-3 mt-1 mr-2 text-text-disabled dark:text-text-disabled-dark group-hover:text-primary transition-colors flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"/>\n </svg>\n <Typography variant=\"caption\" className=\"text-text-secondary dark:text-text-secondary-dark group-hover:text-text-primary dark:group-hover:text-text-primary-dark text-[11px] line-clamp-2 leading-tight flex-grow font-mono\">\n {code.length > 120 ? code.substring(0, 120) + \"…\" : code}\n </Typography>\n </div>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n );\n};\n\n// ─── Sub-components ──────────────────────────────────────────────────\n\nfunction CollectionItem({ collection, onInsertCode }: { collection: CollectionInfo; onInsertCode: (code: string) => void }) {\n const [expanded, setExpanded] = useState(false);\n\n return (\n <div>\n <div\n className=\"flex items-center p-1 cursor-pointer hover:bg-surface-hover rounded transition-colors group relative\"\n onClick={() => setExpanded(!expanded)}\n >\n <svg\n className={cls(\"w-3 h-3 mr-1 transition-transform\", expanded && \"rotate-90\")}\n fill=\"currentColor\" viewBox=\"0 0 20 20\"\n >\n <path d=\"M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z\"/>\n </svg>\n <svg className=\"w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\"/>\n </svg>\n <Typography variant=\"body2\" className=\"text-text-secondary dark:text-text-secondary-dark text-xs truncate flex-1 min-w-0\">\n {collection.slug}\n </Typography>\n <Tooltip title=\"Insert find() snippet\">\n <IconButton\n size=\"smallest\"\n className=\"opacity-0 group-hover:opacity-100 text-text-disabled hover:text-primary transition-all\"\n onClick={(e) => {\n e.stopPropagation();\n onInsertCode(`const result = await client.data.${collection.slug}.find({ limit: 20 });\\nreturn result;`);\n }}\n >\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n </div>\n {expanded && collection.properties.length > 0 && (\n <div className={cls(\"ml-5 mt-1 space-y-0.5 border-l\", defaultBorderMixin)}>\n {collection.properties.map(prop => (\n <div\n key={prop}\n className=\"flex items-center p-1 pl-2 hover:bg-surface-hover rounded-r cursor-pointer transition-colors group/prop relative min-h-[28px]\"\n onClick={() => onInsertCode(`\"${prop}\"`)}\n >\n <svg className=\"w-3 h-3 mr-1.5 text-text-disabled dark:text-text-disabled-dark shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M9 4.5v15m6-15v15m-10.5-1.5h15c.621 0 1.125-.504 1.125-1.125V5.625c0-.621-.504-1.125-1.125-1.125h-15c-.621 0-1.125.504-1.125 1.125v12.75c0 .621.504 1.125 1.125 1.125Z\"/></svg>\n <Typography variant=\"caption\" className=\"text-text-primary dark:text-text-primary-dark text-[11px] truncate flex-grow font-mono\">{prop}</Typography>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction SnippetItem({ snippet, onSelect, onDelete }: { snippet: JSSnippet; onSelect: (code: string) => void; onDelete: (id: string) => void }) {\n return (\n <div\n className={cls(\"group p-2 rounded border hover:border-hairline-strong bg-surface-card transition-all cursor-pointer relative\", defaultBorderMixin)}\n onClick={() => onSelect(snippet.code)}\n >\n <Typography variant=\"body2\" className=\"text-text-primary dark:text-text-primary-dark font-medium text-[13px] truncate pr-6\">\n {snippet.name}\n </Typography>\n <Typography variant=\"caption\" className=\"text-text-secondary dark:text-text-secondary-dark text-[10px] block mt-1 truncate font-mono\">\n {snippet.code}\n </Typography>\n <IconButton\n size=\"smallest\"\n className=\"absolute right-1 top-1 opacity-0 group-hover:opacity-100 text-text-disabled hover:text-red-500 transition-opacity\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(snippet.id);\n }}\n >\n <Trash2Icon size={iconSize.smallest}/>\n </IconButton>\n </div>\n );\n}\n","\nimport React, { useState, useEffect, useCallback, useRef, useMemo } from \"react\";\nimport { Highlight, themes } from \"prism-react-renderer\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\nimport {\n Button,\n CellRendererParams,\n Chip,\n CircularProgress,\n cls,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n DownloadIcon,\n IconButton,\n iconSize,\n Menu,\n MenuIcon,\n MenuItem,\n MoreVerticalIcon,\n PencilIcon,\n PlayIcon,\n PlusIcon,\n ResizablePanels,\n SaveIcon,\n Tab,\n Tabs,\n TerminalIcon,\n TextField,\n Tooltip,\n Typography,\n VirtualTable,\n VirtualTableColumn,\n XIcon\n} from \"@rebasepro/ui\";\nimport { useStudioUrlController, useStudioCollectionRegistry, useStudioSidePanelController } from \"@rebasepro/app\";\nimport { useRebaseContext, useRebaseClient, useSnackbarController, useApiConfig, useTranslation, useModeController, ErrorView, SelectableUser, IconForView } from \"@rebasepro/app\";\n\nimport { createRebaseClient } from \"@rebasepro/client\";\nimport { JSMonacoEditor } from \"./JSMonacoEditor\";\nimport { JSEditorSidebar, JSSnippet } from \"./JSEditorSidebar\";\nimport { AuthSimulationSelector } from \"../AuthSimulationSelector\";\nimport type { AdminCollection } from \"@rebasepro/cms-types\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\ninterface ConsoleEntry {\n type: \"log\" | \"warn\" | \"error\" | \"info\";\n args: unknown[];\n timestamp: number;\n}\n\ninterface ExecutionResult {\n value: unknown;\n console: ConsoleEntry[];\n duration: number;\n error?: string;\n timestamp: number;\n}\n\ninterface EditorTab {\n id: string;\n name: string;\n code: string;\n}\n\n// SelectedUser is now SelectableUser from @rebasepro/app\n\n// ─── Constants ───────────────────────────────────────────────────────\n\nconst STORAGE_PREFIX = \"rebase_js_\";\nconst MAX_HISTORY = 50;\n\n/**\n * The starter script.\n *\n * It runs. That is the point: the console opens with the Run button armed, and\n * a starter that reads `collection(\"your_collection\")` made the first thing a\n * new user did be a 404 — a placeholder presented as working code. `slug` is\n * the project's first collection when one is known.\n */\nconst makeDefaultCode = (slug?: string) => `// Available: client (RebaseClient)\n// Press Cmd+Enter (Ctrl+Enter) to run\n//\n// Examples:\n// const users = await client.admin.listUsers();\n// const session = client.auth.getSession();\n${slug\n ? `\nconst result = await client.data.collection(${JSON.stringify(slug)}).find({ limit: 10 });\nreturn result;\n`\n : `\n// Name one of your collections to query it:\n// const result = await client.data.collection(\"posts\").find({ limit: 10 });\n// return result;\n\nreturn client.auth.getSession();\n`}`;\n\nconst DEFAULT_CODE = makeDefaultCode();\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction loadFromStorage<T>(key: string, fallback: T): T {\n try {\n const raw = localStorage.getItem(STORAGE_PREFIX + key);\n return raw ? JSON.parse(raw) : fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction saveToStorage<T>(key: string, value: T) {\n try {\n localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value));\n } catch { /* quota */ }\n}\n\nfunction formatJSON(value: unknown): string {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n}\n\n// ─── Helpers: Collection matching for JS results ────────────────────\n\ninterface MatchedJSCollection {\n collectionSlug: string;\n collection: AdminCollection;\n pkColumn: string;\n}\n\n/**\n * Given the raw SDK result, try to detect which collections are present.\n * JS SDK results typically come back as `{ data: [{ id, values }] }` or plain arrays.\n * The heuristic: if the executed code contains `collection(\"<slug>\")` or `client.data.<slug>`,\n * and the result has rows with an \"id\" column, we match those slugs.\n */\nfunction detectCollectionsInResult(\n code: string,\n resultValue: unknown,\n collections: AdminCollection[]\n): MatchedJSCollection[] {\n if (!resultValue || !collections?.length) return [];\n\n // Extract collection slugs mentioned in the code\n const mentionedSlugs = new Set<string>();\n\n // Match: collection(\"slug\") or collection('slug')\n const collectionCallRegex = /\\.collection\\([\"']([^\"']+)[\"']\\)/g;\n let m: RegExpExecArray | null;\n while ((m = collectionCallRegex.exec(code)) !== null) {\n mentionedSlugs.add(m[1]);\n }\n\n // Match: client.data.<slug>. (dot-access pattern)\n const dotAccessRegex = /client\\.data\\.([a-zA-Z_][a-zA-Z0-9_]*)\\./g;\n while ((m = dotAccessRegex.exec(code)) !== null) {\n if (![\"collection\", \"find\", \"findById\", \"create\", \"update\", \"delete\"].includes(m[1])) {\n mentionedSlugs.add(m[1]);\n }\n }\n\n if (mentionedSlugs.size === 0) return [];\n\n // Check if result has rows with an \"id\" field\n let rows: Record<string, unknown>[] = [];\n const rv = resultValue as Record<string, unknown>;\n if (rv?.data && Array.isArray(rv.data)) {\n rows = rv.data;\n } else if (Array.isArray(resultValue)) {\n rows = resultValue;\n }\n\n const hasId = rows.length > 0 && rows.some(r => r?.id != null);\n if (!hasId) return [];\n\n const matched: MatchedJSCollection[] = [];\n for (const slug of mentionedSlugs) {\n const normalised = toSnakeCase(slug);\n const col = collections.find(c => {\n const tableName = (\"table\" in c ? c.table : undefined) || toSnakeCase(c.slug);\n return c.slug === slug || tableName === normalised || toSnakeCase(c.slug) === normalised;\n });\n if (col) {\n matched.push({\n collectionSlug: col.slug,\n collection: col,\n pkColumn: \"id\"\n });\n }\n }\n\n return matched;\n}\n\n// ─── Main Component ─────────────────────────────────────────────────\n\nexport function JSEditor() {\n // Contexts\n const rebaseContext = useRebaseContext();\n const rebaseClient = useRebaseClient();\n const apiConfig = useApiConfig();\n const snackbar = useSnackbarController();\n const collectionRegistry = useStudioCollectionRegistry();\n const sidePanelController = useStudioSidePanelController();\n const { t } = useTranslation();\n\n // User management for the \"Run as\" picker\n const currentUser = rebaseContext.authController?.user;\n\n // State\n const [tabs, setTabs] = useState<EditorTab[]>(() =>\n loadFromStorage(\"tabs\", [{ id: \"1\",\nname: \"Script 1\",\ncode: DEFAULT_CODE }])\n );\n const [activeTabId, setActiveTabId] = useState<string>(() =>\n loadFromStorage(\"activeTab\", \"1\")\n );\n const [result, setResult] = useState<ExecutionResult | null>(null);\n const [isRunning, setIsRunning] = useState(false);\n const [snippets, setSnippets] = useState<JSSnippet[]>(() =>\n loadFromStorage(\"snippets\", [])\n );\n const [history, setHistory] = useState<string[]>(() =>\n loadFromStorage(\"history\", [])\n );\n const [showSaveDialog, setShowSaveDialog] = useState(false);\n const [snippetName, setSnippetName] = useState(\"\");\n const [resultView, setResultView] = useState<\"json\" | \"table\" | \"console\">(\"json\");\n\n // \"Run as\" user state — null means \"self\" (current admin)\n const [selectedUser, setSelectedUser] = useState<SelectableUser | null>(null);\n const [authMode, setAuthMode] = useState<\"jwt\" | \"none\">(\"jwt\");\n\n const [sidebarSize, setSidebarSize] = useState<number>(() => {\n try {\n const stored = localStorage.getItem(STORAGE_PREFIX + \"sidebar_size\");\n return stored ? Number(stored) : 18;\n } catch { return 18; }\n });\n const [editorHeight, setEditorHeight] = useState<number>(() => {\n try {\n const stored = localStorage.getItem(STORAGE_PREFIX + \"editor_height\");\n return stored ? Number(stored) : 55;\n } catch { return 55; }\n });\n\n // Derived\n const activeTab = tabs.find(t => t.id === activeTabId) ?? tabs[0];\n\n // Collection info for the sidebar and Monaco\n const collectionInfos = useMemo(() => {\n const collections = collectionRegistry?.collections ?? [];\n return collections.map(col => ({\n slug: col.slug,\n name: col.name,\n properties: Object.keys(col.properties ?? {})\n }));\n }, [collectionRegistry?.collections]);\n\n const collectionSlugs = useMemo(() => collectionInfos.map(c => c.slug), [collectionInfos]);\n\n /**\n * Name a real collection in the starter script once the registry knows one.\n *\n * The registry is empty on first paint — in the hosted console it arrives\n * over the network — so the placeholder-free default cannot be built in the\n * `useState` initialiser. Rewriting only a tab whose content is still\n * *exactly* the untouched default means this can never overwrite anything\n * anyone typed, and never fires twice.\n */\n const firstSlug = collectionSlugs[0];\n useEffect(() => {\n if (!firstSlug) return;\n setTabs(prev => {\n if (!prev.some(tab => tab.code === DEFAULT_CODE)) return prev;\n const seeded = makeDefaultCode(firstSlug);\n return prev.map(tab => (tab.code === DEFAULT_CODE ? { ...tab,\ncode: seeded } : tab));\n });\n }, [firstSlug]);\n\n // Users list for the picker — mapped to SelectableUser shape\n const users = useMemo((): SelectableUser[] => {\n const managed: SelectableUser[] = [];\n // Ensure the current user is in the list\n if (currentUser) {\n managed.push({\n uid: currentUser.uid,\n displayName: currentUser.displayName,\n email: currentUser.email,\n photoURL: currentUser.photoURL,\n roles: currentUser.roles\n });\n }\n return managed;\n }, [currentUser]);\n\n // Current user as SelectableUser for the popover\n const currentSelectableUser = useMemo((): SelectableUser | null => {\n if (!currentUser) return null;\n return {\n uid: currentUser.uid,\n displayName: currentUser.displayName,\n email: currentUser.email,\n photoURL: currentUser.photoURL,\n roles: currentUser.roles\n };\n }, [currentUser]);\n\n // ─── Persistence ─────────────────────────────────────────────\n\n useEffect(() => { saveToStorage(\"tabs\", tabs); }, [tabs]);\n useEffect(() => { saveToStorage(\"activeTab\", activeTabId); }, [activeTabId]);\n useEffect(() => { saveToStorage(\"snippets\", snippets); }, [snippets]);\n useEffect(() => { saveToStorage(\"history\", history); }, [history]);\n\n useEffect(() => {\n try { localStorage.setItem(STORAGE_PREFIX + \"sidebar_size\", sidebarSize.toString()); } catch { /* ignore */ }\n }, [sidebarSize]);\n\n useEffect(() => {\n try { localStorage.setItem(STORAGE_PREFIX + \"editor_height\", editorHeight.toString()); } catch { /* ignore */ }\n }, [editorHeight]);\n\n // ─── Tab management ──────────────────────────────────────────\n\n const updateActiveCode = useCallback((code: string | undefined) => {\n setTabs(prev => prev.map(t => t.id === activeTabId ? { ...t,\ncode: code ?? \"\" } : t));\n }, [activeTabId]);\n\n const addTab = useCallback(() => {\n const id = String(Date.now());\n const newTab: EditorTab = { id,\nname: `Script ${tabs.length + 1}`,\ncode: DEFAULT_CODE };\n setTabs(prev => [...prev, newTab]);\n setActiveTabId(id);\n }, [tabs.length]);\n\n const closeTab = useCallback((tabId: string) => {\n setTabs(prev => {\n const filtered = prev.filter(t => t.id !== tabId);\n if (filtered.length === 0) {\n const fresh = { id: String(Date.now()),\nname: \"Script 1\",\ncode: DEFAULT_CODE };\n setActiveTabId(fresh.id);\n return [fresh];\n }\n if (activeTabId === tabId) {\n setActiveTabId(filtered[filtered.length - 1].id);\n }\n return filtered;\n });\n }, [activeTabId]);\n\n // ─── Create an authenticated client for execution ────────────\n\n const buildClient = useCallback(async () => {\n const apiUrl = apiConfig?.apiUrl;\n const getAuthToken = apiConfig?.getAuthToken;\n\n if (!apiUrl) {\n throw new Error(\"API URL not configured. Make sure apiUrl is set.\");\n }\n\n if (authMode === \"none\") {\n const client = createRebaseClient({\n baseUrl: apiUrl,\n token: undefined\n });\n return { client,\nisScoped: true };\n }\n\n // If not running as another user safely reuse the application's global client.\n if (!selectedUser || (currentUser && selectedUser.uid === currentUser.uid)) {\n if (!rebaseClient) {\n throw new Error(\"Application client is not initialized.\");\n }\n return { client: rebaseClient,\nisScoped: false };\n }\n\n // Get the current auth token\n let token: string | undefined;\n if (getAuthToken) {\n token = (await getAuthToken()) ?? undefined;\n }\n\n if (!token) {\n throw new Error(\"No auth token available. Please sign in first.\");\n }\n\n // Create a fresh 'scoped' client. This enables us to attach custom auth later for this specific execution\n // without mutating the global client.\n const client = createRebaseClient({\n baseUrl: apiUrl,\n token\n });\n\n return { client,\nisScoped: true };\n }, [apiConfig, rebaseClient, selectedUser, currentUser, authMode]);\n\n // ─── Execution engine ────────────────────────────────────────\n\n const executeCode = useCallback(async (codeOverride?: string) => {\n const code = codeOverride ?? activeTab?.code ?? \"\";\n if (!code.trim()) return;\n\n setIsRunning(true);\n setResult(null);\n\n // Add to history\n setHistory(prev => {\n const deduped = prev.filter(h => h !== code);\n return [...deduped, code].slice(-MAX_HISTORY);\n });\n\n const consoleEntries: ConsoleEntry[] = [];\n const startTime = performance.now();\n let scopedClientToCleanUp: { ws?: { disconnect(): void } } | null = null;\n\n // Capture console methods\n const originalConsole = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n info: console.info\n };\n\n const captureConsole = (type: ConsoleEntry[\"type\"]) => (...args: unknown[]) => {\n consoleEntries.push({ type,\nargs,\ntimestamp: Date.now() });\n originalConsole[type](...args);\n };\n\n try {\n console.log = captureConsole(\"log\");\n console.warn = captureConsole(\"warn\");\n console.error = captureConsole(\"error\");\n console.info = captureConsole(\"info\");\n\n // Build an authenticated client\n const { client, isScoped } = await buildClient();\n if (isScoped) {\n scopedClientToCleanUp = client;\n }\n\n // Build context object with useful info about selected user\n const context = {\n user: selectedUser ?? (currentUser ? {\n uid: currentUser.uid,\n displayName: currentUser.displayName,\n email: currentUser.email,\n roles: currentUser.roles\n } : null),\n collections: collectionInfos\n };\n\n // Create async function with `client` and `context` injected\n\n const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;\n const fn = new AsyncFunction(\"client\", \"context\", code);\n\n const value = await fn(client, context);\n const duration = performance.now() - startTime;\n\n setResult({\n value,\n console: consoleEntries,\n duration,\n timestamp: Date.now()\n });\n\n // Auto-detect best view\n const resultObj = value as Record<string, unknown> | undefined;\n if (resultObj?.data && Array.isArray(resultObj.data)) {\n setResultView(\"table\");\n } else if (consoleEntries.length > 0 && value === undefined) {\n setResultView(\"console\");\n } else {\n setResultView(\"json\");\n }\n } catch (err: unknown) {\n const duration = performance.now() - startTime;\n setResult({\n value: undefined,\n console: consoleEntries,\n duration,\n error: err instanceof Error ? err.message : String(err),\n timestamp: Date.now()\n });\n setResultView(\"json\");\n } finally {\n console.log = originalConsole.log;\n console.warn = originalConsole.warn;\n console.error = originalConsole.error;\n console.info = originalConsole.info;\n setIsRunning(false);\n\n if (scopedClientToCleanUp) {\n // Ensure we disconnect WebSockets to prevent leaking connections\n scopedClientToCleanUp.ws?.disconnect();\n }\n }\n }, [activeTab?.code, buildClient, selectedUser, currentUser, collectionInfos]);\n\n // ─── Snippet management ──────────────────────────────────────\n\n const saveSnippet = useCallback(() => {\n if (!snippetName.trim() || !activeTab?.code.trim()) return;\n const snippet: JSSnippet = {\n id: String(Date.now()),\n name: snippetName.trim(),\n code: activeTab.code,\n createdAt: Date.now()\n };\n setSnippets(prev => [snippet, ...prev]);\n setShowSaveDialog(false);\n setSnippetName(\"\");\n snackbar.open({ type: \"success\",\nmessage: \"Snippet saved\" });\n }, [snippetName, activeTab?.code, snackbar]);\n\n const deleteSnippet = useCallback((id: string) => {\n setSnippets(prev => prev.filter(s => s.id !== id));\n }, []);\n\n // ─── Export ──────────────────────────────────────────────────\n\n const exportResult = useCallback(() => {\n if (!result?.value) return;\n const blob = new Blob([formatJSON(result.value)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `rebase-result-${Date.now()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n }, [result]);\n\n // ─── Table columns for array data ────────────────────────────\n\n const tableData = useMemo(() => {\n if (!result?.value) return { columns: [] as VirtualTableColumn[],\ndata: [] as Record<string, unknown>[] };\n\n let rows: Record<string, unknown>[] = [];\n const val = result.value as Record<string, unknown>;\n if (val?.data && Array.isArray(val.data)) {\n rows = (val.data as Record<string, unknown>[]).map((entity) => ({\n id: entity.id,\n ...(entity.values as Record<string, unknown> ?? {}),\n ...(entity.values ? {} : entity)\n }));\n } else if (Array.isArray(result.value)) {\n rows = result.value;\n }\n\n if (rows.length === 0) return { columns: [] as VirtualTableColumn[],\ndata: [] as Record<string, unknown>[] };\n\n const keys = new Set<string>();\n rows.slice(0, 20).forEach(row => {\n if (row && typeof row === \"object\") {\n Object.keys(row).forEach(k => keys.add(k));\n }\n });\n\n const columns: VirtualTableColumn[] = Array.from(keys).map(key => ({\n key,\n title: key,\n width: key === \"id\" ? 100 : 200\n }));\n\n return { columns,\ndata: rows };\n }, [result]);\n\n // ─── Matched collections for entity actions ──────────────────\n\n const matchedCollections = useMemo(() => {\n if (!result?.value || result.error) return [];\n return detectCollectionsInResult(\n activeTab?.code ?? \"\",\n result.value,\n collectionRegistry?.collections ?? []\n );\n }, [result, activeTab?.code, collectionRegistry?.collections]);\n\n const getRowEntityActions = useCallback((rowData: Record<string, unknown>): { collection: MatchedJSCollection; entityId: string | number }[] => {\n if (!rowData || matchedCollections.length === 0) return [];\n return matchedCollections\n .filter(mc => rowData[mc.pkColumn] != null)\n .map(mc => ({\n collection: mc,\n entityId: rowData[mc.pkColumn] as string | number\n }));\n }, [matchedCollections]);\n\n // ─── Export handlers ─────────────────────────────────────────\n\n const handleExportCSV = useCallback(() => {\n if (tableData.data.length === 0) return;\n const headers = tableData.columns.map(c => c.key).join(\",\");\n const rows = tableData.data.map(row =>\n tableData.columns.map(c => {\n const val = row[c.key];\n const str = val === null || val === undefined ? \"\" : String(val);\n return str.includes(\",\") ? `\"${str}\"` : str;\n }).join(\",\")\n );\n const csv = [headers, ...rows].join(\"\\n\");\n const blob = new Blob([csv], { type: \"text/csv\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `js_results_${new Date().toISOString().slice(0, 19)}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n }, [tableData]);\n\n const handleExportMarkdown = useCallback(() => {\n if (tableData.data.length === 0) return;\n const headers = tableData.columns.map(c => c.key);\n const headerRow = `| ${headers.join(\" | \")} |`;\n const dividerRow = `| ${headers.map(() => \"---\").join(\" | \")} |`;\n const dataRows = tableData.data.map(row =>\n `| ${headers.map(h => {\n const val = row[h];\n if (val === null || val === undefined) return \"\";\n return String(val).replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \" \");\n }).join(\" | \")} |`\n );\n const markdown = [headerRow, dividerRow, ...dataRows].join(\"\\n\");\n navigator.clipboard.writeText(markdown).then(() => {\n snackbar.open({ type: \"success\",\nmessage: t(\"studio_sql_markdown_copied\") });\n }).catch(() => {\n snackbar.open({ type: \"error\",\nmessage: t(\"studio_sql_markdown_copy_failed\") });\n });\n }, [tableData, snackbar, t]);\n\n // ─── Render ──────────────────────────────────────────────────\n\n return (\n <div className=\"flex flex-col h-full w-full\">\n {/* Main content */}\n <div className=\"flex-grow overflow-hidden\">\n <ResizablePanels\n orientation=\"horizontal\"\n panelSizePercent={sidebarSize}\n onPanelSizeChange={setSidebarSize}\n minPanelSizePx={200}\n firstPanel={\n <JSEditorSidebar\n collections={collectionInfos}\n snippets={snippets}\n history={history}\n onSelectSnippet={(code) => updateActiveCode(code)}\n onDeleteSnippet={deleteSnippet}\n onInsertCode={(code) => updateActiveCode(code)}\n />\n }\n secondPanel={\n <div className=\"flex flex-col h-full overflow-hidden\">\n {/* Toolbar: matching SQL Editor layout */}\n <div className={cls(\"flex items-center justify-between pr-2 border-b bg-surface-card\", defaultBorderMixin)}>\n <div className=\"flex items-center flex-grow overflow-hidden mr-4\">\n <div className=\"flex items-center no-scrollbar overflow-x-auto min-w-0\">\n <Tabs value={activeTabId} onValueChange={setActiveTabId} variant=\"boxy\" className=\"w-[unset] flex-shrink-0\" innerClassName=\"bg-surface-card\">\n {tabs.map(tab => (\n <Tab key={tab.id} value={tab.id} className=\"flex items-center justify-between group max-w-[200px]\">\n <TerminalIcon size={iconSize.smallest} className=\"text-amber-500 mr-1.5 flex-shrink-0\"/>\n <span className=\"truncate\">{tab.name}</span>\n {tabs.length > 1 && (\n <IconButton\n size=\"smallest\"\n onClick={(e) => { e.stopPropagation(); closeTab(tab.id); }}\n className=\"ml-1 !p-0.5 opacity-0 group-hover:opacity-100 hover:text-red-500 transition-opacity\"\n >\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n )}\n </Tab>\n ))}\n </Tabs>\n <IconButton\n size=\"small\"\n onClick={addTab}\n className=\"ml-2 flex-shrink-0\"\n >\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center justify-end gap-1.5\">\n <Tooltip title=\"Save as snippet\">\n <IconButton\n size=\"small\"\n onClick={() => {\n setSnippetName(\"\");\n setShowSaveDialog(true);\n }}\n disabled={!activeTab?.code.trim()}\n >\n <SaveIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n\n {result?.value != null && (\n <Tooltip title=\"Export result as JSON\">\n <IconButton size=\"small\" onClick={exportResult}>\n <DownloadIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n )}\n\n <Button\n size=\"small\"\n color=\"primary\"\n disabled={isRunning || !activeTab?.code.trim()}\n onClick={() => executeCode()}\n >\n {isRunning ? <CircularProgress size=\"smallest\" className=\"mr-2\"/> : <PlayIcon size={iconSize.smallest} className=\"mr-2\"/>}\n Run\n </Button>\n </div>\n </div>\n\n {/* Editor + Results split */}\n <div className=\"flex-grow overflow-hidden\">\n <ResizablePanels\n orientation=\"vertical\"\n panelSizePercent={editorHeight}\n onPanelSizeChange={setEditorHeight}\n minPanelSizePx={100}\n firstPanel={\n <div className=\"h-full w-full overflow-hidden flex flex-col\">\n {/* Auth Simulation UI */}\n <div className=\"p-2 px-3 border-b border-hairline bg-surface-sheet flex items-center shrink-0\">\n <AuthSimulationSelector\n authMode={authMode}\n setAuthMode={setAuthMode}\n selectedUser={selectedUser}\n setSelectedUser={setSelectedUser}\n users={users}\n loading={false}\n currentUser={currentSelectableUser}\n />\n </div>\n <div className=\"flex-1 min-h-0\">\n <JSMonacoEditor\n value={activeTab?.code ?? \"\"}\n onChange={updateActiveCode}\n onRun={(selectedText) => executeCode(selectedText)}\n collectionSlugs={collectionSlugs}\n collections={collectionInfos}\n autoFocus\n />\n </div>\n </div>\n }\n secondPanel={\n <div className=\"h-full w-full flex flex-col bg-surface-well overflow-hidden min-h-0\">\n {/* Result header — matches SQL editor */}\n <div className={cls(\"p-2 px-4 bg-surface-raised border-b shrink-0 flex items-center\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold text-text-disabled dark:text-text-disabled-dark uppercase tracking-widest text-[10px]\">\n {t(\"studio_sql_query_results\")}\n </Typography>\n\n {result && (\n <>\n <div className=\"flex-grow\"/>\n\n <Tabs value={resultView} onValueChange={(val) => setResultView(val as \"json\" | \"table\" | \"console\")} variant=\"pill\" className=\"w-[unset] mr-2\">\n <Tab value=\"json\">JSON</Tab>\n {tableData.data.length > 0 && <Tab value=\"table\">Table</Tab>}\n {result.console.length > 0 && <Tab value=\"console\">Console ({result.console.length})</Tab>}\n </Tabs>\n\n <Chip size=\"smallest\" colorScheme={result.error ? \"redDarker\" : \"greenDarker\"}>\n {result.error ? \"Error\" : `${result.duration.toFixed(0)}ms`}\n </Chip>\n </>\n )}\n </div>\n\n {/* Result content */}\n <div className=\"flex-grow flex flex-col min-h-0 overflow-hidden\">\n {isRunning && (\n <div className=\"flex-grow flex items-center justify-center\">\n <div className=\"text-center\">\n <CircularProgress size=\"medium\"/>\n <Typography variant=\"body2\" className=\"mt-4 text-text-secondary dark:text-text-secondary-dark font-mono tracking-tight animate-pulse\">\n EXECUTING SCRIPT...\n </Typography>\n </div>\n </div>\n )}\n\n {!isRunning && !result && (\n <div className=\"flex-grow flex items-center justify-center text-text-disabled dark:text-text-disabled-dark\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-4 opacity-50\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1} d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"/></svg>\n <Typography variant=\"body2\">Write JavaScript and press <kbd className=\"px-1.5 py-0.5 rounded bg-surface-raised text-[11px] font-mono\">⌘ Enter</kbd> to run</Typography>\n </div>\n </div>\n )}\n\n {!isRunning && result?.error && (\n <div className=\"flex-grow flex items-center justify-center p-6 overflow-auto\">\n <ErrorView\n title=\"Execution Error\"\n error={result.error}\n />\n </div>\n )}\n\n {!isRunning && result && !result.error && resultView === \"json\" && (\n <pre className=\"flex-grow overflow-auto p-4 text-[13px] font-mono leading-relaxed text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words\">\n {result.value === undefined ? (\n <span className=\"text-text-disabled italic\">undefined (no return value)</span>\n ) : (\n <JSONHighlight value={result.value}/>\n )}\n </pre>\n )}\n\n {!isRunning && result && !result.error && resultView === \"table\" && tableData.data.length > 0 && (\n <div className=\"flex-grow flex flex-col overflow-hidden min-h-0\">\n {/* Collection badges bar — matching SQL editor */}\n {matchedCollections.length > 0 && (\n <div className={cls(\"px-4 py-1.5 border-b flex items-center gap-2 shrink-0 bg-surface-sheet\", defaultBorderMixin)}>\n <Tooltip title={t(\"studio_sql_admin_collections_tooltip\")}>\n <Typography variant=\"caption\" className=\"text-[10px] font-semibold uppercase tracking-widest text-text-disabled dark:text-text-disabled-dark mr-1 shrink-0 cursor-help\">{t(\"studio_sql_collections_label\")}</Typography>\n </Tooltip>\n <div className=\"flex items-center gap-1.5 overflow-x-auto no-scrollbar\">\n {matchedCollections.map(mc => (\n <Tooltip key={mc.collectionSlug} title={`${mc.collection.name} (${mc.collectionSlug})`}>\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium bg-primary/10 dark:bg-primary-dark/15 text-primary dark:text-primary-dark whitespace-nowrap border border-primary/20 dark:border-primary-dark/20\">\n {typeof mc.collection.icon === \"string\" && (\n <IconForView collectionOrView={{ icon: mc.collection.icon } as never} className=\"text-[12px]\"/>\n )}\n {mc.collection.name}\n </span>\n </Tooltip>\n ))}\n </div>\n </div>\n )}\n <div className=\"flex-grow relative h-full min-h-0 min-w-0\">\n <VirtualTable\n data={tableData.data}\n columns={\n matchedCollections.length > 0\n ? [{ key: \"__entity_action__\",\ntitle: \"\",\nwidth: 36,\nsortable: false,\nresizable: false }, ...tableData.columns]\n : tableData.columns\n }\n rowHeight={32}\n headerHeight={32}\n cellRenderer={({ rowData, column, rowIndex }: CellRendererParams<Record<string, unknown>>) => {\n // Entity action column\n if (column.key === \"__entity_action__\") {\n const rowActions = getRowEntityActions(rowData ?? {});\n if (rowActions.length === 0) return <div className=\"h-full w-full\"/>;\n if (rowActions.length === 1) {\n const ra = rowActions[0];\n return (\n <div className=\"h-full flex items-center justify-center\">\n <Tooltip title={t(\"studio_sql_edit_entity\", { name: ra.collection.collection.name,\nid: String(ra.entityId) })}>\n <IconButton\n size=\"small\"\n className=\"text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300\"\n onClick={(e) => {\n e.stopPropagation();\n sidePanelController.open({\n path: ra.collection.collectionSlug,\n entityId: ra.entityId,\n collection: ra.collection.collection,\n updateUrl: false\n });\n }}\n >\n <PencilIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n </div>\n );\n }\n // Multiple matched collections\n return (\n <div className=\"h-full flex items-center justify-center\">\n <Menu\n trigger={\n <IconButton\n size={\"small\"}\n className=\"text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300\"\n onClick={(e) => e.stopPropagation()}\n >\n <MoreVerticalIcon size={iconSize.smallest}/>\n </IconButton>\n }\n >\n {rowActions.map(ra => (\n <MenuItem\n key={ra.collection.collectionSlug}\n dense\n onClick={() => {\n sidePanelController.open({\n path: ra.collection.collectionSlug,\n entityId: ra.entityId,\n collection: ra.collection.collection,\n updateUrl: false\n });\n }}\n >\n {t(\"studio_sql_edit_entity\", { name: ra.collection.collection.name,\nid: String(ra.entityId) })}\n </MenuItem>\n ))}\n </Menu>\n </div>\n );\n }\n\n // Regular data cell\n if (!rowData) return null;\n const val = rowData[column.key];\n const displayValue = typeof val === \"object\" && val !== null ? JSON.stringify(val) : String(val ?? \"\");\n return (\n <div className=\"px-4 py-1.5 h-full flex items-center whitespace-nowrap text-[13px] text-text-primary dark:text-text-primary-dark font-mono\">\n <div className=\"truncate flex-grow\" title={displayValue}>\n {displayValue === \"\" ? <span className=\"text-text-disabled dark:text-text-disabled-dark italic text-[11px]\">NULL</span> : displayValue}\n </div>\n </div>\n );\n }}\n />\n </div>\n </div>\n )}\n\n {!isRunning && result && resultView === \"console\" && (\n <div className=\"flex-grow overflow-auto p-2 space-y-1 font-mono text-[12px]\">\n {result.console.length === 0 ? (\n <Typography variant=\"caption\" className=\"text-text-disabled p-2\">No console output</Typography>\n ) : (\n result.console.map((entry, i) => (\n <div\n key={i}\n className={cls(\n \"px-2 py-1 rounded flex items-start gap-2\",\n entry.type === \"error\" && \"bg-red-50 dark:bg-red-950/30 text-red-700 dark:text-red-400\",\n entry.type === \"warn\" && \"bg-amber-50 dark:bg-amber-950/30 text-amber-700 dark:text-amber-400\",\n entry.type === \"log\" && \"text-text-primary dark:text-text-primary-dark\",\n entry.type === \"info\" && \"text-blue-700 dark:text-blue-400\"\n )}\n >\n <span className=\"text-[10px] opacity-50 flex-shrink-0 mt-0.5\">\n {entry.type === \"error\" ? \"❌\" : entry.type === \"warn\" ? \"⚠️\" : entry.type === \"info\" ? \"ℹ️\" : \"›\"}\n </span>\n <span className=\"whitespace-pre-wrap break-words\">\n {entry.args.map(a => typeof a === \"object\" ? JSON.stringify(a, null, 2) : String(a)).join(\" \")}\n </span>\n </div>\n ))\n )}\n\n {result.value !== undefined && (\n <div className={cls(\"px-2 py-1 mt-2 border-t pt-2\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"text-text-disabled text-[10px] uppercase tracking-wider mb-1 block\">Return Value</Typography>\n <pre className=\"text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words text-[12px]\">\n {formatJSON(result.value)}\n </pre>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Footer bar — matching SQL editor */}\n {!isRunning && result && !result.error && resultView === \"table\" && tableData.data.length > 0 && (\n <div className={cls(\"p-2 px-4 border-t bg-surface-sheet flex justify-between items-center shrink-0\", defaultBorderMixin)}>\n <div className=\"flex space-x-4\">\n <div className=\"flex items-center text-[11px]\">\n <span className=\"font-semibold text-text-disabled dark:text-text-disabled-dark mr-2 uppercase tracking-tighter\">{t(\"studio_sql_rows\")}</span>\n <span className=\"font-mono text-text-secondary dark:text-text-secondary-dark\">{tableData.data.length}</span>\n </div>\n <div className=\"flex items-center text-[11px]\">\n <span className=\"font-semibold text-text-disabled dark:text-text-disabled-dark mr-2 uppercase tracking-tighter\">{t(\"studio_sql_time\")}</span>\n <span className=\"font-mono text-text-secondary dark:text-text-secondary-dark\">{result.duration.toFixed(0)}ms</span>\n </div>\n </div>\n <div className=\"flex gap-2 overflow-x-auto no-scrollbar items-center px-2\">\n <Button\n size=\"small\"\n variant=\"text\"\n className=\"text-[10px] uppercase font-semibold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap\"\n onClick={handleExportMarkdown}\n >\n {t(\"studio_sql_copy_markdown\")}\n </Button>\n <Button\n size=\"small\"\n variant=\"text\"\n className=\"text-[10px] uppercase font-semibold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap\"\n onClick={exportResult}\n >\n {t(\"studio_sql_export_json\")}\n </Button>\n <Button\n size=\"small\"\n variant=\"text\"\n className=\"text-[10px] uppercase font-semibold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap\"\n onClick={handleExportCSV}\n >\n {t(\"studio_sql_export_csv\")}\n </Button>\n </div>\n </div>\n )}\n </div>\n }\n />\n </div>\n </div>\n }\n />\n </div>\n\n {/* Save snippet dialog */}\n <Dialog open={showSaveDialog} onOpenChange={setShowSaveDialog}>\n <DialogTitle>Save Snippet</DialogTitle>\n <DialogContent>\n <TextField\n label=\"Snippet name\"\n value={snippetName}\n onChange={(e) => setSnippetName(e.target.value)}\n placeholder=\"e.g. List all products\"\n autoFocus\n onKeyDown={(e) => {\n if (e.key === \"Enter\") saveSnippet();\n }}\n />\n </DialogContent>\n <DialogActions>\n <Button variant=\"text\" onClick={() => setShowSaveDialog(false)}>Cancel</Button>\n <Button variant=\"filled\" color=\"primary\" onClick={saveSnippet} disabled={!snippetName.trim()}>Save</Button>\n </DialogActions>\n </Dialog>\n </div>\n );\n}\n\n// ─── JSON Syntax Highlighting ────────────────────────────────────────\n\nfunction JSONHighlight({ value }: { value: unknown }) {\n const json = formatJSON(value);\n const { mode } = useModeController();\n\n return (\n <Highlight\n theme={mode === \"dark\" ? themes.vsDark : themes.github}\n code={json}\n language=\"json\"\n >\n {({ style, tokens, getLineProps, getTokenProps }) => (\n <span style={{ ...style,\nbackgroundColor: \"transparent\" }}>\n {tokens.map((line, i) => (\n <div key={i} {...getLineProps({ line })}>\n {line.map((token, key) => (\n <span key={key} {...getTokenProps({ token })}/>\n ))}\n </div>\n ))}\n </span>\n )}\n </Highlight>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"LogsExplorer-Xq8K4oo6.js","names":[],"sources":["../src/components/LogsExplorer/useLogTail.ts","../src/components/LogsExplorer/LogsExplorer.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useApiBase, useApiConfig } from \"@rebasepro/app\";\n\nexport interface LogEntry {\n id: string;\n timestamp: string;\n level: \"debug\" | \"info\" | \"warn\" | \"error\";\n source: \"api\" | \"auth\" | \"storage\" | \"realtime\" | \"system\";\n message: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogTailFilters {\n /** `\"all\"` and `undefined` both mean unfiltered. */\n level?: string;\n source?: string;\n search?: string;\n}\n\n/**\n * How the view is currently being fed.\n *\n * Surfaced rather than kept private: \"connecting\" and \"polling\" look identical\n * from the outside until something is wrong, and the first question about a log\n * view that seems stuck is whether it is actually live.\n */\nexport type LogTransport = \"connecting\" | \"live\" | \"polling\";\n\n/**\n * Entries kept in the browser.\n *\n * The server window is 200; a tail that never forgets would grow without bound\n * on a busy backend, since nothing else here ever drops an entry.\n */\nconst MAX_ENTRIES = 1000;\n\n/** Fallback cadence, used only against a server with no stream route. */\nconst POLL_MS = 3000;\n\nconst RETRY_BASE_MS = 1000;\nconst RETRY_MAX_MS = 15_000;\n\n/** Ids are `log_<n>` off a monotonic counter, so numeric order is push order. */\nexport const idNum = (entry: LogEntry): number => Number(entry.id.slice(\"log_\".length));\n\n/** The window is contiguous and ordered, so identical ends mean identical content. */\nexport const sameLogs = (a: LogEntry[], b: LogEntry[]): boolean =>\n a.length === b.length &&\n a[0]?.id === b[0]?.id &&\n a[a.length - 1]?.id === b[b.length - 1]?.id;\n\n/** One identity for \"nothing\", so clearing an already-empty view is not a change. */\nconst EMPTY: LogEntry[] = [];\n\nconst capped = (entries: LogEntry[]): LogEntry[] =>\n entries.length > MAX_ENTRIES ? entries.slice(entries.length - MAX_ENTRIES) : entries;\n\nexport interface SSEFrame {\n event: string;\n data: string;\n}\n\n/**\n * Split an SSE byte stream into frames.\n *\n * `EventSource` would do this, and cannot be used: logs are admin-only, and\n * `EventSource` has no way to send an `Authorization` header. So the stream is\n * read off `fetch` and framed here.\n *\n * Exported for its own test — the parser is the part with edge cases (a frame\n * split across two chunks, multi-line data, comment keepalives).\n */\nexport async function* readSSEFrames(\n body: ReadableStream<Uint8Array>\n): AsyncGenerator<SSEFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n // `stream: true` keeps a multi-byte character split across two chunks\n // from decoding as two replacement characters.\n buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, \"\\n\");\n\n let boundary = buffer.indexOf(\"\\n\\n\");\n while (boundary !== -1) {\n const raw = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const frame = parseFrame(raw);\n if (frame) yield frame;\n boundary = buffer.indexOf(\"\\n\\n\");\n }\n }\n}\n\nfunction parseFrame(raw: string): SSEFrame | null {\n let event = \"message\";\n const data: string[] = [];\n for (const line of raw.split(\"\\n\")) {\n // A line starting with \":\" is a comment. The server sends them as\n // keepalives, and they carry nothing.\n if (!line || line.startsWith(\":\")) continue;\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n const value = colon === -1 ? \"\" : line.slice(colon + 1).replace(/^ /, \"\");\n if (field === \"event\") event = value;\n else if (field === \"data\") data.push(value);\n }\n if (data.length === 0) return null;\n return { event,\n data: data.join(\"\\n\") };\n}\n\nconst buildParams = (filters: LogTailFilters, limit: number): string => {\n const params = new URLSearchParams();\n if (filters.level && filters.level !== \"all\") params.set(\"level\", filters.level);\n if (filters.source && filters.source !== \"all\") params.set(\"source\", filters.source);\n if (filters.search) params.set(\"search\", filters.search);\n params.set(\"limit\", String(limit));\n return params.toString();\n};\n\n/**\n * Why the log is not being served, in the server's words where it has any.\n *\n * The admin gate answers 501 with a message naming the setting to change (a\n * backend with no `jwtSecret` and no auth adapter cannot tell an admin from the\n * internet, so it refuses rather than opens). Rendering \"HTTP 501\" over the top\n * of that would throw away the only sentence that says what to do.\n */\nasync function failureMessage(resp: { status: number; json?: () => Promise<unknown> }): Promise<string> {\n try {\n const body = await resp.json?.() as { error?: { message?: string } } | undefined;\n const message = body?.error?.message;\n if (typeof message === \"string\" && message) return message;\n } catch {\n /* not every failure has a JSON body */\n }\n return resp.status === 401 || resp.status === 403\n ? \"Not authorised to read logs — an admin role is required.\"\n : `Could not load logs (HTTP ${resp.status}).`;\n}\n\nexport interface LogTail {\n logs: LogEntry[];\n error: string | null;\n transport: LogTransport;\n /**\n * Entries the server had to discard because this client was not draining the\n * stream fast enough, since the connection opened.\n *\n * Surfaced rather than swallowed. The server bounds what it will hold per\n * connection, so under a heavy enough burst there is a hole in the tail — and\n * a tail with a hole in it that says so beats one that silently lies.\n */\n dropped: number;\n}\n\n/**\n * Tail the server's log buffer.\n *\n * Server-sent events, with the 3s poll kept only as a fallback: the studio and\n * the server are versioned and deployed separately, so a frontend that knows\n * about `/logs/stream` will meet servers that do not. A 404 there is not an\n * error, it is an older backend, and it degrades to what that backend supports\n * instead of showing an empty view.\n *\n * The stream sends its own backlog as a `snapshot` frame before any `append`, so\n * a reconnect restores the window without a second request and without a gap\n * where entries logged mid-handshake would belong to neither call.\n */\nexport function useLogTail(filters: LogTailFilters, limit = 200): LogTail {\n const [logs, setLogs] = useState<LogEntry[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [transport, setTransport] = useState<LogTransport>(\"connecting\");\n const [dropped, setDropped] = useState(0);\n const apiConfig = useApiConfig();\n const apiBase = useApiBase();\n\n // Held in a ref, and deliberately not a dependency of the connection effect.\n // `apiConfig` is an object: a provider that forgets to memoize it hands us a\n // new identity per render, and as a dependency that is a reconnect per\n // render — a tail that hammers the server and never stays up. `apiBase` is a\n // string and compares by value, so it can be a dependency and covers the one\n // thing that actually has to reopen the connection.\n const getAuthToken = apiConfig?.getAuthToken;\n const getAuthTokenRef = useRef(getAuthToken);\n useEffect(() => {\n getAuthTokenRef.current = getAuthToken;\n });\n\n const { level, source, search } = filters;\n\n // A hidden tab is not watching. The connection is dropped rather than left\n // to accumulate, and the reconnect's snapshot rebuilds the window — so this\n // costs nothing but the entries nobody saw.\n const [visible, setVisible] = useState(\n typeof document === \"undefined\" || document.visibilityState === \"visible\"\n );\n useEffect(() => {\n const onChange = () => setVisible(document.visibilityState === \"visible\");\n document.addEventListener(\"visibilitychange\", onChange);\n return () => document.removeEventListener(\"visibilitychange\", onChange);\n }, []);\n\n // A filter change replaces the window wholesale. Clearing here rather than in\n // the connection effect means a reconnect (or a tab coming back) keeps what\n // is on screen until the new snapshot lands.\n //\n // Joined on NUL, written as the escape: the search box takes free text, so any\n // printable separator is a character a user can type, and two filters that\n // differ only either side of it would collide into one key and skip the clear.\n // As a raw byte it would make the whole file binary to grep, which reads as an\n // empty search result rather than as an error.\n const filterKey = `${level}\\u0000${source}\\u0000${search}`;\n // `EMPTY` rather than a fresh `[]` so the run on mount, when there is nothing\n // to clear, is not a state change and does not cost a render.\n useEffect(() => {\n setLogs(EMPTY);\n }, [filterKey]);\n\n useEffect(() => {\n if (!apiBase) {\n setError(\"No API URL configured — cannot load logs.\");\n return;\n }\n if (!visible) return;\n\n let cancelled = false;\n let controller: AbortController | null = null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n\n const query = buildParams({ level,\n source,\n search }, limit);\n\n const authHeaders = async (): Promise<Record<string, string>> => {\n const headers: Record<string, string> = {};\n const token = await getAuthTokenRef.current?.() ?? null;\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n return headers;\n };\n\n const retryLater = () => {\n if (cancelled) return;\n const delay = Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_MS);\n attempt++;\n timer = setTimeout(connect, delay);\n };\n\n /** The fallback. Only ever reached against a server with no stream route. */\n const poll = async () => {\n if (cancelled) return;\n try {\n const resp = await fetch(`${apiBase}/logs?${query}`, { headers: await authHeaders() });\n if (cancelled) return;\n if (!resp.ok) {\n setError(await failureMessage(resp));\n } else {\n const data: { entries?: LogEntry[] } = await resp.json();\n // The query returns newest-first; the view tails like a\n // terminal, so flip to chronological order.\n const entries = (data.entries || []).slice().reverse();\n setLogs(prev => sameLogs(prev, entries) ? prev : entries);\n setError(null);\n }\n } catch (e) {\n if (!cancelled) setError(e instanceof Error ? e.message : \"Could not load logs.\");\n }\n if (!cancelled) timer = setTimeout(poll, POLL_MS);\n };\n\n const startPolling = () => {\n if (cancelled) return;\n setTransport(\"polling\");\n poll();\n };\n\n const connect = async () => {\n if (cancelled) return;\n controller = new AbortController();\n try {\n const resp = await fetch(`${apiBase}/logs/stream?${query}`, {\n headers: { ...await authHeaders(),\n Accept: \"text/event-stream\" },\n signal: controller.signal\n });\n if (cancelled) return;\n\n // 404 is an older server, not a failure — it has the query route\n // but not the stream. Same for a body-less response, which is a\n // client with no streaming support at all.\n if (resp.status === 404 || !resp.body) {\n startPolling();\n return;\n }\n if (!resp.ok) {\n setError(await failureMessage(resp));\n retryLater();\n return;\n }\n\n attempt = 0;\n setError(null);\n setTransport(\"live\");\n // Per-connection: the server's counter resets with the socket, so\n // a stale total would outlive the gap it described.\n setDropped(0);\n\n for await (const frame of readSSEFrames(resp.body)) {\n if (cancelled) return;\n if (frame.event === \"snapshot\") {\n const { entries } = JSON.parse(frame.data) as { entries: LogEntry[] };\n setLogs(prev => sameLogs(prev, entries) ? prev : capped(entries));\n } else if (frame.event === \"append\") {\n const parsed = JSON.parse(frame.data) as { entries: LogEntry[]; dropped?: number };\n if (parsed.entries.length > 0) {\n setLogs(prev => capped([...prev, ...parsed.entries]));\n }\n if (parsed.dropped) setDropped(n => n + parsed.dropped!);\n } else if (frame.event === \"error\") {\n setError(frame.data);\n }\n }\n\n // The stream ended on its own — a restarted or redeployed\n // server. Reconnecting is the whole point of the backoff.\n if (!cancelled) {\n setTransport(\"connecting\");\n retryLater();\n }\n } catch (e) {\n if (cancelled || (e instanceof Error && e.name === \"AbortError\")) return;\n setTransport(\"connecting\");\n setError(e instanceof Error ? e.message : \"Could not load logs.\");\n retryLater();\n }\n };\n\n connect();\n\n return () => {\n cancelled = true;\n if (timer) clearTimeout(timer);\n controller?.abort();\n };\n }, [level, source, search, limit, apiBase, visible]);\n\n return { logs,\n error,\n transport,\n dropped };\n}\n","import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { ArrowDownToLineIcon, Checkbox, cls, defaultBorderMixin, Label, Select, SelectItem, TextField, Typography } from \"@rebasepro/ui\";\nimport { idNum, useLogTail } from \"./useLogTail\";\nimport type { LogTransport } from \"./useLogTail\";\n\nconst LEVEL_COLORS: Record<string, string> = {\n debug: \"text-surface-500\",\n info: \"text-blue-600 dark:text-blue-500\",\n warn: \"text-amber-600 dark:text-amber-500\",\n error: \"text-red-600 dark:text-red-500\"\n};\n\nconst SOURCE_COLORS: Record<string, string> = {\n api: \"text-sky-600 dark:text-sky-400\",\n auth: \"text-purple-600 dark:text-purple-400\",\n storage: \"text-green-600 dark:text-green-500\",\n realtime: \"text-orange-600 dark:text-orange-400\",\n system: \"text-surface-600 dark:text-surface-400\"\n};\n\nconst TRANSPORT_LABELS: Record<LogTransport, string> = {\n connecting: \"Connecting\",\n live: \"Live\",\n polling: \"Polling\"\n};\n\nconst TRANSPORT_DOTS: Record<LogTransport, string> = {\n connecting: \"bg-amber-500\",\n live: \"bg-green-500\",\n polling: \"bg-surface-400\"\n};\n\nconst TRANSPORT_TITLES: Record<LogTransport, string> = {\n connecting: \"Reconnecting to the log stream.\",\n live: \"Streaming — entries appear as the server writes them.\",\n polling: \"This server has no log stream; falling back to a refresh every 3 seconds.\"\n};\n\n// How close to the bottom edge still counts as \"at the bottom\".\nconst STICK_THRESHOLD = 40;\n\nexport function LogsExplorer() {\n const [level, setLevel] = useState<string>(\"all\");\n const [source, setSource] = useState<string>(\"all\");\n const [searchInput, setSearchInput] = useState(\"\");\n const [search, setSearch] = useState(\"\");\n const [autoScroll, setAutoScroll] = useState(true);\n // Entries that arrived while the user was scrolled away from the bottom.\n const [newCount, setNewCount] = useState(0);\n const [atBottom, setAtBottom] = useState(true);\n const containerRef = useRef<HTMLDivElement>(null);\n // Whether the view is stuck to the bottom right now. A ref, not state:\n // the scroll handler and the entry-counting effect both read it synchronously.\n const stickRef = useRef(true);\n const lastMaxIdRef = useRef<number | null>(null);\n\n // Debounce the search box so typing doesn't reopen the stream per keystroke.\n useEffect(() => {\n const t = setTimeout(() => setSearch(searchInput), 300);\n return () => clearTimeout(t);\n }, [searchInput]);\n\n const { logs, error, transport, dropped } = useLogTail({ level,\n source,\n search });\n\n // A filter change replaces the window wholesale — \"new since you looked\n // away\" stops meaning anything, so the counter starts over.\n useEffect(() => {\n setNewCount(0);\n }, [level, source, search]);\n\n // Count what arrived while the user was reading further up. Driven off the\n // rendered window rather than off the transport, so it means the same thing\n // whether the entries were streamed or polled.\n useEffect(() => {\n const max = logs.length > 0 ? idNum(logs[logs.length - 1]) : null;\n const prevMax = lastMaxIdRef.current;\n if (prevMax != null && !stickRef.current) {\n const fresh = logs.filter(e => idNum(e) > prevMax).length;\n if (fresh > 0) setNewCount(c => c + fresh);\n }\n lastMaxIdRef.current = max;\n }, [logs]);\n\n const scrollToBottom = useCallback(() => {\n const el = containerRef.current;\n if (!el) return;\n el.scrollTop = el.scrollHeight;\n stickRef.current = true;\n setAtBottom(true);\n setNewCount(0);\n }, []);\n\n // Stick to the bottom only while the user is already there. Scrolling up\n // disengages; scrolling back down re-engages. Our own scrollTop writes\n // always land at the bottom, so they can never disengage it.\n const handleScroll = useCallback(() => {\n const el = containerRef.current;\n if (!el) return;\n const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < STICK_THRESHOLD;\n stickRef.current = nearBottom;\n setAtBottom(nearBottom);\n if (nearBottom) setNewCount(0);\n }, []);\n\n useLayoutEffect(() => {\n if (autoScroll && stickRef.current) {\n const el = containerRef.current;\n if (el) el.scrollTop = el.scrollHeight;\n }\n }, [logs, autoScroll]);\n\n return (\n <div className=\"flex flex-col h-[calc(100vh-64px)] w-full bg-surface-sheet\">\n {/* Toolbar */}\n <div className={cls(\n \"flex gap-2 p-3 border-b items-center flex-wrap shrink-0\",\n defaultBorderMixin\n )}>\n <Select\n value={level}\n onValueChange={setLevel}\n size=\"small\"\n placeholder=\"All Levels\"\n >\n <SelectItem value=\"all\">All Levels</SelectItem>\n <SelectItem value=\"debug\">Debug</SelectItem>\n <SelectItem value=\"info\">Info</SelectItem>\n <SelectItem value=\"warn\">Warn</SelectItem>\n <SelectItem value=\"error\">Error</SelectItem>\n </Select>\n <Select\n value={source}\n onValueChange={setSource}\n size=\"small\"\n placeholder=\"All Sources\"\n >\n <SelectItem value=\"all\">All Sources</SelectItem>\n <SelectItem value=\"api\">API</SelectItem>\n <SelectItem value=\"auth\">Auth</SelectItem>\n <SelectItem value=\"storage\">Storage</SelectItem>\n <SelectItem value=\"realtime\">Realtime</SelectItem>\n <SelectItem value=\"system\">System</SelectItem>\n </Select>\n <TextField\n size=\"small\"\n aria-label=\"Search logs\"\n placeholder=\"Search logs...\"\n value={searchInput}\n onChange={e => setSearchInput(e.target.value)}\n className=\"flex-1 min-w-[200px]\"\n />\n <div className=\"flex items-center gap-1.5 cursor-pointer ml-2\">\n <Checkbox\n id=\"auto-scroll\"\n checked={autoScroll}\n onCheckedChange={(checked: boolean) => {\n setAutoScroll(checked);\n if (checked) scrollToBottom();\n }}\n size=\"small\"\n padding={false}\n />\n <Label\n htmlFor=\"auto-scroll\"\n className=\"text-xs select-none cursor-pointer text-surface-600 dark:text-surface-400\"\n >\n Auto-scroll\n </Label>\n </div>\n {/* Whether the tail is actually attached. A log view with\n nothing in it is ambiguous — quiet server, or a stream that\n dropped — and this is the difference. */}\n <div className=\"ml-auto pl-4 flex items-center gap-3\">\n <div className=\"flex items-center gap-1.5\" title={TRANSPORT_TITLES[transport]}>\n <span\n aria-hidden\n className={cls(\"w-1.5 h-1.5 rounded-full shrink-0\", TRANSPORT_DOTS[transport])}\n />\n <Typography variant=\"caption\" color=\"secondary\">\n {TRANSPORT_LABELS[transport]}\n </Typography>\n </div>\n <Typography variant=\"caption\" color=\"secondary\">\n {logs.length} entries\n </Typography>\n </div>\n </div>\n\n {/* A broken tail must stay visible even while stale logs are on\n screen — otherwise the view quietly freezes. */}\n {error && logs.length > 0 && (\n <div className={cls(\n \"px-4 py-1.5 border-b bg-amber-50 dark:bg-amber-950/20 shrink-0\",\n defaultBorderMixin\n )}>\n <Typography variant=\"caption\" className=\"text-amber-700 dark:text-amber-400\">\n {error}\n </Typography>\n </div>\n )}\n\n {/* The server bounds what it holds per connection, so a burst big\n enough leaves a hole in what is on screen. Say so: a tail that\n silently skips entries is worse than one that admits it. */}\n {dropped > 0 && (\n <div className={cls(\n \"px-4 py-1.5 border-b bg-amber-50 dark:bg-amber-950/20 shrink-0\",\n defaultBorderMixin\n )}>\n <Typography variant=\"caption\" className=\"text-amber-700 dark:text-amber-400\">\n {dropped} {dropped === 1 ? \"entry was\" : \"entries were\"} dropped — the log is\n arriving faster than this view can read it. Narrow the filter to keep up.\n </Typography>\n </div>\n )}\n\n {/* Log entries */}\n <div className=\"relative flex-1 min-h-0\">\n <div\n ref={containerRef}\n onScroll={handleScroll}\n className=\"h-full overflow-auto py-2\"\n >\n {logs.map(log => (\n <div\n key={log.id}\n className={cls(\n \"flex gap-4 px-4 py-[6px] border-b hover:bg-surface-hover transition-colors\",\n defaultBorderMixin\n )}\n >\n <Typography variant=\"body2\" color=\"secondary\" className=\"w-[72px] shrink-0 font-mono\">\n {new Date(log.timestamp).toLocaleTimeString()}\n </Typography>\n <Typography variant=\"body2\" className={cls(\"w-[48px] shrink-0 uppercase font-semibold font-mono\", LEVEL_COLORS[log.level] || \"text-surface-500\")}>\n {log.level}\n </Typography>\n <Typography variant=\"body2\" className={cls(\"w-[80px] shrink-0 font-mono\", SOURCE_COLORS[log.source] || \"text-surface-500\")}>\n [{log.source}]\n </Typography>\n <Typography variant=\"body2\" className=\"flex-1 font-mono break-all whitespace-pre-wrap text-surface-900 dark:text-surface-100\">\n {log.message}\n </Typography>\n </div>\n ))}\n {logs.length === 0 && (\n <div className=\"p-8 text-center\">\n <Typography\n variant=\"body2\"\n className={error ? \"text-red-600 dark:text-red-500\" : undefined}\n color={error ? undefined : \"secondary\"}\n >\n {error ?? \"No log entries yet. Logs will appear here as requests come in.\"}\n </Typography>\n </div>\n )}\n </div>\n\n {autoScroll && !atBottom && newCount > 0 && (\n <button\n onClick={scrollToBottom}\n className=\"absolute bottom-3 left-1/2 -translate-x-1/2 flex items-center gap-1.5 rounded-full bg-primary text-white text-xs font-medium pl-2.5 pr-3 py-1.5 shadow-md hover:bg-primary-dark transition-colors\"\n >\n <ArrowDownToLineIcon size={14}/>\n {newCount} new {newCount === 1 ? \"entry\" : \"entries\"}\n </button>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAkCA,IAAM,cAAc;;AAGpB,IAAM,UAAU;AAEhB,IAAM,gBAAgB;AACtB,IAAM,eAAe;;AAGrB,IAAa,SAAS,UAA4B,OAAO,MAAM,GAAG,MAAM,CAAa,CAAC;;AAGtF,IAAa,YAAY,GAAe,MACpC,EAAE,WAAW,EAAE,UACf,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MACnB,EAAE,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE;;AAG7C,IAAM,QAAoB,CAAC;AAE3B,IAAM,UAAU,YACZ,QAAQ,SAAS,cAAc,QAAQ,MAAM,QAAQ,SAAS,WAAW,IAAI;;;;;;;;;;;AAiBjF,gBAAuB,cACnB,MACwB;CACxB,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,SAAS;CAEb,OAAO,MAAM;EACT,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;EAC1C,IAAI,MAAM;EAGV,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,QAAQ,SAAS,IAAI;EAEvE,IAAI,WAAW,OAAO,QAAQ,MAAM;EACpC,OAAO,aAAa,IAAI;GACpB,MAAM,MAAM,OAAO,MAAM,GAAG,QAAQ;GACpC,SAAS,OAAO,MAAM,WAAW,CAAC;GAClC,MAAM,QAAQ,WAAW,GAAG;GAC5B,IAAI,OAAO,MAAM;GACjB,WAAW,OAAO,QAAQ,MAAM;EACpC;CACJ;AACJ;AAEA,SAAS,WAAW,KAA8B;CAC9C,IAAI,QAAQ;CACZ,MAAM,OAAiB,CAAC;CACxB,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,GAAG;EAGhC,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,GAAG;EACnC,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,KAAK;EACvD,MAAM,QAAQ,UAAU,KAAK,KAAK,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE;EACxE,IAAI,UAAU,SAAS,QAAQ;OAC1B,IAAI,UAAU,QAAQ,KAAK,KAAK,KAAK;CAC9C;CACA,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,OAAO;EAAE;EACL,MAAM,KAAK,KAAK,IAAI;CAAE;AAC9B;AAEA,IAAM,eAAe,SAAyB,UAA0B;CACpE,MAAM,SAAS,IAAI,gBAAgB;CACnC,IAAI,QAAQ,SAAS,QAAQ,UAAU,OAAO,OAAO,IAAI,SAAS,QAAQ,KAAK;CAC/E,IAAI,QAAQ,UAAU,QAAQ,WAAW,OAAO,OAAO,IAAI,UAAU,QAAQ,MAAM;CACnF,IAAI,QAAQ,QAAQ,OAAO,IAAI,UAAU,QAAQ,MAAM;CACvD,OAAO,IAAI,SAAS,OAAO,KAAK,CAAC;CACjC,OAAO,OAAO,SAAS;AAC3B;;;;;;;;;AAUA,eAAe,eAAe,MAA0E;CACpG,IAAI;EAEA,MAAM,WAAU,MADG,KAAK,OAAO,EAAA,EACT,OAAO;EAC7B,IAAI,OAAO,YAAY,YAAY,SAAS,OAAO;CACvD,QAAQ,CAER;CACA,OAAO,KAAK,WAAW,OAAO,KAAK,WAAW,MACxC,6DACA,6BAA6B,KAAK,OAAO;AACnD;;;;;;;;;;;;;;AA8BA,SAAgB,WAAW,SAAyB,QAAQ,KAAc;CACtE,MAAM,CAAC,MAAM,WAAW,SAAqB,CAAC,CAAC;CAC/C,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CACtD,MAAM,CAAC,WAAW,gBAAgB,SAAuB,YAAY;CACrE,MAAM,CAAC,SAAS,cAAc,SAAS,CAAC;CACxC,MAAM,YAAY,aAAa;CAC/B,MAAM,UAAU,WAAW;CAQ3B,MAAM,eAAe,WAAW;CAChC,MAAM,kBAAkB,OAAO,YAAY;CAC3C,gBAAgB;EACZ,gBAAgB,UAAU;CAC9B,CAAC;CAED,MAAM,EAAE,OAAO,QAAQ,WAAW;CAKlC,MAAM,CAAC,SAAS,cAAc,SAC1B,OAAO,aAAa,eAAe,SAAS,oBAAoB,SACpE;CACA,gBAAgB;EACZ,MAAM,iBAAiB,WAAW,SAAS,oBAAoB,SAAS;EACxE,SAAS,iBAAiB,oBAAoB,QAAQ;EACtD,aAAa,SAAS,oBAAoB,oBAAoB,QAAQ;CAC1E,GAAG,CAAC,CAAC;CAcL,gBAAgB;EACZ,QAAQ,KAAK;CACjB,GAAG,CAAC,GALiB,MAAM,QAAQ,OAAO,QAAQ,QAKrC,CAAC;CAEd,gBAAgB;EACZ,IAAI,CAAC,SAAS;GACV,SAAS,2CAA2C;GACpD;EACJ;EACA,IAAI,CAAC,SAAS;EAEd,IAAI,YAAY;EAChB,IAAI,aAAqC;EACzC,IAAI,QAA8C;EAClD,IAAI,UAAU;EAEd,MAAM,QAAQ,YAAY;GAAE;GACxB;GACA;EAAO,GAAG,KAAK;EAEnB,MAAM,cAAc,YAA6C;GAC7D,MAAM,UAAkC,CAAC;GACzC,MAAM,QAAQ,MAAM,gBAAgB,UAAU,KAAK;GACnD,IAAI,OAAO,QAAQ,mBAAmB,UAAU;GAChD,OAAO;EACX;EAEA,MAAM,mBAAmB;GACrB,IAAI,WAAW;GACf,MAAM,QAAQ,KAAK,IAAI,gBAAgB,KAAK,SAAS,YAAY;GACjE;GACA,QAAQ,WAAW,SAAS,KAAK;EACrC;;EAGA,MAAM,OAAO,YAAY;GACrB,IAAI,WAAW;GACf,IAAI;IACA,MAAM,OAAO,MAAM,MAAM,GAAG,QAAQ,QAAQ,SAAS,EAAE,SAAS,MAAM,YAAY,EAAE,CAAC;IACrF,IAAI,WAAW;IACf,IAAI,CAAC,KAAK,IACN,SAAS,MAAM,eAAe,IAAI,CAAC;SAChC;KAIH,MAAM,YAAW,MAH4B,KAAK,KAAK,EAAA,CAGjC,WAAW,CAAC,EAAA,CAAG,MAAM,CAAC,CAAC,QAAQ;KACrD,SAAQ,SAAQ,SAAS,MAAM,OAAO,IAAI,OAAO,OAAO;KACxD,SAAS,IAAI;IACjB;GACJ,SAAS,GAAG;IACR,IAAI,CAAC,WAAW,SAAS,aAAa,QAAQ,EAAE,UAAU,sBAAsB;GACpF;GACA,IAAI,CAAC,WAAW,QAAQ,WAAW,MAAM,OAAO;EACpD;EAEA,MAAM,qBAAqB;GACvB,IAAI,WAAW;GACf,aAAa,SAAS;GACtB,KAAK;EACT;EAEA,MAAM,UAAU,YAAY;GACxB,IAAI,WAAW;GACf,aAAa,IAAI,gBAAgB;GACjC,IAAI;IACA,MAAM,OAAO,MAAM,MAAM,GAAG,QAAQ,eAAe,SAAS;KACxD,SAAS;MAAE,GAAG,MAAM,YAAY;MAC5B,QAAQ;KAAoB;KAChC,QAAQ,WAAW;IACvB,CAAC;IACD,IAAI,WAAW;IAKf,IAAI,KAAK,WAAW,OAAO,CAAC,KAAK,MAAM;KACnC,aAAa;KACb;IACJ;IACA,IAAI,CAAC,KAAK,IAAI;KACV,SAAS,MAAM,eAAe,IAAI,CAAC;KACnC,WAAW;KACX;IACJ;IAEA,UAAU;IACV,SAAS,IAAI;IACb,aAAa,MAAM;IAGnB,WAAW,CAAC;IAEZ,WAAW,MAAM,SAAS,cAAc,KAAK,IAAI,GAAG;KAChD,IAAI,WAAW;KACf,IAAI,MAAM,UAAU,YAAY;MAC5B,MAAM,EAAE,YAAY,KAAK,MAAM,MAAM,IAAI;MACzC,SAAQ,SAAQ,SAAS,MAAM,OAAO,IAAI,OAAO,OAAO,OAAO,CAAC;KACpE,OAAO,IAAI,MAAM,UAAU,UAAU;MACjC,MAAM,SAAS,KAAK,MAAM,MAAM,IAAI;MACpC,IAAI,OAAO,QAAQ,SAAS,GACxB,SAAQ,SAAQ,OAAO,CAAC,GAAG,MAAM,GAAG,OAAO,OAAO,CAAC,CAAC;MAExD,IAAI,OAAO,SAAS,YAAW,MAAK,IAAI,OAAO,OAAQ;KAC3D,OAAO,IAAI,MAAM,UAAU,SACvB,SAAS,MAAM,IAAI;IAE3B;IAIA,IAAI,CAAC,WAAW;KACZ,aAAa,YAAY;KACzB,WAAW;IACf;GACJ,SAAS,GAAG;IACR,IAAI,aAAc,aAAa,SAAS,EAAE,SAAS,cAAe;IAClE,aAAa,YAAY;IACzB,SAAS,aAAa,QAAQ,EAAE,UAAU,sBAAsB;IAChE,WAAW;GACf;EACJ;EAEA,QAAQ;EAER,aAAa;GACT,YAAY;GACZ,IAAI,OAAO,aAAa,KAAK;GAC7B,YAAY,MAAM;EACtB;CACJ,GAAG;EAAC;EAAO;EAAQ;EAAQ;EAAO;EAAS;CAAO,CAAC;CAEnD,OAAO;EAAE;EACL;EACA;EACA;CAAQ;AAChB;;;AC9VA,IAAM,eAAuC;CACzC,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACX;AAEA,IAAM,gBAAwC;CAC1C,KAAK;CACL,MAAM;CACN,SAAS;CACT,UAAU;CACV,QAAQ;AACZ;AAEA,IAAM,mBAAiD;CACnD,YAAY;CACZ,MAAM;CACN,SAAS;AACb;AAEA,IAAM,iBAA+C;CACjD,YAAY;CACZ,MAAM;CACN,SAAS;AACb;AAEA,IAAM,mBAAiD;CACnD,YAAY;CACZ,MAAM;CACN,SAAS;AACb;AAGA,IAAM,kBAAkB;AAExB,SAAgB,eAAe;CAC3B,MAAM,CAAC,OAAO,YAAY,SAAiB,KAAK;CAChD,MAAM,CAAC,QAAQ,aAAa,SAAiB,KAAK;CAClD,MAAM,CAAC,aAAa,kBAAkB,SAAS,EAAE;CACjD,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CACvC,MAAM,CAAC,YAAY,iBAAiB,SAAS,IAAI;CAEjD,MAAM,CAAC,UAAU,eAAe,SAAS,CAAC;CAC1C,MAAM,CAAC,UAAU,eAAe,SAAS,IAAI;CAC7C,MAAM,eAAe,OAAuB,IAAI;CAGhD,MAAM,WAAW,OAAO,IAAI;CAC5B,MAAM,eAAe,OAAsB,IAAI;CAG/C,gBAAgB;EACZ,MAAM,IAAI,iBAAiB,UAAU,WAAW,GAAG,GAAG;EACtD,aAAa,aAAa,CAAC;CAC/B,GAAG,CAAC,WAAW,CAAC;CAEhB,MAAM,EAAE,MAAM,OAAO,WAAW,YAAY,WAAW;EAAE;EACrD;EACA;CAAO,CAAC;CAIZ,gBAAgB;EACZ,YAAY,CAAC;CACjB,GAAG;EAAC;EAAO;EAAQ;CAAM,CAAC;CAK1B,gBAAgB;EACZ,MAAM,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,SAAS,EAAE,IAAI;EAC7D,MAAM,UAAU,aAAa;EAC7B,IAAI,WAAW,QAAQ,CAAC,SAAS,SAAS;GACtC,MAAM,QAAQ,KAAK,QAAO,MAAK,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC;GACnD,IAAI,QAAQ,GAAG,aAAY,MAAK,IAAI,KAAK;EAC7C;EACA,aAAa,UAAU;CAC3B,GAAG,CAAC,IAAI,CAAC;CAET,MAAM,iBAAiB,kBAAkB;EACrC,MAAM,KAAK,aAAa;EACxB,IAAI,CAAC,IAAI;EACT,GAAG,YAAY,GAAG;EAClB,SAAS,UAAU;EACnB,YAAY,IAAI;EAChB,YAAY,CAAC;CACjB,GAAG,CAAC,CAAC;CAKL,MAAM,eAAe,kBAAkB;EACnC,MAAM,KAAK,aAAa;EACxB,IAAI,CAAC,IAAI;EACT,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;EACtE,SAAS,UAAU;EACnB,YAAY,UAAU;EACtB,IAAI,YAAY,YAAY,CAAC;CACjC,GAAG,CAAC,CAAC;CAEL,sBAAsB;EAClB,IAAI,cAAc,SAAS,SAAS;GAChC,MAAM,KAAK,aAAa;GACxB,IAAI,IAAI,GAAG,YAAY,GAAG;EAC9B;CACJ,GAAG,CAAC,MAAM,UAAU,CAAC;CAErB,OACI,qBAAC,OAAD;EAAK,WAAU;YAAf;GAEI,qBAAC,OAAD;IAAK,WAAW,IACZ,2DACA,kBACJ;cAHA;KAII,qBAAC,QAAD;MACI,OAAO;MACP,eAAe;MACf,MAAK;MACL,aAAY;gBAJhB;OAMI,oBAAC,YAAD;QAAY,OAAM;kBAAM;OAAsB,CAAA;OAC9C,oBAAC,YAAD;QAAY,OAAM;kBAAQ;OAAiB,CAAA;OAC3C,oBAAC,YAAD;QAAY,OAAM;kBAAO;OAAgB,CAAA;OACzC,oBAAC,YAAD;QAAY,OAAM;kBAAO;OAAgB,CAAA;OACzC,oBAAC,YAAD;QAAY,OAAM;kBAAQ;OAAiB,CAAA;MACvC;;KACR,qBAAC,QAAD;MACI,OAAO;MACP,eAAe;MACf,MAAK;MACL,aAAY;gBAJhB;OAMI,oBAAC,YAAD;QAAY,OAAM;kBAAM;OAAuB,CAAA;OAC/C,oBAAC,YAAD;QAAY,OAAM;kBAAM;OAAe,CAAA;OACvC,oBAAC,YAAD;QAAY,OAAM;kBAAO;OAAgB,CAAA;OACzC,oBAAC,YAAD;QAAY,OAAM;kBAAU;OAAmB,CAAA;OAC/C,oBAAC,YAAD;QAAY,OAAM;kBAAW;OAAoB,CAAA;OACjD,oBAAC,YAAD;QAAY,OAAM;kBAAS;OAAkB,CAAA;MACzC;;KACR,oBAAC,WAAD;MACI,MAAK;MACL,cAAW;MACX,aAAY;MACZ,OAAO;MACP,WAAU,MAAK,eAAe,EAAE,OAAO,KAAK;MAC5C,WAAU;KACb,CAAA;KACD,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACI,oBAAC,UAAD;OACI,IAAG;OACH,SAAS;OACT,kBAAkB,YAAqB;QACnC,cAAc,OAAO;QACrB,IAAI,SAAS,eAAe;OAChC;OACA,MAAK;OACL,SAAS;MACZ,CAAA,GACD,oBAAC,OAAD;OACI,SAAQ;OACR,WAAU;iBACb;MAEM,CAAA,CACN;;KAIL,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACI,qBAAC,OAAD;OAAK,WAAU;OAA4B,OAAO,iBAAiB;iBAAnE,CACI,oBAAC,QAAD;QACI,eAAA;QACA,WAAW,IAAI,qCAAqC,eAAe,UAAU;OAChF,CAAA,GACD,oBAAC,YAAD;QAAY,SAAQ;QAAU,OAAM;kBAC/B,iBAAiB;OACV,CAAA,CACX;UACL,qBAAC,YAAD;OAAY,SAAQ;OAAU,OAAM;iBAApC,CACK,KAAK,QAAO,UACL;QACX;;IACJ;;GAIJ,SAAS,KAAK,SAAS,KACpB,oBAAC,OAAD;IAAK,WAAW,IACZ,kEACA,kBACJ;cACI,oBAAC,YAAD;KAAY,SAAQ;KAAU,WAAU;eACnC;IACO,CAAA;GACX,CAAA;GAMR,UAAU,KACP,oBAAC,OAAD;IAAK,WAAW,IACZ,kEACA,kBACJ;cACI,qBAAC,YAAD;KAAY,SAAQ;KAAU,WAAU;eAAxC;MACK;MAAQ;MAAE,YAAY,IAAI,cAAc;MAAe;KAEhD;;GACX,CAAA;GAIT,qBAAC,OAAD;IAAK,WAAU;cAAf,CACI,qBAAC,OAAD;KACI,KAAK;KACL,UAAU;KACV,WAAU;eAHd,CAKK,KAAK,KAAI,QACN,qBAAC,OAAD;MAEI,WAAW,IACP,8EACA,kBACJ;gBALJ;OAOI,oBAAC,YAAD;QAAY,SAAQ;QAAQ,OAAM;QAAY,WAAU;kBACnD,IAAI,KAAK,IAAI,SAAS,CAAC,CAAC,mBAAmB;OACpC,CAAA;OACZ,oBAAC,YAAD;QAAY,SAAQ;QAAQ,WAAW,IAAI,uDAAuD,aAAa,IAAI,UAAU,kBAAkB;kBAC1I,IAAI;OACG,CAAA;OACZ,qBAAC,YAAD;QAAY,SAAQ;QAAQ,WAAW,IAAI,+BAA+B,cAAc,IAAI,WAAW,kBAAkB;kBAAzH;SAA4H;SACtH,IAAI;SAAO;QACL;;OACZ,oBAAC,YAAD;QAAY,SAAQ;QAAQ,WAAU;kBACjC,IAAI;OACG,CAAA;MACX;QAlBI,IAAI,EAkBR,CACR,GACA,KAAK,WAAW,KACb,oBAAC,OAAD;MAAK,WAAU;gBACX,oBAAC,YAAD;OACI,SAAQ;OACR,WAAW,QAAQ,mCAAmC,KAAA;OACtD,OAAO,QAAQ,KAAA,IAAY;iBAE1B,SAAS;MACF,CAAA;KACX,CAAA,CAER;QAEJ,cAAc,CAAC,YAAY,WAAW,KACnC,qBAAC,UAAD;KACI,SAAS;KACT,WAAU;eAFd;MAII,oBAAC,qBAAD,EAAqB,MAAM,GAAI,CAAA;MAC9B;MAAS;MAAM,aAAa,IAAI,UAAU;KACvC;MAEX;;EACJ;;AAEb"}