Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
17b8949
Upgraded Kumo to version 1.9.0
NuroDev Mar 4, 2026
df924b4
Added `immer` dependency
NuroDev Mar 4, 2026
b82e242
Added WIP schema editor
NuroDev Mar 4, 2026
0af0cd6
Minor modal refactoring
NuroDev Mar 4, 2026
dd5bdc2
Upgraded to experimental version of `@cloudflare/kumo`
NuroDev Mar 4, 2026
a638764
Revert "Minor modal refactoring"
NuroDev Mar 4, 2026
8227411
Added placeholder header UI
NuroDev Mar 4, 2026
e36a71d
Added initial working table options navigation
NuroDev Mar 4, 2026
ee3038f
Minor JSDoc improvement
NuroDev Mar 4, 2026
ad67d08
Added logic to open table creation tab
NuroDev Mar 4, 2026
3f17504
Fixed split pane border color
NuroDev Mar 4, 2026
cd65771
Fixed schema editor drawer not opening
NuroDev Mar 4, 2026
ada50d9
Replaced "Add column" drawer with dialog
NuroDev Mar 4, 2026
c27c190
Downgrade Kumo & Base UI dependency versions
NuroDev Mar 4, 2026
13edd3a
Restore Kumo select component when adding columns
NuroDev Mar 4, 2026
bf09de0
Fix tab closure after deleting a table
NuroDev Mar 4, 2026
bb10a50
Minor drop table border color fix
NuroDev Mar 4, 2026
55401ca
Select next available tab after closing deleted table tabs
NuroDev Mar 4, 2026
7df4899
Merge branch 'main' into workers-sdk-local-explorer-studio-schema-editor
NuroDev Mar 4, 2026
ad4e387
Merge branch 'main' into workers-sdk-local-explorer-studio-schema-editor
NuroDev Mar 4, 2026
df785dd
Added changeset
NuroDev Mar 4, 2026
59d6531
Minor react type tweaks
NuroDev Mar 4, 2026
7cf340a
Removed TODOs
NuroDev Mar 4, 2026
3156dbc
Added schema editing to durable objects data studio
NuroDev Mar 4, 2026
10e25f9
Minor table explorer design tweaks
NuroDev Mar 4, 2026
ea93ed7
Redesigned schema editing layout
NuroDev Mar 4, 2026
4aa6e5e
Minor refactoring & cleanup
NuroDev Mar 4, 2026
67026a3
Updated pnpm lockfile
NuroDev Mar 4, 2026
86d611d
Updated schema editor header buttons
NuroDev Mar 4, 2026
e4ed968
Fixed legacy border colors
NuroDev Mar 4, 2026
e49c36f
Fixed constraint table rendering
NuroDev Mar 4, 2026
2dd4117
Merge branch 'main' into workers-sdk-local-explorer-studio-schema-editor
NuroDev Mar 4, 2026
e86107f
Restore `TableTarget` interface
NuroDev Mar 4, 2026
dcef650
Merge branch 'main' into workers-sdk-local-explorer-studio-schema-editor
NuroDev Mar 5, 2026
d31ecec
Minor data studio schema editor fixes
NuroDev Mar 5, 2026
1dbebba
Fixed column checkbox a11y bug
NuroDev Mar 5, 2026
1574999
Switch "Add column" to use native selection option
NuroDev Mar 5, 2026
503cd7b
Fixed `ConstraintListEditor` stale prop bug
NuroDev Mar 5, 2026
ce46e11
Fixed duplicate primary key constraint bug
NuroDev Mar 5, 2026
11936ea
Fixed incorrect column list return value
NuroDev Mar 5, 2026
9250a09
Disable delete confirmation button when `isDeleting` is true
NuroDev Mar 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/young-facts-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@cloudflare/local-explorer-ui": minor
---

Add schema editor to data studio

Adds a visual schema editor to the data studio that allows you to create new database tables and edit existing table schemas. The editor provides column management (add, edit, remove), constraint editing (primary keys, unique constraints), and generates the corresponding SQL statements for review before committing changes.

