@joycostudio/studio 0.1.4 → 0.1.6

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
@@ -197,8 +197,9 @@ Live edits also work without `studio dev`, including in production. Studio share
197
197
  same-origin previews and tabs in the same browser using `BroadcastChannel`. Peers match by tool ID,
198
198
  config file, and runtime URL (or no runtime); a newly opened or reloaded preview receives the current
199
199
  config from an open peer. Nothing is persisted: closing every page resets these edits to the shipped
200
- config. Save, Discard, and MCP still require a runtime. If a configured runtime disconnects, browser
201
- sync continues; reconnecting restores the runtime's draft.
200
+ config. Discard works the same either way: it resets the draft to the saved config (the runtime's last
201
+ reported one, or the shipped config) and syncs like any edit. Save and MCP require a runtime. If a configured
202
+ runtime disconnects, browser sync continues; reconnecting restores the runtime's draft.
202
203
 
203
204
  Floating debug pane, with the same controls and save/discard actions:
204
205
 
@@ -257,6 +258,20 @@ Undo is per tool and per page: it restores only the values you changed, leaving
257
258
  in place, and syncs like any other edit. Text fields keep their native undo while focused. History is not persisted.
258
259
  `tool.undo()`, `tool.redo()`, `tool.canUndo`, and `tool.canRedo` expose the same history to host code.
259
260
 
261
+ The inspector footer summarizes unsaved changes on one line. Click it to expand the footer into the full list: each changed
262
+ value shows its saved and current value (with a swatch for colors) and a revert button that resets just that value. A revert
263
+ syncs like any edit and can be undone. The list collapses once nothing is left to save. `tool.diff` lists the same
264
+ `{ path, before, after }` changes, and `tool.revert(path)` resets one from host code.
265
+
266
+ To share a config with a teammate, click the link button in the inspector footer (or **Copy link** in the floating debug
267
+ pane). It copies the current page's URL with the tool's draft encoded in a `studio-share` query parameter; links from
268
+ Studio also select the tool. Opening the link applies that draft as an unsaved edit on top of whatever the recipient has,
269
+ with or without a runtime: it syncs to their previews, other tabs, and runtime like any edit, can be undone, and still needs
270
+ Save to reach the JSON file. The parameter is removed from the address bar once applied, so reloading doesn't reapply it.
271
+ A draft that no longer matches the tool's controls (for example, from another branch) is ignored with a console warning.
272
+ If a configured runtime is unreachable when the link opens, the draft applies locally and the runtime's draft wins once it
273
+ connects. `tool.shareUrl(base?)` builds the same link from host code.
274
+
260
275
  In Studio, **Command+B** (Mac) or **Ctrl+B** slides both side panels out of view; press it again to bring them back.
261
276
  This also works from the same-origin preview iframe. The sidebar's collapse/expand button controls its width separately,
262
277
  and that choice is preserved when hiding and showing the panels. Move the pointer near a hidden panel's screen edge to
@@ -19,9 +19,6 @@ function diff(before, after, path = []) {
19
19
  }
20
20
  return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path, before, after }];
21
21
  }
22
- function changedPaths(before, after) {
23
- return diff(before, after).map(({ path }) => path.join("."));
24
- }
25
22
 
26
23
  // src/client/bridge.ts
