gesso-framework 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # gesso-framework
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cfd37de: For tools that write CSS from a live tree: `easingCss(easing)` names a curve made by `cubicBezier` or `linear` as CSS writes it, a node's `UiModifierSet.modifiers` lists what is attached with its arguments, and the inspector's node report has `interaction`, what the node draws while hovered and pressed.
8
+ - 44fc232: `InspectionService.screenBox(node)` gives where a node is seen, with every transform above it applied, so an overlay can be laid over a frame drawn scaled. `box` is unchanged.
9
+ - 2846e0b: `OverlayScope` gives a part of the tree overlays of its own: a dialog, menu or tooltip opened inside it is drawn inside it, dimmed, centred, placed, clipped and transformed with it, and what is outside stays pressable. `useOverlay` opens in the nearest scope, or the runtime's layer outside every scope. For a screen played inside another tool.
10
+ - d2d860b: What a design tool needs to save a moment of a played screen: `liveInput(name, read)` says what a component holds now, `controlled` and `ScrollArea` say it, and the inspector reports it as an owner's `live`. `StartingInputs` takes a list per name for each instance in the order made, a started input is the parent's again once the parent passes something new, and nested, the outer one wins input by input.
11
+ - 6eb71c2: `StartingInputs` draws what it holds with the components in it started from inputs named by component, over their parents': a screen at a moment, a list scrolled down or a tour at its second step, without a copy of the screen. `ScrollArea` takes `scrollY`, where it is scrolled to.
12
+ - 3ac8a63: For a design tool's keyboard view and scrubber: `FocusService.tabStops(within)` lists the Tab stops under a node in order, and a paused `UiClock` can `seek` to a time while `holding` keeps finished animations at their end, so moving the time back plays them back.
13
+ - 2846e0b: `Tour` walks a person through a screen one element at a time: everything but the element dims, through a rounded hole that follows it, and a card beside it explains it, with Back, Next and a count. Steps point at a `tourTarget()` given to the element as its `ref`. Underneath, an anchored node or overlay entry can be placed `over` its anchor, at the anchor's size grown by the offset, and such an entry leaves presses on the rest of the anchor to the anchor.
14
+ - c6b56b6: A control handed a value and no `onChange` now starts at that value and changes when it is used, as it would with `defaultValue`. A new value from the parent starts it again. Before, it stayed where it was until the application moved it. A screen written as a picture of a moment, `<Switch checked={true} />`, is now one a person can use, and a component of your own that goes through `controlled()` gets the same behavior. A control that only the application may change should say so with `readOnly` or `disabled`. A control whose `onChange` declines a change is unchanged: it still shows what the application writes back. So is a `Drawer` whose owner listens only through `onClose`. A component of your own with a second output like that names it in `controlled()`'s new `heardBy`.
15
+
16
+ ### Patch Changes
17
+
18
+ - 5b5e756: A `UiClock` decides how time passes for the animations under a node: `provide={[provided(UiEnvironmentKeys.clock, clock)]}`. On a `settled` clock every animation lands where it is going the moment it starts, a spinner included; on one with a `speed`, time runs at that rate (0 is paused), and the speed can change mid-flight. Transitions, `motion` states, hover and press read the clock from their node; `AnimationService.animate` and `spring` take a `clock` option, which `Dialog`, `Drawer`, `Skeleton`, `Spinner`, the progress bar and smooth scrolling pass. An overlay now keeps the clock it was opened under, and every environment key the application provides, so a component in a dialog reads with `ctx.environment` what its opener reads.
19
+ - a304d6e: The accessibility mirror's boxes follow a pan or a zoom when it stops, not on every frame of it. The first frame of a run of moving frames sweeps every mirrored box as before; the frames after it send only the focused node's box, which the focus ring is drawn from, and the rest are swept once nothing has moved for 120 ms, or after a second of moving that never stops. In Atelier, a board of 36 screens panned at 15% spent 3 to 4 ms of each 4 to 6 ms frame sweeping the boxes and kept Chromium's main thread 62% busy writing them; it now spends 0.4 ms, and the main thread is 9% busy.
20
+ - d048f25: Motion names resolve where they are drawn. A node can provide a vocabulary with `provide={[provided(UiEnvironmentKeys.motion, motion)]}`, and a name in a `transition`, a `motion` state or `animateLayout` resolves against the nearest one; `AnimationService.setMotion` now provides the app's at the root, so it reaches those too (a `motion` state used to resolve against Gesso's defaults whatever the app installed). `transition` takes a name, `transition={{ opacity: 'fast' }}` and `tween('fast')`, and keeps it as written (`node.transitions.declared`). `defineMotion` builds a vocabulary from Gesso's with only what a design changes, and `UiDurationExtensions`, `UiEasingExtensions` and `UiSpringExtensions` add names of its own, as typography roles are added. An overlay keeps the motion it was opened under. A name the vocabulary lacks is said once and takes the default.
21
+ - 9f3f440: `InspectionService.previewInputs(anchorId, inputs)` draws a component as though its parent passed those inputs, until `endInputPreview(anchorId)` gives it back the ones it was written with. The component is the same live instance, named by an anchor from `inspect(node).owners`. This is for a design tool that jumps a component on a played screen to one of its states. Each input the preview starts or ends is handed its value again, so a control that owns its value starts again from it.
22
+ - ec41942: A key made with `createEnvironmentKey` can now be provided by any element, `provide={[provided(key, value)]}`, and read by a component under it with `ctx.environment(key)`, or its default when nothing above provides it. It is read as the component's body runs, from where the component is placed, because the builder puts a component's node under its parent before the body runs. A value that changes is provided as a source to follow, such as a cell. `ComponentResolver.resolve` is handed the slot's node as a third argument for this.
23
+ - ae9e83c: `sharedValue(ctx, name, start)` is a value a few screens share with no application behind them, such as an email typed on one screen of a prototype and shown on the next. A node provides a `SharedValues` under `SharedValuesKey`, and every component under it asking for the same name has the same cell, made at the first one's start; with none above, each component has a cell of its own. `reset()` puts every value back at its start, and `values` lists them as they are.
24
+ - 8fe90de: A `transition` now animates what is drawn, whoever changed it: the element, or a modifier writing over it, so a press or hover that `interactive` draws eases in and out on an element that declares a transition for that property, as a CSS transition follows a `:hover`. A value written by another animation's frame (a `motion` state, an `AnimationService` tween) is let through rather than chased, and `InspectionService.preview` is drawn at once both ways. `AnimationDriver.isAdvancing` says whether a frame is being written.
25
+ - Updated dependencies [cfd37de]
26
+ - Updated dependencies [00c6f8b]
27
+ - Updated dependencies [5b5e756]
28
+ - Updated dependencies [d048f25]
29
+ - Updated dependencies [33de404]
30
+ - Updated dependencies [ec41942]
31
+ - Updated dependencies [06350a6]
32
+ - Updated dependencies [33de404]
33
+ - Updated dependencies [60b4d52]
34
+ - Updated dependencies [3ac8a63]
35
+ - Updated dependencies [fdbf5be]
36
+ - Updated dependencies [2846e0b]
37
+ - Updated dependencies [8fe90de]
38
+ - gesso-core@0.8.0
39
+
3
40
  ## 0.7.0
4
41
 
5
42
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  import { BehaviorSubject, Observable, Subject, Subscription } from "rxjs";
2
- import { LayoutBox, Reactive, UiChild, UiModifier } from "gesso-core";
2
+ import { LayoutBox, Reactive, UiChild, UiEnvironmentKey, UiModifier } from "gesso-core";
3
3
  //#region src/channel/ChannelToken.d.ts
4
4
  /**
5
5
  * The barrier between the application and the view, declared once.
@@ -636,6 +636,18 @@ interface ComponentContext {
636
636
  * <box modifiers={[track.modifier]} onPointerDown={e => seek(e.x - track.value.x)} />
637
637
  */
638
638
  bounds(label?: string): BoundsCell;
639
+ /**
640
+ * What the component is placed under for `key`: the nearest value a
641
+ * node above it provides (`provide={[provided(key, value)]}`), else
642
+ * the key's default.
643
+ *
644
+ * Read as the body runs, from where the component is placed, so it
645
+ * is a value rather than a stream: a component that has to follow a
646
+ * change is given a source to follow (a cell) rather than each value.
647
+ * A design tool tells the screen it plays from the same screen drawn
648
+ * on its board this way.
649
+ */
650
+ environment<T>(key: UiEnvironmentKey<T>): T;
639
651
  }
