@joycostudio/studio 0.1.3 → 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 +187 -55
- package/dist/chunk-2RGQF5X5.js +470 -0
- package/dist/chunk-2RGQF5X5.js.map +1 -0
- package/dist/{chunk-OIMLF2CL.js → chunk-DEJYGBPJ.js} +6 -5
- 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 +869 -52
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +5 -460
- 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 +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-Dgj_7ibg.d.ts → protocol-DjrMADPm.d.ts} +40 -5
- package/dist/react.d.ts +14 -7
- package/dist/react.js +357 -244
- package/dist/react.js.map +1 -1
- package/dist/runtime.d.ts +32 -4
- package/dist/runtime.js +97 -83
- package/dist/runtime.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +27 -7
- package/dist/chunk-OIMLF2CL.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,83 +11,188 @@ A tool's controls are an [Xyz](https://hub.joyco.studio/toolbox/xyz) debug schem
|
|
|
11
11
|
- mounts in the app's floating pane (`?debug` / <kbd>Option</kbd>+<kbd>D</kbd>) through Xyz's `DebugManager`,
|
|
12
12
|
- validates every write in the runtime (slider bounds, select options, types, and the shape of `initial`).
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Quick start: Next.js App Router
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
In an existing TypeScript Next.js app:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pnpm dlx @joycostudio/studio init
|
|
20
|
+
pnpm dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open `/studio` and select Example. Move the Scale slider and Save to write the adjacent JSON.
|
|
24
|
+
The initializer supports `app/` and `src/app/`, installs dependencies, wraps your dev command,
|
|
25
|
+
and adds generation to your typecheck script. It wraps the existing Next config with `withStudio`
|
|
26
|
+
so production builds also generate their registry on a clean checkout. The home page stays yours.
|
|
27
|
+
Use `studio init <directory>` to target another app, or `--no-install` to prepare files without installing.
|
|
28
|
+
Reruns skip identical files; conflicting or edited files stop initialization before writes.
|
|
29
|
+
If generation fails, initialization restores existing files and removes newly created setup files.
|
|
30
|
+
Route checks include route groups and handlers. If Studio is already declared, install a version
|
|
31
|
+
with the new exports before running init; an older or missing installation is rejected before writes.
|
|
32
|
+
Existing Studio integrations and custom shell dev scripts use the manual migration below.
|
|
33
|
+
|
|
34
|
+
## One folder, one tool
|
|
35
|
+
|
|
36
|
+
Write these three files anywhere under your active app directory:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
src/app/experiments/lighting/
|
|
40
|
+
page.tsx # Your normal Next.js preview
|
|
41
|
+
studio.tool.ts # Controls and explicit stable ID
|
|
42
|
+
config.json # Saved values
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`config.json`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{ "intensity": 1 }
|
|
49
|
+
```
|
|
17
50
|
|
|
18
51
|
```ts
|
|
19
|
-
//
|
|
52
|
+
// studio.tool.ts
|
|
20
53
|
import { defineTool } from '@joycostudio/studio'
|
|
21
|
-
import { property,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
54
|
+
import { property, slider } from '@joycostudio/studio/controls'
|
|
55
|
+
|
|
56
|
+
export default defineTool({
|
|
57
|
+
id: 'lighting',
|
|
29
58
|
controls: (config) => ({
|
|
30
|
-
|
|
31
|
-
tonemap: select(property(config, 'tonemap'), { ACES: 'aces', Filmic: 'filmic' }),
|
|
59
|
+
intensity: slider(property(config, 'intensity'), { min: 0, max: 5 }),
|
|
32
60
|
}),
|
|
33
61
|
})
|
|
34
62
|
```
|
|
35
63
|
|
|
36
|
-
|
|
64
|
+
Studio discovers `studio.tool.ts` and infers the JSON source, save destination, preview URL,
|
|
65
|
+
and title. `id` is explicit so moving the route does not change its identity. Controls receive
|
|
66
|
+
the JSON's inferred TypeScript shape through a generated module augmentation; imports always
|
|
67
|
+
come from `@joycostudio/studio`, never a per-tool generated helper.
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
The preview consumes the live config:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// src/app/experiments/lighting/page.tsx
|
|
73
|
+
'use client'
|
|
74
|
+
import { useToolConfig } from '@joycostudio/studio/react'
|
|
75
|
+
import { studio } from '../../../studio'
|
|
76
|
+
import lighting from './studio.tool'
|
|
77
|
+
|
|
78
|
+
export default function LightingPage() {
|
|
79
|
+
const config = useToolConfig(studio.tool(lighting))
|
|
80
|
+
return <div style={{ filter: `brightness(${config.intensity})` }}>Your scene</div>
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For a render loop, read `studio.tool(lighting).config` directly, or subscribe to changes with
|
|
85
|
+
`studio.tool(lighting).subscribe(apply)`. Tool definitions also load in Node: keep DOM, React
|
|
86
|
+
components, and renderer imports in your page or scene, not in `studio.tool.ts`.
|
|
87
|
+
|
|
88
|
+
### Overrides and discovery scope
|
|
40
89
|
|
|
41
90
|
```ts
|
|
42
|
-
export
|
|
91
|
+
export default defineTool({
|
|
92
|
+
id: 'lighting',
|
|
93
|
+
title: 'Scene Lighting',
|
|
94
|
+
config: '@/settings/lighting.json',
|
|
95
|
+
preview: '/scene',
|
|
96
|
+
controls: (config) => ({/* ... */}),
|
|
97
|
+
})
|
|
43
98
|
```
|
|
44
99
|
|
|
45
|
-
`
|
|
46
|
-
|
|
100
|
+
`config` is a single JSON path for both initial values and saves. It defaults to `./config.json`;
|
|
101
|
+
relative paths resolve from the tool file, and aliases resolve through the app's `tsconfig.json`,
|
|
102
|
+
including inherited paths. JSON must exist and remain inside the project. Two tools cannot write
|
|
103
|
+
the same file. For deterministic generation, default-export `defineTool({ ... })` directly:
|
|
104
|
+
`id`, `config`, and explicit `preview` must be string literals, with no spread/computed option keys.
|
|
105
|
+
Control callbacks can contain normal application logic.
|
|
47
106
|
|
|
48
|
-
|
|
107
|
+
An optional, **user-owned** `studio.config.ts` controls discovery:
|
|
49
108
|
|
|
50
109
|
```ts
|
|
51
|
-
// studio.config.ts (its directory is the project root)
|
|
52
110
|
import { defineConfig } from '@joycostudio/studio'
|
|
53
|
-
import { grading } from './src/tools/grading'
|
|
54
111
|
|
|
55
|
-
export default defineConfig({
|
|
112
|
+
export default defineConfig({
|
|
113
|
+
toolsDir: 'src/app/experiments',
|
|
114
|
+
// appUrl: 'http://localhost:3000', // Optional MCP capture URL
|
|
115
|
+
})
|
|
56
116
|
```
|
|
57
117
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
118
|
+
`toolsDir` is relative to the project root and limits scanning and watching. By default Studio uses
|
|
119
|
+
`app/`, or `src/app/` when there is no root `app/`. Narrowing it does not change preview URLs.
|
|
120
|
+
Hidden directories, node_modules, and symlinked directories are excluded. Route groups such as
|
|
121
|
+
`(experiments)` are stripped from inferred URLs. Dynamic, parallel, intercepted, private, or
|
|
122
|
+
outside-app routes need an explicit concrete `preview`; a missing adjacent page also needs one.
|
|
123
|
+
A preview-only tool can omit `controls` and JSON entirely: `defineTool({ id: 'gallery' })`.
|
|
61
124
|
|
|
62
|
-
|
|
63
|
-
`http://127.0.0.1:5179/mcp` (`--port` or `port` in the config changes both). It stops when the wrapped command exits.
|
|
125
|
+
### Generated files and startup
|
|
64
126
|
|
|
65
|
-
|
|
66
|
-
|
|
127
|
+
Studio owns only `.studio/registry.generated.ts` and `.studio/types.generated.ts`; ignore `.studio/`
|
|
128
|
+
in Git. The registry references the type map, so importing the registry includes its augmentation
|
|
129
|
+
in the application's TypeScript program. Scaffolded app files remain user-owned after init.
|
|
67
130
|
|
|
68
131
|
```ts
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
132
|
+
// src/studio.ts (scaffolded once)
|
|
133
|
+
import { createStudio } from '@joycostudio/studio/next'
|
|
134
|
+
import { tools } from '../.studio/registry.generated'
|
|
72
135
|
|
|
73
|
-
|
|
136
|
+
export const studio = createStudio({ tools })
|
|
137
|
+
```
|
|
74
138
|
|
|
75
139
|
```ts
|
|
76
|
-
//
|
|
77
|
-
import {
|
|
140
|
+
// next.config.ts
|
|
141
|
+
import { withStudio } from '@joycostudio/studio/next/config'
|
|
78
142
|
|
|
79
|
-
export
|
|
80
|
-
tools: [grading],
|
|
81
|
-
runtime: process.env.NODE_ENV === 'development' && `ws://127.0.0.1:${process.env.NEXT_PUBLIC_STUDIO_PORT}`,
|
|
82
|
-
})
|
|
143
|
+
export default withStudio({/* your existing Next options */})
|
|
83
144
|
```
|
|
84
145
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
146
|
+
`withStudio` accepts an object or an async/sync Next configuration function and generates before
|
|
147
|
+
`next dev` or `next build`. `studio dev -- next dev` generates first and owns discovery watching
|
|
148
|
+
when the runtime is running. Plain `next dev` also watches, but uses browser-only config syncing.
|
|
149
|
+
For standalone typechecking, run `studio generate && tsc --noEmit` (init configures this script).
|
|
150
|
+
If your TypeScript project excludes the client/registry, explicitly include `.studio/types.generated.ts`.
|
|
151
|
+
Restart the dev server after changing runtime port, origins, or capture URL settings.
|
|
152
|
+
|
|
153
|
+
During `studio dev`, value-only config changes travel through the runtime transport without
|
|
154
|
+
Next Fast Refresh or a preview remount, including saves and external JSON edits. The generated
|
|
155
|
+
registry embeds stable bootstrap values instead of importing the writable JSON at runtime;
|
|
156
|
+
type-only imports still infer controls from the source JSON. New pages receive current values
|
|
157
|
+
when they connect to the runtime. Changes to JSON structure/types regenerate the bootstrap values.
|
|
158
|
+
A new runtime session, plain `next dev`, `studio generate`, and production builds read fresh values.
|
|
159
|
+
Read live values through `studio.tool(...)` or `useToolConfig`; directly importing the JSON into
|
|
160
|
+
your page or tool helpers gives Next a dependency that will still trigger HMR on saves.
|
|
161
|
+
|
|
162
|
+
Adding/removing/renaming tools refreshes the registry. Normal page-content edits stay with Next's
|
|
163
|
+
HMR. Tool definitions and their local imported helpers are tracked, as are referenced JSON files
|
|
164
|
+
outside `toolsDir` and TypeScript alias settings. Output is written only when changed.
|
|
165
|
+
Missing or malformed JSON configs remain watched after a rejected update, so completing them
|
|
166
|
+
retries discovery automatically, including aliases outside `toolsDir`.
|
|
167
|
+
The runtime preserves valid drafts during refresh; removing or moving a dirty tool, or making
|
|
168
|
+
its draft incompatible with new controls, rejects the refresh with a diagnostic. Save/discard
|
|
169
|
+
that draft to retry. Invalid updates do not replace the last accepted registry. If a deleted
|
|
170
|
+
source is still imported by that registry, restore it before resolving the draft; Next may show
|
|
171
|
+
a temporary module-not-found error while a removed tool is being rediscovered.
|
|
172
|
+
|
|
173
|
+
Mount the editor route once (init creates it):
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
// src/app/studio/page.tsx
|
|
177
|
+
'use client'
|
|
178
|
+
import '@joycostudio/studio/styles.css'
|
|
179
|
+
import { Studio } from '@joycostudio/studio/react'
|
|
180
|
+
import { studio } from '../../studio'
|
|
181
|
+
|
|
182
|
+
export default function StudioPage() {
|
|
183
|
+
return <Studio studio={studio} />
|
|
184
|
+
}
|
|
89
185
|
```
|
|
90
186
|
|
|
187
|
+
### Existing manual integrations
|
|
188
|
+
|
|
189
|
+
Explicit `defineTool({ id, file, initial, controls, preview })` definitions and
|
|
190
|
+
`defineConfig({ tools: [...] })` remain supported. An explicit tools list disables discovery;
|
|
191
|
+
do not combine `tools` with `toolsDir`. To migrate, move the declarations into `studio.tool.ts`,
|
|
192
|
+
keep their IDs, replace `file`/`initial` with an optional `config` path, and default-export them.
|
|
193
|
+
Point the client at the generated registry and wrap Next config with `withStudio`.
|
|
194
|
+
For manual setups, install `@joycostudio/studio`, `@joycostudio/xyz`, and `tweakpane` in the app.
|
|
195
|
+
|
|
91
196
|
Live edits also work without `studio dev`, including in production. Studio shares configs between
|
|
92
197
|
same-origin previews and tabs in the same browser using `BroadcastChannel`. Peers match by tool ID,
|
|
93
198
|
config file, and runtime URL (or no runtime); a newly opened or reloaded preview receives the current
|
|
@@ -95,22 +200,41 @@ config from an open peer. Nothing is persisted: closing every page resets these
|
|
|
95
200
|
config. Save, Discard, and MCP still require a runtime. If a configured runtime disconnects, browser
|
|
96
201
|
sync continues; reconnecting restores the runtime's draft.
|
|
97
202
|
|
|
98
|
-
Floating debug pane, with the same controls
|
|
203
|
+
Floating debug pane, with the same controls and save/discard actions:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
// Mount once in a client component that imports your studio instance.
|
|
207
|
+
import { StudioDebug } from '@joycostudio/studio/react'
|
|
208
|
+
|
|
209
|
+
;<StudioDebug studio={studio} />
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Studio owns lazy pane creation, frame refresh, registration, and cleanup through Xyz's
|
|
213
|
+
DebugManager. `?debug` and Option+D keep their existing behavior. The pane and command menu
|
|
214
|
+
are optional; neither is needed for `/studio`.
|
|
215
|
+
|
|
216
|
+
The Next.js adapter reads the endpoint passed by `studio dev`, including port overrides and
|
|
217
|
+
collision fallback. No `next.config` changes or hand-written environment variables are needed.
|
|
218
|
+
It defaults to browser-only mode without the CLI or in production. Restart the dev server when
|
|
219
|
+
changing runtime settings. Other frameworks can use the explicit client API:
|
|
99
220
|
|
|
100
221
|
```ts
|
|
101
|
-
import {
|
|
102
|
-
import
|
|
103
|
-
import { Pane } from 'tweakpane'
|
|
222
|
+
import { createStudio } from '@joycostudio/studio/client'
|
|
223
|
+
import config from '../studio.config'
|
|
104
224
|
|
|
105
|
-
const
|
|
106
|
-
|
|
225
|
+
export const studio = createStudio(config, { runtime: 'ws://127.0.0.1:5179' })
|
|
226
|
+
// Pass runtime: false for browser-only operation.
|
|
107
227
|
```
|
|
108
228
|
|
|
229
|
+
Existing `createStudio({ tools, runtime })` calls remain supported. Both entry points accept
|
|
230
|
+
an explicit runtime override, including `false`.
|
|
231
|
+
|
|
109
232
|
The `/studio` route:
|
|
110
233
|
|
|
111
234
|
```tsx
|
|
112
235
|
// app/studio/page.tsx
|
|
113
236
|
'use client'
|
|
237
|
+
import '@joycostudio/studio/styles.css'
|
|
114
238
|
import { Studio } from '@joycostudio/studio/react'
|
|
115
239
|
import { studio } from '@/studio'
|
|
116
240
|
|
|
@@ -119,6 +243,10 @@ export default function StudioPage() {
|
|
|
119
243
|
}
|
|
120
244
|
```
|
|
121
245
|
|
|
246
|
+
Studio and the command menu render in their own shadow roots and bundle their styles, so neither needs a
|
|
247
|
+
stylesheet import. Host page CSS (global resets, `!important` rules, theme variables such as shadcn's
|
|
248
|
+
`--foreground`, or a resized `<html>` font) can't change them, and their styles never reach the host page.
|
|
249
|
+
|
|
122
250
|
Use the pin button beside a sidebar tool to keep it at the top. Pinned tools also appear first in the
|
|
123
251
|
**Command/Ctrl+K** menu. Pins are saved in this browser for the current origin and shared across tabs;
|
|
124
252
|
unpinning restores registration order. Pinning a tool keeps the current preview selected.
|
|
@@ -148,14 +276,16 @@ in the same tab. New tabs start with panels shown and the canvas contained.
|
|
|
148
276
|
|
|
149
277
|
### Shared canvas and panel components
|
|
150
278
|
|
|
151
|
-
Import these from `@joycostudio/studio/react`
|
|
279
|
+
Import these from `@joycostudio/studio/react`. Inside Studio (for example, in a `previewComponent`) they
|
|
280
|
+
are already styled; to use them on your own pages, such as a custom-layout tool preview, also import
|
|
281
|
+
`@joycostudio/studio/styles.css` there:
|
|
152
282
|
|
|
153
283
|
| Component | Use |
|
|
154
284
|
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
155
285
|
| `StudioSurface`, `StudioToolbar` | Shared inset card and compact action row, including button states. |
|
|
156
286
|
| `CanvasToolbar` | Pinned/auto-hide header; `label`, optional `expanded`/`onToggle`, and child controls. |
|
|
157
287
|
| `PreviewCanvas` | Iframe panel with reload, expansion, and a `toolbar` slot replacing the title. |
|
|
158
|
-
| `StudioSelect`, `StudioSelectItem` | Labeled
|
|
288
|
+
| `StudioSelect`, `StudioSelectItem` | Labeled Base UI select with Studio field and portaled menu styling. |
|
|
159
289
|
| `StudioInput` | Standard input with logical padding (`ps-*`/`pe-*`) for icon-bearing fields. |
|
|
160
290
|
| `StudioPanel`, `StudioPanelHeader`, `StudioPanelContent`, `StudioPanelFooter` | Floating panel surface, inset sections, and scrolling body used by Studio's inspector. |
|
|
161
291
|
|
|
@@ -173,7 +303,9 @@ these imports. Module graphs, tracing, and scene-specific controls remain host-o
|
|
|
173
303
|
|
|
174
304
|
`Studio` accepts `previewComponent` and `getConfigFileHref`, so integrations no longer need to patch
|
|
175
305
|
the built package. Keep a custom preview component at module scope and forward its iframe ref and
|
|
176
|
-
props; Studio uses them for keyboard shortcuts, comparison, and tool-switch lifecycle
|
|
306
|
+
props; Studio uses them for keyboard shortcuts, comparison, and tool-switch lifecycle. The component
|
|
307
|
+
renders inside Studio's shadow root, so host stylesheets don't apply to it: build its chrome from the
|
|
308
|
+
shared components below (Studio's Tailwind utilities are available too) or inline styles.
|
|
177
309
|
|
|
178
310
|
```tsx
|
|
179
311
|
import { forwardRef, type ComponentPropsWithoutRef } from 'react'
|
|
@@ -211,7 +343,6 @@ Mount `StudioCommandMenu` once in the host app's root layout so it stays availab
|
|
|
211
343
|
```tsx
|
|
212
344
|
// src/command-menu.tsx
|
|
213
345
|
'use client'
|
|
214
|
-
import '@joycostudio/studio/command-menu.css'
|
|
215
346
|
import { StudioCommandMenu } from '@joycostudio/studio/react'
|
|
216
347
|
import { studio } from './studio'
|
|
217
348
|
|
|
@@ -221,7 +352,8 @@ export function CommandMenu() {
|
|
|
221
352
|
```
|
|
222
353
|
|
|
223
354
|
Render `<CommandMenu />` alongside the layout's `{children}`. The host owns mounting it, just like the floating debug pane.
|
|
224
|
-
|
|
355
|
+
It renders in its own shadow root with its styles bundled, so it needs no stylesheet import and is unaffected by the
|
|
356
|
+
host's CSS. (`@joycostudio/studio/command-menu.css` is now empty and only kept so existing imports resolve.)
|
|
225
357
|
|
|
226
358
|
Press **Command+K** (Mac) or **Ctrl+K** to open or close it, type a tool name or ID to search, use **↑/↓** and **Enter** to
|
|
227
359
|
select, or **Escape** to dismiss. Clicking a result also opens it. Results link to `/studio#tool-id`; switching tools from
|