This is a WIP experimental feature.
1 change: 1 addition & 0 deletions packages/local-explorer-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@phosphor-icons/react": "^2.1.10",
"@tailwindcss/vite": "^4.0.15",
"@tanstack/react-router": "^1.158.0",
"immer": "^11.1.4",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.0.15"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function describeExplainNode(d: string): {
label: (
<div className="flex items-center">
<strong>SCAN </strong>
<span className="border border-color p-1 mx-2 rounded flex items-center gap-2">
<span className="border border-border p-1 mx-2 rounded flex items-center gap-2">
<TableIcon />
{d.substring("SCAN ".length)}
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Button, Dialog, Text } from "@cloudflare/kumo";
import { useState } from "react";
import type { IStudioDriver } from "../../../types/studio";
import type { SubmitEvent } from "react";

interface DropTableConfirmationModalProps {
closeModal: () => void;
driver: IStudioDriver;
isOpen: boolean;
onSuccess?: () => void;
schemaName: string;
tableName: string;
}

export function DropTableConfirmationModal({
closeModal,
driver,
isOpen,
onSuccess,
schemaName,
tableName,
}: DropTableConfirmationModalProps): JSX.Element {
const [challengeInput, setChallengeInput] = useState<string>("");
const [error, setError] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState<boolean>(false);

const isValid = challengeInput === tableName;

const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
setIsDeleting(true);
setError(null);

try {
await driver.dropTable(schemaName, tableName);
onSuccess?.();
closeModal();
} catch (err) {
setIsDeleting(false);
setError(err instanceof Error ? err.message : "Failed to delete table");
}
};