640
652
  /**
641
653
  * The inputs a functional component receives: one host-owned cell per
@@ -749,4 +761,4 @@ type ComponentArgs<C> = RequiredKeys<ComponentProps<C>> extends never ? [inputs?
749
761
  declare function isClassComponent(component: ComponentType): component is ClassComponent;
750
762
  //#endregion
751
763
  export { isChannelHostMessage as A, CommandsOf as B, into as C, ChannelHostMessage as D, ChannelClientMessage as E, diffProjection as F, channel as H, ChannelSpec as I, ChannelToken as L, PatchPath as M, applyPatch as N, ChannelPort as O, applyPatches as P, Command as R, input as S, output as T, defineChannel as U, ViewOf as V, viewKeys as W, EmitValue as _, ComponentType as a, OutputTarget as b, Inputs as c, BoundsCell as d, bounds as f, EmitArgs as g, ChannelReplica as h, ComponentProps as i, Patch as j, isChannelClientMessage as k, isClassComponent as l, internalState as m, ComponentArgs as n, FunctionComponent as o, InternalState as p, ComponentContext as r, InputValue as s, ClassComponent as t, Component as u, InputCell as v, isOutputTarget as w, ReadableCell as x, OutputCell as y, CommandMap as z };
752
- //# sourceMappingURL=FunctionComponent-Czkex8fp.d.ts.map
764
+ //# sourceMappingURL=FunctionComponent-BDVrANP0.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkerPorts-m_hLRoPj.js","names":["isPlainObject"],"sources":["../src/channel/structuralEquals.ts","../src/channel/ChannelToken.ts","../src/channel/ChannelProtocol.ts","../src/channel/StorePatch.ts","../src/app/NodeReport.ts","../src/worker/captureConsole.ts","../src/worker/WorkerPorts.ts"],"sourcesContent":["/**\n * Deep comparison for store read models.\n *\n * Projections and selectors allocate a fresh value on every\n * evaluation, so reference equality reports a change on every\n * unrelated state emission. That invalidates bindings and dirties\n * nodes across the whole tree for state the view never read.\n *\n * Scope is deliberately narrow: primitives, arrays, and plain objects\n * — what a projection is allowed to return. Anything else (class\n * instances, Date, Map, Set, functions) compares by reference, which\n * is conservative: it reports a change, so the UI updates when it did\n * not need to rather than failing to update when it did.\n *\n * The same comparison becomes the equality half of the patch differ\n * in Phase E, so a store behaves identically local or remote.\n */\nconst MAX_DEPTH = 100;\n\nexport function structurallyEqual<T>(a: T, b: T): boolean {\n return compare(a, b, 0);\n}\n\nfunction compare(a: unknown, b: unknown, depth: number): boolean {\n if (Object.is(a, b)) {\n return true;\n }\n if (depth > MAX_DEPTH) {\n // Deeper than any sane view model, or cyclic. Report \"changed\"\n // rather than risk a non-terminating walk.\n return false;\n }\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {\n return false;\n }\n\n const aIsArray = Array.isArray(a);\n if (aIsArray !== Array.isArray(b)) {\n return false;\n }\n\n if (aIsArray) {\n const left = a as unknown[];\n const right = b as unknown[];\n if (left.length !== right.length) {\n return false;\n }\n for (let i = 0; i < left.length; i++) {\n if (!compare(left[i], right[i], depth + 1)) {\n return false;\n }\n }\n return true;\n }\n\n if (!isPlainObject(a) || !isPlainObject(b)) {\n return false;\n }\n\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const leftKeys = Object.keys(left);\n if (leftKeys.length !== Object.keys(right).length) {\n return false;\n }\n for (const key of leftKeys) {\n if (!Object.prototype.hasOwnProperty.call(right, key)) {\n return false;\n }\n if (!compare(left[key], right[key], depth + 1)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Objects created from an object literal or a null prototype. Class\n * instances are excluded so they keep reference semantics.\n */\nfunction isPlainObject(value: object): boolean {\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n","/**\n * The barrier between the application and the view, declared once.\n *\n * A token is a name, a shape and an initial value — and no\n * implementation at all. Both threads import it, which is the point:\n * the module holding it has nothing in it to bundle, so an app's api\n * client and domain logic never reach the render worker the way a\n * shared `Store` class dragged them there.\n *\n * What the framework knows about a channel ends here. It diffs plain\n * data and ships patches; where the observables came from — a single\n * subject, or an api → repository → domain → view-model stack — is the\n * application's business and the framework cannot tell the difference.\n *\n * export interface CatalogView {\n * products: ProductRow[];\n * status: 'loading' | 'ready' | 'error';\n * }\n * export interface CatalogCommands {\n * addToCart(id: string): void;\n * }\n * export const Catalog = channel<CatalogView, CatalogCommands>('catalog', {\n * products: [],\n * status: 'loading'\n * });\n */\n\n/**\n * A command a component can send back across the barrier.\n *\n * The arguments are what cross: each is structured-cloned onto the\n * owning thread, so they must be plain data. There may be as many as\n * the command needs, which is why `move(from, to)` is written the way\n * anyone would write it rather than as `move({ from, to })`; a command\n * used to carry one payload and the second argument was dropped on the\n * floor with a warning.\n *\n * Commands return nothing: the effect comes back as a patch, never as\n * a return value, since there is no synchronous answer to be had\n * across a thread.\n */\nexport type Command = (...args: never[]) => void;\n\n/**\n * The internal view of a command set: a bag of callables by name.\n *\n * Public signatures constrain to `object`, not to this. An application\n * declares its commands as an ordinary interface —\n * `interface CatalogCommands { addToCart(id: string): void }` — and an\n * interface has no index signature, so it does not satisfy a\n * `Record<string, …>` constraint however well it fits in spirit.\n * Constraining to `object` accepts what people actually write; this\n * alias is what the implementation casts to when it looks a command up\n * by name.\n */\nexport type CommandMap = Record<string, Command>;\n\n/** The declared barrier for one area of an application. */\nexport interface ChannelToken<View extends object, Commands extends object = Record<string, never>> {\n readonly name: string;\n /**\n * What every view key holds before the first patch arrives.\n *\n * Required rather than optional, so the render thread never observes\n * `undefined` for a declared key. A channel that is genuinely still\n * loading says so in its own shape — a `status` field — rather than\n * leaving the view to infer it from an absence.\n */\n readonly initial: View;\n /** Phantom, carrying the command types to `send`. Never read. */\n readonly commands?: Commands;\n}\n\n/**\n * Declares a channel.\n *\n * The name identifies it across the thread boundary and must be\n * stable; unlike a class name it survives minification, which is why\n * it is written out rather than derived.\n */\nexport function channel<View extends object, Commands extends object = Record<string, never>>(\n name: string,\n initial: View\n): ChannelToken<View, Commands> {\n if (name.length === 0) {\n throw new Error('A channel needs a name: it is how the two threads agree on which one this is.');\n }\n return { name, initial };\n}\n\n/** A channel written as one object: the view with its values, and the commands. */\nexport interface ChannelSpec<View extends object, Commands extends object> {\n /**\n * Every view key with the value it holds before the first patch.\n *\n * The type of the channel's view is the type of this object, so the\n * keys and their initial values are written once instead of in an\n * interface and again in a literal that has to agree with it.\n */\n readonly view: View;\n /**\n * The commands, as a type rather than as implementations.\n *\n * Written `{} as { addToCart(id: string, quantity: number): void }`:\n * the handlers live on the thread that owns the data and are passed\n * to `provide`, so what belongs in the token is only their shape.\n */\n readonly commands?: Commands;\n}\n\n/**\n * Declares a channel from one object.\n *\n * export const Catalog = defineChannel('catalog', {\n * view: {\n * products: [] as readonly ProductRow[],\n * status: 'loading' as ShelfStatus\n * },\n * commands: {} as {\n * addToCart(id: string, quantity: number): void;\n * move(from: number, to: number): void;\n * }\n * });\n *\n * export type CatalogView = ViewOf<typeof Catalog>;\n *\n * The same token `channel()` returns, declared once instead of three\n * times. `channel<View, Commands>(name, initial)` wrote the view as an\n * interface, then as an initial literal that had to agree with it, and\n * a key added to one and forgotten in the other was a type error in a\n * third file. Here the object is the type.\n *\n * A field whose initial value is narrower than the type it holds is\n * given the type it holds: `[]` is `never[]` and `'loading'` is\n * `string` unless it is said, which is what the `as` clauses above are\n * for. `ViewOf` and `CommandsOf` name the resulting types wherever the\n * application used to name its own interface.\n *\n * `channel()` is not deprecated and keeps working exactly as it did.\n * An application with interfaces it wants to keep, because they are\n * shared with something else or because the initial values are built\n * elsewhere, has nothing to migrate.\n */\nexport function defineChannel<View extends object, Commands extends object = Record<string, never>>(\n name: string,\n spec: ChannelSpec<View, Commands>\n): ChannelToken<View, Commands> {\n return channel<View, Commands>(name, spec.view);\n}\n\n/** The view type of a channel token, for an application that wants to name it. */\nexport type ViewOf<T> = T extends ChannelToken<infer View, object> ? View : never;\n\n/** The command type of a channel token. */\nexport type CommandsOf<T> = T extends ChannelToken<object, infer Commands> ? Commands : never;\n\n/**\n * The keys a channel publishes.\n *\n * Structural in its parameter rather than generic over the token, so\n * it does not have to agree with any particular command type to read\n * what is only ever the initial value's shape.\n */\nexport function viewKeys(token: { initial: object }): string[] {\n return Object.keys(token.initial);\n}\n","import type { Patch } from './StorePatch';\n\n/**\n * A two-way channel endpoint. `MessagePort` satisfies it, as does a\n * test double, so nothing in the channel layer depends on Worker.\n */\nexport interface ChannelPort {\n postMessage(message: unknown): void;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/**\n * Render thread → the thread that owns the channel.\n *\n * A command's first argument stayed `payload` when commands learned to\n * take more than one, and the rest travel beside it. That is not\n * tidiness: an older view talking to a newer application worker sends\n * no `rest`, which reads as the one-argument call it is, and an older\n * application worker ignores the field, which is exactly what it did\n * before the field existed.\n */\nexport type ChannelClientMessage =\n | { type: 'channel:sync' }\n | { type: 'channel:command'; command: string; payload: unknown; rest?: unknown[] };\n\n/** The owning thread → render thread. */\nexport type ChannelHostMessage =\n | { type: 'channel:patch'; patches: Patch[] }\n | { type: 'channel:error'; message: string; stack?: string };\n\nexport function isChannelClientMessage(value: unknown): value is ChannelClientMessage {\n const type = (value as { type?: unknown } | null)?.type;\n return type === 'channel:sync' || type === 'channel:command';\n}\n\nexport function isChannelHostMessage(value: unknown): value is ChannelHostMessage {\n const type = (value as { type?: unknown } | null)?.type;\n return type === 'channel:patch' || type === 'channel:error';\n}\n","import { structurallyEqual } from './structuralEquals';\n\nexport type PatchPath = readonly (string | number)[];\n\n/**\n * A change to one projection, as sent from a data worker to a replica.\n *\n * Paths are relative to the projection's root value, so a patch is\n * self-contained: the replica never needs the previous value to apply\n * one, only the value it already holds.\n */\nexport type Patch =\n | { op: 'set'; projection: string; path: PatchPath; value: unknown }\n | { op: 'delete'; projection: string; path: PatchPath }\n | {\n op: 'splice';\n projection: string;\n path: PatchPath;\n index: number;\n deleteCount: number;\n items: readonly unknown[];\n };\n\n/**\n * Describes how to turn `previous` into `current` for one projection.\n *\n * Returns an empty list when nothing changed, which is the common case\n * and the reason this exists: the point of a projection is that most\n * state changes do not alter it, and the ones that do usually alter a\n * small part.\n */\nexport function diffProjection(projection: string, previous: unknown, current: unknown): Patch[] {\n const patches: Patch[] = [];\n diff(projection, [], previous, current, patches);\n return patches;\n}\n\nfunction diff(projection: string, path: PatchPath, previous: unknown, current: unknown, out: Patch[]): void {\n if (structurallyEqual(previous, current)) {\n return;\n }\n\n if (Array.isArray(previous) && Array.isArray(current)) {\n diffArray(projection, path, previous, current, out);\n return;\n }\n\n if (isPlainObject(previous) && isPlainObject(current)) {\n for (const key of Object.keys(current)) {\n if (Object.prototype.hasOwnProperty.call(previous, key)) {\n diff(projection, [...path, key], previous[key], current[key], out);\n } else {\n out.push({ op: 'set', projection, path: [...path, key], value: current[key] });\n }\n }\n for (const key of Object.keys(previous)) {\n if (!Object.prototype.hasOwnProperty.call(current, key)) {\n out.push({ op: 'delete', projection, path: [...path, key] });\n }\n }\n return;\n }\n\n out.push({ op: 'set', projection, path, value: current });\n}\n\n/**\n * Diffs two arrays by trimming the common prefix and suffix.\n *\n * This is not a minimal edit script — a shuffle degrades to replacing\n * the middle wholesale. It is chosen because the operations lists\n * actually undergo (append, prepend, remove one, edit in place) all\n * reduce to a single small patch, and computing a true LCS on every\n * store change would cost more than it saves.\n */\nfunction diffArray(projection: string, path: PatchPath, previous: unknown[], current: unknown[], out: Patch[]): void {\n let start = 0;\n while (start < previous.length && start < current.length && structurallyEqual(previous[start], current[start])) {\n start++;\n }\n\n let previousEnd = previous.length - 1;\n let currentEnd = current.length - 1;\n while (previousEnd >= start && currentEnd >= start && structurallyEqual(previous[previousEnd], current[currentEnd])) {\n previousEnd--;\n currentEnd--;\n }\n\n const previousCount = previousEnd - start + 1;\n const currentCount = currentEnd - start + 1;\n\n if (previousCount === 0 && currentCount === 0) {\n return;\n }\n\n if (previousCount === currentCount) {\n // Same span on both sides: these are edits in place, so recurse\n // and let nested diffs stay small rather than resending elements.\n for (let offset = 0; offset < previousCount; offset++) {\n const index = start + offset;\n diff(projection, [...path, index], previous[index], current[index], out);\n }\n return;\n }\n\n out.push({\n op: 'splice',\n projection,\n path,\n index: start,\n deleteCount: previousCount,\n items: current.slice(start, currentEnd + 1)\n });\n}\n\n/**\n * Applies patches to a projection value, sharing structure with the\n * original everywhere the patch did not reach.\n *\n * Nothing handed in is mutated: bindings hold onto emitted values, so a\n * replica that edited in place would change data a component already\n * rendered.\n *\n * **Each container on a patched path is copied once per batch, not once\n * per patch.** A batch of N patches into one K-key object used to cost\n * N × K: every patch spread the whole object again to change one key.\n * A snapshot keyed by id is exactly that shape — gessologic published\n * `Record<netId, 0 | 1>` for ten thousand nets, about nine hundred\n * patches a publish, and the render worker's patch phase fell minutes\n * behind a 60 Hz stream it could never catch. So the batch remembers\n * the containers it has copied, and writes into those in place: they\n * are its own, made during this call and seen by nobody yet. Values\n * that arrived inside a patch are never written into, because they are\n * the patch's — a devtools log replays the same patch objects again.\n */\nexport function applyPatches(root: unknown, patches: readonly Patch[]): unknown {\n const owned = new Set<object>();\n let next = root;\n for (const patch of patches) {\n next = applyOne(next, patch, owned);\n }\n return next;\n}\n\nexport function applyPatch(root: unknown, patch: Patch): unknown {\n return applyOne(root, patch, new Set());\n}\n\n/** The containers this batch copied, and may therefore write into. */\ntype Owned = Set<object>;\n\nfunction applyOne(root: unknown, patch: Patch, owned: Owned): unknown {\n switch (patch.op) {\n case 'set':\n return setIn(root, patch.path, 0, patch.value, owned);\n case 'delete':\n if (patch.path.length === 0) {\n return undefined;\n }\n return deleteIn(root, patch.path, 0, owned);\n case 'splice':\n return updateIn(root, patch.path, 0, owned, node => {\n // Concatenation rather than `splice(..., ...items)`: spreading\n // the items passes each as an argument, and a batch of two\n // hundred thousand overflows the call stack.\n const array = Array.isArray(node) ? node : [];\n const spliced = array.slice(0, patch.index).concat(patch.items, array.slice(patch.index + patch.deleteCount));\n owned.add(spliced);\n return spliced;\n });\n }\n}\n\nfunction setIn(node: unknown, path: PatchPath, index: number, value: unknown, owned: Owned): unknown {\n if (index === path.length) {\n return value;\n }\n const key = path[index];\n const copy = cloneContainer(node, key, owned);\n setKey(copy, key, setIn(readKey(node, key), path, index + 1, value, owned));\n return copy;\n}\n\nfunction deleteIn(node: unknown, path: PatchPath, index: number, owned: Owned): unknown {\n const key = path[index];\n const copy = cloneContainer(node, key, owned);\n if (index === path.length - 1) {\n if (Array.isArray(copy)) {\n copy.splice(Number(key), 1);\n } else {\n delete (copy as Record<string, unknown>)[String(key)];\n }\n return copy;\n }\n setKey(copy, key, deleteIn(readKey(node, key), path, index + 1, owned));\n return copy;\n}\n\nfunction updateIn(\n node: unknown,\n path: PatchPath,\n index: number,\n owned: Owned,\n update: (node: unknown) => unknown\n): unknown {\n if (index === path.length) {\n return update(node);\n }\n const key = path[index];\n const copy = cloneContainer(node, key, owned);\n setKey(copy, key, updateIn(readKey(node, key), path, index + 1, owned, update));\n return copy;\n}\n\n/**\n * The container to write `key` into: `node` itself when this batch\n * already copied it, and a fresh copy, remembered, when it did not.\n */\nfunction cloneContainer(node: unknown, key: string | number, owned: Owned): unknown[] | Record<string, unknown> {\n if (typeof node === 'object' && node !== null && owned.has(node)) {\n return node as unknown[] | Record<string, unknown>;\n }\n let copy: unknown[] | Record<string, unknown>;\n if (Array.isArray(node)) {\n copy = node.slice();\n } else if (isPlainObject(node)) {\n copy = { ...node };\n } else {\n // The path runs past the end of what the replica holds, which\n // happens when a patch fills in a branch that did not exist yet.\n copy = typeof key === 'number' ? [] : {};\n }\n owned.add(copy);\n return copy;\n}\n\nfunction readKey(node: unknown, key: string | number): unknown {\n if (node === null || node === undefined) {\n return undefined;\n }\n return (node as Record<string | number, unknown>)[key];\n}\n\nfunction setKey(container: unknown[] | Record<string, unknown>, key: string | number, value: unknown): void {\n (container as Record<string | number, unknown>)[key] = value;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n","import type { UiSourceLocation } from 'gesso-core';\n\n/**\n * What a node is, as plain data (the inspector).\n *\n * The inspector shows a node's props, its resolved environment, its\n * bindings and their sources, its layout explanation and which\n * component owns it. Every one of those lives in the render thread,\n * and none of them can cross a `postMessage`: a `UiNode` is a live\n * object, an `Observable` is a subscription, a component instance is a\n * class. So the report is built where the tree is and only strings\n * cross, which is the same rule the existing `inspect` message follows\n * with `formatExplanation`'s text, and the same reason.\n *\n * That makes the report lossy on purpose. It is a description of a\n * node for a person to read, not a handle on one: nothing in it can be\n * clicked back to the object it came from, and nothing in it is worth\n * asserting on beyond the fact that it says what the tree says.\n */\nexport interface UiNodeReport {\n readonly id: string;\n /** `UiNodeType`, as its string value. */\n readonly type: string;\n readonly box: { readonly x: number; readonly y: number; readonly width: number; readonly height: number };\n /**\n * The components that rendered this node, nearest first.\n *\n * A node has no backpointer to a component: the builder gives every\n * component slot a Fragment anchor whose id carries `:component:`,\n * and the resolver keeps one host per anchor id. Walking up the\n * ancestors for those anchors is therefore the only way to answer\n * \"who rendered this\", and it answers it exactly, because that\n * anchor *is* the component's identity.\n */\n readonly owners: readonly UiOwnerReport[];\n readonly props: readonly UiPropReport[];\n readonly environment: readonly UiEnvironmentReport[];\n /** Attached modifier kinds, in the order the element listed them. */\n readonly modifiers: readonly string[];\n /** The event types this node has listeners for, e.g. `click`. */\n readonly listens: readonly string[];\n /**\n * What lies under the pointer beneath this node, topmost first: the\n * nodes this one is painted over and would take a press from. The\n * answer to \"I clicked the button and nothing happened\": the node the\n * inspector shows is the one that took the press, and this is the\n * button it covered. Empty when the pointer is not over the node.\n */\n readonly beneath: readonly UiBeneathReport[];\n readonly semantics?: UiSemanticsReport;\n /** `formatExplanation` of the node's layout, which is L8's answer. */\n readonly explanation: string;\n /**\n * Where the element was written, when it was compiled for\n * development. The one part of the report that is a handle after\n * all: it names a line an editor can open, and an owner's `usedAt`\n * names the line that used the component around it.\n */\n readonly source?: UiSourceLocation;\n}\n\n/** A node under the inspected one at the pointer. */\nexport interface UiBeneathReport {\n readonly id: string;\n readonly type: string;\n /** The component that rendered it, if one did. */\n readonly owner?: string;\n /** The event types it listens for, so a covered button reads as one. */\n readonly listens: readonly string[];\n}\n\nexport interface UiOwnerReport {\n /** The `@Define` tag, else the class or function name. */\n readonly name: string;\n readonly anchorId: string;\n /** Where the component was used, when compiled for development. */\n readonly usedAt?: UiSourceLocation;\n /**\n * What its parent supplied for each input, as far as plain data can\n * say: a word, a number or a flag as it is, and an element (a slot the\n * parent filled) as `{ element: true }`. Handlers, data objects and\n * streams with no current value are left out. What a design tool\n * needs to write a component's use back as code: `label=\"Email\"`.\n */\n readonly inputs?: Readonly<Record<string, UiOwnerInput>>;\n}\n\n/** One input's value in an owner report; see `UiOwnerReport.inputs`. */\nexport type UiOwnerInput = string | number | boolean | null | { readonly element: true };\n\n/** As much of a node as a one-line path needs. */\nexport interface NodePathTarget {\n /** `UiNodeType`, as its string value. */\n readonly type: string;\n readonly id: string;\n /** The node's accessible name, when it has one. */\n readonly label?: string;\n}\n\n/**\n * A node as a path somebody can read: `App > TrackScreen > ActionRow >\n * Button \"Like\"`.\n *\n * Written for error messages rather than for the inspector, and the\n * difference decides the shape. A report is read beside the tree it\n * came from, so an id is a handle; an error is read in a console or an\n * overlay with no tree beside it, and `node-4821` there is a fact\n * about nothing. The owner chain is what a person recognises, because\n * it is the components they wrote.\n *\n * Owners arrive nearest-first, as `UiNodeReport.owners` computes them,\n * and read outermost-first, as a path does. The accessible name is the\n * leaf when there is one: two `Button`s in the same row are only told\n * apart by what they say.\n */\nexport function formatNodePath(owners: readonly UiOwnerReport[], node: NodePathTarget): string {\n const names = owners.map(owner => owner.name).reverse();\n if (names.length === 0) {\n // Nothing rendered it: a node built without a component around it,\n // which is rare enough that the id is the only thing to say.\n return node.label === undefined || node.label === '' ? `${node.type} ${node.id}` : `${node.type} \"${node.label}\"`;\n }\n const path = names.join(' > ');\n return node.label === undefined || node.label === '' ? path : `${path} \"${node.label}\"`;\n}\n\n/** Where a property's current value came from. */\nexport type UiPropOrigin = 'element' | 'binding' | 'modifier';\n\nexport interface UiPropReport {\n readonly name: string;\n readonly value: string;\n readonly origin: UiPropOrigin;\n /**\n * For a modifier write, the modifiers writing it and what the\n * element declared; for a binding, that it is bound. Absent for a\n * plain element value.\n */\n readonly source?: string;\n /** For a bound property, the stream feeding it. Absent otherwise. */\n readonly stream?: UiStreamReport;\n}\n\n/**\n * The stream behind a bound property: which one, what it last said,\n * and when.\n *\n * \"Is this value bound\" was the third question the inspector set\n * out to answer, and the report answered it with a yes. A yes is not\n * enough to debug with: a prop that is not updating and a prop whose\n * stream has not emitted since the screen was built look identical on\n * the node, and so did they in the report.\n *\n * `emittedAt` is on the epoch clock rather than the thread's own,\n * because the panel reading it is one or two threads away and\n * `performance.now()` counts from each thread's creation. The panel\n * subtracts its own `Date.now()`.\n *\n * An epoch stamp rather than an age for a second reason: an age\n * changes on every frame, and the runtime decides whether to re-send a\n * selected node's report by comparing it with the last one. A report\n * carrying an age would differ from itself sixty times a second and\n * the panel would be redrawn for a clock tick.\n */\nexport interface UiStreamReport {\n /**\n * What feeds it: a labelled cell's own name (`queue.current`), else\n * what kind of stream it is.\n */\n readonly source: string;\n /** `cell` when the stream named itself, `observable` when it did not. */\n readonly kind: 'cell' | 'observable';\n /** The last value the stream produced, printed. */\n readonly value: string;\n /** How many values it has produced since the property was bound. */\n readonly emissions: number;\n /** When the last value arrived, as epoch milliseconds, or null before the first. */\n readonly emittedAt: number | null;\n /** Whether the subscription is still live. A false here is a torn-down binding still on the node. */\n readonly connected: boolean;\n}\n\n/**\n * A bound property's stream, as far as the report is concerned.\n *\n * Structural rather than `UiBinding`, so this module keeps its one\n * rule: it describes a node and imports nothing that holds one.\n */\nexport interface BoundStream {\n readonly id: number;\n readonly observable: unknown;\n value(): unknown;\n emissionCount(): number;\n emittedAt(): number | null;\n connected(): boolean;\n}\n\n/**\n * Describes the stream driving a property.\n *\n * `timeOrigin` turns the binding's own reading into an epoch stamp:\n * `performance.timeOrigin` on a thread that has one, and zero where\n * the reading is already an epoch.\n */\nexport function describeStream(binding: BoundStream, timeOrigin: number): UiStreamReport {\n // A labelled cell says where the value comes from: `Card.title`,\n // `queue.current`. An anonymous pipe only has the binding's id.\n const label = (binding.observable as { label?: unknown } | null)?.label;\n const labelled = typeof label === 'string' && label !== '';\n const emittedAt = binding.emittedAt();\n return {\n source: labelled ? label : `observable #${binding.id}`,\n kind: labelled ? 'cell' : 'observable',\n value: printPropValue(binding.value()),\n emissions: binding.emissionCount(),\n emittedAt: emittedAt === null ? null : timeOrigin + emittedAt,\n connected: binding.connected()\n };\n}\n\n/** A stream as one line: what feeds the prop, and how long ago it said so. */\nexport function formatStream(stream: UiStreamReport, now: number = Date.now()): string {\n const when = stream.emittedAt === null ? 'no value yet' : `${formatAge(Math.max(0, now - stream.emittedAt))} ago`;\n const emissions = `${stream.emissions} emission${stream.emissions === 1 ? '' : 's'}`;\n return `${stream.source} · ${emissions} · ${when}${stream.connected ? '' : ' · disconnected'}`;\n}\n\n/** An age in the largest unit that stays readable. */\nexport function formatAge(ms: number): string {\n if (ms < 1000) {\n return `${Math.round(ms)}ms`;\n }\n if (ms < 60_000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n return `${Math.round(ms / 60_000)}m`;\n}\n\nexport interface UiEnvironmentReport {\n readonly key: string;\n readonly value: string;\n /** Whether this node provides the value rather than inheriting it. */\n readonly provided: boolean;\n}\n\nexport interface UiSemanticsReport {\n readonly role?: string;\n readonly label?: string;\n readonly value?: string;\n readonly states?: readonly string[];\n}\n\n/**\n * A value as one line a person can read.\n *\n * `JSON.stringify` alone is not enough: the two values a canvas UI\n * puts in a property that it cannot handle are a `Set` (a node's\n * visual states) and a function (an event handler), and it prints both\n * as `{}`, which in an inspector reads as a bug in the application\n * rather than one in the inspector.\n */\nexport function printPropValue(value: unknown): string {\n if (value === undefined) {\n return 'undefined';\n }\n if (value === null) {\n return 'null';\n }\n if (typeof value === 'function') {\n return `ƒ ${value.name === '' ? '(anonymous)' : value.name}`;\n }\n if (typeof value === 'symbol') {\n return value.toString();\n }\n if (typeof value === 'string') {\n return value;\n }\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n if (value instanceof Set) {\n return `Set { ${[...value].map(printPropValue).join(', ')} }`;\n }\n if (value instanceof Map) {\n return `Map { ${[...value].map(([key, entry]) => `${printPropValue(key)}: ${printPropValue(entry)}`).join(', ')} }`;\n }\n if (Array.isArray(value)) {\n return `[${value.map(printPropValue).join(', ')}]`;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n // A cyclic value, or one with a throwing getter. The inspector\n // must not be the thing that breaks while explaining a break.\n return String(value);\n }\n}\n\n/** The report as text, for a console or a test failure. */\n/** `file:line:column`, the form editors and terminals turn into a link. */\nexport function formatSourceLocation(source: UiSourceLocation): string {\n return `${source.fileName}:${source.lineNumber}:${source.columnNumber}`;\n}\n\nexport function formatNodeReport(report: UiNodeReport): string {\n const lines: string[] = [`${report.type} '${report.id}'`];\n if (report.source !== undefined) {\n lines.push(`written at ${formatSourceLocation(report.source)}`);\n }\n if (report.owners.length > 0) {\n lines.push(\n `rendered by ${report.owners\n .map(owner =>\n owner.usedAt === undefined ? owner.name : `${owner.name} (used at ${formatSourceLocation(owner.usedAt)})`\n )\n .join(' inside ')}`\n );\n }\n if (report.modifiers.length > 0) {\n lines.push(`modifiers: ${report.modifiers.join(', ')}`);\n }\n if (report.listens.length > 0) {\n lines.push(`listens: ${report.listens.join(', ')}`);\n }\n if (report.beneath.length > 0) {\n lines.push('beneath, at the pointer:');\n for (const under of report.beneath) {\n const owner = under.owner === undefined ? '' : ` (${under.owner})`;\n const listens = under.listens.length === 0 ? '' : `, listens: ${under.listens.join(', ')}`;\n lines.push(` ${under.type} ${under.id}${owner}${listens}`);\n }\n }\n if (report.props.length > 0) {\n lines.push('props:');\n for (const prop of report.props) {\n lines.push(` ${prop.name} = ${prop.value}${prop.source === undefined ? '' : ` (${prop.source})`}`);\n if (prop.stream !== undefined) {\n lines.push(` ${formatStream(prop.stream)}`);\n }\n }\n }\n if (report.environment.length > 0) {\n lines.push('environment:');\n for (const entry of report.environment) {\n lines.push(` ${entry.key} = ${entry.value}${entry.provided ? ' (provided here)' : ''}`);\n }\n }\n if (report.semantics !== undefined) {\n const parts = [\n report.semantics.role === undefined ? undefined : `role ${report.semantics.role}`,\n report.semantics.label === undefined ? undefined : `label ${report.semantics.label}`,\n report.semantics.value === undefined ? undefined : `value ${report.semantics.value}`,\n report.semantics.states === undefined || report.semantics.states.length === 0\n ? undefined\n : `states ${report.semantics.states.join(', ')}`\n ].filter(part => part !== undefined);\n if (parts.length > 0) {\n lines.push(`semantics: ${parts.join(' · ')}`);\n }\n }\n lines.push(report.explanation);\n return lines.join('\\n');\n}\n","import { printPropValue } from '../app/NodeReport';\nimport type { ConsoleEntry } from '../app/DevtoolsProtocol';\n\n/**\n * Forwarding a worker's `console.*`.\n *\n * A worker's console is real, but it is in devtools' worker target,\n * which a developer has to know to select and cannot read alongside\n * the page's. So while a devtools panel asks for it, each `console.*`\n * call in the worker is also handed to a sink as plain strings, with\n * the thread named, and the panel shows the three threads as one log.\n *\n * The call still reaches the worker's own console: this is a copy,\n * not a redirect, so turning a panel on changes nothing about what\n * devtools' worker target shows.\n */\n\nexport type ConsoleLevel = ConsoleEntry['level'];\n\nconst LEVELS: readonly ConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug'];\n\n/** An entry before the shell has said which thread it came from. */\nexport type ConsoleEntryBody = Omit<ConsoleEntry, 'thread'>;\n\n/**\n * Copies every `console.*` call on `target` (the worker global's\n * console by default) to `sink`. Returns a function that restores the\n * original methods.\n *\n * Idempotent per target: a second capture on the same console replaces\n * the first's sink rather than nesting, so toggling a panel on twice\n * does not log twice.\n */\nexport function captureConsole(sink: (entry: ConsoleEntryBody) => void, target: Console = console): () => void {\n const installed = (target as Console & { [CAPTURED]?: { sink: typeof sink; restore: () => void } })[CAPTURED];\n if (installed !== undefined) {\n installed.sink = sink;\n return installed.restore;\n }\n const originals = new Map<ConsoleLevel, (...args: unknown[]) => void>();\n const state = {\n sink,\n restore: (): void => {\n for (const [level, original] of originals) {\n target[level] = original;\n }\n delete (target as Console & { [CAPTURED]?: unknown })[CAPTURED];\n }\n };\n for (const level of LEVELS) {\n const original = target[level] as (...args: unknown[]) => void;\n originals.set(level, original);\n target[level] = (...args: unknown[]): void => {\n original.apply(target, args);\n // A sink that throws must not break the application's logging,\n // least of all while a developer is trying to read it.\n try {\n state.sink({ level, args: args.map(formatConsoleArg), at: Date.now() });\n } catch {\n // Nothing to do: the original call already ran.\n }\n };\n }\n (target as Console & { [CAPTURED]?: typeof state })[CAPTURED] = state;\n return state.restore;\n}\n\nconst CAPTURED = Symbol.for('gesso:console-captured');\n\n/**\n * One console argument as the panel prints it.\n *\n * `printPropValue` already prints the values a canvas UI is likely to\n * log; an Error is the one thing it prints badly (`{}`), and the one\n * thing a developer most wants to read whole.\n */\nexport function formatConsoleArg(value: unknown): string {\n if (value instanceof Error) {\n return value.stack !== undefined && value.stack !== '' ? value.stack : `${value.name}: ${value.message}`;\n }\n return printPropValue(value);\n}\n\n/** The shell's request to an application worker to start or stop forwarding. */\nexport interface ConsoleForwardingMessage {\n type: 'gesso:console';\n enabled: boolean;\n}\n\n/** An application worker's forwarded entry. */\nexport interface ConsoleEntryMessage {\n type: 'gesso:console';\n entry: ConsoleEntryBody;\n}\n\nexport function isConsoleForwardingMessage(value: unknown): value is ConsoleForwardingMessage {\n const message = value as { type?: unknown; enabled?: unknown } | null;\n return message?.type === 'gesso:console' && typeof message.enabled === 'boolean';\n}\n\nexport function isConsoleEntryMessage(value: unknown): value is ConsoleEntryMessage {\n const message = value as { type?: unknown; entry?: unknown } | null;\n return message?.type === 'gesso:console' && typeof message.entry === 'object' && message.entry !== null;\n}\n","/**\n * Named `MessagePort`s into a worker.\n *\n * A worker's global `onmessage` is a single channel, so a worker that\n * receives messages on it can host exactly one conversation. That is\n * why a store in a data worker used to mean a worker per store: the\n * client claimed the `Worker` object itself, and a second one had\n * nowhere to go.\n *\n * A handshake fixes it. The client opens a `MessageChannel`, keeps one\n * end and transfers the other with a name; the worker serves that name\n * and the two ends talk privately from then on. The global channel is\n * used once per conversation and carries nothing else.\n *\n * Nothing here knows what travels over a port. It is the transport the\n * store replication in `../store/worker` runs on today and the barrier\n * contract will run on next.\n */\n\nimport { captureConsole, isConsoleForwardingMessage, type ConsoleEntryMessage } from './captureConsole';\n\n/** A port-shaped thing: `MessagePort` and `Worker` both satisfy it. */\nexport interface MessageEndpoint {\n postMessage(message: unknown): void;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/** The one message the global channel carries. */\nexport interface PortHandshake {\n type: 'gesso:port';\n key: string;\n}\n\nexport function isPortHandshake(value: unknown): value is PortHandshake {\n const message = value as { type?: unknown; key?: unknown } | null;\n return message?.type === 'gesso:port' && typeof message.key === 'string';\n}\n\n/**\n * A worker spawned at most once, serving any number of named ports.\n *\n * Handed to several `useChannel` calls, it is what lets one worker\n * hold a whole application layer instead of one channel. Spawning is\n * deferred to the first `open`, so a handle nobody uses costs nothing.\n */\nexport interface WorkerHandle {\n /**\n * Opens a private channel under `key`, spawning the worker if this\n * is the first one.\n */\n open(key: string): MessagePort;\n /** Whether the worker has been spawned. */\n readonly spawned: boolean;\n /** Stops the worker, if it was ever started. */\n terminate(): void;\n}\n\n/**\n * Stands for \"whichever worker the shell spawned for the application\".\n *\n * A registration inside the render worker cannot name that worker: it\n * is created by the shell and its port only arrives with `init`, long\n * after `useChannel` and `useService` have run. This sentinel is what a\n * registration puts there instead, and the render worker swaps it for\n * the real handle once the port shows up.\n *\n * Opening a port on it before then is a bug rather than a race, so it\n * says so.\n */\nexport const APPLICATION_WORKER: WorkerHandle = {\n open(): MessagePort {\n throw new Error(\n 'APPLICATION_WORKER was used directly. It is a placeholder the render worker ' +\n \"replaces with the shell's port; reaching it means no application-logic worker was supplied. \" +\n 'Pass appLogicWorker to createApp.'\n );\n },\n spawned: false,\n terminate(): void {}\n};\n\n/**\n * Anything a handshake can be posted to with a port attached.\n * `Worker` and `MessagePort` both satisfy it.\n */\nexport interface TransferTarget {\n postMessage(message: unknown, transfer: Transferable[]): void;\n}\n\n/**\n * A handle over an endpoint someone else owns.\n *\n * The shell spawns the application worker and hands the render worker\n * one end of a channel to it; this is what the render worker opens\n * named ports over. `terminate` is a no-op — the lifetime belongs to\n * whoever created the endpoint, and a handle that could kill a worker\n * it did not spawn would be a surprising thing to hand out.\n */\nexport function portHandle(endpoint: TransferTarget): WorkerHandle {\n return {\n open(key: string): MessagePort {\n const channel = new MessageChannel();\n endpoint.postMessage({ type: 'gesso:port', key } satisfies PortHandshake, [channel.port2]);\n return channel.port1;\n },\n get spawned(): boolean {\n return true;\n },\n terminate(): void {}\n };\n}\n\n/**\n * Routes handshakes arriving on a transferred port through the same\n * handlers as the worker's own global channel.\n *\n * The shell owns the application worker and gives the render worker a\n * port to it, so handshakes reach this worker two ways: on its global\n * channel (whoever spawned it) and on that port (whoever was given\n * it). Both should be served by the same handlers, and neither end\n * should have to know which route a channel came in on.\n */\nexport interface HubMessage {\n type: 'gesso:hub';\n}\n\nexport function isHubMessage(value: unknown): value is HubMessage {\n return (value as { type?: unknown } | null)?.type === 'gesso:hub';\n}\n\nconst BASE_INSTALLED = Symbol.for('gesso:port-base-installed');\nconst CONSOLE_RESTORE = Symbol.for('gesso:port-console-restore');\n\n/**\n * Starts or stops copying this worker's console to whoever posts to\n * it, as `ConsoleEntryMessage`s. A host with no `postMessage` (a test's\n * bare object) has nowhere to send them and forwards nothing.\n */\nfunction setConsoleForwarding(host: PortHost & { [CONSOLE_RESTORE]?: () => void }, enabled: boolean): void {\n host[CONSOLE_RESTORE]?.();\n delete host[CONSOLE_RESTORE];\n const post = (host as { postMessage?: (message: unknown) => void }).postMessage;\n if (!enabled || typeof post !== 'function') {\n return;\n }\n host[CONSOLE_RESTORE] = captureConsole(entry => {\n const message: ConsoleEntryMessage = { type: 'gesso:console', entry };\n post.call(host, message);\n });\n}\n\n/**\n * The handler every `servePorts` chain sits on top of.\n *\n * It owns the two things no individual server can: routing a hub port\n * through the whole chain, and answering a handshake that nobody\n * accepted. Both have to be innermost — the first because the chain is\n * only complete once every server has wrapped `onmessage`, the second\n * because \"nobody accepted\" is only known after every server has\n * declined.\n */\nfunction installBase(host: PortHost & { [BASE_INSTALLED]?: boolean }): void {\n if (host[BASE_INSTALLED] === true) {\n return;\n }\n host[BASE_INSTALLED] = true;\n const previous = host.onmessage;\n const registered = (host as HostWithNames)[SERVED_NAMES] ?? [];\n\n host.onmessage = event => {\n if (isConsoleForwardingMessage(event.data)) {\n // The shell, on behalf of a devtools panel, asking for this\n // worker's console. Answered here because this is the one handler\n // every application worker has, whatever it serves.\n setConsoleForwarding(host, event.data.enabled);\n return;\n }\n if (isHubMessage(event.data)) {\n const port = event.ports?.[0];\n if (port === undefined) {\n throw new Error('A hub message arrived with no port attached.');\n }\n // The whole chain, not just this handler: by now every\n // `servePorts` on this host has wrapped `onmessage`, and a\n // handshake arriving on the port should reach all of them.\n port.onmessage = host.onmessage;\n return;\n }\n if (!isPortHandshake(event.data)) {\n previous?.(event);\n return;\n }\n const port = event.ports?.[0];\n if (port === undefined) {\n throw new Error(`Port handshake for '${event.data.key}' arrived with no port attached.`);\n }\n // Reaching here means every server declined. Silence would leave\n // the client waiting forever with nothing said.\n const served = registered.flatMap(get => [...get()]).sort();\n const error: PortErrorMessage = {\n type: 'port:error',\n message: `Nothing is served under '${event.data.key}'. This worker serves: ${\n served.length > 0 ? served.join(', ') : '(nothing)'\n }.`\n };\n port.postMessage(error);\n };\n}\n\n/**\n * Wraps a worker factory so the worker is created once and shared.\n *\n * A factory rather than a URL for the same reason the render worker\n * takes one: a bundler only emits a chunk for a worker it can see\n * constructed literally in the calling module.\n *\n * const data = workerHandle(\n * () => new Worker(new URL('./data.worker.ts', import.meta.url), { type: 'module' })\n * );\n */\nexport function workerHandle(factory: () => Worker): WorkerHandle {\n let worker: Worker | undefined;\n return {\n open(key: string): MessagePort {\n worker ??= factory();\n const channel = new MessageChannel();\n // The worker may not have run its module body yet. Messages\n // posted to a worker queue until it installs a handler, so the\n // handshake is safe as long as `servePorts` is called\n // synchronously at the top level of the worker module — the same\n // guarantee `renderRoot` relies on.\n worker.postMessage({ type: 'gesso:port', key } satisfies PortHandshake, [channel.port2]);\n return channel.port1;\n },\n get spawned(): boolean {\n return worker !== undefined;\n },\n terminate(): void {\n worker?.terminate();\n worker = undefined;\n }\n };\n}\n\n/**\n * Minimal view of a worker's global scope, so this module type-checks\n * against the DOM lib without pulling in the WebWorker lib.\n */\nexport interface PortHost {\n onmessage: ((event: { data: unknown; ports?: readonly MessagePort[] }) => void) | null;\n}\n\n/**\n * What a port is answered with when no handler claimed its name.\n *\n * Its own message type rather than a store's or a channel's, because\n * the transport does not know which of them the client is: both\n * recognise it, so a mismatched name is loud either way.\n */\nexport interface PortErrorMessage {\n type: 'port:error';\n message: string;\n}\n\nexport function isPortErrorMessage(value: unknown): value is PortErrorMessage {\n const message = value as { type?: unknown; message?: unknown } | null;\n return message?.type === 'port:error' && typeof message.message === 'string';\n}\n\n/** Every name served on a host, across all `servePorts` calls on it. */\nconst SERVED_NAMES = Symbol.for('gesso:served-port-names');\n\ninterface HostWithNames extends PortHost {\n [SERVED_NAMES]?: Array<() => readonly string[]>;\n}\n\n/**\n * Serves named ports inside a worker.\n *\n * Call it synchronously at the top level of the worker module, before\n * any await, so no handshake is missed.\n *\n * `onPort` returns whether it took the port. Returning false passes\n * the handshake to whatever was serving before, which is what lets two\n * kinds of thing — stores and channels, during the migration — share\n * one worker: each answers for its own names and declines the rest.\n * When nobody accepts, the port is answered with an error naming\n * everything the worker does serve, because a handshake that silently\n * matched nothing leaves the client waiting forever with nothing said.\n *\n * `names` is only read to build that message.\n *\n * Returns a function that stops serving.\n */\nexport function servePorts(\n onPort: (key: string, port: MessagePort) => boolean,\n names: () => readonly string[],\n host: PortHost = self as unknown as PortHost\n): () => void {\n const withNames = host as HostWithNames;\n const registered = (withNames[SERVED_NAMES] ??= []);\n registered.push(names);\n // Before this handler wraps `onmessage`, so the base ends up\n // innermost: it answers a handshake only once every server above it\n // has declined.\n installBase(host);\n\n const previous = host.onmessage;\n host.onmessage = event => {\n if (!isPortHandshake(event.data)) {\n // Not ours. A worker that also speaks its own protocol on the\n // global channel keeps working.\n previous?.(event);\n return;\n }\n const port = event.ports?.[0];\n if (port === undefined) {\n throw new Error(`Port handshake for '${event.data.key}' arrived with no port attached.`);\n }\n if (onPort(event.data.key, port)) {\n return;\n }\n // Declined. Another server may take it; the base answers if none\n // does.\n previous?.(event);\n };\n return () => {\n host.onmessage = previous;\n const index = registered.indexOf(names);\n if (index >= 0) {\n registered.splice(index, 1);\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkerPorts-m_hLRoPj.js","names":["isPlainObject"],"sources":["../src/channel/structuralEquals.ts","../src/channel/ChannelToken.ts","../src/channel/ChannelProtocol.ts","../src/channel/StorePatch.ts","../src/app/NodeReport.ts","../src/worker/captureConsole.ts","../src/worker/WorkerPorts.ts"],"sourcesContent":["/**\n * Deep comparison for store read models.\n *\n * Projections and selectors allocate a fresh value on every\n * evaluation, so reference equality reports a change on every\n * unrelated state emission. That invalidates bindings and dirties\n * nodes across the whole tree for state the view never read.\n *\n * Scope is deliberately narrow: primitives, arrays, and plain objects\n * — what a projection is allowed to return. Anything else (class\n * instances, Date, Map, Set, functions) compares by reference, which\n * is conservative: it reports a change, so the UI updates when it did\n * not need to rather than failing to update when it did.\n *\n * The same comparison becomes the equality half of the patch differ\n * in Phase E, so a store behaves identically local or remote.\n */\nconst MAX_DEPTH = 100;\n\nexport function structurallyEqual<T>(a: T, b: T): boolean {\n return compare(a, b, 0);\n}\n\nfunction compare(a: unknown, b: unknown, depth: number): boolean {\n if (Object.is(a, b)) {\n return true;\n }\n if (depth > MAX_DEPTH) {\n // Deeper than any sane view model, or cyclic. Report \"changed\"\n // rather than risk a non-terminating walk.\n return false;\n }\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {\n return false;\n }\n\n const aIsArray = Array.isArray(a);\n if (aIsArray !== Array.isArray(b)) {\n return false;\n }\n\n if (aIsArray) {\n const left = a as unknown[];\n const right = b as unknown[];\n if (left.length !== right.length) {\n return false;\n }\n for (let i = 0; i < left.length; i++) {\n if (!compare(left[i], right[i], depth + 1)) {\n return false;\n }\n }\n return true;\n }\n\n if (!isPlainObject(a) || !isPlainObject(b)) {\n return false;\n }\n\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const leftKeys = Object.keys(left);\n if (leftKeys.length !== Object.keys(right).length) {\n return false;\n }\n for (const key of leftKeys) {\n if (!Object.prototype.hasOwnProperty.call(right, key)) {\n return false;\n }\n if (!compare(left[key], right[key], depth + 1)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Objects created from an object literal or a null prototype. Class\n * instances are excluded so they keep reference semantics.\n */\nfunction isPlainObject(value: object): boolean {\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n","/**\n * The barrier between the application and the view, declared once.\n *\n * A token is a name, a shape and an initial value — and no\n * implementation at all. Both threads import it, which is the point:\n * the module holding it has nothing in it to bundle, so an app's api\n * client and domain logic never reach the render worker the way a\n * shared `Store` class dragged them there.\n *\n * What the framework knows about a channel ends here. It diffs plain\n * data and ships patches; where the observables came from — a single\n * subject, or an api → repository → domain → view-model stack — is the\n * application's business and the framework cannot tell the difference.\n *\n * export interface CatalogView {\n * products: ProductRow[];\n * status: 'loading' | 'ready' | 'error';\n * }\n * export interface CatalogCommands {\n * addToCart(id: string): void;\n * }\n * export const Catalog = channel<CatalogView, CatalogCommands>('catalog', {\n * products: [],\n * status: 'loading'\n * });\n */\n\n/**\n * A command a component can send back across the barrier.\n *\n * The arguments are what cross: each is structured-cloned onto the\n * owning thread, so they must be plain data. There may be as many as\n * the command needs, which is why `move(from, to)` is written the way\n * anyone would write it rather than as `move({ from, to })`; a command\n * used to carry one payload and the second argument was dropped on the\n * floor with a warning.\n *\n * Commands return nothing: the effect comes back as a patch, never as\n * a return value, since there is no synchronous answer to be had\n * across a thread.\n */\nexport type Command = (...args: never[]) => void;\n\n/**\n * The internal view of a command set: a bag of callables by name.\n *\n * Public signatures constrain to `object`, not to this. An application\n * declares its commands as an ordinary interface —\n * `interface CatalogCommands { addToCart(id: string): void }` — and an\n * interface has no index signature, so it does not satisfy a\n * `Record<string, …>` constraint however well it fits in spirit.\n * Constraining to `object` accepts what people actually write; this\n * alias is what the implementation casts to when it looks a command up\n * by name.\n */\nexport type CommandMap = Record<string, Command>;\n\n/** The declared barrier for one area of an application. */\nexport interface ChannelToken<View extends object, Commands extends object = Record<string, never>> {\n readonly name: string;\n /**\n * What every view key holds before the first patch arrives.\n *\n * Required rather than optional, so the render thread never observes\n * `undefined` for a declared key. A channel that is genuinely still\n * loading says so in its own shape — a `status` field — rather than\n * leaving the view to infer it from an absence.\n */\n readonly initial: View;\n /** Phantom, carrying the command types to `send`. Never read. */\n readonly commands?: Commands;\n}\n\n/**\n * Declares a channel.\n *\n * The name identifies it across the thread boundary and must be\n * stable; unlike a class name it survives minification, which is why\n * it is written out rather than derived.\n */\nexport function channel<View extends object, Commands extends object = Record<string, never>>(\n name: string,\n initial: View\n): ChannelToken<View, Commands> {\n if (name.length === 0) {\n throw new Error('A channel needs a name: it is how the two threads agree on which one this is.');\n }\n return { name, initial };\n}\n\n/** A channel written as one object: the view with its values, and the commands. */\nexport interface ChannelSpec<View extends object, Commands extends object> {\n /**\n * Every view key with the value it holds before the first patch.\n *\n * The type of the channel's view is the type of this object, so the\n * keys and their initial values are written once instead of in an\n * interface and again in a literal that has to agree with it.\n */\n readonly view: View;\n /**\n * The commands, as a type rather than as implementations.\n *\n * Written `{} as { addToCart(id: string, quantity: number): void }`:\n * the handlers live on the thread that owns the data and are passed\n * to `provide`, so what belongs in the token is only their shape.\n */\n readonly commands?: Commands;\n}\n\n/**\n * Declares a channel from one object.\n *\n * export const Catalog = defineChannel('catalog', {\n * view: {\n * products: [] as readonly ProductRow[],\n * status: 'loading' as ShelfStatus\n * },\n * commands: {} as {\n * addToCart(id: string, quantity: number): void;\n * move(from: number, to: number): void;\n * }\n * });\n *\n * export type CatalogView = ViewOf<typeof Catalog>;\n *\n * The same token `channel()` returns, declared once instead of three\n * times. `channel<View, Commands>(name, initial)` wrote the view as an\n * interface, then as an initial literal that had to agree with it, and\n * a key added to one and forgotten in the other was a type error in a\n * third file. Here the object is the type.\n *\n * A field whose initial value is narrower than the type it holds is\n * given the type it holds: `[]` is `never[]` and `'loading'` is\n * `string` unless it is said, which is what the `as` clauses above are\n * for. `ViewOf` and `CommandsOf` name the resulting types wherever the\n * application used to name its own interface.\n *\n * `channel()` is not deprecated and keeps working exactly as it did.\n * An application with interfaces it wants to keep, because they are\n * shared with something else or because the initial values are built\n * elsewhere, has nothing to migrate.\n */\nexport function defineChannel<View extends object, Commands extends object = Record<string, never>>(\n name: string,\n spec: ChannelSpec<View, Commands>\n): ChannelToken<View, Commands> {\n return channel<View, Commands>(name, spec.view);\n}\n\n/** The view type of a channel token, for an application that wants to name it. */\nexport type ViewOf<T> = T extends ChannelToken<infer View, object> ? View : never;\n\n/** The command type of a channel token. */\nexport type CommandsOf<T> = T extends ChannelToken<object, infer Commands> ? Commands : never;\n\n/**\n * The keys a channel publishes.\n *\n * Structural in its parameter rather than generic over the token, so\n * it does not have to agree with any particular command type to read\n * what is only ever the initial value's shape.\n */\nexport function viewKeys(token: { initial: object }): string[] {\n return Object.keys(token.initial);\n}\n","import type { Patch } from './StorePatch';\n\n/**\n * A two-way channel endpoint. `MessagePort` satisfies it, as does a\n * test double, so nothing in the channel layer depends on Worker.\n */\nexport interface ChannelPort {\n postMessage(message: unknown): void;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/**\n * Render thread → the thread that owns the channel.\n *\n * A command's first argument stayed `payload` when commands learned to\n * take more than one, and the rest travel beside it. That is not\n * tidiness: an older view talking to a newer application worker sends\n * no `rest`, which reads as the one-argument call it is, and an older\n * application worker ignores the field, which is exactly what it did\n * before the field existed.\n */\nexport type ChannelClientMessage =\n | { type: 'channel:sync' }\n | { type: 'channel:command'; command: string; payload: unknown; rest?: unknown[] };\n\n/** The owning thread → render thread. */\nexport type ChannelHostMessage =\n | { type: 'channel:patch'; patches: Patch[] }\n | { type: 'channel:error'; message: string; stack?: string };\n\nexport function isChannelClientMessage(value: unknown): value is ChannelClientMessage {\n const type = (value as { type?: unknown } | null)?.type;\n return type === 'channel:sync' || type === 'channel:command';\n}\n\nexport function isChannelHostMessage(value: unknown): value is ChannelHostMessage {\n const type = (value as { type?: unknown } | null)?.type;\n return type === 'channel:patch' || type === 'channel:error';\n}\n","import { structurallyEqual } from './structuralEquals';\n\nexport type PatchPath = readonly (string | number)[];\n\n/**\n * A change to one projection, as sent from a data worker to a replica.\n *\n * Paths are relative to the projection's root value, so a patch is\n * self-contained: the replica never needs the previous value to apply\n * one, only the value it already holds.\n */\nexport type Patch =\n | { op: 'set'; projection: string; path: PatchPath; value: unknown }\n | { op: 'delete'; projection: string; path: PatchPath }\n | {\n op: 'splice';\n projection: string;\n path: PatchPath;\n index: number;\n deleteCount: number;\n items: readonly unknown[];\n };\n\n/**\n * Describes how to turn `previous` into `current` for one projection.\n *\n * Returns an empty list when nothing changed, which is the common case\n * and the reason this exists: the point of a projection is that most\n * state changes do not alter it, and the ones that do usually alter a\n * small part.\n */\nexport function diffProjection(projection: string, previous: unknown, current: unknown): Patch[] {\n const patches: Patch[] = [];\n diff(projection, [], previous, current, patches);\n return patches;\n}\n\nfunction diff(projection: string, path: PatchPath, previous: unknown, current: unknown, out: Patch[]): void {\n if (structurallyEqual(previous, current)) {\n return;\n }\n\n if (Array.isArray(previous) && Array.isArray(current)) {\n diffArray(projection, path, previous, current, out);\n return;\n }\n\n if (isPlainObject(previous) && isPlainObject(current)) {\n for (const key of Object.keys(current)) {\n if (Object.prototype.hasOwnProperty.call(previous, key)) {\n diff(projection, [...path, key], previous[key], current[key], out);\n } else {\n out.push({ op: 'set', projection, path: [...path, key], value: current[key] });\n }\n }\n for (const key of Object.keys(previous)) {\n if (!Object.prototype.hasOwnProperty.call(current, key)) {\n out.push({ op: 'delete', projection, path: [...path, key] });\n }\n }\n return;\n }\n\n out.push({ op: 'set', projection, path, value: current });\n}\n\n/**\n * Diffs two arrays by trimming the common prefix and suffix.\n *\n * This is not a minimal edit script — a shuffle degrades to replacing\n * the middle wholesale. It is chosen because the operations lists\n * actually undergo (append, prepend, remove one, edit in place) all\n * reduce to a single small patch, and computing a true LCS on every\n * store change would cost more than it saves.\n */\nfunction diffArray(projection: string, path: PatchPath, previous: unknown[], current: unknown[], out: Patch[]): void {\n let start = 0;\n while (start < previous.length && start < current.length && structurallyEqual(previous[start], current[start])) {\n start++;\n }\n\n let previousEnd = previous.length - 1;\n let currentEnd = current.length - 1;\n while (previousEnd >= start && currentEnd >= start && structurallyEqual(previous[previousEnd], current[currentEnd])) {\n previousEnd--;\n currentEnd--;\n }\n\n const previousCount = previousEnd - start + 1;\n const currentCount = currentEnd - start + 1;\n\n if (previousCount === 0 && currentCount === 0) {\n return;\n }\n\n if (previousCount === currentCount) {\n // Same span on both sides: these are edits in place, so recurse\n // and let nested diffs stay small rather than resending elements.\n for (let offset = 0; offset < previousCount; offset++) {\n const index = start + offset;\n diff(projection, [...path, index], previous[index], current[index], out);\n }\n return;\n }\n\n out.push({\n op: 'splice',\n projection,\n path,\n index: start,\n deleteCount: previousCount,\n items: current.slice(start, currentEnd + 1)\n });\n}\n\n/**\n * Applies patches to a projection value, sharing structure with the\n * original everywhere the patch did not reach.\n *\n * Nothing handed in is mutated: bindings hold onto emitted values, so a\n * replica that edited in place would change data a component already\n * rendered.\n *\n * **Each container on a patched path is copied once per batch, not once\n * per patch.** A batch of N patches into one K-key object used to cost\n * N × K: every patch spread the whole object again to change one key.\n * A snapshot keyed by id is exactly that shape — gessologic published\n * `Record<netId, 0 | 1>` for ten thousand nets, about nine hundred\n * patches a publish, and the render worker's patch phase fell minutes\n * behind a 60 Hz stream it could never catch. So the batch remembers\n * the containers it has copied, and writes into those in place: they\n * are its own, made during this call and seen by nobody yet. Values\n * that arrived inside a patch are never written into, because they are\n * the patch's — a devtools log replays the same patch objects again.\n */\nexport function applyPatches(root: unknown, patches: readonly Patch[]): unknown {\n const owned = new Set<object>();\n let next = root;\n for (const patch of patches) {\n next = applyOne(next, patch, owned);\n }\n return next;\n}\n\nexport function applyPatch(root: unknown, patch: Patch): unknown {\n return applyOne(root, patch, new Set());\n}\n\n/** The containers this batch copied, and may therefore write into. */\ntype Owned = Set<object>;\n\nfunction applyOne(root: unknown, patch: Patch, owned: Owned): unknown {\n switch (patch.op) {\n case 'set':\n return setIn(root, patch.path, 0, patch.value, owned);\n case 'delete':\n if (patch.path.length === 0) {\n return undefined;\n }\n return deleteIn(root, patch.path, 0, owned);\n case 'splice':\n return updateIn(root, patch.path, 0, owned, node => {\n // Concatenation rather than `splice(..., ...items)`: spreading\n // the items passes each as an argument, and a batch of two\n // hundred thousand overflows the call stack.\n const array = Array.isArray(node) ? node : [];\n const spliced = array.slice(0, patch.index).concat(patch.items, array.slice(patch.index + patch.deleteCount));\n owned.add(spliced);\n return spliced;\n });\n }\n}\n\nfunction setIn(node: unknown, path: PatchPath, index: number, value: unknown, owned: Owned): unknown {\n if (index === path.length) {\n return value;\n }\n const key = path[index];\n const copy = cloneContainer(node, key, owned);\n setKey(copy, key, setIn(readKey(node, key), path, index + 1, value, owned));\n return copy;\n}\n\nfunction deleteIn(node: unknown, path: PatchPath, index: number, owned: Owned): unknown {\n const key = path[index];\n const copy = cloneContainer(node, key, owned);\n if (index === path.length - 1) {\n if (Array.isArray(copy)) {\n copy.splice(Number(key), 1);\n } else {\n delete (copy as Record<string, unknown>)[String(key)];\n }\n return copy;\n }\n setKey(copy, key, deleteIn(readKey(node, key), path, index + 1, owned));\n return copy;\n}\n\nfunction updateIn(\n node: unknown,\n path: PatchPath,\n index: number,\n owned: Owned,\n update: (node: unknown) => unknown\n): unknown {\n if (index === path.length) {\n return update(node);\n }\n const key = path[index];\n const copy = cloneContainer(node, key, owned);\n setKey(copy, key, updateIn(readKey(node, key), path, index + 1, owned, update));\n return copy;\n}\n\n/**\n * The container to write `key` into: `node` itself when this batch\n * already copied it, and a fresh copy, remembered, when it did not.\n */\nfunction cloneContainer(node: unknown, key: string | number, owned: Owned): unknown[] | Record<string, unknown> {\n if (typeof node === 'object' && node !== null && owned.has(node)) {\n return node as unknown[] | Record<string, unknown>;\n }\n let copy: unknown[] | Record<string, unknown>;\n if (Array.isArray(node)) {\n copy = node.slice();\n } else if (isPlainObject(node)) {\n copy = { ...node };\n } else {\n // The path runs past the end of what the replica holds, which\n // happens when a patch fills in a branch that did not exist yet.\n copy = typeof key === 'number' ? [] : {};\n }\n owned.add(copy);\n return copy;\n}\n\nfunction readKey(node: unknown, key: string | number): unknown {\n if (node === null || node === undefined) {\n return undefined;\n }\n return (node as Record<string | number, unknown>)[key];\n}\n\nfunction setKey(container: unknown[] | Record<string, unknown>, key: string | number, value: unknown): void {\n (container as Record<string | number, unknown>)[key] = value;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n","import type { UiSourceLocation } from 'gesso-core';\n\n/**\n * What a node is, as plain data (the inspector).\n *\n * The inspector shows a node's props, its resolved environment, its\n * bindings and their sources, its layout explanation and which\n * component owns it. Every one of those lives in the render thread,\n * and none of them can cross a `postMessage`: a `UiNode` is a live\n * object, an `Observable` is a subscription, a component instance is a\n * class. So the report is built where the tree is and only strings\n * cross, which is the same rule the existing `inspect` message follows\n * with `formatExplanation`'s text, and the same reason.\n *\n * That makes the report lossy on purpose. It is a description of a\n * node for a person to read, not a handle on one: nothing in it can be\n * clicked back to the object it came from, and nothing in it is worth\n * asserting on beyond the fact that it says what the tree says.\n */\nexport interface UiNodeReport {\n readonly id: string;\n /** `UiNodeType`, as its string value. */\n readonly type: string;\n readonly box: { readonly x: number; readonly y: number; readonly width: number; readonly height: number };\n /**\n * The components that rendered this node, nearest first.\n *\n * A node has no backpointer to a component: the builder gives every\n * component slot a Fragment anchor whose id carries `:component:`,\n * and the resolver keeps one host per anchor id. Walking up the\n * ancestors for those anchors is therefore the only way to answer\n * \"who rendered this\", and it answers it exactly, because that\n * anchor *is* the component's identity.\n */\n readonly owners: readonly UiOwnerReport[];\n readonly props: readonly UiPropReport[];\n readonly environment: readonly UiEnvironmentReport[];\n /** Attached modifier kinds, in the order the element listed them. */\n readonly modifiers: readonly string[];\n /** The event types this node has listeners for, e.g. `click`. */\n readonly listens: readonly string[];\n /**\n * What lies under the pointer beneath this node, topmost first: the\n * nodes this one is painted over and would take a press from. The\n * answer to \"I clicked the button and nothing happened\": the node the\n * inspector shows is the one that took the press, and this is the\n * button it covered. Empty when the pointer is not over the node.\n */\n readonly beneath: readonly UiBeneathReport[];\n readonly semantics?: UiSemanticsReport;\n /**\n * What the node draws while hovered and while pressed, from its\n * `interactive` modifiers: the props it writes then, as the element\n * would write them. For a design tool's handoff, which writes them\n * as `:hover` and `:active`. Absent when it changes nothing.\n */\n readonly interaction?: UiInteractionReport;\n /** `formatExplanation` of the node's layout, which is L8's answer. */\n readonly explanation: string;\n /**\n * Where the element was written, when it was compiled for\n * development. The one part of the report that is a handle after\n * all: it names a line an editor can open, and an owner's `usedAt`\n * names the line that used the component around it.\n */\n readonly source?: UiSourceLocation;\n}\n\n/** A node under the inspected one at the pointer. */\nexport interface UiBeneathReport {\n readonly id: string;\n readonly type: string;\n /** The component that rendered it, if one did. */\n readonly owner?: string;\n /** The event types it listens for, so a covered button reads as one. */\n readonly listens: readonly string[];\n}\n\nexport interface UiOwnerReport {\n /** The `@Define` tag, else the class or function name. */\n readonly name: string;\n readonly anchorId: string;\n /** Where the component was used, when compiled for development. */\n readonly usedAt?: UiSourceLocation;\n /**\n * What its parent supplied for each input, as far as plain data can\n * say: a word, a number or a flag as it is, and an element (a slot the\n * parent filled) as `{ element: true }`. Handlers, data objects and\n * streams with no current value are left out. What a design tool\n * needs to write a component's use back as code: `label=\"Email\"`.\n */\n readonly inputs?: Readonly<Record<string, UiOwnerInput>>;\n /**\n * What the component says its inputs hold now, as it keeps them\n * (`liveInput`): a switch a person turned on, a list scrolled to 600.\n * Plain values only. What a design tool saves a moment of a played\n * screen from.\n */\n readonly live?: Readonly<Record<string, string | number | boolean | null>>;\n}\n\n/** One input's value in an owner report; see `UiOwnerReport.inputs`. */\nexport type UiOwnerInput = string | number | boolean | null | { readonly element: true };\n\n/** As much of a node as a one-line path needs. */\nexport interface NodePathTarget {\n /** `UiNodeType`, as its string value. */\n readonly type: string;\n readonly id: string;\n /** The node's accessible name, when it has one. */\n readonly label?: string;\n}\n\n/**\n * A node as a path somebody can read: `App > TrackScreen > ActionRow >\n * Button \"Like\"`.\n *\n * Written for error messages rather than for the inspector, and the\n * difference decides the shape. A report is read beside the tree it\n * came from, so an id is a handle; an error is read in a console or an\n * overlay with no tree beside it, and `node-4821` there is a fact\n * about nothing. The owner chain is what a person recognises, because\n * it is the components they wrote.\n *\n * Owners arrive nearest-first, as `UiNodeReport.owners` computes them,\n * and read outermost-first, as a path does. The accessible name is the\n * leaf when there is one: two `Button`s in the same row are only told\n * apart by what they say.\n */\nexport function formatNodePath(owners: readonly UiOwnerReport[], node: NodePathTarget): string {\n const names = owners.map(owner => owner.name).reverse();\n if (names.length === 0) {\n // Nothing rendered it: a node built without a component around it,\n // which is rare enough that the id is the only thing to say.\n return node.label === undefined || node.label === '' ? `${node.type} ${node.id}` : `${node.type} \"${node.label}\"`;\n }\n const path = names.join(' > ');\n return node.label === undefined || node.label === '' ? path : `${path} \"${node.label}\"`;\n}\n\n/** Where a property's current value came from. */\nexport type UiPropOrigin = 'element' | 'binding' | 'modifier';\n\nexport interface UiPropReport {\n readonly name: string;\n readonly value: string;\n readonly origin: UiPropOrigin;\n /**\n * For a modifier write, the modifiers writing it and what the\n * element declared; for a binding, that it is bound. Absent for a\n * plain element value.\n */\n readonly source?: string;\n /** For a bound property, the stream feeding it. Absent otherwise. */\n readonly stream?: UiStreamReport;\n}\n\n/**\n * The stream behind a bound property: which one, what it last said,\n * and when.\n *\n * \"Is this value bound\" was the third question the inspector set\n * out to answer, and the report answered it with a yes. A yes is not\n * enough to debug with: a prop that is not updating and a prop whose\n * stream has not emitted since the screen was built look identical on\n * the node, and so did they in the report.\n *\n * `emittedAt` is on the epoch clock rather than the thread's own,\n * because the panel reading it is one or two threads away and\n * `performance.now()` counts from each thread's creation. The panel\n * subtracts its own `Date.now()`.\n *\n * An epoch stamp rather than an age for a second reason: an age\n * changes on every frame, and the runtime decides whether to re-send a\n * selected node's report by comparing it with the last one. A report\n * carrying an age would differ from itself sixty times a second and\n * the panel would be redrawn for a clock tick.\n */\nexport interface UiStreamReport {\n /**\n * What feeds it: a labelled cell's own name (`queue.current`), else\n * what kind of stream it is.\n */\n readonly source: string;\n /** `cell` when the stream named itself, `observable` when it did not. */\n readonly kind: 'cell' | 'observable';\n /** The last value the stream produced, printed. */\n readonly value: string;\n /** How many values it has produced since the property was bound. */\n readonly emissions: number;\n /** When the last value arrived, as epoch milliseconds, or null before the first. */\n readonly emittedAt: number | null;\n /** Whether the subscription is still live. A false here is a torn-down binding still on the node. */\n readonly connected: boolean;\n}\n\n/**\n * A bound property's stream, as far as the report is concerned.\n *\n * Structural rather than `UiBinding`, so this module keeps its one\n * rule: it describes a node and imports nothing that holds one.\n */\nexport interface BoundStream {\n readonly id: number;\n readonly observable: unknown;\n value(): unknown;\n emissionCount(): number;\n emittedAt(): number | null;\n connected(): boolean;\n}\n\n/**\n * Describes the stream driving a property.\n *\n * `timeOrigin` turns the binding's own reading into an epoch stamp:\n * `performance.timeOrigin` on a thread that has one, and zero where\n * the reading is already an epoch.\n */\nexport function describeStream(binding: BoundStream, timeOrigin: number): UiStreamReport {\n // A labelled cell says where the value comes from: `Card.title`,\n // `queue.current`. An anonymous pipe only has the binding's id.\n const label = (binding.observable as { label?: unknown } | null)?.label;\n const labelled = typeof label === 'string' && label !== '';\n const emittedAt = binding.emittedAt();\n return {\n source: labelled ? label : `observable #${binding.id}`,\n kind: labelled ? 'cell' : 'observable',\n value: printPropValue(binding.value()),\n emissions: binding.emissionCount(),\n emittedAt: emittedAt === null ? null : timeOrigin + emittedAt,\n connected: binding.connected()\n };\n}\n\n/** A stream as one line: what feeds the prop, and how long ago it said so. */\nexport function formatStream(stream: UiStreamReport, now: number = Date.now()): string {\n const when = stream.emittedAt === null ? 'no value yet' : `${formatAge(Math.max(0, now - stream.emittedAt))} ago`;\n const emissions = `${stream.emissions} emission${stream.emissions === 1 ? '' : 's'}`;\n return `${stream.source} · ${emissions} · ${when}${stream.connected ? '' : ' · disconnected'}`;\n}\n\n/** An age in the largest unit that stays readable. */\nexport function formatAge(ms: number): string {\n if (ms < 1000) {\n return `${Math.round(ms)}ms`;\n }\n if (ms < 60_000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n return `${Math.round(ms / 60_000)}m`;\n}\n\nexport interface UiEnvironmentReport {\n readonly key: string;\n readonly value: string;\n /** Whether this node provides the value rather than inheriting it. */\n readonly provided: boolean;\n}\n\nexport interface UiInteractionReport {\n readonly hovered?: Readonly<Record<string, unknown>>;\n readonly pressed?: Readonly<Record<string, unknown>>;\n}\n\nexport interface UiSemanticsReport {\n readonly role?: string;\n readonly label?: string;\n readonly value?: string;\n readonly states?: readonly string[];\n}\n\n/**\n * A value as one line a person can read.\n *\n * `JSON.stringify` alone is not enough: the two values a canvas UI\n * puts in a property that it cannot handle are a `Set` (a node's\n * visual states) and a function (an event handler), and it prints both\n * as `{}`, which in an inspector reads as a bug in the application\n * rather than one in the inspector.\n */\nexport function printPropValue(value: unknown): string {\n if (value === undefined) {\n return 'undefined';\n }\n if (value === null) {\n return 'null';\n }\n if (typeof value === 'function') {\n return `ƒ ${value.name === '' ? '(anonymous)' : value.name}`;\n }\n if (typeof value === 'symbol') {\n return value.toString();\n }\n if (typeof value === 'string') {\n return value;\n }\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n if (value instanceof Set) {\n return `Set { ${[...value].map(printPropValue).join(', ')} }`;\n }\n if (value instanceof Map) {\n return `Map { ${[...value].map(([key, entry]) => `${printPropValue(key)}: ${printPropValue(entry)}`).join(', ')} }`;\n }\n if (Array.isArray(value)) {\n return `[${value.map(printPropValue).join(', ')}]`;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n // A cyclic value, or one with a throwing getter. The inspector\n // must not be the thing that breaks while explaining a break.\n return String(value);\n }\n}\n\n/** The report as text, for a console or a test failure. */\n/** `file:line:column`, the form editors and terminals turn into a link. */\nexport function formatSourceLocation(source: UiSourceLocation): string {\n return `${source.fileName}:${source.lineNumber}:${source.columnNumber}`;\n}\n\nexport function formatNodeReport(report: UiNodeReport): string {\n const lines: string[] = [`${report.type} '${report.id}'`];\n if (report.source !== undefined) {\n lines.push(`written at ${formatSourceLocation(report.source)}`);\n }\n if (report.owners.length > 0) {\n lines.push(\n `rendered by ${report.owners\n .map(owner =>\n owner.usedAt === undefined ? owner.name : `${owner.name} (used at ${formatSourceLocation(owner.usedAt)})`\n )\n .join(' inside ')}`\n );\n }\n if (report.modifiers.length > 0) {\n lines.push(`modifiers: ${report.modifiers.join(', ')}`);\n }\n if (report.listens.length > 0) {\n lines.push(`listens: ${report.listens.join(', ')}`);\n }\n if (report.beneath.length > 0) {\n lines.push('beneath, at the pointer:');\n for (const under of report.beneath) {\n const owner = under.owner === undefined ? '' : ` (${under.owner})`;\n const listens = under.listens.length === 0 ? '' : `, listens: ${under.listens.join(', ')}`;\n lines.push(` ${under.type} ${under.id}${owner}${listens}`);\n }\n }\n if (report.props.length > 0) {\n lines.push('props:');\n for (const prop of report.props) {\n lines.push(` ${prop.name} = ${prop.value}${prop.source === undefined ? '' : ` (${prop.source})`}`);\n if (prop.stream !== undefined) {\n lines.push(` ${formatStream(prop.stream)}`);\n }\n }\n }\n if (report.environment.length > 0) {\n lines.push('environment:');\n for (const entry of report.environment) {\n lines.push(` ${entry.key} = ${entry.value}${entry.provided ? ' (provided here)' : ''}`);\n }\n }\n if (report.semantics !== undefined) {\n const parts = [\n report.semantics.role === undefined ? undefined : `role ${report.semantics.role}`,\n report.semantics.label === undefined ? undefined : `label ${report.semantics.label}`,\n report.semantics.value === undefined ? undefined : `value ${report.semantics.value}`,\n report.semantics.states === undefined || report.semantics.states.length === 0\n ? undefined\n : `states ${report.semantics.states.join(', ')}`\n ].filter(part => part !== undefined);\n if (parts.length > 0) {\n lines.push(`semantics: ${parts.join(' · ')}`);\n }\n }\n lines.push(report.explanation);\n return lines.join('\\n');\n}\n","import { printPropValue } from '../app/NodeReport';\nimport type { ConsoleEntry } from '../app/DevtoolsProtocol';\n\n/**\n * Forwarding a worker's `console.*`.\n *\n * A worker's console is real, but it is in devtools' worker target,\n * which a developer has to know to select and cannot read alongside\n * the page's. So while a devtools panel asks for it, each `console.*`\n * call in the worker is also handed to a sink as plain strings, with\n * the thread named, and the panel shows the three threads as one log.\n *\n * The call still reaches the worker's own console: this is a copy,\n * not a redirect, so turning a panel on changes nothing about what\n * devtools' worker target shows.\n */\n\nexport type ConsoleLevel = ConsoleEntry['level'];\n\nconst LEVELS: readonly ConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug'];\n\n/** An entry before the shell has said which thread it came from. */\nexport type ConsoleEntryBody = Omit<ConsoleEntry, 'thread'>;\n\n/**\n * Copies every `console.*` call on `target` (the worker global's\n * console by default) to `sink`. Returns a function that restores the\n * original methods.\n *\n * Idempotent per target: a second capture on the same console replaces\n * the first's sink rather than nesting, so toggling a panel on twice\n * does not log twice.\n */\nexport function captureConsole(sink: (entry: ConsoleEntryBody) => void, target: Console = console): () => void {\n const installed = (target as Console & { [CAPTURED]?: { sink: typeof sink; restore: () => void } })[CAPTURED];\n if (installed !== undefined) {\n installed.sink = sink;\n return installed.restore;\n }\n const originals = new Map<ConsoleLevel, (...args: unknown[]) => void>();\n const state = {\n sink,\n restore: (): void => {\n for (const [level, original] of originals) {\n target[level] = original;\n }\n delete (target as Console & { [CAPTURED]?: unknown })[CAPTURED];\n }\n };\n for (const level of LEVELS) {\n const original = target[level] as (...args: unknown[]) => void;\n originals.set(level, original);\n target[level] = (...args: unknown[]): void => {\n original.apply(target, args);\n // A sink that throws must not break the application's logging,\n // least of all while a developer is trying to read it.\n try {\n state.sink({ level, args: args.map(formatConsoleArg), at: Date.now() });\n } catch {\n // Nothing to do: the original call already ran.\n }\n };\n }\n (target as Console & { [CAPTURED]?: typeof state })[CAPTURED] = state;\n return state.restore;\n}\n\nconst CAPTURED = Symbol.for('gesso:console-captured');\n\n/**\n * One console argument as the panel prints it.\n *\n * `printPropValue` already prints the values a canvas UI is likely to\n * log; an Error is the one thing it prints badly (`{}`), and the one\n * thing a developer most wants to read whole.\n */\nexport function formatConsoleArg(value: unknown): string {\n if (value instanceof Error) {\n return value.stack !== undefined && value.stack !== '' ? value.stack : `${value.name}: ${value.message}`;\n }\n return printPropValue(value);\n}\n\n/** The shell's request to an application worker to start or stop forwarding. */\nexport interface ConsoleForwardingMessage {\n type: 'gesso:console';\n enabled: boolean;\n}\n\n/** An application worker's forwarded entry. */\nexport interface ConsoleEntryMessage {\n type: 'gesso:console';\n entry: ConsoleEntryBody;\n}\n\nexport function isConsoleForwardingMessage(value: unknown): value is ConsoleForwardingMessage {\n const message = value as { type?: unknown; enabled?: unknown } | null;\n return message?.type === 'gesso:console' && typeof message.enabled === 'boolean';\n}\n\nexport function isConsoleEntryMessage(value: unknown): value is ConsoleEntryMessage {\n const message = value as { type?: unknown; entry?: unknown } | null;\n return message?.type === 'gesso:console' && typeof message.entry === 'object' && message.entry !== null;\n}\n","/**\n * Named `MessagePort`s into a worker.\n *\n * A worker's global `onmessage` is a single channel, so a worker that\n * receives messages on it can host exactly one conversation. That is\n * why a store in a data worker used to mean a worker per store: the\n * client claimed the `Worker` object itself, and a second one had\n * nowhere to go.\n *\n * A handshake fixes it. The client opens a `MessageChannel`, keeps one\n * end and transfers the other with a name; the worker serves that name\n * and the two ends talk privately from then on. The global channel is\n * used once per conversation and carries nothing else.\n *\n * Nothing here knows what travels over a port. It is the transport the\n * store replication in `../store/worker` runs on today and the barrier\n * contract will run on next.\n */\n\nimport { captureConsole, isConsoleForwardingMessage, type ConsoleEntryMessage } from './captureConsole';\n\n/** A port-shaped thing: `MessagePort` and `Worker` both satisfy it. */\nexport interface MessageEndpoint {\n postMessage(message: unknown): void;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/** The one message the global channel carries. */\nexport interface PortHandshake {\n type: 'gesso:port';\n key: string;\n}\n\nexport function isPortHandshake(value: unknown): value is PortHandshake {\n const message = value as { type?: unknown; key?: unknown } | null;\n return message?.type === 'gesso:port' && typeof message.key === 'string';\n}\n\n/**\n * A worker spawned at most once, serving any number of named ports.\n *\n * Handed to several `useChannel` calls, it is what lets one worker\n * hold a whole application layer instead of one channel. Spawning is\n * deferred to the first `open`, so a handle nobody uses costs nothing.\n */\nexport interface WorkerHandle {\n /**\n * Opens a private channel under `key`, spawning the worker if this\n * is the first one.\n */\n open(key: string): MessagePort;\n /** Whether the worker has been spawned. */\n readonly spawned: boolean;\n /** Stops the worker, if it was ever started. */\n terminate(): void;\n}\n\n/**\n * Stands for \"whichever worker the shell spawned for the application\".\n *\n * A registration inside the render worker cannot name that worker: it\n * is created by the shell and its port only arrives with `init`, long\n * after `useChannel` and `useService` have run. This sentinel is what a\n * registration puts there instead, and the render worker swaps it for\n * the real handle once the port shows up.\n *\n * Opening a port on it before then is a bug rather than a race, so it\n * says so.\n */\nexport const APPLICATION_WORKER: WorkerHandle = {\n open(): MessagePort {\n throw new Error(\n 'APPLICATION_WORKER was used directly. It is a placeholder the render worker ' +\n \"replaces with the shell's port; reaching it means no application-logic worker was supplied. \" +\n 'Pass appLogicWorker to createApp.'\n );\n },\n spawned: false,\n terminate(): void {}\n};\n\n/**\n * Anything a handshake can be posted to with a port attached.\n * `Worker` and `MessagePort` both satisfy it.\n */\nexport interface TransferTarget {\n postMessage(message: unknown, transfer: Transferable[]): void;\n}\n\n/**\n * A handle over an endpoint someone else owns.\n *\n * The shell spawns the application worker and hands the render worker\n * one end of a channel to it; this is what the render worker opens\n * named ports over. `terminate` is a no-op — the lifetime belongs to\n * whoever created the endpoint, and a handle that could kill a worker\n * it did not spawn would be a surprising thing to hand out.\n */\nexport function portHandle(endpoint: TransferTarget): WorkerHandle {\n return {\n open(key: string): MessagePort {\n const channel = new MessageChannel();\n endpoint.postMessage({ type: 'gesso:port', key } satisfies PortHandshake, [channel.port2]);\n return channel.port1;\n },\n get spawned(): boolean {\n return true;\n },\n terminate(): void {}\n };\n}\n\n/**\n * Routes handshakes arriving on a transferred port through the same\n * handlers as the worker's own global channel.\n *\n * The shell owns the application worker and gives the render worker a\n * port to it, so handshakes reach this worker two ways: on its global\n * channel (whoever spawned it) and on that port (whoever was given\n * it). Both should be served by the same handlers, and neither end\n * should have to know which route a channel came in on.\n */\nexport interface HubMessage {\n type: 'gesso:hub';\n}\n\nexport function isHubMessage(value: unknown): value is HubMessage {\n return (value as { type?: unknown } | null)?.type === 'gesso:hub';\n}\n\nconst BASE_INSTALLED = Symbol.for('gesso:port-base-installed');\nconst CONSOLE_RESTORE = Symbol.for('gesso:port-console-restore');\n\n/**\n * Starts or stops copying this worker's console to whoever posts to\n * it, as `ConsoleEntryMessage`s. A host with no `postMessage` (a test's\n * bare object) has nowhere to send them and forwards nothing.\n */\nfunction setConsoleForwarding(host: PortHost & { [CONSOLE_RESTORE]?: () => void }, enabled: boolean): void {\n host[CONSOLE_RESTORE]?.();\n delete host[CONSOLE_RESTORE];\n const post = (host as { postMessage?: (message: unknown) => void }).postMessage;\n if (!enabled || typeof post !== 'function') {\n return;\n }\n host[CONSOLE_RESTORE] = captureConsole(entry => {\n const message: ConsoleEntryMessage = { type: 'gesso:console', entry };\n post.call(host, message);\n });\n}\n\n/**\n * The handler every `servePorts` chain sits on top of.\n *\n * It owns the two things no individual server can: routing a hub port\n * through the whole chain, and answering a handshake that nobody\n * accepted. Both have to be innermost — the first because the chain is\n * only complete once every server has wrapped `onmessage`, the second\n * because \"nobody accepted\" is only known after every server has\n * declined.\n */\nfunction installBase(host: PortHost & { [BASE_INSTALLED]?: boolean }): void {\n if (host[BASE_INSTALLED] === true) {\n return;\n }\n host[BASE_INSTALLED] = true;\n const previous = host.onmessage;\n const registered = (host as HostWithNames)[SERVED_NAMES] ?? [];\n\n host.onmessage = event => {\n if (isConsoleForwardingMessage(event.data)) {\n // The shell, on behalf of a devtools panel, asking for this\n // worker's console. Answered here because this is the one handler\n // every application worker has, whatever it serves.\n setConsoleForwarding(host, event.data.enabled);\n return;\n }\n if (isHubMessage(event.data)) {\n const port = event.ports?.[0];\n if (port === undefined) {\n throw new Error('A hub message arrived with no port attached.');\n }\n // The whole chain, not just this handler: by now every\n // `servePorts` on this host has wrapped `onmessage`, and a\n // handshake arriving on the port should reach all of them.\n port.onmessage = host.onmessage;\n return;\n }\n if (!isPortHandshake(event.data)) {\n previous?.(event);\n return;\n }\n const port = event.ports?.[0];\n if (port === undefined) {\n throw new Error(`Port handshake for '${event.data.key}' arrived with no port attached.`);\n }\n // Reaching here means every server declined. Silence would leave\n // the client waiting forever with nothing said.\n const served = registered.flatMap(get => [...get()]).sort();\n const error: PortErrorMessage = {\n type: 'port:error',\n message: `Nothing is served under '${event.data.key}'. This worker serves: ${\n served.length > 0 ? served.join(', ') : '(nothing)'\n }.`\n };\n port.postMessage(error);\n };\n}\n\n/**\n * Wraps a worker factory so the worker is created once and shared.\n *\n * A factory rather than a URL for the same reason the render worker\n * takes one: a bundler only emits a chunk for a worker it can see\n * constructed literally in the calling module.\n *\n * const data = workerHandle(\n * () => new Worker(new URL('./data.worker.ts', import.meta.url), { type: 'module' })\n * );\n */\nexport function workerHandle(factory: () => Worker): WorkerHandle {\n let worker: Worker | undefined;\n return {\n open(key: string): MessagePort {\n worker ??= factory();\n const channel = new MessageChannel();\n // The worker may not have run its module body yet. Messages\n // posted to a worker queue until it installs a handler, so the\n // handshake is safe as long as `servePorts` is called\n // synchronously at the top level of the worker module — the same\n // guarantee `renderRoot` relies on.\n worker.postMessage({ type: 'gesso:port', key } satisfies PortHandshake, [channel.port2]);\n return channel.port1;\n },\n get spawned(): boolean {\n return worker !== undefined;\n },\n terminate(): void {\n worker?.terminate();\n worker = undefined;\n }\n };\n}\n\n/**\n * Minimal view of a worker's global scope, so this module type-checks\n * against the DOM lib without pulling in the WebWorker lib.\n */\nexport interface PortHost {\n onmessage: ((event: { data: unknown; ports?: readonly MessagePort[] }) => void) | null;\n}\n\n/**\n * What a port is answered with when no handler claimed its name.\n *\n * Its own message type rather than a store's or a channel's, because\n * the transport does not know which of them the client is: both\n * recognise it, so a mismatched name is loud either way.\n */\nexport interface PortErrorMessage {\n type: 'port:error';\n message: string;\n}\n\nexport function isPortErrorMessage(value: unknown): value is PortErrorMessage {\n const message = value as { type?: unknown; message?: unknown } | null;\n return message?.type === 'port:error' && typeof message.message === 'string';\n}\n\n/** Every name served on a host, across all `servePorts` calls on it. */\nconst SERVED_NAMES = Symbol.for('gesso:served-port-names');\n\ninterface HostWithNames extends PortHost {\n [SERVED_NAMES]?: Array<() => readonly string[]>;\n}\n\n/**\n * Serves named ports inside a worker.\n *\n * Call it synchronously at the top level of the worker module, before\n * any await, so no handshake is missed.\n *\n * `onPort` returns whether it took the port. Returning false passes\n * the handshake to whatever was serving before, which is what lets two\n * kinds of thing — stores and channels, during the migration — share\n * one worker: each answers for its own names and declines the rest.\n * When nobody accepts, the port is answered with an error naming\n * everything the worker does serve, because a handshake that silently\n * matched nothing leaves the client waiting forever with nothing said.\n *\n * `names` is only read to build that message.\n *\n * Returns a function that stops serving.\n */\nexport function servePorts(\n onPort: (key: string, port: MessagePort) => boolean,\n names: () => readonly string[],\n host: PortHost = self as unknown as PortHost\n): () => void {\n const withNames = host as HostWithNames;\n const registered = (withNames[SERVED_NAMES] ??= []);\n registered.push(names);\n // Before this handler wraps `onmessage`, so the base ends up\n // innermost: it answers a handshake only once every server above it\n // has declined.\n installBase(host);\n\n const previous = host.onmessage;\n host.onmessage = event => {\n if (!isPortHandshake(event.data)) {\n // Not ours. A worker that also speaks its own protocol on the\n // global channel keeps working.\n previous?.(event);\n return;\n }\n const port = event.ports?.[0];\n if (port === undefined) {\n throw new Error(`Port handshake for '${event.data.key}' arrived with no port attached.`);\n }\n if (onPort(event.data.key, port)) {\n return;\n }\n // Declined. Another server may take it; the base answers if none\n // does.\n previous?.(event);\n };\n return () => {\n host.onmessage = previous;\n const index = registered.indexOf(names);\n if (index >= 0) {\n registered.splice(index, 1);\n }\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { A as AgentResource, B as ServedChannel, D as designedTheme, E as DesignThemes, F as AgentToolResult, I as WAITS_UP_TO, L as agentSurface, M as AgentSurfaceLike, N as AgentSurfaceOptions, O as AgentConfirmation, P as AgentTool, R as resourceUri, T as AgentThemeName, X as WorkerHandle, a as toolSurface, at as JsonSchema, c as UiRefs, d as resolveTarget, f as uiSurface, h as renderModule, i as agentText, j as AgentSurface, k as AgentContent, l as UiSurfaceOptions, m as RenderRequest, n as agentError, o as SCREEN_TOOL_NAMES, p as RenderDependencies, r as agentImages, s as UiHost, t as AgentToolDefinition, u as outline, z as resultText } from "../tools-BFBwuinE.js";
1
+ import { A as AgentResource, B as ServedChannel, D as designedTheme, E as DesignThemes, F as AgentToolResult, I as WAITS_UP_TO, L as agentSurface, M as AgentSurfaceLike, N as AgentSurfaceOptions, O as AgentConfirmation, P as AgentTool, R as resourceUri, T as AgentThemeName, X as WorkerHandle, a as toolSurface, at as JsonSchema, c as UiRefs, d as resolveTarget, f as uiSurface, h as renderModule, i as agentText, j as AgentSurface, k as AgentContent, l as UiSurfaceOptions, m as RenderRequest, n as agentError, o as SCREEN_TOOL_NAMES, p as RenderDependencies, r as agentImages, s as UiHost, t as AgentToolDefinition, u as outline, z as resultText } from "../tools-Bl8LZ7LR.js";
2
2
  import { UiTheme } from "gesso-core";
3
3
  //#region src/agent/mcp.d.ts
4
4
  /**
@@ -241,6 +241,8 @@ interface DevAgentResponse {
241
241
  declare const DEV_AGENT_EVENTS: {
242
242
  readonly ready: 'gesso:agent:ready';
243
243
  readonly request: 'gesso:agent:request';
244
+ /** Page to server: a request has arrived and is being answered, so the page is running. */
245
+ readonly received: 'gesso:agent:received';
244
246
  readonly response: 'gesso:agent:response';
245
247
  };
246
248
  declare function connectDevAgent(app: DevAgentApp, hot: DevAgentHot, info?: McpServerInfo): void;
@@ -1,4 +1,4 @@
1
- import { t as renderModule } from "../render-cydQ10Nd.js";
1
+ import { t as renderModule } from "../render-W2XO444K.js";
2
2
  import { a as WAITS_UP_TO, c as resultText, i as serveAgentPort, l as validate, n as combineSurfaces, o as agentSurface, r as remoteSurface, s as resourceUri, t as AGENT_PORT } from "../remote-B4bjdTzg.js";
3
3
  import { a as outline, c as formatThemeTokens, d as agentImages, f as agentText, i as UiRefs, l as themeTokens, m as designedTheme, n as serveApplicationAgent, o as resolveTarget, p as toolSurface, r as SCREEN_TOOL_NAMES, s as uiSurface, u as agentError } from "../app-De3z58x7.js";
4
4
  import { i as registerWebMcpTools, n as connectWebMcp, r as pageModelContext, t as confirmInWindow } from "../webmcp-D7IlA0fu.js";
@@ -199,6 +199,8 @@ function text(status, body) {
199
199
  const DEV_AGENT_EVENTS = {
200
200
  ready: "gesso:agent:ready",
201
201
  request: "gesso:agent:request",
202
+ /** Page to server: a request has arrived and is being answered, so the page is running. */
203
+ received: "gesso:agent:received",
202
204
  response: "gesso:agent:response"
203
205
  };
204
206
  function connectDevAgent(app, hot, info = {}) {
@@ -215,6 +217,7 @@ function connectDevAgent(app, hot, info = {}) {
215
217
  return surface;
216
218
  };
217
219
  hot.on(DEV_AGENT_EVENTS.request, (request) => {
220
+ hot.send(DEV_AGENT_EVENTS.received, { id: request.id });
218
221
  handleMcpMessage(reach(), request.message, {
219
222
  name: info.name ?? (document.title || "gesso"),
220
223
  ...info
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/agent/mcp.ts","../../src/agent/dev.ts"],"sourcesContent":["import type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * An agent surface, served over the Model Context Protocol.\n *\n * Two layers. `handleMcpMessage` answers one JSON-RPC message and knows\n * nothing about how it arrived, which is all a stdio transport or a\n * relay needs. `mcpHandler` wraps it in MCP's Streamable HTTP\n * transport as a `fetch`-style function, `(Request) => Response`,\n * which is what `Bun.serve`, Deno, a service worker and every modern\n * Node server framework take, so the framework imports no server.\n *\n * What it implements is the part a channel needs: `initialize`, `ping`,\n * `tools/list`, `tools/call`, `resources/list` and `resources/read`.\n * Responses are plain JSON rather than an event stream, which the\n * transport allows, and the GET stream for server-initiated messages\n * is declined with 405, which it also allows: nothing here sends one,\n * so the capabilities say neither `listChanged` nor `subscribe`.\n *\n * `mcpHandler` keeps sessions, as the transport describes: `initialize`\n * is answered with an `Mcp-Session-Id`, the client sends it back on\n * every request after, and `DELETE` with it ends the session. A session\n * remembers the client's `clientInfo`, so an application can tell one\n * agent from another while a request runs (`McpHandlerOptions.around`).\n * A request without a session, or with one the server has forgotten, is\n * still answered, as one from an unknown caller.\n */\n\n/** The protocol revisions this server speaks, newest first. */\nexport const MCP_PROTOCOL_VERSIONS = ['2025-11-25', '2025-06-18', '2025-03-26'] as const;\n\nexport interface McpServerInfo {\n /** The name a client shows for this server. Defaults to `gesso`. */\n name?: string;\n version?: string;\n /** Told to the agent on connecting: what this application is, in a sentence or two. */\n instructions?: string;\n}\n\ntype JsonRpcId = string | number;\n\ninterface JsonRpcRequest {\n readonly jsonrpc: '2.0';\n readonly id?: JsonRpcId;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\nexport type JsonRpcResponse =\n | { jsonrpc: '2.0'; id: JsonRpcId | null; result: unknown }\n | { jsonrpc: '2.0'; id: JsonRpcId | null; error: { code: number; message: string } };\n\nconst PARSE_ERROR = -32700;\nconst INVALID_REQUEST = -32600;\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n/** MCP's code for a resource that does not exist. */\nconst RESOURCE_NOT_FOUND = -32002;\n\n/**\n * Answers one JSON-RPC message. Returns null for a notification, which\n * takes no answer, and never throws: a malformed message is answered\n * with the JSON-RPC error that says what was wrong with it.\n */\nexport async function handleMcpMessage(\n surface: AgentSurfaceLike,\n message: unknown,\n info: McpServerInfo = {}\n): Promise<JsonRpcResponse | null> {\n if (!isRequest(message)) {\n return error(null, INVALID_REQUEST, 'Expected a JSON-RPC 2.0 request with a method.');\n }\n if (message.id === undefined) {\n // `notifications/initialized` and `notifications/cancelled` are the\n // ones a client sends. Neither needs anything from a server whose\n // every call answers before the next is read.\n return null;\n }\n const { id } = message;\n const params = message.params ?? {};\n switch (message.method) {\n case 'initialize': {\n const asked = params.protocolVersion;\n const version = MCP_PROTOCOL_VERSIONS.find(supported => supported === asked) ?? MCP_PROTOCOL_VERSIONS[0];\n return ok(id, {\n protocolVersion: version,\n capabilities: { tools: {}, resources: {} },\n serverInfo: { name: info.name ?? 'gesso', version: info.version ?? '0.0.0' },\n ...(info.instructions === undefined ? {} : { instructions: info.instructions })\n });\n }\n case 'ping':\n return ok(id, {});\n case 'tools/list':\n return ok(id, { tools: await surface.tools() });\n case 'tools/call': {\n if (typeof params.name !== 'string') {\n return error(id, INVALID_PARAMS, 'tools/call needs the name of a tool.');\n }\n const args = params.arguments;\n if (args !== undefined && (args === null || typeof args !== 'object' || Array.isArray(args))) {\n return error(id, INVALID_PARAMS, 'tools/call arguments must be an object.');\n }\n return ok(id, await surface.call(params.name, args as Record<string, unknown> | undefined));\n }\n case 'resources/list':\n return ok(id, { resources: await surface.resources() });\n case 'resources/templates/list':\n return ok(id, { resourceTemplates: [] });\n case 'resources/read': {\n const uri = params.uri;\n const view = typeof uri === 'string' ? await surface.read(uri) : undefined;\n if (typeof uri !== 'string' || view === undefined) {\n return error(id, RESOURCE_NOT_FOUND, `No resource at ${String(uri)}.`);\n }\n return ok(id, { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(view) }] });\n }\n default:\n return error(id, METHOD_NOT_FOUND, `This server does not implement ${message.method}.`);\n }\n}\n\n/**\n * Who sent a request: the session `initialize` began, and the client it\n * said it was. Handed to `McpHandlerOptions.around` for each request.\n */\nexport interface McpCaller {\n /** The session's id, as sent in `Mcp-Session-Id`. */\n readonly session: string;\n /** The order sessions began in since the handler was made, from 1: a short way to tell two of one client apart. */\n readonly number: number;\n /** What the client said it was in `initialize`'s `clientInfo`; an empty name when it said nothing. */\n readonly client: { readonly name: string; readonly version: string };\n}\n\n/** The most sessions remembered at once; the oldest is forgotten first, and its requests are then from an unknown caller. */\nconst MAX_SESSIONS = 256;\n\nexport interface McpHandlerOptions extends McpServerInfo {\n /**\n * Origins a browser may call from. A request carrying any other\n * `Origin` is refused, as the transport requires: without it, a web\n * page the person happens to have open could reach a server bound to\n * their own machine and send commands to their application. Agents\n * that are not browsers send no `Origin` and are unaffected.\n */\n allowedOrigins?: readonly string[];\n /**\n * A secret every request must carry as `Authorization: Bearer\n * <token>`. Worth setting whenever the port is reachable by anything\n * other than the person's own agents.\n */\n token?: string;\n /**\n * Runs around every request, told who sent it: the session and the\n * client it belongs to, or null for a request outside any session. A\n * place to keep the caller in an `AsyncLocalStorage`, say, so the\n * commands a tool call runs can tell which agent asked:\n *\n * around: (caller, handle) => agents.run(caller, handle)\n *\n * It must call `handle` and return what it returns.\n */\n around?: (caller: McpCaller | null, handle: () => Promise<JsonRpcResponse | null>) => Promise<JsonRpcResponse | null>;\n}\n\n/**\n * The Streamable HTTP transport, as a `fetch` handler.\n *\n * Bun.serve({ hostname: '127.0.0.1', port: 7310, fetch: mcpHandler(surface, { name: 'notes' }) });\n *\n * It answers every path; mount it where the server routes `/mcp`.\n */\nexport function mcpHandler(\n surface: AgentSurfaceLike,\n options: McpHandlerOptions = {}\n): (request: Request) => Promise<Response> {\n const sessions = new Map<string, McpCaller>();\n let begun = 0;\n return async request => {\n const origin = request.headers.get('origin');\n if (origin !== null && !(options.allowedOrigins ?? []).includes(origin)) {\n return text(403, `Requests from ${origin} are not allowed.`);\n }\n if (\n options.token !== undefined &&\n !sameText(request.headers.get('authorization') ?? '', `Bearer ${options.token}`)\n ) {\n return text(401, 'This server needs a bearer token.');\n }\n const sessionId = request.headers.get('mcp-session-id');\n if (request.method === 'DELETE' && sessionId !== null) {\n sessions.delete(sessionId);\n return new Response(null, { status: 204 });\n }\n const version = request.headers.get('mcp-protocol-version');\n if (version !== null && !(MCP_PROTOCOL_VERSIONS as readonly string[]).includes(version)) {\n return text(400, `Unsupported MCP protocol version ${version}.`);\n }\n if (request.method !== 'POST') {\n // GET is the optional stream for messages the server starts. There\n // are no such messages, and 405 is how the transport says so.\n return new Response(null, { status: 405, headers: { allow: 'POST, DELETE' } });\n }\n let message: unknown;\n try {\n message = await request.json();\n } catch {\n return json(400, error(null, PARSE_ERROR, 'The body is not JSON.'));\n }\n // `initialize` begins a session; anything else belongs to the one it names, if it is known.\n let started: McpCaller | null = null;\n if (isRequest(message) && message.method === 'initialize' && message.id !== undefined) {\n const info = (message.params?.clientInfo ?? {}) as { name?: unknown; version?: unknown };\n started = {\n session: newSessionId(),\n number: ++begun,\n client: {\n name: typeof info.name === 'string' ? info.name : '',\n version: typeof info.version === 'string' ? info.version : ''\n }\n };\n sessions.set(started.session, started);\n if (sessions.size > MAX_SESSIONS) sessions.delete(sessions.keys().next().value!);\n }\n const caller = started ?? (sessionId === null ? null : (sessions.get(sessionId) ?? null));\n const handle = () => handleMcpMessage(surface, message, options);\n const response = await (options.around === undefined ? handle() : options.around(caller, handle));\n if (response === null) return new Response(null, { status: 202 });\n const answered = json(200, response);\n if (started !== null) answered.headers.set('mcp-session-id', started.session);\n return answered;\n };\n}\n\n/** A session id no one can guess: 128 random bits, as hex. */\nfunction newSessionId(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Whether two strings are the same, taking as long however early they\n * differ, so a token cannot be guessed a character at a time from how\n * quickly it is refused.\n */\nfunction sameText(a: string, b: string): boolean {\n let differs = a.length ^ b.length;\n for (let i = 0; i < b.length; i++) differs |= (a.charCodeAt(i % Math.max(a.length, 1)) || 0) ^ b.charCodeAt(i);\n return differs === 0;\n}\n\nfunction isRequest(message: unknown): message is JsonRpcRequest {\n const candidate = message as Partial<JsonRpcRequest> | null;\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n !Array.isArray(candidate) &&\n candidate.jsonrpc === '2.0' &&\n typeof candidate.method === 'string'\n );\n}\n\nfunction ok(id: JsonRpcId, result: unknown): JsonRpcResponse {\n return { jsonrpc: '2.0', id, result };\n}\n\nfunction error(id: JsonRpcId | null, code: number, message: string): JsonRpcResponse {\n return { jsonrpc: '2.0', id, error: { code, message } };\n}\n\nfunction json(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n}\n\nfunction text(status: number, body: string): Response {\n return new Response(body, { status, headers: { 'content-type': 'text/plain; charset=utf-8' } });\n}\n","import { handleMcpMessage, type JsonRpcResponse, type McpServerInfo } from './mcp';\nimport { AGENT_PORT, combineSurfaces, remoteSurface } from './remote';\nimport { confirmInWindow } from './webmcp';\nimport type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * The page's half of the development agent bridge.\n *\n * `gesso-vite-plugin` serves MCP at `/__gesso/mcp` on the dev server,\n * but the channels are in the page's workers, and a server cannot\n * reach a worker. The page can, and it already holds a socket to the\n * server: Vite's HMR connection. So the server relays each JSON-RPC\n * message down that socket, this answers it against the render\n * worker's agent port, and the answer goes back up the same way.\n *\n * MCP is spoken here, in the page, rather than on the server, so the\n * server needs nothing from the framework and the workers need nothing\n * from MCP. The plugin injects the call in a dev server only; a build\n * carries no reference to this module.\n */\n\n/** The part of `import.meta.hot` this uses. */\nexport interface DevAgentHot {\n send(event: string, data?: unknown): void;\n on(event: string, listener: (data: never) => void): void;\n}\n\n/** What the bridge needs of an application: a way to reach its render worker. */\nexport interface DevAgentApp {\n openRenderPort(key: string): MessagePort | undefined;\n}\n\n/** Server to page. */\nexport interface DevAgentRequest {\n readonly id: number;\n readonly message: unknown;\n}\n\n/** Page to server. */\nexport interface DevAgentResponse {\n readonly id: number;\n readonly response: JsonRpcResponse | null;\n}\n\n/** The HMR events the bridge speaks. The plugin's half uses the same names. */\nexport const DEV_AGENT_EVENTS = {\n ready: 'gesso:agent:ready',\n request: 'gesso:agent:request',\n response: 'gesso:agent:response'\n} as const;\n\nexport function connectDevAgent(app: DevAgentApp, hot: DevAgentHot, info: McpServerInfo = {}): void {\n let surface: AgentSurfaceLike | undefined;\n /**\n * Opened on the first request, because the render worker does not\n * exist until the app mounts, and an agent may well ask first.\n */\n const reach = (): AgentSurfaceLike => {\n if (surface !== undefined) {\n return surface;\n }\n const port = app.openRenderPort(AGENT_PORT);\n if (port === undefined) {\n return combineSurfaces([]);\n }\n // A command marked `@confirm` is put to the person here, in the\n // page they are looking at. A browser's own dialog: in development\n // that is enough, and it cannot be mistaken for the application's.\n surface = remoteSurface(port, { confirm: confirmInWindow });\n return surface;\n };\n hot.on(DEV_AGENT_EVENTS.request, (request: DevAgentRequest) => {\n void handleMcpMessage(reach(), request.message, {\n name: info.name ?? (document.title || 'gesso'),\n ...info\n }).then(response => hot.send(DEV_AGENT_EVENTS.response, { id: request.id, response } satisfies DevAgentResponse));\n });\n const announce = () => hot.send(DEV_AGENT_EVENTS.ready, { title: document.title, url: location.href });\n announce();\n // A restarted dev server is a new one that has never heard of this\n // page, and the page reconnects to it without reloading. Saying so\n // again on every connection is what keeps an agent from being told\n // no page is open while one plainly is.\n hot.on('vite:ws:connect', announce);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/agent/mcp.ts","../../src/agent/dev.ts"],"sourcesContent":["import type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * An agent surface, served over the Model Context Protocol.\n *\n * Two layers. `handleMcpMessage` answers one JSON-RPC message and knows\n * nothing about how it arrived, which is all a stdio transport or a\n * relay needs. `mcpHandler` wraps it in MCP's Streamable HTTP\n * transport as a `fetch`-style function, `(Request) => Response`,\n * which is what `Bun.serve`, Deno, a service worker and every modern\n * Node server framework take, so the framework imports no server.\n *\n * What it implements is the part a channel needs: `initialize`, `ping`,\n * `tools/list`, `tools/call`, `resources/list` and `resources/read`.\n * Responses are plain JSON rather than an event stream, which the\n * transport allows, and the GET stream for server-initiated messages\n * is declined with 405, which it also allows: nothing here sends one,\n * so the capabilities say neither `listChanged` nor `subscribe`.\n *\n * `mcpHandler` keeps sessions, as the transport describes: `initialize`\n * is answered with an `Mcp-Session-Id`, the client sends it back on\n * every request after, and `DELETE` with it ends the session. A session\n * remembers the client's `clientInfo`, so an application can tell one\n * agent from another while a request runs (`McpHandlerOptions.around`).\n * A request without a session, or with one the server has forgotten, is\n * still answered, as one from an unknown caller.\n */\n\n/** The protocol revisions this server speaks, newest first. */\nexport const MCP_PROTOCOL_VERSIONS = ['2025-11-25', '2025-06-18', '2025-03-26'] as const;\n\nexport interface McpServerInfo {\n /** The name a client shows for this server. Defaults to `gesso`. */\n name?: string;\n version?: string;\n /** Told to the agent on connecting: what this application is, in a sentence or two. */\n instructions?: string;\n}\n\ntype JsonRpcId = string | number;\n\ninterface JsonRpcRequest {\n readonly jsonrpc: '2.0';\n readonly id?: JsonRpcId;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\nexport type JsonRpcResponse =\n | { jsonrpc: '2.0'; id: JsonRpcId | null; result: unknown }\n | { jsonrpc: '2.0'; id: JsonRpcId | null; error: { code: number; message: string } };\n\nconst PARSE_ERROR = -32700;\nconst INVALID_REQUEST = -32600;\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n/** MCP's code for a resource that does not exist. */\nconst RESOURCE_NOT_FOUND = -32002;\n\n/**\n * Answers one JSON-RPC message. Returns null for a notification, which\n * takes no answer, and never throws: a malformed message is answered\n * with the JSON-RPC error that says what was wrong with it.\n */\nexport async function handleMcpMessage(\n surface: AgentSurfaceLike,\n message: unknown,\n info: McpServerInfo = {}\n): Promise<JsonRpcResponse | null> {\n if (!isRequest(message)) {\n return error(null, INVALID_REQUEST, 'Expected a JSON-RPC 2.0 request with a method.');\n }\n if (message.id === undefined) {\n // `notifications/initialized` and `notifications/cancelled` are the\n // ones a client sends. Neither needs anything from a server whose\n // every call answers before the next is read.\n return null;\n }\n const { id } = message;\n const params = message.params ?? {};\n switch (message.method) {\n case 'initialize': {\n const asked = params.protocolVersion;\n const version = MCP_PROTOCOL_VERSIONS.find(supported => supported === asked) ?? MCP_PROTOCOL_VERSIONS[0];\n return ok(id, {\n protocolVersion: version,\n capabilities: { tools: {}, resources: {} },\n serverInfo: { name: info.name ?? 'gesso', version: info.version ?? '0.0.0' },\n ...(info.instructions === undefined ? {} : { instructions: info.instructions })\n });\n }\n case 'ping':\n return ok(id, {});\n case 'tools/list':\n return ok(id, { tools: await surface.tools() });\n case 'tools/call': {\n if (typeof params.name !== 'string') {\n return error(id, INVALID_PARAMS, 'tools/call needs the name of a tool.');\n }\n const args = params.arguments;\n if (args !== undefined && (args === null || typeof args !== 'object' || Array.isArray(args))) {\n return error(id, INVALID_PARAMS, 'tools/call arguments must be an object.');\n }\n return ok(id, await surface.call(params.name, args as Record<string, unknown> | undefined));\n }\n case 'resources/list':\n return ok(id, { resources: await surface.resources() });\n case 'resources/templates/list':\n return ok(id, { resourceTemplates: [] });\n case 'resources/read': {\n const uri = params.uri;\n const view = typeof uri === 'string' ? await surface.read(uri) : undefined;\n if (typeof uri !== 'string' || view === undefined) {\n return error(id, RESOURCE_NOT_FOUND, `No resource at ${String(uri)}.`);\n }\n return ok(id, { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(view) }] });\n }\n default:\n return error(id, METHOD_NOT_FOUND, `This server does not implement ${message.method}.`);\n }\n}\n\n/**\n * Who sent a request: the session `initialize` began, and the client it\n * said it was. Handed to `McpHandlerOptions.around` for each request.\n */\nexport interface McpCaller {\n /** The session's id, as sent in `Mcp-Session-Id`. */\n readonly session: string;\n /** The order sessions began in since the handler was made, from 1: a short way to tell two of one client apart. */\n readonly number: number;\n /** What the client said it was in `initialize`'s `clientInfo`; an empty name when it said nothing. */\n readonly client: { readonly name: string; readonly version: string };\n}\n\n/** The most sessions remembered at once; the oldest is forgotten first, and its requests are then from an unknown caller. */\nconst MAX_SESSIONS = 256;\n\nexport interface McpHandlerOptions extends McpServerInfo {\n /**\n * Origins a browser may call from. A request carrying any other\n * `Origin` is refused, as the transport requires: without it, a web\n * page the person happens to have open could reach a server bound to\n * their own machine and send commands to their application. Agents\n * that are not browsers send no `Origin` and are unaffected.\n */\n allowedOrigins?: readonly string[];\n /**\n * A secret every request must carry as `Authorization: Bearer\n * <token>`. Worth setting whenever the port is reachable by anything\n * other than the person's own agents.\n */\n token?: string;\n /**\n * Runs around every request, told who sent it: the session and the\n * client it belongs to, or null for a request outside any session. A\n * place to keep the caller in an `AsyncLocalStorage`, say, so the\n * commands a tool call runs can tell which agent asked:\n *\n * around: (caller, handle) => agents.run(caller, handle)\n *\n * It must call `handle` and return what it returns.\n */\n around?: (caller: McpCaller | null, handle: () => Promise<JsonRpcResponse | null>) => Promise<JsonRpcResponse | null>;\n}\n\n/**\n * The Streamable HTTP transport, as a `fetch` handler.\n *\n * Bun.serve({ hostname: '127.0.0.1', port: 7310, fetch: mcpHandler(surface, { name: 'notes' }) });\n *\n * It answers every path; mount it where the server routes `/mcp`.\n */\nexport function mcpHandler(\n surface: AgentSurfaceLike,\n options: McpHandlerOptions = {}\n): (request: Request) => Promise<Response> {\n const sessions = new Map<string, McpCaller>();\n let begun = 0;\n return async request => {\n const origin = request.headers.get('origin');\n if (origin !== null && !(options.allowedOrigins ?? []).includes(origin)) {\n return text(403, `Requests from ${origin} are not allowed.`);\n }\n if (\n options.token !== undefined &&\n !sameText(request.headers.get('authorization') ?? '', `Bearer ${options.token}`)\n ) {\n return text(401, 'This server needs a bearer token.');\n }\n const sessionId = request.headers.get('mcp-session-id');\n if (request.method === 'DELETE' && sessionId !== null) {\n sessions.delete(sessionId);\n return new Response(null, { status: 204 });\n }\n const version = request.headers.get('mcp-protocol-version');\n if (version !== null && !(MCP_PROTOCOL_VERSIONS as readonly string[]).includes(version)) {\n return text(400, `Unsupported MCP protocol version ${version}.`);\n }\n if (request.method !== 'POST') {\n // GET is the optional stream for messages the server starts. There\n // are no such messages, and 405 is how the transport says so.\n return new Response(null, { status: 405, headers: { allow: 'POST, DELETE' } });\n }\n let message: unknown;\n try {\n message = await request.json();\n } catch {\n return json(400, error(null, PARSE_ERROR, 'The body is not JSON.'));\n }\n // `initialize` begins a session; anything else belongs to the one it names, if it is known.\n let started: McpCaller | null = null;\n if (isRequest(message) && message.method === 'initialize' && message.id !== undefined) {\n const info = (message.params?.clientInfo ?? {}) as { name?: unknown; version?: unknown };\n started = {\n session: newSessionId(),\n number: ++begun,\n client: {\n name: typeof info.name === 'string' ? info.name : '',\n version: typeof info.version === 'string' ? info.version : ''\n }\n };\n sessions.set(started.session, started);\n if (sessions.size > MAX_SESSIONS) sessions.delete(sessions.keys().next().value!);\n }\n const caller = started ?? (sessionId === null ? null : (sessions.get(sessionId) ?? null));\n const handle = () => handleMcpMessage(surface, message, options);\n const response = await (options.around === undefined ? handle() : options.around(caller, handle));\n if (response === null) return new Response(null, { status: 202 });\n const answered = json(200, response);\n if (started !== null) answered.headers.set('mcp-session-id', started.session);\n return answered;\n };\n}\n\n/** A session id no one can guess: 128 random bits, as hex. */\nfunction newSessionId(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Whether two strings are the same, taking as long however early they\n * differ, so a token cannot be guessed a character at a time from how\n * quickly it is refused.\n */\nfunction sameText(a: string, b: string): boolean {\n let differs = a.length ^ b.length;\n for (let i = 0; i < b.length; i++) differs |= (a.charCodeAt(i % Math.max(a.length, 1)) || 0) ^ b.charCodeAt(i);\n return differs === 0;\n}\n\nfunction isRequest(message: unknown): message is JsonRpcRequest {\n const candidate = message as Partial<JsonRpcRequest> | null;\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n !Array.isArray(candidate) &&\n candidate.jsonrpc === '2.0' &&\n typeof candidate.method === 'string'\n );\n}\n\nfunction ok(id: JsonRpcId, result: unknown): JsonRpcResponse {\n return { jsonrpc: '2.0', id, result };\n}\n\nfunction error(id: JsonRpcId | null, code: number, message: string): JsonRpcResponse {\n return { jsonrpc: '2.0', id, error: { code, message } };\n}\n\nfunction json(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n}\n\nfunction text(status: number, body: string): Response {\n return new Response(body, { status, headers: { 'content-type': 'text/plain; charset=utf-8' } });\n}\n","import { handleMcpMessage, type JsonRpcResponse, type McpServerInfo } from './mcp';\nimport { AGENT_PORT, combineSurfaces, remoteSurface } from './remote';\nimport { confirmInWindow } from './webmcp';\nimport type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * The page's half of the development agent bridge.\n *\n * `gesso-vite-plugin` serves MCP at `/__gesso/mcp` on the dev server,\n * but the channels are in the page's workers, and a server cannot\n * reach a worker. The page can, and it already holds a socket to the\n * server: Vite's HMR connection. So the server relays each JSON-RPC\n * message down that socket, this answers it against the render\n * worker's agent port, and the answer goes back up the same way.\n *\n * MCP is spoken here, in the page, rather than on the server, so the\n * server needs nothing from the framework and the workers need nothing\n * from MCP. The plugin injects the call in a dev server only; a build\n * carries no reference to this module.\n */\n\n/** The part of `import.meta.hot` this uses. */\nexport interface DevAgentHot {\n send(event: string, data?: unknown): void;\n on(event: string, listener: (data: never) => void): void;\n}\n\n/** What the bridge needs of an application: a way to reach its render worker. */\nexport interface DevAgentApp {\n openRenderPort(key: string): MessagePort | undefined;\n}\n\n/** Server to page. */\nexport interface DevAgentRequest {\n readonly id: number;\n readonly message: unknown;\n}\n\n/** Page to server. */\nexport interface DevAgentResponse {\n readonly id: number;\n readonly response: JsonRpcResponse | null;\n}\n\n/** The HMR events the bridge speaks. The plugin's half uses the same names. */\nexport const DEV_AGENT_EVENTS = {\n ready: 'gesso:agent:ready',\n request: 'gesso:agent:request',\n /** Page to server: a request has arrived and is being answered, so the page is running. */\n received: 'gesso:agent:received',\n response: 'gesso:agent:response'\n} as const;\n\nexport function connectDevAgent(app: DevAgentApp, hot: DevAgentHot, info: McpServerInfo = {}): void {\n let surface: AgentSurfaceLike | undefined;\n /**\n * Opened on the first request, because the render worker does not\n * exist until the app mounts, and an agent may well ask first.\n */\n const reach = (): AgentSurfaceLike => {\n if (surface !== undefined) {\n return surface;\n }\n const port = app.openRenderPort(AGENT_PORT);\n if (port === undefined) {\n return combineSurfaces([]);\n }\n // A command marked `@confirm` is put to the person here, in the\n // page they are looking at. A browser's own dialog: in development\n // that is enough, and it cannot be mistaken for the application's.\n surface = remoteSurface(port, { confirm: confirmInWindow });\n return surface;\n };\n hot.on(DEV_AGENT_EVENTS.request, (request: DevAgentRequest) => {\n // Said at once, before the answer: a page paused in a hidden window\n // says nothing, and the server tells the agent so in moments.\n hot.send(DEV_AGENT_EVENTS.received, { id: request.id });\n void handleMcpMessage(reach(), request.message, {\n name: info.name ?? (document.title || 'gesso'),\n ...info\n }).then(response => hot.send(DEV_AGENT_EVENTS.response, { id: request.id, response } satisfies DevAgentResponse));\n });\n const announce = () => hot.send(DEV_AGENT_EVENTS.ready, { title: document.title, url: location.href });\n announce();\n // A restarted dev server is a new one that has never heard of this\n // page, and the page reconnects to it without reloading. Saying so\n // again on every connection is what keeps an agent from being told\n // no page is open while one plainly is.\n hot.on('vite:ws:connect', announce);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,wBAAwB;CAAC;CAAc;CAAc;AAAY;AAuB9E,MAAM,cAAc;AACpB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;;AAEvB,MAAM,qBAAqB;;;;;;AAO3B,eAAsB,iBACpB,SACA,SACA,OAAsB,CAAC,GACU;CACjC,IAAI,CAAC,UAAU,OAAO,GACpB,OAAO,MAAM,MAAM,iBAAiB,gDAAgD;CAEtF,IAAI,QAAQ,OAAO,KAAA,GAIjB,OAAO;CAET,MAAM,EAAE,OAAO;CACf,MAAM,SAAS,QAAQ,UAAU,CAAC;CAClC,QAAQ,QAAQ,QAAhB;EACE,KAAK,cAAc;GACjB,MAAM,QAAQ,OAAO;GAErB,OAAO,GAAG,IAAI;IACZ,iBAFc,sBAAsB,MAAK,cAAa,cAAc,KAAK,KAAK,sBAAsB;IAGpG,cAAc;KAAE,OAAO,CAAC;KAAG,WAAW,CAAC;IAAE;IACzC,YAAY;KAAE,MAAM,KAAK,QAAQ;KAAS,SAAS,KAAK,WAAW;IAAQ;IAC3E,GAAI,KAAK,iBAAiB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc,KAAK,aAAa;GAC/E,CAAC;EACH;EACA,KAAK,QACH,OAAO,GAAG,IAAI,CAAC,CAAC;EAClB,KAAK,cACH,OAAO,GAAG,IAAI,EAAE,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC;EAChD,KAAK,cAAc;GACjB,IAAI,OAAO,OAAO,SAAS,UACzB,OAAO,MAAM,IAAI,gBAAgB,sCAAsC;GAEzE,MAAM,OAAO,OAAO;GACpB,IAAI,SAAS,KAAA,MAAc,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,IACxF,OAAO,MAAM,IAAI,gBAAgB,yCAAyC;GAE5E,OAAO,GAAG,IAAI,MAAM,QAAQ,KAAK,OAAO,MAAM,IAA2C,CAAC;EAC5F;EACA,KAAK,kBACH,OAAO,GAAG,IAAI,EAAE,WAAW,MAAM,QAAQ,UAAU,EAAE,CAAC;EACxD,KAAK,4BACH,OAAO,GAAG,IAAI,EAAE,mBAAmB,CAAC,EAAE,CAAC;EACzC,KAAK,kBAAkB;GACrB,MAAM,MAAM,OAAO;GACnB,MAAM,OAAO,OAAO,QAAQ,WAAW,MAAM,QAAQ,KAAK,GAAG,IAAI,KAAA;GACjE,IAAI,OAAO,QAAQ,YAAY,SAAS,KAAA,GACtC,OAAO,MAAM,IAAI,oBAAoB,kBAAkB,OAAO,GAAG,EAAE,EAAE;GAEvE,OAAO,GAAG,IAAI,EAAE,UAAU,CAAC;IAAE;IAAK,UAAU;IAAoB,MAAM,KAAK,UAAU,IAAI;GAAE,CAAC,EAAE,CAAC;EACjG;EACA,SACE,OAAO,MAAM,IAAI,kBAAkB,kCAAkC,QAAQ,OAAO,EAAE;CAC1F;AACF;;AAgBA,MAAM,eAAe;;;;;;;;AAqCrB,SAAgB,WACd,SACA,UAA6B,CAAC,GACW;CACzC,MAAM,2BAAW,IAAI,IAAuB;CAC5C,IAAI,QAAQ;CACZ,OAAO,OAAM,YAAW;EACtB,MAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;EAC3C,IAAI,WAAW,QAAQ,EAAE,QAAQ,kBAAkB,CAAC,EAAA,CAAG,SAAS,MAAM,GACpE,OAAO,KAAK,KAAK,iBAAiB,OAAO,kBAAkB;EAE7D,IACE,QAAQ,UAAU,KAAA,KAClB,CAAC,SAAS,QAAQ,QAAQ,IAAI,eAAe,KAAK,IAAI,UAAU,QAAQ,OAAO,GAE/E,OAAO,KAAK,KAAK,mCAAmC;EAEtD,MAAM,YAAY,QAAQ,QAAQ,IAAI,gBAAgB;EACtD,IAAI,QAAQ,WAAW,YAAY,cAAc,MAAM;GACrD,SAAS,OAAO,SAAS;GACzB,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAC3C;EACA,MAAM,UAAU,QAAQ,QAAQ,IAAI,sBAAsB;EAC1D,IAAI,YAAY,QAAQ,CAAE,sBAA4C,SAAS,OAAO,GACpF,OAAO,KAAK,KAAK,oCAAoC,QAAQ,EAAE;EAEjE,IAAI,QAAQ,WAAW,QAGrB,OAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,SAAS,EAAE,OAAO,eAAe;EAAE,CAAC;EAE/E,IAAI;EACJ,IAAI;GACF,UAAU,MAAM,QAAQ,KAAK;EAC/B,QAAQ;GACN,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,uBAAuB,CAAC;EACpE;EAEA,IAAI,UAA4B;EAChC,IAAI,UAAU,OAAO,KAAK,QAAQ,WAAW,gBAAgB,QAAQ,OAAO,KAAA,GAAW;GACrF,MAAM,OAAQ,QAAQ,QAAQ,cAAc,CAAC;GAC7C,UAAU;IACR,SAAS,aAAa;IACtB,QAAQ,EAAE;IACV,QAAQ;KACN,MAAM,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;KAClD,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;IAC7D;GACF;GACA,SAAS,IAAI,QAAQ,SAAS,OAAO;GACrC,IAAI,SAAS,OAAO,cAAc,SAAS,OAAO,SAAS,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM;EACjF;EACA,MAAM,SAAS,YAAY,cAAc,OAAO,OAAQ,SAAS,IAAI,SAAS,KAAK;EACnF,MAAM,eAAe,iBAAiB,SAAS,SAAS,OAAO;EAC/D,MAAM,WAAW,OAAO,QAAQ,WAAW,KAAA,IAAY,OAAO,IAAI,QAAQ,OAAO,QAAQ,MAAM;EAC/F,IAAI,aAAa,MAAM,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAChE,MAAM,WAAW,KAAK,KAAK,QAAQ;EACnC,IAAI,YAAY,MAAM,SAAS,QAAQ,IAAI,kBAAkB,QAAQ,OAAO;EAC5E,OAAO;CACT;AACF;;AAGA,SAAS,eAAuB;CAC9B,MAAM,QAAQ,OAAO,gCAAgB,IAAI,WAAW,EAAE,CAAC;CACvD,OAAO,MAAM,KAAK,QAAO,SAAQ,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AAC9E;;;;;;AAOA,SAAS,SAAS,GAAW,GAAoB;CAC/C,IAAI,UAAU,EAAE,SAAS,EAAE;CAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,YAAY,EAAE,WAAW,IAAI,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,EAAE,WAAW,CAAC;CAC7G,OAAO,YAAY;AACrB;AAEA,SAAS,UAAU,SAA6C;CAC9D,MAAM,YAAY;CAClB,OACE,OAAO,cAAc,YACrB,cAAc,QACd,CAAC,MAAM,QAAQ,SAAS,KACxB,UAAU,YAAY,SACtB,OAAO,UAAU,WAAW;AAEhC;AAEA,SAAS,GAAG,IAAe,QAAkC;CAC3D,OAAO;EAAE,SAAS;EAAO;EAAI;CAAO;AACtC;AAEA,SAAS,MAAM,IAAsB,MAAc,SAAkC;CACnF,OAAO;EAAE,SAAS;EAAO;EAAI,OAAO;GAAE;GAAM;EAAQ;CAAE;AACxD;AAEA,SAAS,KAAK,QAAgB,MAAyB;CACrD,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EAAE;EAAQ,SAAS,EAAE,gBAAgB,mBAAmB;CAAE,CAAC;AACvG;AAEA,SAAS,KAAK,QAAgB,MAAwB;CACpD,OAAO,IAAI,SAAS,MAAM;EAAE;EAAQ,SAAS,EAAE,gBAAgB,4BAA4B;CAAE,CAAC;AAChG;;;;ACxOA,MAAa,mBAAmB;CAC9B,OAAO;CACP,SAAS;;CAET,UAAU;CACV,UAAU;AACZ;AAEA,SAAgB,gBAAgB,KAAkB,KAAkB,OAAsB,CAAC,GAAS;CAClG,IAAI;;;;;CAKJ,MAAM,cAAgC;EACpC,IAAI,YAAY,KAAA,GACd,OAAO;EAET,MAAM,OAAO,IAAI,eAAe,UAAU;EAC1C,IAAI,SAAS,KAAA,GACX,OAAO,gBAAgB,CAAC,CAAC;EAK3B,UAAU,cAAc,MAAM,EAAE,SAAS,gBAAgB,CAAC;EAC1D,OAAO;CACT;CACA,IAAI,GAAG,iBAAiB,UAAU,YAA6B;EAG7D,IAAI,KAAK,iBAAiB,UAAU,EAAE,IAAI,QAAQ,GAAG,CAAC;EACtD,iBAAsB,MAAM,GAAG,QAAQ,SAAS;GAC9C,MAAM,KAAK,SAAS,SAAS,SAAS;GACtC,GAAG;EACL,CAAC,CAAC,CAAC,MAAK,aAAY,IAAI,KAAK,iBAAiB,UAAU;GAAE,IAAI,QAAQ;GAAI;EAAS,CAA4B,CAAC;CAClH,CAAC;CACD,MAAM,iBAAiB,IAAI,KAAK,iBAAiB,OAAO;EAAE,OAAO,SAAS;EAAO,KAAK,SAAS;CAAK,CAAC;CACrG,SAAS;CAKT,IAAI,GAAG,mBAAmB,QAAQ;AACpC"}
@@ -78,6 +78,16 @@ var InputCell = class extends BehaviorSubject {
78
78
  super.next(value);
79
79
  }
80
80
  };
81
+ /**
82
+ * Hands a cell a value even when it holds it already, which `next`
83
+ * drops: an input a design tool's preview starts or ends is told again,
84
+ * so a control that owns what it was started at starts again from it.
85
+ * For the host, not for components.
86
+ */
87
+ function forceInput(cell, value) {
88
+ if (Object.is(value, cell.getValue())) BehaviorSubject.prototype.next.call(cell, value);
89
+ else cell.next(value);
90
+ }
81
91
  /** The component whose function body is running, while one is. */
82
92
  let bodyOf = null;
83
93
  /** The component whose body is running, for a cell that wants to remember being read there. */
@@ -572,6 +582,6 @@ function sameSet(a, b) {
572
582
  return true;
573
583
  }
574
584
  //#endregion
575
- export { InternalState as a, input as c, output as d, outputTargetOf as f, equalityOf as i, into as l, withBodyOf as m, computed as n, internalState as o, trackRead as p, derive as r, InputCell as s, ComputedCell as t, isOutputTarget as u };
585
+ export { InternalState as a, forceInput as c, isOutputTarget as d, output as f, withBodyOf as h, equalityOf as i, input as l, trackRead as m, computed as n, internalState as o, outputTargetOf as p, derive as r, InputCell as s, ComputedCell as t, into as u };
576
586
 
577
- //# sourceMappingURL=computed-_cYcdczA.js.map
587
+ //# sourceMappingURL=computed-DyW9SPpq.js.map