27
24
  function connectBridge(url, handlers) {
@@ -105,7 +102,8 @@ function connectBrowserSync(definition, runtime, handlers) {
105
102
  return {
106
103
  publish(value) {
107
104
  if (closed) return;
108
- latest = { type: "config", clock: ++clock, sender, value: structuredClone(value) };
105
+ clock = Math.max(clock + 1, Date.now());
106
+ latest = { type: "config", clock, sender, value: structuredClone(value) };
109
107
  channel.postMessage(latest);
110
108
  },
111
109
  close() {
@@ -199,6 +197,7 @@ function live(target, onWrite) {
199
197
  return wrap(target);
200
198
  }
201
199
  function assignDeep(target, source) {
200
+ for (const key of Object.keys(target)) if (!(key in source)) delete target[key];
202
201
  for (const [key, value] of Object.entries(source)) {
203
202
  const current = target[key];
204
203
  const nested = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -208,6 +207,47 @@ function assignDeep(target, source) {
208
207
  }
209
208
  }
210
209
 
210
+ // src/client/share.ts
211
+ var PARAM = "studio-share";
212
+ var toBase64Url = (text) => {
213
+ let binary = "";
214
+ for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);
215
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
216
+ };
217
+ var fromBase64Url = (data) => new TextDecoder().decode(
218
+ Uint8Array.from(atob(data.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0))
219
+ );
220
+ function shareUrl(id, config, base = location.href) {
221
+ const url = new URL(base);
222
+ url.searchParams.set(PARAM, `${id}.${toBase64Url(JSON.stringify(config))}`);
223
+ return url.href;
224
+ }
225
+ function readShared(id) {
226
+ if (typeof window === "undefined" || !window.location?.search) return null;
227
+ const param = new URLSearchParams(location.search).get(PARAM);
228
+ if (!param?.startsWith(`${id}.`)) return null;
229
+ try {
230
+ return { value: JSON.parse(fromBase64Url(param.slice(id.length + 1))) };
231
+ } catch {
232
+ console.warn(`[studio] ${id}: ignoring a malformed share link`);
233
+ return null;
234
+ }
235
+ }
236
+ function clearShared() {
237
+ const url = new URL(location.href);
238
+ if (!url.searchParams.has(PARAM)) return;
239
+ url.searchParams.delete(PARAM);
240
+ history.replaceState(history.state, "", url);
241
+ }
242
+ async function copyLink(url) {
243
+ if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(url);
244
+ window.prompt("Copy this link", url);
245
+ }
246
+ function afterLoad(apply) {
247
+ if (document.readyState === "complete") setTimeout(apply);
248
+ else addEventListener("load", () => setTimeout(apply), { once: true });
249
+ }
250
+
211
251
  // src/client/studio.ts
212
252
  var COMPARE_MESSAGE = "joyco-studio:compare";
213
253
  function postCompare(target, tool, showSaved) {
@@ -264,14 +304,19 @@ function createStudio(config, options = {}) {
264
304
  version++;
265
305
  for (const listener of studioListeners) listener();
266
306
  };
307
+ const runtimeOwnsDrafts = () => connection.phase === "connected";
267
308
  const createHandle = (definition) => {
268
309
  const raw = structuredClone(definition.initial);
269
310
  const listeners = /* @__PURE__ */ new Set();
270
311
  let pushing = Promise.resolve();
271
312
  let scheduled = false;
313
+ let saved = definition.initial;
272
314
  let stashed = null;
273
- const history = createHistory();
315
+ const history2 = createHistory();
274
316
  let snapshot = structuredClone(raw);
317
+ const draft = () => stashed ?? raw;
318
+ let shared = readShared(definition.id);
319
+ let closed = false;
275
320
  const changed = () => {
276
321
  snapshot = structuredClone(raw);
277
322
  handle.version++;
@@ -280,25 +325,24 @@ function createStudio(config, options = {}) {
280
325
  };
281
326
  const adopt = (state) => {
282
327
  handle.state = state;
328
+ saved = state.saved.value;
283
329
  touch();
284
330
  };
285
- const receive = (state) => {
286
- if (stashed) stashed = structuredClone(state.draft.value);
287
- else assignDeep(raw, state.draft.value);
288
- adopt(state);
289
- if (!stashed) changed();
290
- browserSync?.publish(state.draft.value);
291
- };
292
- const resync = async () => {
293
- const result = await bridge?.request("tool.get", { tool: definition.id });
294
- if (result?.ok) receive(result.data);
331
+ const replace = (value) => {
332
+ if (stashed) {
333
+ stashed = structuredClone(value);
334
+ touch();
335
+ } else {
336
+ assignDeep(raw, value);
337
+ changed();
338
+ }
295
339
  };
296
340
  const push = () => {
297
341
  if (scheduled || !bridge) return;
298
342
  scheduled = true;
299
343
  pushing = Promise.resolve().then(async () => {
300
344
  scheduled = false;
301
- const result = await bridge?.request("tool.update", { tool: definition.id, value: raw });
345
+ const result = await bridge?.request("tool.update", { tool: definition.id, value: draft() });
302
346
  if (!result) return;
303
347
  if (result.ok) adopt(result.data);
304
348
  else if (result.error !== "runtime_unavailable") {
@@ -307,32 +351,65 @@ function createStudio(config, options = {}) {
307
351
  }
308
352
  });
309
353
  };
354
+ const sync = () => {
355
+ browserSync?.publish(draft());
356
+ if (runtimeOwnsDrafts()) push();
357
+ };
358
+ const receive = (state) => {
359
+ adopt(state);
360
+ replace(state.draft.value);
361
+ browserSync?.publish(draft());
362
+ };
363
+ const resync = async () => {
364
+ const result = await bridge?.request("tool.get", { tool: definition.id });
365
+ if (result?.ok) receive(result.data);
366
+ };
310
367
  const edited = () => {
368
+ if (stashed) return changed();
369
+ history2.record(snapshot, raw);
311
370
  changed();
312
- if (!stashed) {
313
- browserSync?.publish(raw);
314
- push();
315
- }
371
+ sync();
372
+ };
373
+ const hydrate = () => {
374
+ if (!shared) return;
375
+ const { value } = shared;
376
+ shared = null;
377
+ afterLoad(() => {
378
+ if (closed) return;
379
+ clearShared();
380
+ const result = validateConfig(definition, value);
381
+ if (!result.ok) return console.warn(`[studio] ${definition.id}: ignoring shared config: ${result.message}`);
382
+ handle.compare(false);
383
+ commit(() => assignDeep(raw, result.data));
384
+ });
385
+ };
386
+ const commit = (apply) => {
387
+ const before = snapshot;
388
+ apply();
389
+ changed();
390
+ history2.record(before, raw, { merge: false });
391
+ sync();
316
392
  };
317
393
  const restore = (patch) => {
318
394
  if (!patch) return false;
319
395
  for (const { path, value } of patch) writePath(raw, path, value);
320
- edited();
396
+ changed();
397
+ sync();
321
398
  return true;
322
399
  };
323
400
  const handle = {
324
401
  definition,
325
- config: live(raw, () => {
326
- if (!stashed) history.record(snapshot, raw);
327
- edited();
328
- }),
402
+ config: live(raw, edited),
329
403
  state: null,
330
404
  version: 0,
331
405
  get saved() {
332
- return handle.state?.saved.value ?? definition.initial;
406
+ return saved;
407
+ },
408
+ get diff() {
409
+ return diff(saved, draft());
333
410
  },
334
411
  get changes() {
335
- return changedPaths(handle.saved, stashed ?? raw);
412
+ return handle.diff.map(({ path }) => path.join("."));
336
413
  },
337
414
  get dirty() {
338
415
  return handle.changes.length > 0;
@@ -341,30 +418,31 @@ function createStudio(config, options = {}) {
341
418
  compare(showSaved) {
342
419
  if (showSaved === handle.comparing) return;
343
420
  if (showSaved) stashed = structuredClone(raw);
344
- const next = showSaved ? handle.saved : stashed;
421
+ const next = showSaved ? saved : stashed;
345
422
  assignDeep(raw, next);
346
423
  if (!showSaved) stashed = null;
347
424
  handle.comparing = showSaved;
348
425
  changed();
349
426
  },
350
427
  get canUndo() {
351
- return history.canUndo;
428
+ return history2.canUndo;
352
429
  },
353
430
  get canRedo() {
354
- return history.canRedo;
431
+ return history2.canRedo;
355
432
  },
356
- undo: () => !stashed && restore(history.undo()),
357
- redo: () => !stashed && restore(history.redo()),
433
+ undo: () => !stashed && restore(history2.undo()),
434
+ redo: () => !stashed && restore(history2.redo()),
358
435
  subscribe(listener) {
359
436
  listeners.add(listener);
360
437
  return () => void listeners.delete(listener);
361
438
  },
362
439
  controls: () => definition.controls(handle.config),
363
440
  debug() {
364
- const actions = runtime ? {
365
- save: button("Save", () => handle.save().then(fail), "Saved \u2713"),
366
- discard: button("Discard", () => handle.discard().then(fail), "Discarded")
367
- } : {};
441
+ const actions = {
442
+ ...runtime ? { save: button("Save", () => handle.save().then(fail), "Saved \u2713") } : {},
443
+ discard: button("Discard", () => void handle.discard(), "Discarded"),
444
+ share: button("Copy link", () => copyLink(handle.shareUrl()), "Copied \u2713")
445
+ };
368
446
  return {
369
447
  title: definition.title ?? definition.id,
370
448
  schema: () => ({ ...liveSchema(handle.controls()), ...actions }),
@@ -380,33 +458,43 @@ function createStudio(config, options = {}) {
380
458
  if (result.ok) adopt(result.data);
381
459
  return result;
382
460
  },
383
- async discard() {
384
- if (!bridge) return error("runtime_unavailable");
385
- await pushing;
461
+ discard() {
462
+ if (!handle.dirty) return false;
386
463
  const before = snapshot;
387
- const result = await bridge.request("tool.discard", { tool: definition.id });
388
- if (result.ok) {
389
- receive(result.data);
390
- if (!stashed) history.record(before, raw, { merge: false });
391
- }
392
- return result;
464
+ replace(saved);
465
+ if (!stashed) history2.record(before, raw, { merge: false });
466
+ sync();
467
+ return true;
468
+ },
469
+ revert(path) {
470
+ const change = handle.diff.find(
471
+ (change2) => typeof path === "string" ? change2.path.join(".") === path : change2.path.length === path.length && change2.path.every((key, i) => key === path[i])
472
+ );
473
+ if (stashed || !change) return false;
474
+ commit(() => writePath(raw, change.path, change.before));
475
+ return true;
476
+ },
477
+ shareUrl: (base) => shareUrl(definition.id, draft(), base),
478
+ /**
479
+ * Connection changes: the runtime's draft wins on (re)connect; without one there is no runtime state.
480
+ * A share link applies on top of the runtime's draft, or locally once the runtime proves unreachable.
481
+ */
482
+ connection() {
483
+ if (runtimeOwnsDrafts()) return void resync().then(hydrate);
484
+ handle.state = null;
485
+ if (connection.phase === "offline") hydrate();
393
486
  },
394
487
  receive,
395
- resync,
396
- close: () => browserSync?.close()
488
+ close() {
489
+ closed = true;
490
+ browserSync?.close();
491
+ }
397
492
  };
398
493
  const browserSync = connectBrowserSync(definition, runtime, {
399
- enabled: () => connection.phase !== "connected",
400
- receive(value) {
401
- if (stashed) {
402
- stashed = value;
403
- touch();
404
- } else {
405
- assignDeep(raw, value);
406
- changed();
407
- }
408
- }
494
+ enabled: () => !runtimeOwnsDrafts(),
495
+ receive: replace
409
496
  });
497
+ if (!runtime) hydrate();
410
498
  return handle;
411
499
  };
412
500
  const handles = new Map(tools.filter(hasConfig).map((definition) => [definition.id, createHandle(definition)]));
@@ -421,8 +509,8 @@ function createStudio(config, options = {}) {
421
509
  state: (state) => handles.get(state.tool)?.receive(state),
422
510
  connection(next) {
423
511
  connection = next;
512
+ for (const handle of handles.values()) handle.connection();
424
513
  touch();
425
- if (next.phase === "connected") for (const handle of handles.values()) void handle.resync();
426
514
  }
427
515
  });
428
516
  }
@@ -467,4 +555,4 @@ export {
467
555
  everyFrame,
468
556
  createStudio
469
557
  };
470
- //# sourceMappingURL=chunk-2RGQF5X5.js.map
558
+ //# sourceMappingURL=chunk-6KZJEKOL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/studio.ts","../src/core/diff.ts","../src/client/bridge.ts","../src/client/browser-sync.ts","../src/client/history.ts","../src/client/live.ts","../src/client/share.ts"],"sourcesContent":["import {\n button,\n type DebugDefinition,\n type DebugField,\n type DebugFolder,\n type DebugSchema,\n schemaValues,\n} from '@joycostudio/xyz/debug'\nimport { diff, type Change } from '../core/diff'\nimport type { ToolState } from '../core/protocol'\nimport { error, type Result } from '../core/result'\nimport { hasConfig, type StudioTool, type ToolDefinition, type ToolReference } from '../core/tool'\nimport { validateConfig } from '../core/validate'\nimport { connectBridge, type BridgeClient, type Connection } from './bridge'\nimport { connectBrowserSync } from './browser-sync'\nimport { createHistory, writePath } from './history'\nimport { assignDeep, live } from './live'\nimport { afterLoad, clearShared, copyLink, readShared, shareUrl } from './share'\n\nexport type ToolHandle<TConfig extends object = any> = {\n readonly definition: ToolDefinition<TConfig>\n /** The live config. Writes sync to same-origin pages and, when connected, the runtime. */\n readonly config: TConfig\n /** Runtime-owned state; null while no runtime is connected or until it answers. */\n readonly state: ToolState<TConfig> | null\n /** Bumps on every config change. Handy as a React snapshot. */\n readonly version: number\n /** Called after every config change, local or remote. */\n subscribe(listener: (config: TConfig) => void): () => void\n /** The tool's Xyz schema bound to the live config. */\n controls(): DebugSchema\n /** Xyz `Debuggable`: `debugManager.register(handle)` mounts this tool in the app's floating pane. */\n debug(): DebugFolder\n /** The config on disk: the runtime's last reported saved value or, until one answers, the bundled `initial`. */\n readonly saved: TConfig\n /** Dotted paths where the draft differs from `saved`. Tracked locally, so it works without a runtime too. */\n readonly changes: string[]\n /** Each leaf where the draft differs from `saved`: `before` is the saved value, `after` the draft's. */\n readonly diff: Change[]\n /** The draft differs from `saved`. */\n readonly dirty: boolean\n /** True while `compare(true)` shows the saved config in place of the draft. */\n readonly comparing: boolean\n /**\n * Temporarily show `saved` (true) or go back to the draft (false). Local only: nothing\n * reaches the runtime, and edits made while comparing are dropped when it ends.\n */\n compare(showSaved: boolean): void\n /** True when `undo()` has an edit made on this page to step back through. */\n readonly canUndo: boolean\n readonly canRedo: boolean\n /**\n * Step back through edits made on this page (a slider drag counts as one), then sync the result like any\n * other edit. Only the leaves each edit changed are restored. Returns false with nothing to undo or while comparing.\n */\n undo(): boolean\n redo(): boolean\n /** Persist the draft. Needs a connected runtime. */\n save(options?: { force?: boolean }): Promise<Result<ToolState<TConfig>>>\n /**\n * Reset the draft to `saved`, with or without a runtime, then sync it like any other edit.\n * Undoable as its own step. Returns false when there is nothing to discard.\n */\n discard(): boolean\n /**\n * Reset one changed leaf (a dotted path from `changes`, or its keys, which also address keys containing dots) to its saved value, then sync it like any\n * other edit. Undoable as its own step. Returns false when that leaf is unchanged or while comparing.\n */\n revert(path: string | readonly string[]): boolean\n /**\n * A link to `base` (the current page by default) carrying the draft. Opening it on another machine applies\n * that draft as an unsaved, undoable edit; a draft that no longer fits the tool's controls is ignored.\n */\n shareUrl(base?: string | URL): string\n}\n\nexport type Studio = {\n /** Every registered tool in order, including preview-only ones. */\n readonly definitions: readonly StudioTool[]\n /** A handle per tool with a config. Preview-only tools have none. */\n readonly tools: readonly ToolHandle[]\n tool<TConfig extends object>(definition: ToolReference<TConfig>): ToolHandle<TConfig>\n readonly connection: Connection\n /** Bumps on connection or any tool state change. */\n readonly version: number\n subscribe(listener: () => void): () => void\n close(): void\n}\n\nexport type StudioOptions = {\n tools: readonly StudioTool[]\n /**\n * Runtime bridge URL (e.g. `ws://127.0.0.1:5179`). Omit or pass false for browser-only syncing, as in production.\n * Only browsers connect, so a module-scope `createStudio` is safe to evaluate during SSR.\n */\n runtime?: string | false\n}\n\nconst COMPARE_MESSAGE = 'joyco-studio:compare'\ntype CompareMessage = { type: typeof COMPARE_MESSAGE; tool: string; showSaved: boolean }\n\n/** Make the studio running in `target` (e.g. a preview iframe) compare `tool` as well. */\nexport function postCompare(target: Window | null | undefined, tool: string, showSaved: boolean) {\n const message: CompareMessage = { type: COMPARE_MESSAGE, tool, showSaved }\n target?.postMessage(message, location.origin)\n}\n\n/** Mark every value control live so a pane registered with `registerFrame` reflects remote edits. */\nfunction liveSchema(schema: DebugSchema): DebugSchema {\n return Object.fromEntries(\n Object.entries(schema).map(([key, field]): [string, DebugField] => {\n if (field.kind === 'button') return [key, field]\n if (field.kind === 'folder')\n return [\n key,\n { ...field, definition: { ...field.definition, schema: () => liveSchema(field.definition.schema()) } },\n ]\n return [key, { ...field, live: true }]\n }),\n )\n}\n\n/**\n * Xyz `extend` for a folder bound to `handle.controls()` (optionally followed by more fields, as in `debug()`):\n * double-clicking a slider resets it to its saved value.\n */\nexport function resetSlidersOnDoubleClick(handle: ToolHandle): NonNullable<DebugDefinition['extend']> {\n return (folder, { fields }) => {\n const sliders = fields().filter(({ field }) => field.kind === 'slider')\n const onDoubleClick = (event: MouseEvent) => {\n // Double-clicking the number input selects its text instead.\n if (handle.comparing || (event.target as Element).closest('input')) return\n const slider = sliders.find(({ blade }) => blade.element.contains(event.target as Node))\n if (slider?.field.kind !== 'slider') return\n const saved = slider.path.reduce<unknown>(\n (values, key) => (values as Record<string, unknown> | undefined)?.[key],\n schemaValues(handle.definition.controls(structuredClone(handle.saved))),\n )\n if (typeof saved === 'number') slider.field.write(saved)\n }\n folder.element.addEventListener('dblclick', onDoubleClick)\n return () => folder.element.removeEventListener('dblclick', onDoubleClick)\n }\n}\n\n/** A `registerFrame` for Xyz's `DebugManager` / `bindDebugSchema`: refreshes live controls every animation frame. */\nexport function everyFrame(refresh: () => void) {\n let frame = requestAnimationFrame(function tick() {\n refresh()\n frame = requestAnimationFrame(tick)\n })\n return () => cancelAnimationFrame(frame)\n}\n\n/** One studio per runtime URL (or local tool set) per page; replace HMR instances without leaking transports. */\nconst instances: Map<string, Studio> = ((globalThis as { __joycoStudio?: Map<string, Studio> }).__joycoStudio ??=\n new Map())\n\nexport function createStudio(config: StudioOptions, options: Pick<StudioOptions, 'runtime'> = {}): Studio {\n const { tools } = config\n const runtime = options.runtime ?? config.runtime\n const instanceKey = runtime || JSON.stringify(tools.map(({ id, file }) => [id, file]).sort())\n let bridge: BridgeClient | null = null\n let connection: Connection = { phase: runtime ? 'connecting' : 'local' }\n let version = 0\n const studioListeners = new Set<() => void>()\n const touch = () => {\n version++\n for (const listener of studioListeners) listener()\n }\n\n /**\n * Who owns drafts: the runtime while connected, otherwise same-origin pages share them over BroadcastChannel.\n * The only place a draft change depends on the connection; reconnecting hands ownership back to the runtime.\n */\n const runtimeOwnsDrafts = () => connection.phase === 'connected'\n\n const createHandle = <TConfig extends object>(definition: ToolDefinition<TConfig>) => {\n const raw = structuredClone(definition.initial)\n const listeners = new Set<(config: TConfig) => void>()\n let pushing: Promise<unknown> = Promise.resolve()\n let scheduled = false\n /** The last saved config the runtime reported, kept while it is away; the bundled `initial` until one answers. */\n let saved: TConfig = definition.initial\n /** The draft to restore while comparing; null when showing the draft. */\n let stashed: TConfig | null = null\n const history = createHistory()\n /** A copy of `raw` as of the last change, so the next local edit can be diffed against it. */\n let snapshot = structuredClone(raw)\n /** The draft, even while `raw` shows the saved config for comparing. */\n const draft = () => stashed ?? raw\n /** A draft from this page's share link, applied once the runtime (if any) has had its say. */\n let shared = readShared(definition.id)\n let closed = false\n\n const changed = () => {\n snapshot = structuredClone(raw)\n handle.version++\n for (const listener of listeners) listener(handle.config)\n touch()\n }\n const adopt = (state: ToolState) => {\n handle.state = state as ToolState<TConfig>\n saved = state.saved.value as TConfig\n touch()\n }\n /** Replace the whole draft. While comparing, the shown saved config stays put; `changes` reads the new draft. */\n const replace = (value: TConfig) => {\n if (stashed) {\n stashed = structuredClone(value)\n touch()\n } else {\n assignDeep(raw as Record<string, unknown>, value as Record<string, unknown>)\n changed()\n }\n }\n const push = () => {\n if (scheduled || !bridge) return\n scheduled = true\n pushing = Promise.resolve().then(async () => {\n scheduled = false\n const result = await bridge?.request('tool.update', { tool: definition.id, value: draft() })\n if (!result) return\n if (result.ok) adopt(result.data)\n else if (result.error !== 'runtime_unavailable') {\n console.warn(`[studio] ${definition.id}: ${result.message ?? result.error}`)\n await resync()\n }\n })\n }\n /**\n * Send a local draft change on: to the runtime while it owns drafts. Same-origin pages always get a copy,\n * so one opened after the runtime stops starts from it, but they only read it while no runtime is connected.\n */\n const sync = () => {\n browserSync?.publish(draft())\n if (runtimeOwnsDrafts()) push()\n }\n /** The runtime's state, from a broadcast or a reply. */\n const receive = (state: ToolState) => {\n adopt(state)\n replace(state.draft.value as TConfig)\n browserSync?.publish(draft())\n }\n const resync = async () => {\n const result = await bridge?.request('tool.get', { tool: definition.id })\n if (result?.ok) receive(result.data)\n }\n /** Every local change to the live config. */\n const edited = () => {\n // Edits made while comparing stay on this page and are dropped when it ends.\n if (stashed) return changed()\n history.record(snapshot, raw)\n changed()\n sync()\n }\n /** Apply the share link as a local edit, so it syncs and undoes like one. Runs once. */\n const hydrate = () => {\n if (!shared) return\n const { value } = shared\n shared = null\n afterLoad(() => {\n if (closed) return\n clearShared()\n const result = validateConfig(definition, value)\n if (!result.ok) return console.warn(`[studio] ${definition.id}: ignoring shared config: ${result.message}`)\n handle.compare(false)\n commit(() => assignDeep(raw as Record<string, unknown>, result.data as Record<string, unknown>))\n })\n }\n /** Apply a change to the draft as an undo step of its own, never merged with the edits around it. */\n const commit = (apply: () => void) => {\n const before = snapshot\n apply()\n changed()\n history.record(before, raw, { merge: false })\n sync()\n }\n const restore = (patch: ReturnType<typeof history.undo>) => {\n if (!patch) return false\n for (const { path, value } of patch) writePath(raw as Record<string, unknown>, path, value)\n changed()\n sync()\n return true\n }\n\n const handle = {\n definition,\n config: live(raw, edited),\n state: null as ToolState<TConfig> | null,\n version: 0,\n get saved(): TConfig {\n return saved\n },\n get diff() {\n return diff(saved, draft())\n },\n get changes() {\n return handle.diff.map(({ path }) => path.join('.'))\n },\n get dirty() {\n return handle.changes.length > 0\n },\n comparing: false,\n compare(showSaved: boolean) {\n if (showSaved === handle.comparing) return\n if (showSaved) stashed = structuredClone(raw)\n const next = showSaved ? saved : stashed!\n assignDeep(raw as Record<string, unknown>, next as Record<string, unknown>)\n if (!showSaved) stashed = null\n handle.comparing = showSaved\n changed()\n },\n get canUndo() {\n return history.canUndo\n },\n get canRedo() {\n return history.canRedo\n },\n undo: () => !stashed && restore(history.undo()),\n redo: () => !stashed && restore(history.redo()),\n subscribe(listener: (config: TConfig) => void) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n controls: () => definition.controls(handle.config),\n debug(): DebugFolder {\n const actions: DebugSchema = {\n ...(runtime ? { save: button('Save', () => handle.save().then(fail), 'Saved ✓') } : {}),\n discard: button('Discard', () => void handle.discard(), 'Discarded'),\n share: button('Copy link', () => copyLink(handle.shareUrl()), 'Copied ✓'),\n }\n return {\n title: definition.title ?? definition.id,\n schema: () => ({ ...liveSchema(handle.controls()), ...actions }),\n extend: resetSlidersOnDoubleClick(handle),\n }\n },\n async save({ force = false } = {}) {\n if (!bridge) return error('runtime_unavailable')\n await pushing\n const revision = handle.state?.draft.revision\n if (revision === undefined) return error('runtime_unavailable')\n const result = await bridge.request('tool.save', { tool: definition.id, revision, force })\n if (result.ok) adopt(result.data)\n return result as Result<ToolState<TConfig>>\n },\n discard() {\n if (!handle.dirty) return false\n const before = snapshot\n replace(saved)\n // Undoable, as a step of its own.\n if (!stashed) history.record(before, raw, { merge: false })\n sync()\n return true\n },\n revert(path: string | readonly string[]) {\n const change = handle.diff.find((change) =>\n typeof path === 'string'\n ? change.path.join('.') === path\n : change.path.length === path.length && change.path.every((key, i) => key === path[i]),\n )\n if (stashed || !change) return false\n commit(() => writePath(raw as Record<string, unknown>, change.path, change.before))\n return true\n },\n shareUrl: (base?: string | URL) => shareUrl(definition.id, draft(), base),\n /**\n * Connection changes: the runtime's draft wins on (re)connect; without one there is no runtime state.\n * A share link applies on top of the runtime's draft, or locally once the runtime proves unreachable.\n */\n connection() {\n if (runtimeOwnsDrafts()) return void resync().then(hydrate)\n handle.state = null\n if (connection.phase === 'offline') hydrate()\n },\n receive,\n close() {\n closed = true\n browserSync?.close()\n },\n }\n const browserSync = connectBrowserSync(definition, runtime, {\n enabled: () => !runtimeOwnsDrafts(),\n receive: replace,\n })\n if (!runtime) hydrate()\n return handle\n }\n\n const handles = new Map(tools.filter(hasConfig).map((definition) => [definition.id, createHandle(definition)]))\n\n // /studio asks its same-origin preview iframe to compare too (see `postCompare`).\n const onMessage = (event: MessageEvent) => {\n const message = event.data as CompareMessage | undefined\n if (event.origin !== location.origin || message?.type !== COMPARE_MESSAGE) return\n handles.get(message.tool)?.compare(message.showSaved)\n }\n if (typeof window !== 'undefined') window.addEventListener?.('message', onMessage)\n\n if (runtime && typeof window !== 'undefined') {\n bridge = connectBridge(runtime, {\n state: (state) => handles.get(state.tool)?.receive(state),\n connection(next) {\n connection = next\n for (const handle of handles.values()) handle.connection()\n touch()\n },\n })\n }\n\n const studio: Studio = {\n definitions: tools,\n tools: [...handles.values()],\n tool<TConfig extends object>(definition: ToolDefinition<TConfig>) {\n const handle = handles.get(definition.id)\n if (!handle) throw new Error(`[studio] tool \"${definition.id}\" was not passed to createStudio`)\n return handle as unknown as ToolHandle<TConfig>\n },\n get connection() {\n return connection\n },\n get version() {\n return version\n },\n subscribe(listener) {\n studioListeners.add(listener)\n return () => void studioListeners.delete(listener)\n },\n close() {\n bridge?.close()\n for (const handle of handles.values()) handle.close()\n studioListeners.clear()\n if (typeof window !== 'undefined') window.removeEventListener?.('message', onMessage)\n if (instances.get(instanceKey) === studio) instances.delete(instanceKey)\n },\n }\n if (typeof window !== 'undefined') {\n instances.get(instanceKey)?.close()\n instances.set(instanceKey, studio)\n }\n return studio\n}\n\nfunction fail(result: Result<unknown>) {\n if (!result.ok) throw new Error(result.message ?? result.error)\n}\n","export type Change = { path: string[]; before: unknown; after: unknown }\n\n/** Every leaf that differs between two configs of the same shape. Arrays count as leaves. */\nexport function diff(before: unknown, after: unknown, path: string[] = []): Change[] {\n const nested = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n if (nested(before) && nested(after)) {\n const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n return [...keys].flatMap((key) => diff(before[key], after[key], [...path, key]))\n }\n return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path, before, after }]\n}\n","import type { Op, Operations, ServerMessage, ToolState } from '../core/protocol'\nimport { error, type Result } from '../core/result'\n\nexport type Connection = { phase: 'local' } | { phase: 'connecting' } | { phase: 'connected' } | { phase: 'offline' }\n\ntype Pending = (result: Result<unknown>) => void\n\n/** A reconnecting WebSocket to the Studio runtime with request/response correlation. */\nexport function connectBridge(\n url: string,\n handlers: { state: (state: ToolState) => void; connection: (connection: Connection) => void },\n) {\n let socket: WebSocket | null = null\n let nextId = 0\n let retry = 0\n let timer: ReturnType<typeof setTimeout> | undefined\n let closed = false\n const pending = new Map<number, Pending>()\n\n const open = () => {\n handlers.connection({ phase: 'connecting' })\n const ws = new WebSocket(url)\n socket = ws\n ws.onopen = () => {\n retry = 0\n handlers.connection({ phase: 'connected' })\n }\n ws.onmessage = (event) => {\n const message = JSON.parse(String(event.data)) as ServerMessage\n if (message.type === 'state') return handlers.state(message.state)\n pending.get(message.id)?.(message.result)\n pending.delete(message.id)\n }\n ws.onclose = () => {\n socket = null\n for (const resolve of pending.values()) resolve(error('runtime_unavailable'))\n pending.clear()\n if (closed) return\n handlers.connection({ phase: 'offline' })\n timer = setTimeout(open, Math.min(500 * 2 ** retry++, 5000))\n }\n }\n open()\n\n return {\n request<TOp extends Op>(op: TOp, params: Operations[TOp]['params']): Promise<Result<Operations[TOp]['result']>> {\n if (socket?.readyState !== WebSocket.OPEN) return Promise.resolve(error('runtime_unavailable'))\n const id = nextId++\n socket.send(JSON.stringify({ id, op, params }))\n return new Promise((resolve) => pending.set(id, resolve as Pending))\n },\n close() {\n closed = true\n clearTimeout(timer)\n socket?.close()\n },\n }\n}\n\nexport type BridgeClient = ReturnType<typeof connectBridge>\n","import type { ToolDefinition } from '../core/tool'\nimport { validateConfig } from '../core/validate'\n\ntype Snapshot = { type: 'config'; clock: number; sender: string; value: unknown }\n\n/** Ephemeral config sharing between same-origin pages, including Studio's preview iframe. */\nexport function connectBrowserSync<T extends object>(\n definition: ToolDefinition<T>,\n runtime: string | false | undefined,\n handlers: { enabled(): boolean; receive(value: T): void },\n) {\n // Also exclude SSR and Node environments that expose their own BroadcastChannel implementation.\n if (typeof window === 'undefined' || !window.location?.origin || !window.BroadcastChannel) return null\n\n let channel: BroadcastChannel\n try {\n channel = new window.BroadcastChannel(\n JSON.stringify(['joyco-studio:config', runtime || null, definition.id, definition.file]),\n )\n } catch {\n return null\n }\n const sender = crypto.getRandomValues(new Uint32Array(4)).join('-')\n let clock = 0\n let latest: Snapshot | undefined\n let closed = false\n\n channel.onmessage = ({ data }) => {\n if (closed || !data || typeof data !== 'object') return\n if (data.type === 'request') {\n if (latest) channel.postMessage(latest)\n return\n }\n if (\n data.type !== 'config' ||\n !Number.isSafeInteger(data.clock) ||\n data.clock < 1 ||\n typeof data.sender !== 'string'\n )\n return\n clock = Math.max(clock, data.clock)\n if (!handlers.enabled()) return\n // A stable tie-break keeps simultaneous edits and multiple snapshot replies from diverging.\n if (latest && (data.clock < latest.clock || (data.clock === latest.clock && data.sender <= latest.sender))) return\n const result = validateConfig(definition, data.value)\n if (!result.ok) return\n latest = { type: 'config', clock: data.clock, sender: data.sender, value: result.data }\n handlers.receive(result.data)\n }\n // A newly mounted/reloaded preview asks existing peers for their current draft.\n channel.postMessage({ type: 'request' })\n\n return {\n publish(value: T) {\n if (closed) return\n // Wall time as a floor (peers share this browser's clock), so an edit made on a page that just opened,\n // such as an applied share link, outranks older drafts it hasn't heard about yet.\n clock = Math.max(clock + 1, Date.now())\n latest = { type: 'config', clock, sender, value: structuredClone(value) }\n channel.postMessage(latest)\n },\n close() {\n closed = true\n channel.onmessage = null\n channel.close()\n },\n }\n}\n","import { diff, type Change } from '../core/diff'\n\ntype Entry = { changes: Change[]; key: string; at: number }\n\nconst LIMIT = 100\n/** Edits to the same leaves this close together (a slider drag) undo as one step. */\nconst MERGE_MS = 500\n\n/**\n * Undo/redo for one page's edits. Entries hold only the leaves an edit changed, so stepping back\n * never reverts what another page, the runtime, or an agent changed in between.\n */\nexport function createHistory() {\n const past: Entry[] = []\n let future: Entry[] = []\n /** The newest entry while later edits to the same leaves may still extend it. */\n let open: Entry | null = null\n\n const step = (from: Entry[], to: Entry[], side: 'before' | 'after') => {\n const entry = from.pop()\n if (!entry) return null\n to.push(entry)\n open = null\n return entry.changes.map((change) => ({ path: change.path, value: change[side] }))\n }\n\n return {\n get canUndo() {\n return past.length > 0\n },\n get canRedo() {\n return future.length > 0\n },\n /** Record the edit between two snapshots. `merge: false` keeps it a step of its own (e.g. Discard). */\n record(before: unknown, after: unknown, { merge = true } = {}) {\n // Cloned so later in-place edits (e.g. to an array leaf) cannot rewrite history.\n const changes = structuredClone(diff(before, after))\n if (!changes.length) return\n future = []\n const key = JSON.stringify(changes.map(({ path }) => path))\n const now = Date.now()\n if (merge && open && open.key === key && now - open.at < MERGE_MS) {\n open.changes = open.changes.map((change, i) => ({ ...change, after: changes[i]!.after }))\n open.at = now\n // Dragged back to where it started: nothing left to undo.\n if (open.changes.every((change) => JSON.stringify(change.before) === JSON.stringify(change.after))) {\n past.pop()\n open = null\n }\n return\n }\n const entry = { changes, key, at: now }\n past.push(entry)\n if (past.length > LIMIT) past.shift()\n open = merge ? entry : null\n },\n /** The leaves to write back, or null when there is nothing to undo. */\n undo: () => step(past, future, 'before'),\n redo: () => step(future, past, 'after'),\n }\n}\n\n/** Write `value` at `path` in place; undefined removes the key (it did not exist before the edit). */\nexport function writePath(target: Record<string, unknown>, path: string[], value: unknown) {\n const key = path.at(-1)!\n let parent = target\n for (const segment of path.slice(0, -1)) {\n const next = parent[segment]\n parent = (typeof next === 'object' && next !== null ? next : (parent[segment] = {})) as Record<string, unknown>\n }\n if (value === undefined) delete parent[key]\n else parent[key] = structuredClone(value)\n}\n","/**\n * A deep proxy that reports every write, so plain `property(config, 'key')`\n * bindings from Xyz feed Studio without the tool author wiring callbacks.\n */\nexport function live<T extends object>(target: T, onWrite: () => void): T {\n const proxies = new WeakMap<object, object>()\n const wrap = <O extends object>(object: O): O => {\n let proxy = proxies.get(object)\n if (!proxy) {\n proxy = new Proxy(object, {\n get(target, key, receiver) {\n const value = Reflect.get(target, key, receiver)\n return typeof value === 'object' && value !== null ? wrap(value) : value\n },\n set(target, key, value, receiver) {\n const changed = Reflect.get(target, key, receiver) !== value\n const ok = Reflect.set(target, key, value, receiver)\n if (changed) onWrite()\n return ok\n },\n })\n proxies.set(object, proxy)\n }\n return proxy as O\n }\n return wrap(target)\n}\n\n/**\n * Make `target` equal `source` in place, keeping nested object identities (and every binding to them) intact.\n * Keys `source` lacks are removed.\n */\nexport function assignDeep(target: Record<string, unknown>, source: Record<string, unknown>) {\n for (const key of Object.keys(target)) if (!(key in source)) delete target[key]\n for (const [key, value] of Object.entries(source)) {\n const current = target[key]\n const nested = (v: unknown) => typeof v === 'object' && v !== null && !Array.isArray(v)\n if (nested(current) && nested(value))\n assignDeep(current as Record<string, unknown>, value as Record<string, unknown>)\n else target[key] = structuredClone(value)\n }\n}\n","/**\n * Share links carry one tool's draft in the URL: `?studio-share=<tool id>.<base64url JSON>`.\n * Opening one applies it as an unsaved edit, so teammates can review, save, or discard it.\n */\nconst PARAM = 'studio-share'\n\nconst toBase64Url = (text: string) => {\n // Byte by byte: spreading a large config into `String.fromCharCode` would exceed the argument limit.\n let binary = ''\n for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nconst fromBase64Url = (data: string) =>\n new TextDecoder().decode(\n Uint8Array.from(atob(data.replace(/-/g, '+').replace(/_/g, '/')), (char) => char.charCodeAt(0)),\n )\n\n/** `base` (the current page by default) with `config` for tool `id` encoded into it. */\nexport function shareUrl(id: string, config: unknown, base: string | URL = location.href) {\n const url = new URL(base)\n url.searchParams.set(PARAM, `${id}.${toBase64Url(JSON.stringify(config))}`)\n return url.href\n}\n\n/** The config this page's URL shares for tool `id`, if any. Unvalidated. */\nexport function readShared(id: string): { value: unknown } | null {\n if (typeof window === 'undefined' || !window.location?.search) return null\n const param = new URLSearchParams(location.search).get(PARAM)\n if (!param?.startsWith(`${id}.`)) return null\n try {\n return { value: JSON.parse(fromBase64Url(param.slice(id.length + 1))) }\n } catch {\n console.warn(`[studio] ${id}: ignoring a malformed share link`)\n return null\n }\n}\n\n/** Drop the share from the address bar, so reloading after further edits doesn't reapply it. */\nexport function clearShared() {\n const url = new URL(location.href)\n if (!url.searchParams.has(PARAM)) return\n url.searchParams.delete(PARAM)\n history.replaceState(history.state, '', url)\n}\n\n/** Copy a share link. Plain-HTTP origins other than localhost have no clipboard API, so show it to copy by hand. */\nexport async function copyLink(url: string) {\n if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(url)\n window.prompt('Copy this link', url)\n}\n\n/**\n * Run `apply` once the page has loaded. Routers such as Next.js rewrite the URL while hydrating,\n * which would restore a share parameter cleared any earlier.\n */\nexport function afterLoad(apply: () => void) {\n if (document.readyState === 'complete') setTimeout(apply)\n else addEventListener('load', () => setTimeout(apply), { once: true })\n}\n"],"mappings":"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","names":["target","history","change"]}
package/dist/cli.js CHANGED
@@ -970,7 +970,7 @@ import { parseArgs } from "util";
970
970
  // package.json
971
971
  var package_default = {
972
972
  name: "@joycostudio/studio",
973
- version: "0.1.4",
973
+ version: "0.1.6",
974
974
  description: "Plug project-specific tools into a /studio route, a floating debug pane, and MCP agents.",
975
975
  type: "module",
976
976
  license: "ISC",