@joycostudio/studio 0.1.3 → 0.1.5

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.
@@ -0,0 +1,135 @@
1
+ import {
2
+ DiscoveryInputError,
3
+ generate
4
+ } from "./chunk-HVGJXFXT.js";
5
+
6
+ // src/discovery/watch.ts
7
+ import { existsSync, statSync, watch } from "fs";
8
+ import path from "path";
9
+ function watchDiscovery(initial, refresh) {
10
+ let current = initial;
11
+ let retryInputs = /* @__PURE__ */ new Set();
12
+ let watchers2 = [];
13
+ let timer;
14
+ let running = false;
15
+ let pending = false;
16
+ let closed = false;
17
+ const schedule = () => {
18
+ if (closed) return;
19
+ pending = true;
20
+ clearTimeout(timer);
21
+ timer = setTimeout(() => void run(), 80);
22
+ };
23
+ const run = async () => {
24
+ if (running || closed) return;
25
+ pending = false;
26
+ running = true;
27
+ try {
28
+ const next = await refresh();
29
+ if (next && !closed) {
30
+ current = next;
31
+ retryInputs.clear();
32
+ bind();
33
+ }
34
+ } catch (cause) {
35
+ if (cause instanceof DiscoveryInputError && !closed) {
36
+ retryInputs = cause.inputs;
37
+ bind();
38
+ }
39
+ console.error("[studio] discovery update rejected:", cause instanceof Error ? cause.message : cause);
40
+ } finally {
41
+ running = false;
42
+ if (pending) schedule();
43
+ }
44
+ };
45
+ const bind = () => {
46
+ for (const watcher of watchers2) watcher.close();
47
+ watchers2 = [];
48
+ const pages = new Map(
49
+ [...current.sources].flatMap(
50
+ (file) => ["tsx", "ts", "jsx", "js"].map((ext) => {
51
+ const page = path.join(path.dirname(file), `page.${ext}`);
52
+ return [page, existsSync(page)];
53
+ })
54
+ )
55
+ );
56
+ const add = (directory, recursive, listener) => {
57
+ if (!existsSync(directory)) return;
58
+ const watcher = watch(directory, { recursive }, listener);
59
+ watcher.on("error", (cause) => console.error("[studio] discovery watcher:", cause.message));
60
+ watcher.unref();
61
+ watchers2.push(watcher);
62
+ };
63
+ add(current.toolsDir, true, (event, filename) => {
64
+ if (!filename) return schedule();
65
+ const relative = filename.toString();
66
+ if (relative.split(path.sep).some((part) => part.startsWith(".") || part === "node_modules")) return;
67
+ const file = path.resolve(current.toolsDir, relative);
68
+ if (path.basename(file) === "studio.tool.ts" || current.dependencies.has(file)) return schedule();
69
+ if (pages.has(file)) {
70
+ if (pages.get(file) !== existsSync(file)) schedule();
71
+ return;
72
+ }
73
+ if (event === "rename" && (/^page\.[jt]sx?$/.test(path.basename(file)) || !path.extname(file) || statSync(file, { throwIfNoEntry: false })?.isDirectory()))
74
+ schedule();
75
+ });
76
+ const targets = /* @__PURE__ */ new Set([
77
+ ...current.dependencies,
78
+ ...retryInputs,
79
+ ...["studio.config.ts", "studio.config.mts", "studio.config.js", "studio.config.mjs", "tsconfig.json"].map(
80
+ (file) => path.join(current.root, file)
81
+ )
82
+ ]);
83
+ const byDirectory = /* @__PURE__ */ new Map();
84
+ for (const file of targets) {
85
+ let directory = path.dirname(file);
86
+ while (!existsSync(directory) && path.dirname(directory) !== directory) directory = path.dirname(directory);
87
+ const names = byDirectory.get(directory) ?? /* @__PURE__ */ new Set();
88
+ names.add(path.relative(directory, file).split(path.sep)[0]);
89
+ byDirectory.set(directory, names);
90
+ }
91
+ for (const [directory, names] of byDirectory) {
92
+ add(directory, false, (_, filename) => {
93
+ if (!filename || names.has(filename.toString())) schedule();
94
+ });
95
+ }
96
+ const directoryId = statSync(current.toolsDir, { throwIfNoEntry: false })?.ino;
97
+ add(path.dirname(current.toolsDir), false, (_, filename) => {
98
+ if ((!filename || filename.toString() === path.basename(current.toolsDir)) && statSync(current.toolsDir, { throwIfNoEntry: false })?.ino !== directoryId)
99
+ schedule();
100
+ });
101
+ };
102
+ bind();
103
+ return {
104
+ retry: schedule,
105
+ close() {
106
+ closed = true;
107
+ clearTimeout(timer);
108
+ for (const watcher of watchers2) watcher.close();
109
+ }
110
+ };
111
+ }
112
+
113
+ // src/next-config.ts
114
+ var watchers = /* @__PURE__ */ new Map();
115
+ function withStudio(config) {
116
+ return async (phase, context) => {
117
+ const next = typeof config === "function" ? await config(phase, context) : config;
118
+ if (phase !== "phase-production-server" && !process.env.STUDIO_DISCOVERY_MANAGED) {
119
+ const root = process.cwd();
120
+ const explicit = process.env.STUDIO_CONFIG;
121
+ const discovery = await generate(root, explicit);
122
+ if (discovery && phase === "phase-development-server" && !process.env.STUDIO_DISCOVERY_MANAGED && !watchers.has(root)) {
123
+ watchers.set(
124
+ root,
125
+ watchDiscovery(discovery, () => generate(root, explicit))
126
+ );
127
+ }
128
+ }
129
+ return next;
130
+ };
131
+ }
132
+ export {
133
+ withStudio
134
+ };
135
+ //# sourceMappingURL=next-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/discovery/watch.ts","../src/next-config.ts"],"sourcesContent":["import { existsSync, statSync, watch, type FSWatcher } from 'node:fs'\nimport path from 'node:path'\nimport { DiscoveryInputError, type Discovery } from './generate'\n\n/** Watch the selected tree, plus exact imported dependencies outside it. No page-content triggers. */\nexport function watchDiscovery(initial: Discovery, refresh: () => Promise<Discovery | undefined>) {\n let current = initial\n let retryInputs = new Set<string>()\n let watchers: FSWatcher[] = []\n let timer: NodeJS.Timeout | undefined\n let running = false\n let pending = false\n let closed = false\n const schedule = () => {\n if (closed) return\n pending = true\n clearTimeout(timer)\n timer = setTimeout(() => void run(), 80)\n }\n const run = async () => {\n if (running || closed) return\n pending = false\n running = true\n try {\n const next = await refresh()\n if (next && !closed) {\n current = next\n retryInputs.clear()\n bind()\n }\n } catch (cause) {\n if (cause instanceof DiscoveryInputError && !closed) {\n retryInputs = cause.inputs\n bind()\n }\n console.error('[studio] discovery update rejected:', cause instanceof Error ? cause.message : cause)\n } finally {\n running = false\n if (pending) schedule()\n }\n }\n const bind = () => {\n for (const watcher of watchers) watcher.close()\n watchers = []\n const pages = new Map(\n [...current.sources].flatMap((file) =>\n ['tsx', 'ts', 'jsx', 'js'].map((ext) => {\n const page = path.join(path.dirname(file), `page.${ext}`)\n return [page, existsSync(page)] as const\n }),\n ),\n )\n const add = (directory: string, recursive: boolean, listener: (event: string, filename: string | null) => void) => {\n if (!existsSync(directory)) return\n const watcher = watch(directory, { recursive }, listener)\n watcher.on('error', (cause) => console.error('[studio] discovery watcher:', cause.message))\n watcher.unref()\n watchers.push(watcher)\n }\n add(current.toolsDir, true, (event, filename) => {\n if (!filename) return schedule()\n const relative = filename.toString()\n if (relative.split(path.sep).some((part) => part.startsWith('.') || part === 'node_modules')) return\n const file = path.resolve(current.toolsDir, relative)\n if (path.basename(file) === 'studio.tool.ts' || current.dependencies.has(file)) return schedule()\n if (pages.has(file)) {\n if (pages.get(file) !== existsSync(file)) schedule()\n return\n }\n if (\n event === 'rename' &&\n (/^page\\.[jt]sx?$/.test(path.basename(file)) ||\n !path.extname(file) ||\n statSync(file, { throwIfNoEntry: false })?.isDirectory())\n )\n schedule()\n })\n const targets = new Set([\n ...current.dependencies,\n ...retryInputs,\n ...['studio.config.ts', 'studio.config.mts', 'studio.config.js', 'studio.config.mjs', 'tsconfig.json'].map(\n (file) => path.join(current.root, file),\n ),\n ])\n const byDirectory = new Map<string, Set<string>>()\n for (const file of targets) {\n let directory = path.dirname(file)\n // Watch the nearest existing parent until missing alias/relative-path directories appear.\n while (!existsSync(directory) && path.dirname(directory) !== directory) directory = path.dirname(directory)\n const names = byDirectory.get(directory) ?? new Set<string>()\n names.add(path.relative(directory, file).split(path.sep)[0]!)\n byDirectory.set(directory, names)\n }\n for (const [directory, names] of byDirectory) {\n add(directory, false, (_, filename) => {\n if (!filename || names.has(filename.toString())) schedule()\n })\n }\n const directoryId = statSync(current.toolsDir, { throwIfNoEntry: false })?.ino\n add(path.dirname(current.toolsDir), false, (_, filename) => {\n if (\n (!filename || filename.toString() === path.basename(current.toolsDir)) &&\n statSync(current.toolsDir, { throwIfNoEntry: false })?.ino !== directoryId\n )\n schedule()\n })\n }\n bind()\n return {\n retry: schedule,\n close() {\n closed = true\n clearTimeout(timer)\n for (const watcher of watchers) watcher.close()\n },\n }\n}\n","import { generate } from './discovery/generate'\nimport { watchDiscovery } from './discovery/watch'\n\nconst watchers = new Map<string, ReturnType<typeof watchDiscovery>>()\n\n/** Generate discovered tools before Next compiles, including a clean production build. */\nexport function withStudio<T extends object>(config: T | ((phase: string, context: any) => T | Promise<T>)) {\n return async (phase: string, context: any): Promise<T> => {\n const next = typeof config === 'function' ? await config(phase, context) : config\n if (phase !== 'phase-production-server' && !process.env.STUDIO_DISCOVERY_MANAGED) {\n const root = process.cwd()\n const explicit = process.env.STUDIO_CONFIG\n const discovery = await generate(root, explicit)\n if (\n discovery &&\n phase === 'phase-development-server' &&\n !process.env.STUDIO_DISCOVERY_MANAGED &&\n !watchers.has(root)\n ) {\n watchers.set(\n root,\n watchDiscovery(discovery, () => generate(root, explicit)),\n )\n }\n }\n return next\n }\n}\n"],"mappings":"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","names":["watchers"]}
package/dist/next.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { StudioOptions, Studio } from './client.js';
2
+ import '@joycostudio/xyz/debug';
3
+ import './protocol-DjrMADPm.js';
4
+
5
+ /** Next.js replaces this public environment variable in browser bundles. */
6
+ declare function createStudio(config: StudioOptions, options?: Pick<StudioOptions, 'runtime'>): Studio;
7
+
8
+ export { createStudio };
package/dist/next.js ADDED
@@ -0,0 +1,15 @@
1
+ import {
2
+ createStudio
3
+ } from "./chunk-ZH3NEN4V.js";
4
+ import "./chunk-DEJYGBPJ.js";
5
+
6
+ // src/next.ts
7
+ function createStudio2(config, options = {}) {
8
+ return createStudio(config, {
9
+ runtime: options.runtime ?? config.runtime ?? (process.env.NODE_ENV === "development" && (process.env.NEXT_PUBLIC_STUDIO_RUNTIME || false))
10
+ });
11
+ }
12
+ export {
13
+ createStudio2 as createStudio
14
+ };
15
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/next.ts"],"sourcesContent":["import { createStudio as createClient, type StudioOptions } from './client/studio'\n\n/** Next.js replaces this public environment variable in browser bundles. */\nexport function createStudio(config: StudioOptions, options: Pick<StudioOptions, 'runtime'> = {}) {\n return createClient(config, {\n runtime:\n options.runtime ??\n config.runtime ??\n (process.env.NODE_ENV === 'development' && (process.env.NEXT_PUBLIC_STUDIO_RUNTIME || false)),\n })\n}\n"],"mappings":";;;;;;AAGO,SAASA,cAAa,QAAuB,UAA0C,CAAC,GAAG;AAChG,SAAO,aAAa,QAAQ;AAAA,IAC1B,SACE,QAAQ,WACR,OAAO,YACN,QAAQ,IAAI,aAAa,kBAAkB,QAAQ,IAAI,8BAA8B;AAAA,EAC1F,CAAC;AACH;","names":["createStudio"]}
@@ -59,12 +59,47 @@ type AnyTool = ToolDefinition<any>;
59
59
  /** Anything `defineTool` returns: the kind follows from whether it declares a config. */
