@rebasepro/studio 0.14.1 → 0.14.2-canary.g27a129e

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.
package/dist/index.es.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IconForView, SchemaDriftBanner, StudioBridgeContext, StudioBridgeProvider, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useStudioBreadcrumbs, useStudioBreadcrumbs as useStudioBreadcrumbs$1, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController } from "@rebasepro/app";
2
- import React, { Suspense, lazy, useEffect, useLayoutEffect, useMemo, useState } from "react";
3
- import { Card, CircularProgressCenter, Container, ExpandablePanel, Typography, cls } from "@rebasepro/ui";
2
+ import React, { Suspense, useEffect, useLayoutEffect, useMemo, useState } from "react";
3
+ import { Card, CircularProgressCenter, Container, ExpandablePanel, Typography, cls, lazyChunk } from "@rebasepro/ui";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useNavigate } from "react-router";
6
6
  //#region src/components/StudioHomePage.tsx
@@ -549,17 +549,17 @@ function SyntaxHighlightedSnippet() {
549
549
  }
550
550
  //#endregion
551
551
  //#region src/components/RebaseStudio.tsx
552
- var SQLEditor = lazy(() => import("./SQLEditor-BmI0iIWK.js").then((m) => ({ default: m.SQLEditor })));
553
- var JSEditor = lazy(() => import("./JSEditor-BaSrzWhF.js").then((m) => ({ default: m.JSEditor })));
554
- var RLSEditor = lazy(() => import("./RLSEditor-CV8R1G3W.js").then((m) => ({ default: m.RLSEditor })));
555
- var StorageView = lazy(() => import("./StorageView-CwDI4sBG.js").then((m) => ({ default: m.StorageView })));
556
- var CronJobsView = lazy(() => import("./CronJobsView-BHtJZeJZ.js").then((m) => ({ default: m.CronJobsView })));
557
- var SchemaVisualizer = lazy(() => import("./SchemaVisualizer-vxKSG80C.js").then((m) => ({ default: m.SchemaVisualizer })));
558
- var BranchesView = lazy(() => import("./BranchesView-CM3B-ER3.js").then((m) => ({ default: m.BranchesView })));
559
- var BackupsView = lazy(() => import("./BackupsView-DNS6LdVg.js").then((m) => ({ default: m.BackupsView })));
560
- var ApiExplorer = lazy(() => import("./ApiExplorer-CDOlUQzk.js").then((m) => ({ default: m.ApiExplorer })));
561
- var LogsExplorer = lazy(() => import("./LogsExplorer-Ctk2f3MM.js").then((m) => ({ default: m.LogsExplorer })));
562
- var ApiKeysView = lazy(() => import("./ApiKeysView-C_UoUPuR.js").then((m) => ({ default: m.ApiKeysView })));
552
+ var SQLEditor = lazyChunk(() => import("./SQLEditor-BmI0iIWK.js").then((m) => ({ default: m.SQLEditor })));
553
+ var JSEditor = lazyChunk(() => import("./JSEditor-BaSrzWhF.js").then((m) => ({ default: m.JSEditor })));
554
+ var RLSEditor = lazyChunk(() => import("./RLSEditor-CV8R1G3W.js").then((m) => ({ default: m.RLSEditor })));
555
+ var StorageView = lazyChunk(() => import("./StorageView-CwDI4sBG.js").then((m) => ({ default: m.StorageView })));
556
+ var CronJobsView = lazyChunk(() => import("./CronJobsView-BHtJZeJZ.js").then((m) => ({ default: m.CronJobsView })));
557
+ var SchemaVisualizer = lazyChunk(() => import("./SchemaVisualizer-vxKSG80C.js").then((m) => ({ default: m.SchemaVisualizer })));
558
+ var BranchesView = lazyChunk(() => import("./BranchesView-CM3B-ER3.js").then((m) => ({ default: m.BranchesView })));
559
+ var BackupsView = lazyChunk(() => import("./BackupsView-DNS6LdVg.js").then((m) => ({ default: m.BackupsView })));
560
+ var ApiExplorer = lazyChunk(() => import("./ApiExplorer-CDOlUQzk.js").then((m) => ({ default: m.ApiExplorer })));
561
+ var LogsExplorer = lazyChunk(() => import("./LogsExplorer-DI8SVpya.js").then((m) => ({ default: m.LogsExplorer })));
562
+ var ApiKeysView = lazyChunk(() => import("./ApiKeysView-C_UoUPuR.js").then((m) => ({ default: m.ApiKeysView })));
563
563
  /**
564
564
  * Declarative component to configure the Studio in Rebase.
565
565
  * Renders nothing — purely registers config into the RebaseRegistry.
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/components/StudioHomePage.tsx","../src/components/RebaseStudio.tsx"],"sourcesContent":["import type { AppView, HomePageSection, PluginGenericProps } from \"@rebasepro/admin-types\";\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { Card, cls, Container, ExpandablePanel, Typography } from \"@rebasepro/ui\";\nimport { IconForView, useRebaseContext, useRebaseRegistry, useRestoreScroll, useSlot } from \"@rebasepro/app\";\nimport { useNavigate } from \"react-router\";\nimport { useStudioBreadcrumbs, SchemaDriftBanner } from \"@rebasepro/app\";\n\n/* ═══════════════════════════════════════════════════════════════\n Studio tool sections, derived from the registered Studio views\n ═══════════════════════════════════════════════════════════════ */\n\ninterface StudioTool {\n path: string;\n name: string;\n description: string;\n icon?: string | React.ReactNode;\n}\n\ninterface StudioSection {\n label: string;\n tools: StudioTool[];\n}\n\n/** The view metadata the cards need — not the rendered `view` itself. */\ntype StudioViewMeta = Pick<AppView, \"slug\" | \"name\" | \"group\" | \"description\" | \"icon\" | \"hideFromNavigation\">;\n\n/** Group order for the home page; unknown groups are appended in view order. */\nconst GROUP_ORDER = [\"Database\", \"Compute\", \"Storage\", \"API\", \"Access Control\"];\n\nconst UNGROUPED_LABEL = \"Tools\";\n\n/**\n * Build the home page sections from the Studio views that are actually\n * registered, so a card exists if and only if its route does. A hand-written\n * list drifts: it used to advertise Users and Roles pages that 404, while\n * omitting the Backups view that does exist.\n */\nfunction buildSections(views: StudioViewMeta[]): StudioSection[] {\n const byGroup = new Map<string, StudioTool[]>();\n\n for (const view of views) {\n if (view.hideFromNavigation) continue;\n const label = view.group ?? UNGROUPED_LABEL;\n const tools = byGroup.get(label) ?? [];\n tools.push({\n path: `/${view.slug}`,\n name: view.name,\n description: view.description ?? \"\",\n icon: view.icon\n });\n byGroup.set(label, tools);\n }\n\n const ordered = [\n ...GROUP_ORDER.filter(g => byGroup.has(g)),\n ...[...byGroup.keys()].filter(g => !GROUP_ORDER.includes(g))\n ];\n\n return ordered.map(label => ({ label,\ntools: byGroup.get(label)! }));\n}\n\n/* ═══════════════════════════════════════════════════════════════ */\n\nconst COLLAPSED_STORAGE_KEY = \"rebase-studio-home-collapsed\";\n\nfunction useStudioCollapsedGroups(groupNames: string[]) {\n const [collapsed, setCollapsed] = useState<Set<string>>(() => {\n try {\n const stored = localStorage.getItem(COLLAPSED_STORAGE_KEY);\n return stored ? new Set(JSON.parse(stored)) : new Set<string>();\n } catch {\n return new Set<string>();\n }\n });\n\n const isGroupCollapsed = (name: string) => collapsed.has(name);\n\n const toggleGroupCollapsed = (name: string) => {\n setCollapsed(prev => {\n const next = new Set(prev);\n if (next.has(name)) {\n next.delete(name);\n } else {\n next.add(name);\n }\n try {\n localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...next]));\n } catch { /* noop */ }\n return next;\n });\n };\n\n return { isGroupCollapsed, toggleGroupCollapsed };\n}\n\nexport function StudioHomePage({\n additionalActions,\n additionalChildrenStart,\n additionalChildrenEnd,\n sections,\n hiddenGroups\n}: {\n additionalActions?: React.ReactNode;\n additionalChildrenStart?: React.ReactNode;\n additionalChildrenEnd?: React.ReactNode;\n sections?: HomePageSection[];\n hiddenGroups?: string[];\n}) {\n const context = useRebaseContext();\n const breadcrumbs = useStudioBreadcrumbs();\n const navigate = useNavigate();\n const registry = useRebaseRegistry();\n\n useEffect(() => {\n breadcrumbs.set({ breadcrumbs: [] });\n }, [breadcrumbs.set]);\n\n const { containerRef } = useRestoreScroll();\n\n const sectionProps: PluginGenericProps = { context };\n\n const pluginActions = useSlot(\"home.actions\", sectionProps);\n\n // The collection editor (\"schema\") is not part of `devViews` — RebaseNavigation\n // injects it when the admin enables a collection editor. Mirror that condition so\n // the card tracks the route.\n const schemaEnabled = Boolean(registry.studioConfig && registry.cmsConfig?.collectionEditor);\n\n const filteredSections = useMemo(() => {\n const views: StudioViewMeta[] = [];\n if (schemaEnabled) {\n views.push({\n slug: \"schema\",\n name: \"Collections\",\n group: \"Database\",\n icon: \"LayoutList\",\n description: \"Define and manage your data model and collection schemas\"\n });\n }\n views.push(...(registry.studioConfig?.devViews ?? []));\n return buildSections(views).filter(s => !hiddenGroups?.includes(s.label));\n }, [registry.studioConfig?.devViews, schemaEnabled, hiddenGroups]);\n\n const groupNames = useMemo(\n () => filteredSections.map(s => s.label),\n [filteredSections]\n );\n\n const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups(groupNames);\n\n return (\n <div ref={containerRef} className=\"py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800\">\n <Container maxWidth=\"6xl\">\n <div className=\"mb-4 flex flex-col gap-2\">\n <SchemaDriftBanner />\n </div>\n\n {(additionalActions || pluginActions) && (\n <div className=\"w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4 justify-end\">\n {additionalActions}\n {pluginActions}\n </div>\n )}\n\n {additionalChildrenStart}\n\n {/* ── Tool sections ── */}\n {filteredSections.map((section) => {\n const sectionCollapsed = isGroupCollapsed(section.label);\n\n return (\n <div key={section.label} className=\"my-10\">\n <ExpandablePanel\n invisible\n expanded={!sectionCollapsed}\n onExpandedChange={(open) => {\n if (open !== !sectionCollapsed) {\n toggleGroupCollapsed(section.label);\n }\n }}\n className=\"mt-6\"\n titleClassName={cls(\n \"min-h-0 p-0 border-none\",\n \"rounded flex items-center justify-between w-full\",\n \"hover:bg-transparent\",\n \"cursor-pointer select-none\",\n sectionCollapsed && \"bg-surface-100 dark:bg-surface-900/50\"\n )}\n innerClassName=\"mt-4 pt-0\"\n title={\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {section.label}\n </Typography>\n }\n >\n <div className=\"mt-4 pt-0\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {section.tools.map((tool) => (\n <Card\n key={tool.path}\n onClick={() => {\n navigate(tool.path);\n context.analyticsController?.onAnalyticsEvent?.(\n \"home_navigate_to_view\",\n { path: tool.path }\n );\n }}\n className={cls(\n \"group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out\",\n \"hover:bg-primary/5 dark:hover:bg-primary/5\"\n )}\n >\n <div className=\"flex flex-col h-full\">\n {/* Header: icon + title */}\n <div className=\"flex items-center w-full justify-between mb-1\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary\">\n <IconForView\n collectionOrView={{ slug: tool.path, name: tool.name, icon: tool.icon }}\n size=\"small\"\n />\n </div>\n <Typography variant=\"subtitle1\" component=\"h2\">\n {tool.name}\n </Typography>\n </div>\n </div>\n\n {/* Description indented to align with title */}\n <div className=\"pl-8\">\n {tool.description && (\n <Typography variant=\"caption\" color=\"secondary\" component=\"div\">\n {tool.description}\n </Typography>\n )}\n </div>\n\n {/* Spacer */}\n <div className=\"grow\"/>\n </div>\n </Card>\n ))}\n </div>\n </div>\n </ExpandablePanel>\n </div>\n );\n })}\n\n {/* ── SDK Quick Start ── */}\n <div className=\"mt-10 mb-6\">\n <div className=\"flex items-center mb-1\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n Quick Start\n </Typography>\n </div>\n\n <Typography variant=\"body2\" color=\"secondary\" className=\"mb-4 max-w-2xl\">\n Generate a fully-typed SDK from your collections with{\" \"}\n <code className=\"text-emerald-400 font-mono text-xs bg-emerald-400/10 px-1.5 py-0.5 rounded\">\n npx rebase generate-sdk\n </code>\n {\" \"}and start querying your data with full TypeScript autocompletion.\n </Typography>\n\n <div className=\"rounded-lg border border-surface-200/40 dark:border-surface-700/40 bg-white dark:bg-surface-950 overflow-hidden\">\n {/* Title bar */}\n <div className=\"flex items-center justify-between px-4 py-2.5 border-b border-surface-200/40 dark:border-surface-700/40 bg-surface-50 dark:bg-surface-900/80\">\n <div className=\"flex items-center gap-2.5\">\n <div className=\"flex gap-1.5\">\n <span className=\"w-2.5 h-2.5 rounded-full bg-red-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-amber-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-emerald-400/60\"/>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500 ml-1\">\n app.ts\n </span>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500\">\n TypeScript\n </span>\n </div>\n\n {/* Syntax-highlighted code */}\n <div className=\"px-5 py-4 overflow-x-auto text-[13px] leading-6 font-mono\">\n <SyntaxHighlightedSnippet/>\n </div>\n </div>\n </div>\n\n {/* ── Extra sections from props ── */}\n {sections?.map((s) => (\n <div key={s.key} className=\"my-10\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {s.title}\n </Typography>\n <div className=\"mt-4\">{s.children}</div>\n </div>\n ))}\n\n {additionalChildrenEnd}\n </Container>\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Hand-crafted syntax-highlighted code snippet.\n Uses inline spans with Tailwind color classes to avoid\n pulling in a syntax highlighting library.\n ═══════════════════════════════════════════════════════════════ */\n\nfunction SyntaxHighlightedSnippet() {\n const kw = \"text-violet-600 dark:text-violet-400\"; // keywords\n const str = \"text-emerald-600 dark:text-emerald-400\"; // strings\n const typ = \"text-amber-600 dark:text-amber-300\"; // types\n const fn = \"text-blue-600 dark:text-blue-400\"; // functions\n const cm = \"text-surface-500 dark:text-surface-400 italic\"; // comments\n const op = \"text-surface-500 dark:text-surface-400\"; // operators / punctuation\n const tx = \"text-surface-950 dark:text-surface-200\"; // plain text\n\n return (\n <pre className=\"m-0 whitespace-pre\">\n <span className={kw}>import</span>\n <span className={tx}>{\" { \"}</span>\n <span className={fn}>createRebaseClient</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>&apos;@rebasepro/client&apos;</span>\n <span className={op}>;</span>\n {\"\\n\"}\n\n <span className={kw}>import</span>\n <span className={tx}> </span>\n <span className={kw}>type</span>\n <span className={tx}>{\" { \"}</span>\n <span className={typ}>Database</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>&apos;./database.types&apos;</span>\n <span className={op}>;</span>\n {\"\\n\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}> rebase </span>\n <span className={op}>= </span>\n <span className={fn}>createRebaseClient</span>\n <span className={op}>{\"<\"}</span>\n <span className={typ}>Database</span>\n <span className={op}>{\">(\"}</span>\n <span className={tx}>{\"{\"}</span>\n {\"\\n\"}\n <span className={tx}>{\" baseUrl\"}</span>\n <span className={op}>: </span>\n <span className={str}>&apos;http://localhost:3001&apos;</span>\n <span className={op}>,</span>\n {\"\\n\"}\n <span className={tx}>{\"}\"}</span>\n <span className={op}>);</span>\n {\"\\n\\n\"}\n\n <span className={cm}>{\"// Fully typed — autocompletion for tables and columns\"}</span>\n {\"\\n\"}\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>users</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={tx}>users</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n {\"\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>posts</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={fn}>collection</span>\n <span className={op}>(</span>\n <span className={str}>&apos;posts&apos;</span>\n <span className={op}>)</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n </pre>\n );\n}\n","import React, { lazy, Suspense, useLayoutEffect, useMemo } from \"react\";\nimport { useRebaseRegistryDispatch } from \"@rebasepro/app\";\nimport type { RebaseStudioConfig, AppView } from \"@rebasepro/admin-types\";\nimport { CircularProgressCenter } from \"@rebasepro/ui\";\n\n// Lazy-loaded studio tools — each fetched only when its route is visited.\n// This keeps Monaco, @xyflow/react, dagre, pgsql-ast-parser etc. out of the initial bundle.\nconst SQLEditor = lazy(() => import(\"./SQLEditor/SQLEditor\").then(m => ({ default: m.SQLEditor })));\nconst JSEditor = lazy(() => import(\"./JSEditor/JSEditor\").then(m => ({ default: m.JSEditor })));\nconst RLSEditor = lazy(() => import(\"./RLSEditor/RLSEditor\").then(m => ({ default: m.RLSEditor })));\nconst StorageView = lazy(() => import(\"./StorageView/StorageView\").then(m => ({ default: m.StorageView })));\nconst CronJobsView = lazy(() => import(\"./CronJobs/CronJobsView\").then(m => ({ default: m.CronJobsView })));\nconst SchemaVisualizer = lazy(() => import(\"./SchemaVisualizer/SchemaVisualizer\").then(m => ({ default: m.SchemaVisualizer })));\nconst BranchesView = lazy(() => import(\"./Branches/BranchesView\").then(m => ({ default: m.BranchesView })));\nconst BackupsView = lazy(() => import(\"./Backups/BackupsView\").then(m => ({ default: m.BackupsView })));\nconst ApiExplorer = lazy(() => import(\"./ApiExplorer/ApiExplorer\").then(m => ({ default: m.ApiExplorer })));\nconst LogsExplorer = lazy(() => import(\"./LogsExplorer/LogsExplorer\").then(m => ({ default: m.LogsExplorer })));\nconst ApiKeysView = lazy(() => import(\"./ApiKeys/ApiKeysView\").then(m => ({ default: m.ApiKeysView })));\n\nimport { StudioHomePage } from \"./StudioHomePage\";\n\n/**\n * Declarative component to configure the Studio in Rebase.\n * Renders nothing — purely registers config into the RebaseRegistry.\n *\n * The \"schema\" tool (collection editor view) is now a built-in admin feature.\n * When `<RebaseAdmin collectionEditor={...}>` is used, the schema view is\n * automatically injected into Studio — no manual wiring needed.\n */\nconst DEFAULT_HOME_PAGE = <StudioHomePage/>;\n\nexport function RebaseStudio({ tools, homePage }: RebaseStudioConfig) {\n const dispatch = useRebaseRegistryDispatch();\n\n const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;\n\n /**\n * The tool list reduced to a value, so that re-rendering the host does not\n * republish the registry.\n *\n * `tools` is an array, and callers write it inline — `tools={[...TOOLS]}`\n * in the hosted console. A fresh array every render meant a fresh\n * `devViews` every render, which meant the effect below unregistered and\n * re-registered on every render. Because each registration builds *new*\n * `<Suspense><SQLEditor/></Suspense>` elements, whatever view was on screen\n * was torn down and remounted each time: a half-typed SQL query, a storage\n * folder you had navigated into, an open policy editor — all discarded, and\n * every mount reissued the view's initial requests.\n */\n const toolsKey = tools ? tools.join(\",\") : \"\";\n\n const devViews: AppView[] = useMemo(() => {\n const views: AppView[] = [];\n const activeTools = tools ?? [\"sql\", \"js\", \"rls\", \"storage\", \"cron\", \"schema-visualizer\", \"branches\", \"backups\", \"api\", \"logs\", \"api-keys\"];\n const suspense = (el: React.ReactNode) => <Suspense fallback={<CircularProgressCenter/>}>{el}</Suspense>;\n\n if (activeTools.includes(\"sql\")) {\n views.push({ slug: \"sql\",\nname: \"SQL Console\",\ngroup: \"Database\",\nicon: \"terminal\",\ndescription: \"Execute SQL queries\",\nview: suspense(<SQLEditor/>) });\n }\n if (activeTools.includes(\"js\")) {\n views.push({ slug: \"js\",\nname: \"JS Console\",\ngroup: \"Compute\",\nicon: \"code\",\ndescription: \"Execute JavaScript\",\nview: suspense(<JSEditor/>) });\n }\n if (activeTools.includes(\"rls\")) {\n views.push({ slug: \"rls\",\nname: \"RLS Policies\",\ngroup: \"Database\",\nicon: \"ShieldCheck\",\ndescription: \"Row Level Security\",\nview: suspense(<RLSEditor/>) });\n }\n if (activeTools.includes(\"storage\")) {\n views.push({ slug: \"storage\",\nname: \"Storage\",\ngroup: \"Storage\",\nicon: \"HardDrive\",\ndescription: \"Manage storage files\",\nview: suspense(<StorageView/>) });\n }\n if (activeTools.includes(\"cron\")) {\n views.push({ slug: \"cron\",\nname: \"Cron Jobs\",\ngroup: \"Compute\",\nicon: \"Clock\",\ndescription: \"Manage scheduled tasks\",\nview: suspense(<CronJobsView/>) });\n }\n if (activeTools.includes(\"schema-visualizer\")) {\n views.push({ slug: \"schema-visualizer\",\nname: \"Schema Visualizer\",\ngroup: \"Database\",\nicon: \"Network\",\ndescription: \"Interactive database ERD\",\nview: suspense(<SchemaVisualizer/>) });\n }\n if (activeTools.includes(\"branches\")) {\n views.push({ slug: \"branches\",\nname: \"Branches\",\ngroup: \"Database\",\nicon: \"GitBranch\",\ndescription: \"Create and manage database branches\",\nview: suspense(<BranchesView/>) });\n }\n if (activeTools.includes(\"backups\")) {\n views.push({ slug: \"backups\",\nname: \"Backups\",\ngroup: \"Database\",\nicon: \"Database\",\ndescription: \"Download database backups\",\nview: suspense(<BackupsView/>) });\n }\n if (activeTools.includes(\"api\")) {\n views.push({ slug: \"api\",\nname: \"API Explorer\",\ngroup: \"API\",\nicon: \"BookOpen\",\ndescription: \"Interactive API documentation and testing\",\nview: suspense(<ApiExplorer/>) });\n }\n if (activeTools.includes(\"logs\")) {\n views.push({ slug: \"logs\",\nname: \"Logs Explorer\",\ngroup: \"Database\",\nicon: \"Activity\",\ndescription: \"Real-time system and query logs\",\nview: suspense(<LogsExplorer/>) });\n }\n if (activeTools.includes(\"api-keys\")) {\n views.push({ slug: \"api-keys\",\nname: \"API Keys\",\ngroup: \"Access Control\",\nicon: \"KeyRound\",\ndescription: \"Create and manage scoped API keys\",\nview: suspense(<ApiKeysView/>) });\n }\n // Note: \"schema\" tool is auto-injected by RebaseShell when collectionEditor is enabled.\n // It is NOT registered here anymore.\n return views;\n // Keyed by the tool list's *contents*; see `toolsKey`. `tools` itself is\n // read inside, and is exactly determined by the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [toolsKey]);\n\n // Use a ref for homePage so it never destabilizes the effect.\n // homePage is a React element — its identity doesn't matter for registration.\n const homePageRef = React.useRef(resolvedHomePage);\n homePageRef.current = resolvedHomePage;\n\n // Same reasoning for `tools`: it is registered as config, but a new array\n // with the same contents must not count as a change.\n const toolsRef = React.useRef(tools);\n toolsRef.current = tools;\n\n useLayoutEffect(() => {\n dispatch.registerStudio({ tools: toolsRef.current,\nhomePage: homePageRef.current,\ndevViews });\n return () => dispatch.unregisterStudio();\n }, [dispatch, devViews]);\n\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/components/StudioHomePage.tsx","../src/components/RebaseStudio.tsx"],"sourcesContent":["import type { AppView, HomePageSection, PluginGenericProps } from \"@rebasepro/admin-types\";\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { Card, cls, Container, ExpandablePanel, Typography } from \"@rebasepro/ui\";\nimport { IconForView, useRebaseContext, useRebaseRegistry, useRestoreScroll, useSlot } from \"@rebasepro/app\";\nimport { useNavigate } from \"react-router\";\nimport { useStudioBreadcrumbs, SchemaDriftBanner } from \"@rebasepro/app\";\n\n/* ═══════════════════════════════════════════════════════════════\n Studio tool sections, derived from the registered Studio views\n ═══════════════════════════════════════════════════════════════ */\n\ninterface StudioTool {\n path: string;\n name: string;\n description: string;\n icon?: string | React.ReactNode;\n}\n\ninterface StudioSection {\n label: string;\n tools: StudioTool[];\n}\n\n/** The view metadata the cards need — not the rendered `view` itself. */\ntype StudioViewMeta = Pick<AppView, \"slug\" | \"name\" | \"group\" | \"description\" | \"icon\" | \"hideFromNavigation\">;\n\n/** Group order for the home page; unknown groups are appended in view order. */\nconst GROUP_ORDER = [\"Database\", \"Compute\", \"Storage\", \"API\", \"Access Control\"];\n\nconst UNGROUPED_LABEL = \"Tools\";\n\n/**\n * Build the home page sections from the Studio views that are actually\n * registered, so a card exists if and only if its route does. A hand-written\n * list drifts: it used to advertise Users and Roles pages that 404, while\n * omitting the Backups view that does exist.\n */\nfunction buildSections(views: StudioViewMeta[]): StudioSection[] {\n const byGroup = new Map<string, StudioTool[]>();\n\n for (const view of views) {\n if (view.hideFromNavigation) continue;\n const label = view.group ?? UNGROUPED_LABEL;\n const tools = byGroup.get(label) ?? [];\n tools.push({\n path: `/${view.slug}`,\n name: view.name,\n description: view.description ?? \"\",\n icon: view.icon\n });\n byGroup.set(label, tools);\n }\n\n const ordered = [\n ...GROUP_ORDER.filter(g => byGroup.has(g)),\n ...[...byGroup.keys()].filter(g => !GROUP_ORDER.includes(g))\n ];\n\n return ordered.map(label => ({ label,\ntools: byGroup.get(label)! }));\n}\n\n/* ═══════════════════════════════════════════════════════════════ */\n\nconst COLLAPSED_STORAGE_KEY = \"rebase-studio-home-collapsed\";\n\nfunction useStudioCollapsedGroups(groupNames: string[]) {\n const [collapsed, setCollapsed] = useState<Set<string>>(() => {\n try {\n const stored = localStorage.getItem(COLLAPSED_STORAGE_KEY);\n return stored ? new Set(JSON.parse(stored)) : new Set<string>();\n } catch {\n return new Set<string>();\n }\n });\n\n const isGroupCollapsed = (name: string) => collapsed.has(name);\n\n const toggleGroupCollapsed = (name: string) => {\n setCollapsed(prev => {\n const next = new Set(prev);\n if (next.has(name)) {\n next.delete(name);\n } else {\n next.add(name);\n }\n try {\n localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...next]));\n } catch { /* noop */ }\n return next;\n });\n };\n\n return { isGroupCollapsed, toggleGroupCollapsed };\n}\n\nexport function StudioHomePage({\n additionalActions,\n additionalChildrenStart,\n additionalChildrenEnd,\n sections,\n hiddenGroups\n}: {\n additionalActions?: React.ReactNode;\n additionalChildrenStart?: React.ReactNode;\n additionalChildrenEnd?: React.ReactNode;\n sections?: HomePageSection[];\n hiddenGroups?: string[];\n}) {\n const context = useRebaseContext();\n const breadcrumbs = useStudioBreadcrumbs();\n const navigate = useNavigate();\n const registry = useRebaseRegistry();\n\n useEffect(() => {\n breadcrumbs.set({ breadcrumbs: [] });\n }, [breadcrumbs.set]);\n\n const { containerRef } = useRestoreScroll();\n\n const sectionProps: PluginGenericProps = { context };\n\n const pluginActions = useSlot(\"home.actions\", sectionProps);\n\n // The collection editor (\"schema\") is not part of `devViews` — RebaseNavigation\n // injects it when the admin enables a collection editor. Mirror that condition so\n // the card tracks the route.\n const schemaEnabled = Boolean(registry.studioConfig && registry.cmsConfig?.collectionEditor);\n\n const filteredSections = useMemo(() => {\n const views: StudioViewMeta[] = [];\n if (schemaEnabled) {\n views.push({\n slug: \"schema\",\n name: \"Collections\",\n group: \"Database\",\n icon: \"LayoutList\",\n description: \"Define and manage your data model and collection schemas\"\n });\n }\n views.push(...(registry.studioConfig?.devViews ?? []));\n return buildSections(views).filter(s => !hiddenGroups?.includes(s.label));\n }, [registry.studioConfig?.devViews, schemaEnabled, hiddenGroups]);\n\n const groupNames = useMemo(\n () => filteredSections.map(s => s.label),\n [filteredSections]\n );\n\n const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups(groupNames);\n\n return (\n <div ref={containerRef} className=\"py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800\">\n <Container maxWidth=\"6xl\">\n <div className=\"mb-4 flex flex-col gap-2\">\n <SchemaDriftBanner />\n </div>\n\n {(additionalActions || pluginActions) && (\n <div className=\"w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4 justify-end\">\n {additionalActions}\n {pluginActions}\n </div>\n )}\n\n {additionalChildrenStart}\n\n {/* ── Tool sections ── */}\n {filteredSections.map((section) => {\n const sectionCollapsed = isGroupCollapsed(section.label);\n\n return (\n <div key={section.label} className=\"my-10\">\n <ExpandablePanel\n invisible\n expanded={!sectionCollapsed}\n onExpandedChange={(open) => {\n if (open !== !sectionCollapsed) {\n toggleGroupCollapsed(section.label);\n }\n }}\n className=\"mt-6\"\n titleClassName={cls(\n \"min-h-0 p-0 border-none\",\n \"rounded flex items-center justify-between w-full\",\n \"hover:bg-transparent\",\n \"cursor-pointer select-none\",\n sectionCollapsed && \"bg-surface-100 dark:bg-surface-900/50\"\n )}\n innerClassName=\"mt-4 pt-0\"\n title={\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {section.label}\n </Typography>\n }\n >\n <div className=\"mt-4 pt-0\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {section.tools.map((tool) => (\n <Card\n key={tool.path}\n onClick={() => {\n navigate(tool.path);\n context.analyticsController?.onAnalyticsEvent?.(\n \"home_navigate_to_view\",\n { path: tool.path }\n );\n }}\n className={cls(\n \"group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out\",\n \"hover:bg-primary/5 dark:hover:bg-primary/5\"\n )}\n >\n <div className=\"flex flex-col h-full\">\n {/* Header: icon + title */}\n <div className=\"flex items-center w-full justify-between mb-1\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary\">\n <IconForView\n collectionOrView={{ slug: tool.path, name: tool.name, icon: tool.icon }}\n size=\"small\"\n />\n </div>\n <Typography variant=\"subtitle1\" component=\"h2\">\n {tool.name}\n </Typography>\n </div>\n </div>\n\n {/* Description indented to align with title */}\n <div className=\"pl-8\">\n {tool.description && (\n <Typography variant=\"caption\" color=\"secondary\" component=\"div\">\n {tool.description}\n </Typography>\n )}\n </div>\n\n {/* Spacer */}\n <div className=\"grow\"/>\n </div>\n </Card>\n ))}\n </div>\n </div>\n </ExpandablePanel>\n </div>\n );\n })}\n\n {/* ── SDK Quick Start ── */}\n <div className=\"mt-10 mb-6\">\n <div className=\"flex items-center mb-1\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n Quick Start\n </Typography>\n </div>\n\n <Typography variant=\"body2\" color=\"secondary\" className=\"mb-4 max-w-2xl\">\n Generate a fully-typed SDK from your collections with{\" \"}\n <code className=\"text-emerald-400 font-mono text-xs bg-emerald-400/10 px-1.5 py-0.5 rounded\">\n npx rebase generate-sdk\n </code>\n {\" \"}and start querying your data with full TypeScript autocompletion.\n </Typography>\n\n <div className=\"rounded-lg border border-surface-200/40 dark:border-surface-700/40 bg-white dark:bg-surface-950 overflow-hidden\">\n {/* Title bar */}\n <div className=\"flex items-center justify-between px-4 py-2.5 border-b border-surface-200/40 dark:border-surface-700/40 bg-surface-50 dark:bg-surface-900/80\">\n <div className=\"flex items-center gap-2.5\">\n <div className=\"flex gap-1.5\">\n <span className=\"w-2.5 h-2.5 rounded-full bg-red-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-amber-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-emerald-400/60\"/>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500 ml-1\">\n app.ts\n </span>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500\">\n TypeScript\n </span>\n </div>\n\n {/* Syntax-highlighted code */}\n <div className=\"px-5 py-4 overflow-x-auto text-[13px] leading-6 font-mono\">\n <SyntaxHighlightedSnippet/>\n </div>\n </div>\n </div>\n\n {/* ── Extra sections from props ── */}\n {sections?.map((s) => (\n <div key={s.key} className=\"my-10\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {s.title}\n </Typography>\n <div className=\"mt-4\">{s.children}</div>\n </div>\n ))}\n\n {additionalChildrenEnd}\n </Container>\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Hand-crafted syntax-highlighted code snippet.\n Uses inline spans with Tailwind color classes to avoid\n pulling in a syntax highlighting library.\n ═══════════════════════════════════════════════════════════════ */\n\nfunction SyntaxHighlightedSnippet() {\n const kw = \"text-violet-600 dark:text-violet-400\"; // keywords\n const str = \"text-emerald-600 dark:text-emerald-400\"; // strings\n const typ = \"text-amber-600 dark:text-amber-300\"; // types\n const fn = \"text-blue-600 dark:text-blue-400\"; // functions\n const cm = \"text-surface-500 dark:text-surface-400 italic\"; // comments\n const op = \"text-surface-500 dark:text-surface-400\"; // operators / punctuation\n const tx = \"text-surface-950 dark:text-surface-200\"; // plain text\n\n return (\n <pre className=\"m-0 whitespace-pre\">\n <span className={kw}>import</span>\n <span className={tx}>{\" { \"}</span>\n <span className={fn}>createRebaseClient</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>&apos;@rebasepro/client&apos;</span>\n <span className={op}>;</span>\n {\"\\n\"}\n\n <span className={kw}>import</span>\n <span className={tx}> </span>\n <span className={kw}>type</span>\n <span className={tx}>{\" { \"}</span>\n <span className={typ}>Database</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>&apos;./database.types&apos;</span>\n <span className={op}>;</span>\n {\"\\n\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}> rebase </span>\n <span className={op}>= </span>\n <span className={fn}>createRebaseClient</span>\n <span className={op}>{\"<\"}</span>\n <span className={typ}>Database</span>\n <span className={op}>{\">(\"}</span>\n <span className={tx}>{\"{\"}</span>\n {\"\\n\"}\n <span className={tx}>{\" baseUrl\"}</span>\n <span className={op}>: </span>\n <span className={str}>&apos;http://localhost:3001&apos;</span>\n <span className={op}>,</span>\n {\"\\n\"}\n <span className={tx}>{\"}\"}</span>\n <span className={op}>);</span>\n {\"\\n\\n\"}\n\n <span className={cm}>{\"// Fully typed — autocompletion for tables and columns\"}</span>\n {\"\\n\"}\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>users</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={tx}>users</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n {\"\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>posts</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={fn}>collection</span>\n <span className={op}>(</span>\n <span className={str}>&apos;posts&apos;</span>\n <span className={op}>)</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n </pre>\n );\n}\n","import React, { Suspense, useLayoutEffect, useMemo } from \"react\";\nimport { useRebaseRegistryDispatch } from \"@rebasepro/app\";\nimport type { RebaseStudioConfig, AppView } from \"@rebasepro/admin-types\";\nimport { CircularProgressCenter, lazyChunk } from \"@rebasepro/ui\";\n\n// Lazy-loaded studio tools — each fetched only when its route is visited.\n// This keeps Monaco, @xyflow/react, dagre, pgsql-ast-parser etc. out of the initial bundle.\nconst SQLEditor = lazyChunk(() => import(\"./SQLEditor/SQLEditor\").then(m => ({ default: m.SQLEditor })));\nconst JSEditor = lazyChunk(() => import(\"./JSEditor/JSEditor\").then(m => ({ default: m.JSEditor })));\nconst RLSEditor = lazyChunk(() => import(\"./RLSEditor/RLSEditor\").then(m => ({ default: m.RLSEditor })));\nconst StorageView = lazyChunk(() => import(\"./StorageView/StorageView\").then(m => ({ default: m.StorageView })));\nconst CronJobsView = lazyChunk(() => import(\"./CronJobs/CronJobsView\").then(m => ({ default: m.CronJobsView })));\nconst SchemaVisualizer = lazyChunk(() => import(\"./SchemaVisualizer/SchemaVisualizer\").then(m => ({ default: m.SchemaVisualizer })));\nconst BranchesView = lazyChunk(() => import(\"./Branches/BranchesView\").then(m => ({ default: m.BranchesView })));\nconst BackupsView = lazyChunk(() => import(\"./Backups/BackupsView\").then(m => ({ default: m.BackupsView })));\nconst ApiExplorer = lazyChunk(() => import(\"./ApiExplorer/ApiExplorer\").then(m => ({ default: m.ApiExplorer })));\nconst LogsExplorer = lazyChunk(() => import(\"./LogsExplorer/LogsExplorer\").then(m => ({ default: m.LogsExplorer })));\nconst ApiKeysView = lazyChunk(() => import(\"./ApiKeys/ApiKeysView\").then(m => ({ default: m.ApiKeysView })));\n\nimport { StudioHomePage } from \"./StudioHomePage\";\n\n/**\n * Declarative component to configure the Studio in Rebase.\n * Renders nothing — purely registers config into the RebaseRegistry.\n *\n * The \"schema\" tool (collection editor view) is now a built-in admin feature.\n * When `<RebaseAdmin collectionEditor={...}>` is used, the schema view is\n * automatically injected into Studio — no manual wiring needed.\n */\nconst DEFAULT_HOME_PAGE = <StudioHomePage/>;\n\nexport function RebaseStudio({ tools, homePage }: RebaseStudioConfig) {\n const dispatch = useRebaseRegistryDispatch();\n\n const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;\n\n /**\n * The tool list reduced to a value, so that re-rendering the host does not\n * republish the registry.\n *\n * `tools` is an array, and callers write it inline — `tools={[...TOOLS]}`\n * in the hosted console. A fresh array every render meant a fresh\n * `devViews` every render, which meant the effect below unregistered and\n * re-registered on every render. Because each registration builds *new*\n * `<Suspense><SQLEditor/></Suspense>` elements, whatever view was on screen\n * was torn down and remounted each time: a half-typed SQL query, a storage\n * folder you had navigated into, an open policy editor — all discarded, and\n * every mount reissued the view's initial requests.\n */\n const toolsKey = tools ? tools.join(\",\") : \"\";\n\n const devViews: AppView[] = useMemo(() => {\n const views: AppView[] = [];\n const activeTools = tools ?? [\"sql\", \"js\", \"rls\", \"storage\", \"cron\", \"schema-visualizer\", \"branches\", \"backups\", \"api\", \"logs\", \"api-keys\"];\n const suspense = (el: React.ReactNode) => <Suspense fallback={<CircularProgressCenter/>}>{el}</Suspense>;\n\n if (activeTools.includes(\"sql\")) {\n views.push({ slug: \"sql\",\nname: \"SQL Console\",\ngroup: \"Database\",\nicon: \"terminal\",\ndescription: \"Execute SQL queries\",\nview: suspense(<SQLEditor/>) });\n }\n if (activeTools.includes(\"js\")) {\n views.push({ slug: \"js\",\nname: \"JS Console\",\ngroup: \"Compute\",\nicon: \"code\",\ndescription: \"Execute JavaScript\",\nview: suspense(<JSEditor/>) });\n }\n if (activeTools.includes(\"rls\")) {\n views.push({ slug: \"rls\",\nname: \"RLS Policies\",\ngroup: \"Database\",\nicon: \"ShieldCheck\",\ndescription: \"Row Level Security\",\nview: suspense(<RLSEditor/>) });\n }\n if (activeTools.includes(\"storage\")) {\n views.push({ slug: \"storage\",\nname: \"Storage\",\ngroup: \"Storage\",\nicon: \"HardDrive\",\ndescription: \"Manage storage files\",\nview: suspense(<StorageView/>) });\n }\n if (activeTools.includes(\"cron\")) {\n views.push({ slug: \"cron\",\nname: \"Cron Jobs\",\ngroup: \"Compute\",\nicon: \"Clock\",\ndescription: \"Manage scheduled tasks\",\nview: suspense(<CronJobsView/>) });\n }\n if (activeTools.includes(\"schema-visualizer\")) {\n views.push({ slug: \"schema-visualizer\",\nname: \"Schema Visualizer\",\ngroup: \"Database\",\nicon: \"Network\",\ndescription: \"Interactive database ERD\",\nview: suspense(<SchemaVisualizer/>) });\n }\n if (activeTools.includes(\"branches\")) {\n views.push({ slug: \"branches\",\nname: \"Branches\",\ngroup: \"Database\",\nicon: \"GitBranch\",\ndescription: \"Create and manage database branches\",\nview: suspense(<BranchesView/>) });\n }\n if (activeTools.includes(\"backups\")) {\n views.push({ slug: \"backups\",\nname: \"Backups\",\ngroup: \"Database\",\nicon: \"Database\",\ndescription: \"Download database backups\",\nview: suspense(<BackupsView/>) });\n }\n if (activeTools.includes(\"api\")) {\n views.push({ slug: \"api\",\nname: \"API Explorer\",\ngroup: \"API\",\nicon: \"BookOpen\",\ndescription: \"Interactive API documentation and testing\",\nview: suspense(<ApiExplorer/>) });\n }\n if (activeTools.includes(\"logs\")) {\n views.push({ slug: \"logs\",\nname: \"Logs Explorer\",\ngroup: \"Database\",\nicon: \"Activity\",\ndescription: \"Real-time system and query logs\",\nview: suspense(<LogsExplorer/>) });\n }\n if (activeTools.includes(\"api-keys\")) {\n views.push({ slug: \"api-keys\",\nname: \"API Keys\",\ngroup: \"Access Control\",\nicon: \"KeyRound\",\ndescription: \"Create and manage scoped API keys\",\nview: suspense(<ApiKeysView/>) });\n }\n // Note: \"schema\" tool is auto-injected by RebaseShell when collectionEditor is enabled.\n // It is NOT registered here anymore.\n return views;\n // Keyed by the tool list's *contents*; see `toolsKey`. `tools` itself is\n // read inside, and is exactly determined by the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [toolsKey]);\n\n // Use a ref for homePage so it never destabilizes the effect.\n // homePage is a React element — its identity doesn't matter for registration.\n const homePageRef = React.useRef(resolvedHomePage);\n homePageRef.current = resolvedHomePage;\n\n // Same reasoning for `tools`: it is registered as config, but a new array\n // with the same contents must not count as a change.\n const toolsRef = React.useRef(tools);\n toolsRef.current = tools;\n\n useLayoutEffect(() => {\n dispatch.registerStudio({ tools: toolsRef.current,\nhomePage: homePageRef.current,\ndevViews });\n return () => dispatch.unregisterStudio();\n }, [dispatch, devViews]);\n\n return null;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/studio",
3
3
  "type": "module",
4
- "version": "0.14.1",
4
+ "version": "0.14.2-canary.g27a129e",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.es.js",
7
7
  "module": "./dist/index.es.js",
@@ -16,19 +16,19 @@
16
16
  "pgsql-ast-parser": "12.0.2",
17
17
  "prism-react-renderer": "^2.4.1",
18
18
  "react-dropzone": "^19.1.1",
19
- "@rebasepro/admin-types": "0.14.1",
20
- "@rebasepro/common": "0.14.1",
21
- "@rebasepro/client": "0.14.1",
22
- "@rebasepro/types": "0.14.1",
23
- "@rebasepro/app": "0.14.1",
24
- "@rebasepro/utils": "0.14.1",
25
- "@rebasepro/ui": "0.14.1"
19
+ "@rebasepro/admin-types": "0.14.2-canary.g27a129e",
20
+ "@rebasepro/types": "0.14.2-canary.g27a129e",
21
+ "@rebasepro/app": "0.14.2-canary.g27a129e",
22
+ "@rebasepro/common": "0.14.2-canary.g27a129e",
23
+ "@rebasepro/ui": "0.14.2-canary.g27a129e",
24
+ "@rebasepro/utils": "0.14.2-canary.g27a129e",
25
+ "@rebasepro/client": "0.14.2-canary.g27a129e"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=19.2.7",
29
29
  "react-dom": ">=19.2.7",
30
30
  "react-router": "^8.3.0",
31
- "@rebasepro/admin": "0.14.1"
31
+ "@rebasepro/admin": "0.14.2-canary.g27a129e"
32
32
  },