return (
<Dialog.Root
onOpenChange={(open: boolean) => {
if (!open) {
closeModal();
}
}}
open={isOpen}
>
<Dialog className="p-6">
<div className="flex items-start justify-between gap-4 mb-4">
{/* @ts-expect-error - Type mismatch due to pnpm monorepo @types/react version conflict */}
<Dialog.Title className="text-lg font-semibold">
Delete table?
</Dialog.Title>
</div>

<form onSubmit={handleSubmit}>
<div className="space-y-4">
{/* @ts-expect-error - Type mismatch due to pnpm monorepo @types/react version conflict */}
<Dialog.Description className="text-kumo-subtle">
Are you sure you want to delete the table{" "}
<strong>{tableName}</strong>? This action cannot be undone.
</Dialog.Description>

<div className="space-y-2">
<Text size="sm">
Type <strong>{tableName}</strong> to confirm
</Text>
<input
autoComplete="off"
autoFocus
className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm"
onChange={(e) => setChallengeInput(e.target.value)}
value={challengeInput}
/>
</div>

{error && (
<div className="rounded-md bg-red-50 p-3 text-red-700">
{error}
</div>
)}
</div>

<div className="flex gap-2 justify-end mt-4">
<Button onClick={closeModal} variant="secondary">
Cancel
</Button>

<Button
disabled={!isValid || isDeleting}
loading={isDeleting}
type="submit"
variant="destructive"
>
Delete
</Button>
</div>
</form>
</Dialog>
</Dialog.Root>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { cn } from "@cloudflare/kumo";
import type { HTMLAttributes } from "react";

interface SkeletonBlockProps extends HTMLAttributes<HTMLDivElement> {
height?: number | string;
mb?: number;
p?: number;
width?: number | string;
}

export function SkeletonBlock({
className,
height,
mb,
p,
style,
width,
...props
}: SkeletonBlockProps) {
return (
<div
className={cn(
"relative overflow-hidden rounded-md bg-surface-tertiary",
"before:absolute before:inset-0 before:animate-pulse before:bg-linear-to-r before:from-transparent before:via-white/20 before:to-transparent",
className
)}
style={{
height: typeof height === "number" ? `${height}px` : height,
width: typeof width === "number" ? `${width}px` : width,
marginBottom: mb ? `${mb * 4}px` : undefined,
padding: p ? `${p * 4}px` : undefined,
...style,
}}
{...props}
>
{/* Non-breaking space for sizing */}
&nbsp;
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BinocularsIcon, TableIcon } from "@phosphor-icons/react";
import { BinocularsIcon, PencilIcon, TableIcon } from "@phosphor-icons/react";
import { StudioCreateUpdateTableTab } from "./Tabs/CreateUpdateTable";
import { StudioQueryTab } from "./Tabs/Query";
import { StudioTableExplorerTab } from "./Tabs/TableExplorer";
import type { Icon } from "@phosphor-icons/react";
Expand Down Expand Up @@ -26,7 +27,31 @@ const TableTab: TabDefinition<{
type: "table",
};

const RegisteredTabDefinition = [QueryTab, TableTab];
const EditTableTab: TabDefinition<{
schemaName: string;
tableName: string;
type: "edit-table";
}> = {
icon: PencilIcon,
makeComponent: ({ schemaName, tableName }) => (
<StudioCreateUpdateTableTab schemaName={schemaName} tableName={tableName} />
),
makeIdentifier: (tab) => `edit-table/${tab.schemaName}.${tab.tableName}`,
makeTitle: ({ tableName }) => tableName,
type: "edit-table",
};

const NewTableTab: TabDefinition<{
type: "create-table";
}> = {
icon: PencilIcon,
makeComponent: () => <StudioCreateUpdateTableTab />,
makeIdentifier: () => `create-table`,
makeTitle: () => "Create table",
type: "create-table",
};

const RegisteredTabDefinition = [QueryTab, TableTab, EditTableTab, NewTableTab];

export interface TabDefinition<T extends { type: string }> {
icon: Icon;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Button, DropdownMenu } from "@cloudflare/kumo";
import { CopyIcon, TableIcon, TextTIcon } from "@phosphor-icons/react";
import { useCallback } from "react";
import type { IStudioDriver } from "../../../types/studio";

export interface TableTarget {
schemaName: string;
tableName: string;
}

interface TableActionsDropdownProps {
/**
* The currently selected table. When null/undefined, the dropdown trigger is disabled.
*/
currentTable: string | null | undefined;

/**
* The database driver used to perform operations like fetching schema and dropping tables.
*/
driver: IStudioDriver;

/**
* The schema name for the current table.
*
* @default 'main'
*/
schemaName?: string;
}

export function StudioTableActionsDropdown({
currentTable,
driver,
schemaName = "main",
}: TableActionsDropdownProps): JSX.Element {
const handleCopyTableName = useCallback(async (): Promise<void> => {
if (!currentTable) {
return;
}

await window.navigator.clipboard.writeText(currentTable);
}, [currentTable]);

const handleCopyTableSchema = useCallback(async (): Promise<void> => {
if (!currentTable) {
return;
}

const tableSchema = await driver.tableSchema(schemaName, currentTable);
if (!tableSchema.createScript) {
return;
}

await window.navigator.clipboard.writeText(tableSchema.createScript);
}, [currentTable, driver, schemaName]);

return (
<>
<DropdownMenu>
<DropdownMenu.Trigger
render={
<Button
aria-label="Copy"
disabled={!currentTable}
icon={CopyIcon}
shape="square"
/>
}
/>

<DropdownMenu.Content>
<DropdownMenu.Item
className="space-x-2 cursor-pointer"
icon={TextTIcon}
onClick={handleCopyTableName}
>
Copy table name
</DropdownMenu.Item>

<DropdownMenu.Item
className="space-x-2 cursor-pointer"
icon={TableIcon}
onClick={handleCopyTableSchema}
>
Copy table schema
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function InputCellEditor({
{popover &&
createPortal(
<div
className="bg-surface border border-color rounded fixed shadow flex flex-col"
className="bg-surface border border-border rounded fixed shadow flex flex-col"
ref={refs.setFloating}
style={{
...(floatingStyles as React.CSSProperties),
Expand Down
Loading
Loading