60
60
  type StudioTool = AnyTool | PreviewToolDefinition;
61
61
  declare const hasConfig: (tool: StudioTool) => tool is AnyTool;
62
- type ConfigOf<TTool> = TTool extends ToolDefinition<infer TConfig> ? TConfig : never;
63
- /** Declare `file`, `initial`, and `controls` for a tunable tool, or only a `preview` for a preview-only one. */
62
+ /** Filled by .studio/types.generated.ts in each consuming project. */
63
+ interface DiscoveredToolConfigs {
64
+ }
65
+ /** A discovered declaration is resolved into a full definition by the generated registry. */
66
+ type DiscoveredToolDefinition<TConfig extends object = object> = ToolBase & {
67
+ /** JSON source relative to this tool file, or a tsconfig path alias. Defaults to ./config.json. */
68
+ config?: string;
69
+ controls: (config: TConfig) => DebugSchema;
70
+ file?: never;
71
+ initial?: never;
72
+ };
73
+ type ToolReference<TConfig extends object> = Pick<ToolDefinition<TConfig>, 'id' | 'controls'>;
74
+ type ConfigOf<TTool> = TTool extends ToolReference<infer TConfig> ? TConfig : never;
75
+ /** Discovered tools infer their config from the generated ID-to-JSON type map. */
76
+ declare function defineTool<K extends keyof DiscoveredToolConfigs & string>(tool: Omit<DiscoveredToolDefinition<Extract<DiscoveredToolConfigs[K], object>>, 'id'> & {
77
+ id: K;
78
+ }): DiscoveredToolDefinition<Extract<DiscoveredToolConfigs[K], object>>;
79
+ /** Existing explicitly registered tools remain supported. */
64
80
  declare function defineTool<TConfig extends object>(tool: ToolDefinition<TConfig>): ToolDefinition<TConfig>;