33
33
  "peerDependenciesMeta": {
34
34
  "@rebasepro/admin": {
@@ -1,15 +1,7 @@
1
1
  import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
2
  import { ArrowDownToLineIcon, Checkbox, cls, defaultBorderMixin, Label, Select, SelectItem, TextField, Typography } from "@rebasepro/ui";
3
- import { useApiBase, useApiConfig } from "@rebasepro/app";
4
-
5
- interface LogEntry {
6
- id: string;
7
- timestamp: string;
8
- level: "debug" | "info" | "warn" | "error";
9
- source: "api" | "auth" | "storage" | "realtime" | "system";
10
- message: string;
11
- metadata?: Record<string, unknown>;
12
- }
3
+ import { idNum, useLogTail } from "./useLogTail";
4
+ import type { LogTransport } from "./useLogTail";
13
5
 
14
6
  const LEVEL_COLORS: Record<string, string> = {
15
7
  debug: "text-surface-500",
@@ -26,129 +18,70 @@ const SOURCE_COLORS: Record<string, string> = {
26
18
  system: "text-surface-600 dark:text-surface-400"
27
19
  };
28
20
 
29
- // Ids are `log_<n>` with a monotonic counter, so numeric comparison tells
30
- // old entries from new ones across polls.
31
- const idNum = (entry: LogEntry): number => Number(entry.id.slice("log_".length));
21
+ const TRANSPORT_LABELS: Record<LogTransport, string> = {
22
+ connecting: "Connecting",
23
+ live: "Live",
24
+ polling: "Polling"
25
+ };
32
26
 
33
- // The window is contiguous and ordered, so identical ends mean identical content.
34
- const sameLogs = (a: LogEntry[], b: LogEntry[]): boolean =>
35
- a.length === b.length &&
36
- a[0]?.id === b[0]?.id &&
37
- a[a.length - 1]?.id === b[b.length - 1]?.id;
27
+ const TRANSPORT_DOTS: Record<LogTransport, string> = {
28
+ connecting: "bg-amber-500",
29
+ live: "bg-green-500",
30
+ polling: "bg-surface-400"
31
+ };
32
+
33
+ const TRANSPORT_TITLES: Record<LogTransport, string> = {
34
+ connecting: "Reconnecting to the log stream.",
35
+ live: "Streaming — entries appear as the server writes them.",
36
+ polling: "This server has no log stream; falling back to a refresh every 3 seconds."
37
+ };
38
38
 
39
39
  // How close to the bottom edge still counts as "at the bottom".
40
40
  const STICK_THRESHOLD = 40;
41
41
 
42
42
  export function LogsExplorer() {
43
- const [logs, setLogs] = useState<LogEntry[]>([]);
44
43
  const [level, setLevel] = useState<string>("all");
45
44
  const [source, setSource] = useState<string>("all");
46
45
  const [searchInput, setSearchInput] = useState("");
47
46
  const [search, setSearch] = useState("");
48
47
  const [autoScroll, setAutoScroll] = useState(true);
49
- const [error, setError] = useState<string | null>(null);
50
48
  // Entries that arrived while the user was scrolled away from the bottom.
51
49
  const [newCount, setNewCount] = useState(0);
52
50
  const [atBottom, setAtBottom] = useState(true);
53
51
  const containerRef = useRef<HTMLDivElement>(null);
54
52
  // Whether the view is stuck to the bottom right now. A ref, not state:
55
- // the scroll handler and the fetch loop both read it synchronously.
53
+ // the scroll handler and the entry-counting effect both read it synchronously.
56
54
  const stickRef = useRef(true);
57
55
  const lastMaxIdRef = useRef<number | null>(null);
58
- const apiConfig = useApiConfig();
59
- const apiBase = useApiBase();
60
56
 
61
- // Debounce the search box so typing doesn't refetch per keystroke.
57
+ // Debounce the search box so typing doesn't reopen the stream per keystroke.
62
58
  useEffect(() => {
63
59
  const t = setTimeout(() => setSearch(searchInput), 300);
64
60
  return () => clearTimeout(t);
65
61
  }, [searchInput]);
66
62
 
67
- const fetchLogs = useCallback(async () => {
68
- if (!apiConfig?.apiUrl) {
69
- setError("No API URL configured — cannot load logs.");
70
- return;
71
- }
72
- try {
73
- const params = new URLSearchParams();
74
- if (level && level !== "all") params.set("level", level);
75
- if (source && source !== "all") params.set("source", source);
76
- if (search) params.set("search", search);
77
- params.set("limit", "200");
78
-
79
- // Logs are admin-only, so the request must carry the auth token. The
80
- // URL is absolute: a relative one would resolve against the frontend
81
- // origin, which serves index.html rather than the API.
82
- const headers: Record<string, string> = {};
83
- const token = apiConfig.getAuthToken ? await apiConfig.getAuthToken() : null;
84
- if (token) headers["Authorization"] = `Bearer ${token}`;
85
-
86
- const resp = await fetch(`${apiBase}/logs?${params}`, { headers });
87
- if (!resp.ok) {
88
- setError(resp.status === 401 || resp.status === 403
89
- ? "Not authorised to read logs — an admin role is required."
90
- : `Could not load logs (HTTP ${resp.status}).`);
91
- return;
92
- }
93
- const data: { entries?: LogEntry[] } = await resp.json();
94
- // The API returns newest-first; the view tails like a terminal, so
95
- // flip to chronological order (newest at the bottom).
96
- const entries = (data.entries || []).slice().reverse();
63
+ const { logs, error, transport, dropped } = useLogTail({ level,
64
+ source,
65
+ search });
97
66
 
98
- const prevMax = lastMaxIdRef.current;
99
- if (prevMax != null && !stickRef.current) {
100
- const fresh = entries.filter(e => idNum(e) > prevMax).length;
101
- if (fresh > 0) setNewCount(c => c + fresh);
102
- }
103
- if (entries.length > 0) lastMaxIdRef.current = idNum(entries[entries.length - 1]);
104
-
105
- // Unchanged window → keep the previous array so React leaves the
106
- // DOM alone (hover states and text selection survive the poll).
107
- setLogs(prev => sameLogs(prev, entries) ? prev : entries);
108
- setError(null);
109
- } catch (e) {
110
- setError(e instanceof Error ? e.message : "Could not load logs.");
111
- }
112
- }, [level, source, search, apiConfig, apiBase]);
113
-
114
- // A filter change replaces the window wholesale — "new entries since last
115
- // poll" stops meaning anything, so the counter starts over.
67
+ // A filter change replaces the window wholesale — "new since you looked
68
+ // away" stops meaning anything, so the counter starts over.
116
69
  useEffect(() => {
117
- lastMaxIdRef.current = null;
118
70
  setNewCount(0);
119
71
  }, [level, source, search]);
120
72
 
73
+ // Count what arrived while the user was reading further up. Driven off the
74
+ // rendered window rather than off the transport, so it means the same thing
75
+ // whether the entries were streamed or polled.
121
76
  useEffect(() => {
122
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
123
- let cancelled = false;
124
-
125
- fetchLogs();
126
-
127
- const scheduleNext = () => {
128
- if (cancelled) return;
129
- timeoutId = setTimeout(async () => {
130
- if (document.visibilityState === "visible") {
131
- await fetchLogs();
132
- }
133
- scheduleNext();
134
- }, 3000);
135
- };
136
-
137
- scheduleNext();
138
-
139
- const handleVisibility = () => {
140
- if (document.visibilityState === "visible") {
141
- fetchLogs();
142
- }
143
- };
144
- document.addEventListener("visibilitychange", handleVisibility);
145
-
146
- return () => {
147
- cancelled = true;
148
- if (timeoutId) clearTimeout(timeoutId);
149
- document.removeEventListener("visibilitychange", handleVisibility);
150
- };
151
- }, [fetchLogs]);
77
+ const max = logs.length > 0 ? idNum(logs[logs.length - 1]) : null;
78
+ const prevMax = lastMaxIdRef.current;
79
+ if (prevMax != null && !stickRef.current) {
80
+ const fresh = logs.filter(e => idNum(e) > prevMax).length;
81
+ if (fresh > 0) setNewCount(c => c + fresh);
82
+ }
83
+ lastMaxIdRef.current = max;
84
+ }, [logs]);
152
85
 
153
86
  const scrollToBottom = useCallback(() => {
154
87
  const el = containerRef.current;
@@ -236,14 +169,26 @@ export function LogsExplorer() {
236
169
  Auto-scroll
237
170
  </Label>
238
171
  </div>
239
- <div className="ml-auto pl-4">
172
+ {/* Whether the tail is actually attached. A log view with
173
+ nothing in it is ambiguous — quiet server, or a stream that
174
+ dropped — and this is the difference. */}
175
+ <div className="ml-auto pl-4 flex items-center gap-3">
176
+ <div className="flex items-center gap-1.5" title={TRANSPORT_TITLES[transport]}>
177
+ <span
178
+ aria-hidden
179
+ className={cls("w-1.5 h-1.5 rounded-full shrink-0", TRANSPORT_DOTS[transport])}
180
+ />
181
+ <Typography variant="caption" color="secondary">
182
+ {TRANSPORT_LABELS[transport]}
183
+ </Typography>
184
+ </div>
240
185
  <Typography variant="caption" color="secondary">
241
186
  {logs.length} entries
242
187
  </Typography>
243
188
  </div>
244
189
  </div>
245
190
 
246
- {/* A failing poll must stay visible even while stale logs are on
191
+ {/* A broken tail must stay visible even while stale logs are on
247
192
  screen — otherwise the view quietly freezes. */}
248
193
  {error && logs.length > 0 && (
249
194
  <div className={cls(
@@ -256,6 +201,21 @@ export function LogsExplorer() {
256
201
  </div>
257
202
  )}
258
203
 
204
+ {/* The server bounds what it holds per connection, so a burst big
205
+ enough leaves a hole in what is on screen. Say so: a tail that
206
+ silently skips entries is worse than one that admits it. */}
207
+ {dropped > 0 && (
208
+ <div className={cls(
209
+ "px-4 py-1.5 border-b bg-amber-50 dark:bg-amber-950/20 shrink-0",
210
+ defaultBorderMixin
211
+ )}>
212
+ <Typography variant="caption" className="text-amber-700 dark:text-amber-400">
213
+ {dropped} {dropped === 1 ? "entry was" : "entries were"} dropped — the log is
214
+ arriving faster than this view can read it. Narrow the filter to keep up.
215
+ </Typography>
216
+ </div>
217
+ )}
218
+
259
219
  {/* Log entries */}
260
220
  <div className="relative flex-1 min-h-0">
261
221
  <div