@joycostudio/studio 0.1.2 → 0.1.4
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/README.md +199 -50
- package/dist/chunk-2RGQF5X5.js +470 -0
- package/dist/chunk-2RGQF5X5.js.map +1 -0
- package/dist/{chunk-X4YLM5HA.js → chunk-DEJYGBPJ.js} +22 -1
- package/dist/chunk-DEJYGBPJ.js.map +1 -0
- package/dist/chunk-HVGJXFXT.js +314 -0
- package/dist/chunk-HVGJXFXT.js.map +1 -0
- package/dist/cli.js +878 -56
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +7 -4
- package/dist/client.js +5 -458
- package/dist/client.js.map +1 -1
- package/dist/command-menu.css +4 -1
- package/dist/controls.d.ts +1 -0
- package/dist/controls.js +3 -0
- package/dist/controls.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -12
- package/dist/index.js.map +1 -1
- package/dist/next-config.d.ts +4 -0
- package/dist/next-config.js +135 -0
- package/dist/next-config.js.map +1 -0
- package/dist/next.d.ts +8 -0
- package/dist/next.js +15 -0
- package/dist/next.js.map +1 -0
- package/dist/{protocol-BrHDtNkL.d.ts → protocol-DjrMADPm.d.ts} +66 -15
- package/dist/react.d.ts +40 -17
- package/dist/react.js +418 -274
- package/dist/react.js.map +1 -1
- package/dist/runtime.d.ts +33 -5
- package/dist/runtime.js +106 -87
- package/dist/runtime.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +27 -7
- package/dist/chunk-X4YLM5HA.js.map +0 -1
|
@@ -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-2RGQF5X5.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
|
package/dist/next.js.map
ADDED
|
@@ -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"]}
|
|
@@ -19,18 +19,24 @@ declare const error: (code: string, message?: string) => Failure;
|
|
|
19
19
|
* Lucide's core package (`import { Palette } from 'lucide'`) exports exactly this shape.
|
|
20
20
|
*/
|
|
21
21
|
type ToolIcon = readonly (readonly [tag: string, attrs: Record<string, string | number | undefined>])[];
|
|
22
|
+
type ToolBase = {
|
|
23
|
+
/** Unique, URL-safe identifier. */
|
|
24
|
+
id: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Shown next to the tool in /studio. Data, not a component, so contracts stay loadable in Node. */
|
|
27
|
+
icon?: ToolIcon;
|
|
28
|
+
/** App route that renders this tool's result, shown next to the controls in /studio. */
|
|
29
|
+
preview?: string;
|
|
30
|
+
/** Use a bare iframe for tools that already own their canvas layout and headers. */
|
|
31
|
+
previewLayout?: 'canvas' | 'custom';
|
|
32
|
+
};
|
|
22
33
|
/**
|
|
23
34
|
* A tool's contract. Isomorphic on purpose: the runtime loads it in Node to
|
|
24
35
|
* validate and persist, the app loads it in the browser to apply and debug.
|
|
25
36
|
*
|
|
26
37
|
* `TConfig` is the one type that is read, edited, applied, and persisted.
|
|
27
38
|
*/
|
|
28
|
-
type ToolDefinition<TConfig extends object = object> = {
|
|
29
|
-
/** Unique, URL-safe identifier. */
|
|
30
|
-
id: string;
|
|
31
|
-
title?: string;
|
|
32
|
-
/** Shown next to the tool in /studio. Data, not a component, so contracts stay loadable in Node. */
|
|
33
|
-
icon?: ToolIcon;
|
|
39
|
+
type ToolDefinition<TConfig extends object = object> = ToolBase & {
|
|
34
40
|
/** Repo-relative JSON file the config persists to. Import that same file as `initial`. */
|
|
35
41
|
file: string;
|
|
36
42
|
/** Config used before the runtime answers (and in production). */
|
|
@@ -40,16 +46,60 @@ type ToolDefinition<TConfig extends object = object> = {
|
|
|
40
46
|
* Doubles as the runtime validator: every control's bounds are enforced on write.
|
|
41
47
|
*/
|
|
42
48
|
controls: (config: TConfig) => DebugSchema;
|
|
43
|
-
/** App route that renders this tool's result, shown next to the controls in /studio. */
|
|
44
|
-
preview?: string;
|
|
45
|
-
/** Use a bare iframe for tools that already own their canvas layout and headers. */
|
|
46
|
-
previewLayout?: 'canvas' | 'custom';
|
|
47
49
|
};
|
|
50
|
+
/** A tool with nothing to tune: a sidebar entry for its preview, without an inspector or config file. */
|
|
51
|
+
type PreviewToolDefinition = ToolBase & {
|
|
52
|
+
preview: string;
|
|
53
|
+
file?: never;
|
|
54
|
+
initial?: never;
|
|
55
|
+
controls?: never;
|
|
56
|
+
};
|
|
57
|
+
/** A tool with a config. */
|
|
48
58
|
type AnyTool = ToolDefinition<any>;
|
|
49
|
-
|
|
59
|
+
/** Anything `defineTool` returns: the kind follows from whether it declares a config. */
|
|
60
|
+
type StudioTool = AnyTool | PreviewToolDefinition;
|
|
61
|
+
declare const hasConfig: (tool: StudioTool) => tool is AnyTool;
|
|
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. */
|
|
50
80
|
declare function defineTool<TConfig extends object>(tool: ToolDefinition<TConfig>): ToolDefinition<TConfig>;
|
|
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;
|
|
51
89
|
type StudioConfig = {
|
|
52
|
-
|
|
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;
|
|
53
103
|
/** Runtime WebSocket and HTTP MCP port. Defaults to 5179. */
|
|
54
104
|
port?: number;
|
|
55
105
|
/** Browser origins allowed on the bridge and MCP endpoint. Defaults to localhost origins. */
|
|
@@ -58,7 +108,7 @@ type StudioConfig = {
|
|
|
58
108
|
appUrl?: string;
|
|
59
109
|
};
|
|
60
110
|
declare const DEFAULT_PORT = 5179;
|
|
61
|
-
declare function defineConfig(config:
|
|
111
|
+
declare function defineConfig<T extends StudioConfig>(config: T): T;
|
|
62
112
|
|
|
63
113
|
/** A tool's live state as owned by the runtime. */
|
|
64
114
|
type ToolState<TConfig = unknown> = {
|
|
@@ -77,10 +127,11 @@ type ToolState<TConfig = unknown> = {
|
|
|
77
127
|
/** The file changed on disk while the draft had unsaved edits. */
|
|
78
128
|
conflict: boolean;
|
|
79
129
|
};
|
|
130
|
+
/** `file` is absent for preview-only tools, which have no config to read or edit. */
|
|
80
131
|
type ToolSummary = {
|
|
81
132
|
id: string;
|
|
82
133
|
title: string;
|
|
83
|
-
file
|
|
134
|
+
file?: string;
|
|
84
135
|
icon?: ToolIcon;
|
|
85
136
|
preview?: string;
|
|
86
137
|
};
|
|
@@ -137,4 +188,4 @@ type ServerMessage = {
|
|
|
137
188
|
state: ToolState;
|
|
138
189
|
};
|
|
139
190
|
|
|
140
|
-
export { type AnyTool as A, type ConfigOf as C, DEFAULT_PORT as D, type Failure as F, type Op as O, type Result as R, type ServerMessage as S, type ToolDefinition as T, type
|
|
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 '
|
|
4
|
+
import { Select } from '@base-ui/react/select';
|
|
5
5
|
|
|
6
6
|
type Success<T> = {
|
|
7
7
|
ok: true;
|
|
@@ -20,18 +20,24 @@ type Result<T> = Success<T> | Failure;
|
|
|
20
20
|
* Lucide's core package (`import { Palette } from 'lucide'`) exports exactly this shape.
|
|
21
21
|
*/
|
|
22
22
|
type ToolIcon = readonly (readonly [tag: string, attrs: Record<string, string | number | undefined>])[];
|
|
23
|
+
type ToolBase = {
|
|
24
|
+
/** Unique, URL-safe identifier. */
|
|
25
|
+
id: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
/** Shown next to the tool in /studio. Data, not a component, so contracts stay loadable in Node. */
|
|
28
|
+
icon?: ToolIcon;
|
|
29
|
+
/** App route that renders this tool's result, shown next to the controls in /studio. */
|
|
30
|
+
preview?: string;
|
|
31
|
+
/** Use a bare iframe for tools that already own their canvas layout and headers. */
|
|
32
|
+
previewLayout?: 'canvas' | 'custom';
|
|
33
|
+
};
|
|
23
34
|
/**
|
|
24
35
|
* A tool's contract. Isomorphic on purpose: the runtime loads it in Node to
|
|
25
36
|
* validate and persist, the app loads it in the browser to apply and debug.
|
|
26
37
|
*
|
|
27
38
|
* `TConfig` is the one type that is read, edited, applied, and persisted.
|
|
28
39
|
*/
|
|
29
|
-
type ToolDefinition<TConfig extends object = object> = {
|
|
30
|
-
/** Unique, URL-safe identifier. */
|
|
31
|
-
id: string;
|
|
32
|
-
title?: string;
|
|
33
|
-
/** Shown next to the tool in /studio. Data, not a component, so contracts stay loadable in Node. */
|
|
34
|
-
icon?: ToolIcon;
|
|
40
|
+
type ToolDefinition<TConfig extends object = object> = ToolBase & {
|
|
35
41
|
/** Repo-relative JSON file the config persists to. Import that same file as `initial`. */
|
|
36
42
|
file: string;
|
|
37
43
|
/** Config used before the runtime answers (and in production). */
|
|
@@ -41,11 +47,19 @@ type ToolDefinition<TConfig extends object = object> = {
|
|
|
41
47
|
* Doubles as the runtime validator: every control's bounds are enforced on write.
|
|
42
48
|
*/
|
|
43
49
|
controls: (config: TConfig) => DebugSchema;
|
|
44
|
-
/** App route that renders this tool's result, shown next to the controls in /studio. */
|
|
45
|
-
preview?: string;
|
|
46
|
-
/** Use a bare iframe for tools that already own their canvas layout and headers. */
|
|
47
|
-
previewLayout?: 'canvas' | 'custom';
|
|
48
50
|
};
|
|
51
|
+
/** A tool with nothing to tune: a sidebar entry for its preview, without an inspector or config file. */
|
|
52
|
+
type PreviewToolDefinition = ToolBase & {
|
|
53
|
+
preview: string;
|
|
54
|
+
file?: never;
|
|
55
|
+
initial?: never;
|
|
56
|
+
controls?: never;
|
|
57
|
+
};
|
|
58
|
+
/** A tool with a config. */
|
|
59
|
+
type AnyTool = ToolDefinition<any>;
|
|
60
|
+
/** Anything `defineTool` returns: the kind follows from whether it declares a config. */
|
|
61
|
+
type StudioTool = AnyTool | PreviewToolDefinition;
|
|
62
|
+
type ToolReference<TConfig extends object> = Pick<ToolDefinition<TConfig>, 'id' | 'controls'>;
|
|
49
63
|
|
|
50
64
|
/** A tool's live state as owned by the runtime. */
|
|
51
65
|
type ToolState<TConfig = unknown> = {
|
|
@@ -117,8 +131,11 @@ type ToolHandle<TConfig extends object = any> = {
|
|
|
117
131
|
discard(): Promise<Result<ToolState<TConfig>>>;
|
|
118
132
|
};
|
|
119
133
|
type Studio$1 = {
|
|
134
|
+
/** Every registered tool in order, including preview-only ones. */
|
|
135
|
+
readonly definitions: readonly StudioTool[];
|
|
136
|
+
/** A handle per tool with a config. Preview-only tools have none. */
|
|
120
137
|
readonly tools: readonly ToolHandle[];
|
|
121
|
-
tool<TConfig extends object>(definition:
|
|
138
|
+
tool<TConfig extends object>(definition: ToolReference<TConfig>): ToolHandle<TConfig>;
|
|
122
139
|
readonly connection: Connection;
|
|
123
140
|
/** Bumps on connection or any tool state change. */
|
|
124
141
|
readonly version: number;
|
|
@@ -136,6 +153,11 @@ interface StudioProps {
|
|
|
136
153
|
/** The /studio route: every registered tool, its preview, and its Tweakpane inspector. */
|
|
137
154
|
declare function Studio({ studio, previewComponent, getConfigFileHref }: StudioProps): react.JSX.Element | null;
|
|
138
155
|
|
|
156
|
+
/** Mount once in the host app for ?debug / Option+D controls. */
|
|
157
|
+
declare function StudioDebug({ studio }: {
|
|
158
|
+
studio: Studio$1;
|
|
159
|
+
}): null;
|
|
160
|
+
|
|
139
161
|
type StudioCommandMenuProps = {
|
|
140
162
|
studio: Studio$1;
|
|
141
163
|
/** Path to the host's Studio route. Defaults to /studio. */
|
|
@@ -144,7 +166,7 @@ type StudioCommandMenuProps = {
|
|
|
144
166
|
navigate?: (href: string) => void;
|
|
145
167
|
};
|
|
146
168
|
/** Mount once in the host layout, including /studio, to jump to any registered tool with ⌘/Ctrl+K. */
|
|
147
|
-
declare function StudioCommandMenu(
|
|
169
|
+
declare function StudioCommandMenu(props: StudioCommandMenuProps): react.JSX.Element;
|
|
148
170
|
|
|
149
171
|
/** Re-render on connection and tool state changes. */
|
|
150
172
|
declare function useStudio(studio: Studio$1): Studio$1;
|
|
@@ -175,14 +197,15 @@ interface PreviewCanvasProps extends ComponentPropsWithoutRef<'iframe'> {
|
|
|
175
197
|
/** Expands the same iframe in place. Native iframe props and the ref reach the iframe. */
|
|
176
198
|
declare const PreviewCanvas: react.ForwardRefExoticComponent<PreviewCanvasProps & react.RefAttributes<HTMLIFrameElement>>;
|
|
177
199
|
|
|
178
|
-
type StudioSelectProps =
|
|
200
|
+
type StudioSelectProps = Omit<Select.Root.Props<string>, 'children'> & {
|
|
179
201
|
label: string;
|
|
180
202
|
title?: string;
|
|
181
203
|
className?: string;
|
|
204
|
+
children?: ReactNode;
|
|
182
205
|
};
|
|
183
|
-
/** Compact
|
|
206
|
+
/** Compact select shared by canvas and activity toolbars. */
|
|
184
207
|
declare function StudioSelect({ label, title, className, children, ...props }: StudioSelectProps): react.JSX.Element;
|
|
185
|
-
declare function StudioSelectItem({ className, children, ...props }:
|
|
208
|
+
declare function StudioSelectItem({ className, children, ...props }: Select.Item.Props): react.JSX.Element;
|
|
186
209
|
|
|
187
210
|
/** Logical padding lets icon-bearing fields override only their leading inset. */
|
|
188
211
|
declare const Input: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & react.RefAttributes<HTMLInputElement>>;
|
|
@@ -196,4 +219,4 @@ declare function PanelHeader({ className, ...props }: ComponentProps<'header'>):
|
|
|
196
219
|
declare const PanelContent: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
|
|
197
220
|
declare function PanelFooter({ className, ...props }: ComponentProps<'footer'>): react.JSX.Element;
|
|
198
221
|
|
|
199
|
-
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 };
|
|
222
|
+
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 };
|