65
81
  declare function defineTool(tool: PreviewToolDefinition): PreviewToolDefinition;
82
+ /** A discovered preview-only tool may infer its preview from the adjacent page. */
83
+ declare function defineTool(tool: ToolBase & {
84
+ controls?: never;
85
+ config?: never;
86
+ file?: never;
87
+ initial?: never;
88
+ }): ToolBase;
66
89
  type StudioConfig = {
67
- tools: readonly StudioTool[];
90
+ /** Explicit registry; omit to enable convention-based discovery. */
91
+ tools?: readonly StudioTool[];
92
+ /**
93
+ * Directory to scan and watch for `studio.tool.ts` files, relative to
94
+ * the project root.
95
+ *
96
+ * Defaults to the active Next.js app directory: `app/` or `src/app/`.
97
+ * Set a narrower directory to reduce scanning and discovery triggers.
98
+ * This does not change preview URLs.
99
+ *
100
+ * @example 'src/app/experiments'
101
+ */
102
+ toolsDir?: string;
68
103
  /** Runtime WebSocket and HTTP MCP port. Defaults to 5179. */
69
104
  port?: number;
70
105
  /** Browser origins allowed on the bridge and MCP endpoint. Defaults to localhost origins. */
@@ -73,7 +108,7 @@ type StudioConfig = {
73
108
  appUrl?: string;
74
109
  };
75
110
  declare const DEFAULT_PORT = 5179;
76
- declare function defineConfig(config: StudioConfig): StudioConfig;
111
+ declare function defineConfig<T extends StudioConfig>(config: T): T;
77
112
 
78
113
  /** A tool's live state as owned by the runtime. */
79
114
  type ToolState<TConfig = unknown> = {
@@ -153,4 +188,4 @@ type ServerMessage = {
153
188
  state: ToolState;
154
189
  };
155
190
 
156
- export { type AnyTool as A, type ConfigOf as C, DEFAULT_PORT as D, type Failure as F, type Op as O, type PreviewToolDefinition as P, type Result as R, type StudioTool as S, type ToolDefinition as T, type ToolState as a, type Operations as b, type Request as c, type ServerMessage as d, type StudioConfig as e, type Success as f, type ToolIcon as g, type ToolSummary as h, defineConfig as i, defineTool as j, error as k, hasConfig as l, success as s };
191
+ export { type AnyTool as A, type ConfigOf as C, DEFAULT_PORT as D, type Failure as F, type Op as O, type PreviewToolDefinition as P, type Result as R, type ServerMessage as S, type ToolDefinition as T, type DiscoveredToolConfigs as a, type DiscoveredToolDefinition as b, type Operations as c, type Request as d, type StudioConfig as e, type StudioTool as f, type Success as g, type ToolIcon as h, type ToolReference as i, type ToolState as j, type ToolSummary as k, defineConfig as l, defineTool as m, error as n, hasConfig as o, success as s };
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentType, ComponentPropsWithRef, ReactNode, ComponentPropsWithoutRef, ComponentProps } from 'react';
3
3
  import { DebugSchema, DebugFolder } from '@joycostudio/xyz/debug';
4
- import { Select } from 'radix-ui';
4
+ import { Select } from '@base-ui/react/select';
5
5
 
6
6
  type Success<T> = {
7
7
  ok: true;
@@ -59,6 +59,7 @@ type PreviewToolDefinition = ToolBase & {
59
59
  type AnyTool = ToolDefinition<any>;
60
60
  /** Anything `defineTool` returns: the kind follows from whether it declares a config. */
61
61
  type StudioTool = AnyTool | PreviewToolDefinition;
62
+ type ToolReference<TConfig extends object> = Pick<ToolDefinition<TConfig>, 'id' | 'controls'>;
62
63
 
63
64
  /** A tool's live state as owned by the runtime. */
64
65
  type ToolState<TConfig = unknown> = {
@@ -92,7 +93,7 @@ type ToolHandle<TConfig extends object = any> = {
92
93
  readonly definition: ToolDefinition<TConfig>;
93
94
  /** The live config. Writes sync to same-origin pages and, when connected, the runtime. */
94
95
  readonly config: TConfig;
95
- /** Runtime-owned state; null until the runtime answers (always null without one). */
96
+ /** Runtime-owned state; null while no runtime is connected or until it answers. */
96
97
  readonly state: ToolState<TConfig> | null;
97
98
  /** Bumps on every config change. Handy as a React snapshot. */
98
99
  readonly version: number;
@@ -102,7 +103,7 @@ type ToolHandle<TConfig extends object = any> = {
102
103
  controls(): DebugSchema;
103
104
  /** Xyz `Debuggable`: `debugManager.register(handle)` mounts this tool in the app's floating pane. */
104
105
  debug(): DebugFolder;
105
- /** The config on disk: the runtime's saved value or, until one answers, the bundled `initial`. */
106
+ /** The config on disk: the runtime's last reported saved value or, until one answers, the bundled `initial`. */
106
107
  readonly saved: TConfig;
107
108
  /** Dotted paths where the draft differs from `saved`. Tracked locally, so it works without a runtime too. */
108
109
  readonly changes: string[];
@@ -124,17 +125,22 @@ type ToolHandle<TConfig extends object = any> = {
124
125
  */
125
126
  undo(): boolean;
126
127
  redo(): boolean;
128
+ /** Persist the draft. Needs a connected runtime. */
127
129
  save(options?: {
128
130
  force?: boolean;
129
131
  }): Promise<Result<ToolState<TConfig>>>;
130
- discard(): Promise<Result<ToolState<TConfig>>>;
132
+ /**
133
+ * Reset the draft to `saved`, with or without a runtime, then sync it like any other edit.
134
+ * Undoable as its own step. Returns false when there is nothing to discard.
135
+ */
136
+ discard(): boolean;
131
137
  };
132
138
  type Studio$1 = {
133
139
  /** Every registered tool in order, including preview-only ones. */
134
140
  readonly definitions: readonly StudioTool[];
135
141
  /** A handle per tool with a config. Preview-only tools have none. */
136
142
  readonly tools: readonly ToolHandle[];
137
- tool<TConfig extends object>(definition: ToolDefinition<TConfig>): ToolHandle<TConfig>;
143
+ tool<TConfig extends object>(definition: ToolReference<TConfig>): ToolHandle<TConfig>;
138
144
  readonly connection: Connection;
139
145
  /** Bumps on connection or any tool state change. */
140
146
  readonly version: number;
@@ -152,6 +158,11 @@ interface StudioProps {
152
158
  /** The /studio route: every registered tool, its preview, and its Tweakpane inspector. */
153
159
  declare function Studio({ studio, previewComponent, getConfigFileHref }: StudioProps): react.JSX.Element | null;
154
160
 
161
+ /** Mount once in the host app for ?debug / Option+D controls. */
162
+ declare function StudioDebug({ studio }: {
163
+ studio: Studio$1;
164
+ }): null;
165
+
155
166
  type StudioCommandMenuProps = {
156
167
  studio: Studio$1;
157
168
  /** Path to the host's Studio route. Defaults to /studio. */
@@ -160,7 +171,7 @@ type StudioCommandMenuProps = {
160
171
  navigate?: (href: string) => void;
161
172
  };
162
173
  /** Mount once in the host layout, including /studio, to jump to any registered tool with ⌘/Ctrl+K. */
163
- declare function StudioCommandMenu({ studio, studioPath, navigate }: StudioCommandMenuProps): react.JSX.Element;
174
+ declare function StudioCommandMenu(props: StudioCommandMenuProps): react.JSX.Element;
164
175
 
165
176
  /** Re-render on connection and tool state changes. */
166
177
  declare function useStudio(studio: Studio$1): Studio$1;
@@ -191,14 +202,15 @@ interface PreviewCanvasProps extends ComponentPropsWithoutRef<'iframe'> {
191
202
  /** Expands the same iframe in place. Native iframe props and the ref reach the iframe. */
192
203
  declare const PreviewCanvas: react.ForwardRefExoticComponent<PreviewCanvasProps & react.RefAttributes<HTMLIFrameElement>>;
193
204
 
194
- type StudioSelectProps = ComponentProps<typeof Select.Root> & {
205
+ type StudioSelectProps = Omit<Select.Root.Props<string>, 'children'> & {
195
206
  label: string;
196
207
  title?: string;
197
208
  className?: string;
209
+ children?: ReactNode;
198
210
  };
199
- /** Compact Radix select shared by canvas and activity toolbars. */
211
+ /** Compact select shared by canvas and activity toolbars. */
200
212
  declare function StudioSelect({ label, title, className, children, ...props }: StudioSelectProps): react.JSX.Element;
201
- declare function StudioSelectItem({ className, children, ...props }: ComponentProps<typeof Select.Item>): react.JSX.Element;
213
+ declare function StudioSelectItem({ className, children, ...props }: Select.Item.Props): react.JSX.Element;
202
214
 
203
215
  /** Logical padding lets icon-bearing fields override only their leading inset. */
204
216
  declare const Input: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & react.RefAttributes<HTMLInputElement>>;
@@ -212,4 +224,4 @@ declare function PanelHeader({ className, ...props }: ComponentProps<'header'>):
212
224
  declare const PanelContent: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
213
225
  declare function PanelFooter({ className, ...props }: ComponentProps<'footer'>): react.JSX.Element;
214
226
 
215
- export { CanvasToolbar, type CanvasToolbarProps, PreviewCanvas, type PreviewCanvasProps, Studio, StudioCommandMenu, type StudioCommandMenuProps, Input as StudioInput, Panel as StudioPanel, PanelContent as StudioPanelContent, PanelFooter as StudioPanelFooter, PanelHeader as StudioPanelHeader, type StudioProps, StudioSelect, StudioSelectItem, type StudioSelectProps, StudioSurface, StudioToolbar, useStudio, useToolConfig };
227
+ export { CanvasToolbar, type CanvasToolbarProps, PreviewCanvas, type PreviewCanvasProps, Studio, StudioCommandMenu, type StudioCommandMenuProps, StudioDebug, Input as StudioInput, Panel as StudioPanel, PanelContent as StudioPanelContent, PanelFooter as StudioPanelFooter, PanelHeader as StudioPanelHeader, type StudioProps, StudioSelect, StudioSelectItem, type StudioSelectProps, StudioSurface, StudioToolbar, useStudio, useToolConfig };