@joycostudio/studio 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,272 @@
1
- # Temporary Holding Version
1
+ # @joycostudio/studio
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Plug project-specific tools into a `/studio` route, the app's floating debug pane, and MCP agents, with one contract per tool.
4
+
5
+ For contributor instructions on stable releases, betas, and commit previews, see the
6
+ [release workflow guide](../../.changeset/README.md).
7
+
8
+ A tool's controls are an [Xyz](https://hub.joyco.studio/toolbox/xyz) debug schema (Tweakpane). That same schema:
9
+
10
+ - renders the inspector in `/studio`,
11
+ - mounts in the app's floating pane (`?debug` / <kbd>Option</kbd>+<kbd>D</kbd>) through Xyz's `DebugManager`,
12
+ - validates every write in the runtime (slider bounds, select options, types, and the shape of `initial`).
13
+
14
+ ## 1. Define a tool
15
+
16
+ Tool files are loaded by the runtime in Node too, so keep them free of DOM/three imports and `@/` aliases.
17
+
18
+ ```ts
19
+ // src/tools/grading.ts
20
+ import { defineTool } from '@joycostudio/studio'
21
+ import { property, select, slider } from '@joycostudio/xyz/debug'
22
+ import initial from './grading.json'
23
+
24
+ export const grading = defineTool({
25
+ id: 'color-grading',
26
+ file: 'src/tools/grading.json', // where saves go, repo-relative
27
+ initial, // the same file, imported: production ships the saved config
28
+ preview: '/', // route shown next to the inspector in /studio
29
+ controls: (config) => ({
30
+ exposure: slider(property(config, 'exposure'), { min: 0, max: 3 }),
31
+ tonemap: select(property(config, 'tonemap'), { ACES: 'aces', Filmic: 'filmic' }),
32
+ }),
33
+ })
34
+ ```
35
+
36
+ ## 2. Register tools with the runtime
37
+
38
+ ```ts
39
+ // studio.config.ts (its directory is the project root)
40
+ import { defineConfig } from '@joycostudio/studio'
41
+ import { grading } from './src/tools/grading'
42
+
43
+ export default defineConfig({ tools: [grading] })
44
+ ```
45
+
46
+ ```json
47
+ { "scripts": { "dev": "studio dev -- next dev" } }
48
+ ```
49
+
50
+ `studio dev` starts the WebSocket bridge on `ws://127.0.0.1:5179` and the HTTP MCP endpoint on
51
+ `http://127.0.0.1:5179/mcp` (`--port` changes both). It stops when the wrapped command exits.
52
+
53
+ ## 3. Use it in the app
54
+
55
+ ```ts
56
+ // src/studio.ts
57
+ import { createStudio } from '@joycostudio/studio/client'
58
+
59
+ export const studio = createStudio({
60
+ tools: [grading],
61
+ runtime: process.env.NODE_ENV === 'development' && 'ws://127.0.0.1:5179',
62
+ })
63
+ ```
64
+
65
+ ```ts
66
+ const tool = studio.tool(grading)
67
+ apply(tool.config) // a live object: read it in your render loop…
68
+ tool.subscribe(apply) // …or react to every change, local or remote
69
+ ```
70
+
71
+ Floating debug pane, with the same controls (plus save/discard when the runtime is up):
72
+
73
+ ```ts
74
+ import { everyFrame } from '@joycostudio/studio/client'
75
+ import { DebugManager } from '@joycostudio/xyz/debug'
76
+ import { Pane } from 'tweakpane'
77
+
78
+ const debug = new DebugManager({ createPane: () => new Pane(), binding: { registerFrame: everyFrame } })
79
+ for (const tool of studio.tools) debug.register(tool)
80
+ ```
81
+
82
+ The `/studio` route:
83
+
84
+ ```tsx
85
+ // app/studio/page.tsx
86
+ 'use client'
87
+ import { Studio } from '@joycostudio/studio/react'
88
+ import { studio } from '@/studio'
89
+
90
+ export default function StudioPage() {
91
+ return <Studio studio={studio} />
92
+ }
93
+ ```
94
+
95
+ Use the pin button beside a sidebar tool to keep it at the top. Pinned tools also appear first in the
96
+ **Command/Ctrl+K** menu. Pins are saved in this browser for the current origin and shared across tabs;
97
+ unpinning restores registration order. Pinning a tool keeps the current preview selected.
98
+
99
+ In Studio, **Command+B** (Mac) or **Ctrl+B** slides both side panels out of view; press it again to bring them back.
100
+ This also works from the same-origin preview iframe. The sidebar's collapse/expand button controls its width separately,
101
+ and that choice is preserved when hiding and showing the panels. Move the pointer near either screen edge to reveal a small
102
+ tab; click it to bring both panels back. The tabs also appear on keyboard focus and stay visible on touch devices.
103
+ With reduced motion enabled, panels toggle instantly.
104
+
105
+ Previews use the shared `PreviewCanvas`: Reload and the tool title sit on the left, with Expand and
106
+ header pinning on the right. Expand/Contain keeps the iframe and its scene mounted. Headers start
107
+ pinned; enabling auto-hide overlays the header and hides it after three seconds away. Hover, keyboard
108
+ focus, and open menus keep it visible, and touch devices always show the controls. Contained previews
109
+ fit between the side panels, with the inspector stacked below on narrow screens.
110
+
111
+ ### Shared canvas and panel components
112
+
113
+ Import these from `@joycostudio/studio/react` with `@joycostudio/studio/styles.css`:
114
+
115
+ | Component | Use |
116
+ | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
117
+ | `StudioSurface`, `StudioToolbar` | Shared inset card and compact action row, including button states. |
118
+ | `CanvasToolbar` | Pinned/auto-hide header; `label`, optional `expanded`/`onToggle`, and child controls. |
119
+ | `PreviewCanvas` | Iframe panel with reload, expansion, and a `toolbar` slot replacing the title. |
120
+ | `StudioSelect`, `StudioSelectItem` | Labeled Radix select with Studio field and portaled menu styling. |
121
+ | `StudioInput` | Standard input with logical padding (`ps-*`/`pe-*`) for icon-bearing fields. |
122
+ | `StudioPanel`, `StudioPanelHeader`, `StudioPanelContent`, `StudioPanelFooter` | Floating panel surface, inset sections, and scrolling body used by Studio's inspector. |
123
+
124
+ `PreviewCanvas` handles expansion itself, or accepts controlled `expanded`/`onExpandedChange` props.
125
+ Native iframe props (including `className`, `onLoad`, and `title`) and its ref reach the actual iframe;
126
+ `onReload` runs before an explicit reload. `StudioSurface` and `StudioPanel` support `asChild` to reuse
127
+ their appearance on semantic elements without an extra wrapper. Panel layout is caller-owned.
128
+
129
+ The package owns the styles and interactions extracted from the BZRP Studio workspace, including
130
+ 6px toolbar padding, 4px gaps, 8px padding on title text, and continuous sidebar header/footer surfaces.
131
+ Consumers can replace local copies of the surface, toolbar, preview, select, and input components with
132
+ these imports. Module graphs, tracing, and scene-specific controls remain host-owned.
133
+
134
+ ### Host preview and source-link extensions
135
+
136
+ `Studio` accepts `previewComponent` and `getConfigFileHref`, so integrations no longer need to patch
137
+ the built package. Keep a custom preview component at module scope and forward its iframe ref and
138
+ props; Studio uses them for keyboard shortcuts, comparison, and tool-switch lifecycle:
139
+
140
+ ```tsx
141
+ import { forwardRef, type ComponentPropsWithoutRef } from 'react'
142
+ import { PreviewCanvas, Studio } from '@joycostudio/studio/react'
143
+
144
+ const ProjectPreview = forwardRef<HTMLIFrameElement, ComponentPropsWithoutRef<'iframe'>>(
145
+ (props, ref) => <PreviewCanvas {...props} ref={ref} />,
146
+ )
147
+
148
+ <Studio
149
+ studio={studio}
150
+ previewComponent={ProjectPreview}
151
+ getConfigFileHref={(file) => new URL(file, sourceBaseUrl).href}
152
+ />
153
+ ```
154
+
155
+ The source base URL belongs to the host: it can point to a source browser or a development-only
156
+ `vscode://file/` URL for the current checkout. Without the callback, config paths remain plain text.
157
+ See the playground's `/studio/components` route for controlled expansion, custom toolbar fields,
158
+ and source links in use.
159
+
160
+ For a tool that already renders its own panels and headers, set `previewLayout: 'custom'` in its
161
+ `defineTool` definition. Studio uses a bare iframe fitted to the available panel area, avoiding
162
+ route-specific checks such as `/studio/modules`. A supplied `previewComponent` takes precedence.
163
+ Use `previewComponent="iframe"` to retain the original full-window preview behavior for all tools.
164
+
165
+ When migrating the BZRP integration, remove its custom-preview/config-link pnpm patch, use these
166
+ exports, and remove the generic shell/toolbar CSS that Studio now supplies. Keep application-specific
167
+ module runtime expansion, source indexing, and session-local inspector adapters in the host.
168
+
169
+ ### Jump to tools from anywhere
170
+
171
+ Mount `StudioCommandMenu` once in the host app's root layout so it stays available on every route, including `/studio`:
172
+
173
+ ```tsx
174
+ // src/command-menu.tsx
175
+ 'use client'
176
+ import '@joycostudio/studio/command-menu.css'
177
+ import { StudioCommandMenu } from '@joycostudio/studio/react'
178
+ import { studio } from './studio'
179
+
180
+ export function CommandMenu() {
181
+ return <StudioCommandMenu studio={studio} />
182
+ }
183
+ ```
184
+
185
+ Render `<CommandMenu />` alongside the layout's `{children}`. The host owns mounting it, just like the floating debug pane.
186
+ Its stylesheet is scoped to the menu and can be imported globally without applying Studio's page styles to the host app.
187
+
188
+ Press **Command+K** (Mac) or **Ctrl+K** to open or close it, type a tool name or ID to search, use **↑/↓** and **Enter** to
189
+ select, or **Escape** to dismiss. Clicking a result also opens it. Results link to `/studio#tool-id`; switching tools from
190
+ inside Studio changes the hash without reloading the page. The shortcut also works while focus is in Studio's same-origin
191
+ preview iframe, opening the menu above Studio rather than inside the preview.
192
+
193
+ Pass `studioPath="/custom/studio"` for another route. For client-side navigation with a host router, pass its navigation
194
+ function, for example `<StudioCommandMenu studio={studio} navigate={router.push} />` with Next.js `useRouter()`.
195
+ Without a router callback, navigation from another route uses the browser. The menu works without a runtime connection.
196
+
197
+ To run its browser tests, start the playground and run
198
+ `STUDIO_TEST_APP_URL=http://localhost:3000 pnpm --filter @joycostudio/studio exec vitest run test/command-menu.test.ts`.
199
+ Install Chromium first with `pnpm exec studio install-browser` if needed.
200
+
201
+ ## 4. Connect an MCP agent
202
+
203
+ Start the app with `studio dev -- <your dev command>` as above. Add a **Streamable HTTP** MCP server to your agent with
204
+ this URL:
205
+
206
+ ```text
207
+ http://127.0.0.1:5179/mcp
208
+ ```
209
+
210
+ Use the URL printed by the CLI when running on a different port. Each project's runtime needs its own port when several
211
+ projects run at once. The endpoint is local to the machine running Studio; a remote/cloud agent cannot reach it through
212
+ its own `localhost`.
213
+
214
+ The MCP endpoint shares the running app's drafts. It supports both the 2025 MCP initialization flow and the 2026 MCP
215
+ discovery flow, with no separate runtime or MCP process to launch.
216
+
217
+ | MCP tool | Arguments | Result |
218
+ | ------------------------ | ---------------------------------------------- | ---------------------------------------------------------------- |
219
+ | `studio_list_tools` | none | Registered IDs, titles, file paths, and preview routes |
220
+ | `studio_get_tool` | `tool` | Complete draft, revision, saved value, dirty and conflict status |
221
+ | `studio_update_tool` | `tool`, `revision`, `value` | Validated draft; all connected previews update immediately |
222
+ | `studio_save_tool` | `tool`, `revision`, optional `force` | Draft persisted to its registered JSON file |
223
+ | `studio_discard_tool` | `tool`, `revision` | Draft reset to the runtime's latest saved value |
224
+ | `studio_capture_preview` | `tool`, optional `revision`, `width`, `height` | PNG image and draft revision from a fresh preview page |
225
+
226
+ Read before editing, copy `data.draft.value`, change the intended fields, then pass the **complete config** and
227
+ `data.draft.revision` to `studio_update_tool`. Saving is separate. Use the new revision returned by the update to save or
228
+ discard. If the playground or another agent edits in between, a `stale_revision` error tells you to read again and reconcile
229
+ the changes. `force` bypasses a disk conflict, never a stale revision.
230
+
231
+ Every tool returns the runtime's `{ ok: true, data }` or `{ ok: false, error, message? }` as structured content and JSON text.
232
+ Runtime failures also set MCP's `isError`, so invalid configs, unknown tools, stale revisions, and disk conflicts remain
233
+ visible to the agent. Expected failures leave the draft intact.
234
+
235
+ ### Preview images
236
+
237
+ Set the running app's URL once in `studio.config.ts` (or override it with `studio dev --app-url <url>`):
238
+
239
+ ```ts
240
+ export default defineConfig({ tools: [grading], appUrl: 'http://localhost:3000' })
241
+ ```
242
+
243
+ Install Chromium once with `pnpm exec studio install-browser`. Then call `studio_capture_preview` with a tool ID.
244
+ The existing `preview` route must use the Studio client connected to this runtime. No capture code or readiness hooks
245
+ are needed in tool definitions, HTML components, or canvas renderers.
246
+
247
+ Capture starts a shared headless Chromium browser on first use and opens an isolated page for each request. It reads the
248
+ live unsaved draft, waits for the client to receive that revision, then waits for fonts, current HTML images, and two
249
+ animation frames. CSS animations are disabled for the screenshot. This is automatic settling, not a guarantee that
250
+ arbitrary async work, delayed UI, or WebGL assets have finished. A draft change during capture returns `stale_revision`
251
+ instead of an image. Simultaneous captures return `capture_busy`; retry after the active capture finishes.
252
+
253
+ The result includes a PNG as MCP image content and metadata with `tool`, `revision`, `url`, `width`, `height`, and
254
+ `settling: 'automatic'`. The viewport defaults to 1200×800 at 1× scale; width accepts 320–2560 and height accepts 240–1600.
255
+ The page starts fresh: it does not inherit your open tab's login, scroll, or interaction state. Preview routes and
256
+ top-level navigation must stay on the configured app origin. Pages that never connect or settle time out.
257
+
258
+ For the browser integration tests, run `pnpm exec studio install-browser` and
259
+ `pnpm --filter @joycostudio/studio test:capture`. The regular test suite runs setup validation without requiring Chromium.
260
+
261
+ The server binds to `127.0.0.1` by default and the MCP endpoint checks localhost Host headers and the same browser Origin
262
+ allowlist as the WebSocket bridge. Native agent clients do not need an Origin header. This is a local development endpoint
263
+ without authentication; do not expose it through a public proxy.
264
+
265
+ ## How it fits together
266
+
267
+ - **Runtime** (`studio dev`) is the only writer. It validates every update, keeps a draft revision per tool, writes atomically,
268
+ refuses paths outside the project (symlinks included), only accepts localhost browser origins, and watches files for
269
+ external edits (an external edit over unsaved work becomes a `conflict` that needs discard or force save).
270
+ - **Bridge**: WebSocket (`tools.list`, `tool.get`, `tool.update`, `tool.save`, `tool.discard` + `state` broadcasts) and HTTP MCP
271
+ on the same port. `/studio`, every app tab, and MCP all go through these same runtime operations.
272
+ - **Without a runtime** (production) tools stay local: the floating pane still tweaks, nothing persists.
@@ -0,0 +1,13 @@
1
+ // src/core/result.ts
2
+ var success = (data) => ({ ok: true, data });
3
+ var error = (code, message) => ({
4
+ ok: false,
5
+ error: code,
6
+ ...message === void 0 ? {} : { message }
7
+ });
8
+
9
+ export {
10
+ success,
11
+ error
12
+ };
13
+ //# sourceMappingURL=chunk-QKEAOM2U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/result.ts"],"sourcesContent":["export type Success<T> = { ok: true; data: T }\n\nexport type Failure = {\n ok: false\n /** snake_case wire code, e.g. `invalid_config`. */\n error: string\n message?: string\n}\n\nexport type Result<T> = Success<T> | Failure\n\nexport const success = <T>(data: T): Success<T> => ({ ok: true, data })\n\nexport const error = (code: string, message?: string): Failure => ({\n ok: false,\n error: code,\n ...(message === undefined ? {} : { message }),\n})\n"],"mappings":";AAWO,IAAM,UAAU,CAAI,UAAyB,EAAE,IAAI,MAAM,KAAK;AAE9D,IAAM,QAAQ,CAAC,MAAc,aAA+B;AAAA,EACjE,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,GAAI,YAAY,SAAY,CAAC,IAAI,EAAE,QAAQ;AAC7C;","names":[]}
@@ -0,0 +1,75 @@
1
+ import {
2
+ error,
3
+ success
4
+ } from "./chunk-QKEAOM2U.js";
5
+
6
+ // src/core/validate.ts
7
+ function validateConfig(tool, candidate) {
8
+ const mismatch = shapeMismatch(tool.initial, candidate, "config");
9
+ if (mismatch) return error("invalid_config", mismatch);
10
+ const config = structuredClone(candidate);
11
+ const invalid = invalidControl(tool.controls(config));
12
+ return invalid ? error("invalid_config", invalid) : success(config);
13
+ }
14
+ function kind(value) {
15
+ if (value === null) return "null";
16
+ if (Array.isArray(value)) return "array";
17
+ return typeof value;
18
+ }
19
+ function shapeMismatch(reference, value, path) {
20
+ const expected = kind(reference);
21
+ if (kind(value) !== expected) return `${path}: expected ${expected}, got ${kind(value)}`;
22
+ if (expected === "number" && !Number.isFinite(value)) return `${path}: expected a finite number`;
23
+ if (expected === "array") {
24
+ const [first] = reference;
25
+ if (first === void 0) return null;
26
+ for (const [index, item] of value.entries()) {
27
+ const nested = shapeMismatch(first, item, `${path}[${index}]`);
28
+ if (nested) return nested;
29
+ }
30
+ }
31
+ if (expected === "object") {
32
+ const ref = reference;
33
+ const obj = value;
34
+ for (const key of Object.keys(obj)) if (!(key in ref)) return `${path}.${key}: unknown key`;
35
+ for (const key of Object.keys(ref)) {
36
+ if (!(key in obj)) return `${path}.${key}: missing`;
37
+ const nested = shapeMismatch(ref[key], obj[key], `${path}.${key}`);
38
+ if (nested) return nested;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ var isRgb = (value) => typeof value === "object" && value !== null && ["r", "g", "b"].every((channel) => Number.isFinite(value[channel]));
44
+ function invalidControl(schema, path = "") {
45
+ for (const [key, field] of Object.entries(schema)) {
46
+ if (field.kind === "button") continue;
47
+ const name = `${path}${field.label ?? key}`;
48
+ if (field.kind === "folder") {
49
+ const nested = invalidControl(field.definition.schema(), `${name}/`);
50
+ if (nested) return nested;
51
+ continue;
52
+ }
53
+ const value = field.read();
54
+ if (field.kind === "slider") {
55
+ if (typeof value !== "number" || value < field.min || value > field.max)
56
+ return `${name}: expected a number in [${field.min}, ${field.max}], got ${JSON.stringify(value)}`;
57
+ } else if (field.kind === "bool") {
58
+ if (typeof value !== "boolean") return `${name}: expected a boolean`;
59
+ } else if (field.kind === "select") {
60
+ const options = Object.values(field.options);
61
+ if (!options.includes(value))
62
+ return `${name}: expected one of ${JSON.stringify(options)}, got ${JSON.stringify(value)}`;
63
+ } else if (field.kind === "color-string") {
64
+ if (typeof value !== "string") return `${name}: expected a color string`;
65
+ } else if (field.kind === "color") {
66
+ if (!isRgb(value)) return `${name}: expected an { r, g, b } color`;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+
72
+ export {
73
+ validateConfig
74
+ };
75
+ //# sourceMappingURL=chunk-VYNEG5Z7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/validate.ts"],"sourcesContent":["import type { DebugSchema } from '@joycostudio/xyz/debug'\nimport { error, success, type Result } from './result'\nimport type { ToolDefinition } from './tool'\n\n/**\n * Accept `candidate` as a config for `tool` only if it has the exact shape of\n * `tool.initial` and every control in `tool.controls` accepts its value.\n * Returns a detached copy, never the input.\n */\nexport function validateConfig<TConfig extends object>(\n tool: ToolDefinition<TConfig>,\n candidate: unknown,\n): Result<TConfig> {\n const mismatch = shapeMismatch(tool.initial, candidate, 'config')\n if (mismatch) return error('invalid_config', mismatch)\n const config = structuredClone(candidate) as TConfig\n const invalid = invalidControl(tool.controls(config))\n return invalid ? error('invalid_config', invalid) : success(config)\n}\n\nfunction kind(value: unknown) {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value\n}\n\nfunction shapeMismatch(reference: unknown, value: unknown, path: string): string | null {\n const expected = kind(reference)\n if (kind(value) !== expected) return `${path}: expected ${expected}, got ${kind(value)}`\n if (expected === 'number' && !Number.isFinite(value)) return `${path}: expected a finite number`\n if (expected === 'array') {\n const [first] = reference as unknown[]\n if (first === undefined) return null\n for (const [index, item] of (value as unknown[]).entries()) {\n const nested = shapeMismatch(first, item, `${path}[${index}]`)\n if (nested) return nested\n }\n }\n if (expected === 'object') {\n const ref = reference as Record<string, unknown>\n const obj = value as Record<string, unknown>\n for (const key of Object.keys(obj)) if (!(key in ref)) return `${path}.${key}: unknown key`\n for (const key of Object.keys(ref)) {\n if (!(key in obj)) return `${path}.${key}: missing`\n const nested = shapeMismatch(ref[key], obj[key], `${path}.${key}`)\n if (nested) return nested\n }\n }\n return null\n}\n\nconst isRgb = (value: unknown) =>\n typeof value === 'object' &&\n value !== null &&\n ['r', 'g', 'b'].every((channel) => Number.isFinite((value as Record<string, unknown>)[channel]))\n\nfunction invalidControl(schema: DebugSchema, path = ''): string | null {\n for (const [key, field] of Object.entries(schema)) {\n if (field.kind === 'button') continue\n const name = `${path}${field.label ?? key}`\n if (field.kind === 'folder') {\n const nested = invalidControl(field.definition.schema(), `${name}/`)\n if (nested) return nested\n continue\n }\n const value: unknown = field.read()\n if (field.kind === 'slider') {\n if (typeof value !== 'number' || value < field.min || value > field.max)\n return `${name}: expected a number in [${field.min}, ${field.max}], got ${JSON.stringify(value)}`\n } else if (field.kind === 'bool') {\n if (typeof value !== 'boolean') return `${name}: expected a boolean`\n } else if (field.kind === 'select') {\n const options = Object.values(field.options)\n if (!options.includes(value as string | number))\n return `${name}: expected one of ${JSON.stringify(options)}, got ${JSON.stringify(value)}`\n } else if (field.kind === 'color-string') {\n if (typeof value !== 'string') return `${name}: expected a color string`\n } else if (field.kind === 'color') {\n if (!isRgb(value)) return `${name}: expected an { r, g, b } color`\n }\n }\n return null\n}\n"],"mappings":"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","names":[]}