foldkit 0.157.0 → 0.158.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.
Files changed (82) hide show
  1. package/dist/devTools/host.d.ts +2 -2
  2. package/dist/devTools/host.d.ts.map +1 -1
  3. package/dist/devTools/host.js +1 -1
  4. package/dist/experimental/machine/machine.d.ts +278 -84
  5. package/dist/experimental/machine/machine.d.ts.map +1 -1
  6. package/dist/experimental/machine/machine.js +362 -212
  7. package/dist/runtime/browserListeners.d.ts +7 -1
  8. package/dist/runtime/browserListeners.d.ts.map +1 -1
  9. package/dist/runtime/browserScheduler.d.ts +3 -0
  10. package/dist/runtime/browserScheduler.d.ts.map +1 -0
  11. package/dist/runtime/browserScheduler.js +22 -0
  12. package/dist/runtime/crashUI.d.ts +36 -5
  13. package/dist/runtime/crashUI.d.ts.map +1 -1
  14. package/dist/runtime/crashUI.js +55 -2
  15. package/dist/runtime/devToolsConfig.d.ts +85 -0
  16. package/dist/runtime/devToolsConfig.d.ts.map +1 -0
  17. package/dist/runtime/devToolsConfig.js +49 -0
  18. package/dist/runtime/devToolsIntegration.d.ts +56 -0
  19. package/dist/runtime/devToolsIntegration.d.ts.map +1 -0
  20. package/dist/runtime/devToolsIntegration.js +186 -0
  21. package/dist/runtime/dispatch.d.ts +10 -0
  22. package/dist/runtime/dispatch.d.ts.map +1 -0
  23. package/dist/runtime/dispatch.js +4 -0
  24. package/dist/runtime/documentMetadata.d.ts +3 -0
  25. package/dist/runtime/documentMetadata.d.ts.map +1 -0
  26. package/dist/runtime/documentMetadata.js +148 -0
  27. package/dist/runtime/duplicateIdScanner.d.ts +16 -0
  28. package/dist/runtime/duplicateIdScanner.d.ts.map +1 -0
  29. package/dist/runtime/duplicateIdScanner.js +70 -0
  30. package/dist/runtime/hmrModelBridge.d.ts +4 -0
  31. package/dist/runtime/hmrModelBridge.d.ts.map +1 -0
  32. package/dist/runtime/hmrModelBridge.js +43 -0
  33. package/dist/runtime/hostConnector.d.ts +61 -0
  34. package/dist/runtime/hostConnector.d.ts.map +1 -0
  35. package/dist/runtime/hostConnector.js +141 -0
  36. package/dist/runtime/hydrationHandoff.d.ts +44 -0
  37. package/dist/runtime/hydrationHandoff.d.ts.map +1 -0
  38. package/dist/runtime/hydrationHandoff.js +395 -0
  39. package/dist/runtime/index.d.ts +16 -1
  40. package/dist/runtime/index.d.ts.map +1 -1
  41. package/dist/runtime/index.js +5 -1
  42. package/dist/runtime/makeApplication.d.ts +71 -0
  43. package/dist/runtime/makeApplication.d.ts.map +1 -0
  44. package/dist/runtime/makeApplication.js +98 -0
  45. package/dist/runtime/makeElement.d.ts +69 -0
  46. package/dist/runtime/makeElement.d.ts.map +1 -0
  47. package/dist/runtime/makeElement.js +81 -0
  48. package/dist/runtime/managedResourceFibers.d.ts +18 -0
  49. package/dist/runtime/managedResourceFibers.d.ts.map +1 -0
  50. package/dist/runtime/managedResourceFibers.js +47 -0
  51. package/dist/runtime/messageQueue.d.ts +27 -0
  52. package/dist/runtime/messageQueue.d.ts.map +1 -0
  53. package/dist/runtime/messageQueue.js +133 -0
  54. package/dist/runtime/public.d.ts +2 -3
  55. package/dist/runtime/public.d.ts.map +1 -1
  56. package/dist/runtime/public.js +1 -1
  57. package/dist/runtime/renderer.d.ts +69 -0
  58. package/dist/runtime/renderer.d.ts.map +1 -0
  59. package/dist/runtime/renderer.js +512 -0
  60. package/dist/runtime/resourceProvider.d.ts +33 -0
  61. package/dist/runtime/resourceProvider.d.ts.map +1 -0
  62. package/dist/runtime/resourceProvider.js +98 -0
  63. package/dist/runtime/runtime.d.ts +133 -412
  64. package/dist/runtime/runtime.d.ts.map +1 -1
  65. package/dist/runtime/runtime.js +118 -2184
  66. package/dist/runtime/runtimeStatus.d.ts +15 -0
  67. package/dist/runtime/runtimeStatus.d.ts.map +1 -0
  68. package/dist/runtime/runtimeStatus.js +6 -0
  69. package/dist/runtime/slowPhase.d.ts +82 -0
  70. package/dist/runtime/slowPhase.d.ts.map +1 -0
  71. package/dist/runtime/slowPhase.js +83 -0
  72. package/dist/runtime/start.d.ts +96 -0
  73. package/dist/runtime/start.d.ts.map +1 -0
  74. package/dist/runtime/start.js +99 -0
  75. package/dist/runtime/subscriptionFibers.d.ts +21 -0
  76. package/dist/runtime/subscriptionFibers.d.ts.map +1 -0
  77. package/dist/runtime/subscriptionFibers.js +47 -0
  78. package/dist/runtime/visibility.d.ts +8 -0
  79. package/dist/runtime/visibility.d.ts.map +1 -0
  80. package/dist/runtime/visibility.js +6 -0
  81. package/dist/test/scene.js +1 -1
  82. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"browserListeners.d.ts","sourceRoot":"","sources":["../../src/runtime/browserListeners.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,eAAO,MAAM,2BAA2B,GAAI,OAAO,YACvC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,iBACrB,aAAa,CAAC,OAAO,CAAC,KACpC,CAAC,MAAM,IAAI,CAWb,CAAA;AAgBD,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAChC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,iBACrB,aAAa,CAAC,OAAO,CAAC,KACpC,CAAC,MAAM,IAAI,CAwDb,CAAA"}
1
+ {"version":3,"file":"browserListeners.d.ts","sourceRoot":"","sources":["../../src/runtime/browserListeners.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAErC,oFAAoF;AACpF,MAAM,MAAM,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC5C,YAAY,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAA;IAC9C,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAA;CACnC,CAAC,CAAA;AAEF,eAAO,MAAM,2BAA2B,GAAI,OAAO,YACvC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,iBACrB,aAAa,CAAC,OAAO,CAAC,KACpC,CAAC,MAAM,IAAI,CAWb,CAAA;AAgBD,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAChC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,iBACrB,aAAa,CAAC,OAAO,CAAC,KACpC,CAAC,MAAM,IAAI,CAwDb,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { Effect } from 'effect';
2
+ export declare const provideBrowserScheduler: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
3
+ //# sourceMappingURL=browserScheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserScheduler.d.ts","sourceRoot":"","sources":["../../src/runtime/browserScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,QAAQ,CAAA;AA0BjD,eAAO,MAAM,uBAAuB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UACrC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CACsD,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { Effect, Layer, Scheduler } from 'effect';
2
+ // NOTE: scheduling fix for browser performance. Effect needs to defer work
3
+ // onto a future tick of the event loop. The default browser scheduler picks
4
+ // `setTimeout(f, 0)`, but browsers clamp `setTimeout` to a minimum of 4ms.
5
+ // `queueMicrotask` runs on the very next tick (sub-millisecond). Dispatch no
6
+ // longer routes through the Effect scheduler, but Command and Subscription
7
+ // fibers still do; without this override every fiber yield (for example, an
8
+ // op-budget suspension, or a Stream step) would take an extra 4-16ms
9
+ // round-trip before
10
+ // its result Message lands.
11
+ const microtaskSetImmediate = (callback) => {
12
+ let cancelled = false;
13
+ queueMicrotask(() => {
14
+ if (!cancelled)
15
+ callback();
16
+ });
17
+ return () => {
18
+ cancelled = true;
19
+ };
20
+ };
21
+ const browserScheduler = new Scheduler.MixedScheduler('async', microtaskSetImmediate);
22
+ export const provideBrowserScheduler = (effect) => Effect.provide(effect, Layer.succeed(Scheduler.Scheduler, browserScheduler));
@@ -1,10 +1,41 @@
1
- import { Effect } from 'effect';
2
- import { Document } from '../html/index.js';
1
+ import { Effect, Option } from 'effect';
2
+ import { Document, Html, type HtmlBuilder } from '../html/index.js';
3
+ import { VNode } from '../vdom.js';
4
+ /** Context provided to crash.view and crash.report when the runtime encounters
5
+ * an unrecoverable error. `message` is the Message being processed when the
6
+ * crash occurred, present as an `Option` because a crash during the initial
7
+ * render has no triggering Message. */
8
+ export type CrashContext<Model, Message> = Readonly<{
9
+ error: Error;
10
+ model: Model;
11
+ message: Option.Option<Message>;
12
+ }>;
13
+ /** Configuration for crash handling, with custom crash UI and/or crash
14
+ * reporting. The crash view renders after the dispatch loop has stopped, so
15
+ * its builder's Message is `never` and no handler is expressible. Reload or
16
+ * navigate with a raw DOM attribute instead. */
17
+ export type CrashConfig<Model, Message> = Readonly<{
18
+ view?: (context: CrashContext<Model, Message>, h: HtmlBuilder<never>) => Document;
19
+ report?: (context: CrashContext<Model, Message>) => void;
20
+ }>;
21
+ /** Configuration for crash handling in a `makeElement` app. The crash view
22
+ * returns `Html`, not a `Document`, because a scoped app never owns the
23
+ * document `<head>`. */
24
+ export type ElementCrashConfig<Model, Message> = Readonly<{
25
+ view?: (context: CrashContext<Model, Message>, h: HtmlBuilder<never>) => Html;
26
+ report?: (context: CrashContext<Model, Message>) => void;
27
+ }>;
3
28
  export declare const noOpDispatch: {
4
29
  dispatchAsync: (_message: unknown) => Effect.Effect<void, never, never>;
5
30
  dispatchSync: (_message: unknown) => void;
6
31
  };
7
- export declare const defaultCrashView: (context: Readonly<{
8
- error: Error;
9
- }>, viewError?: unknown) => Document;
32
+ export declare const defaultCrashView: (context: CrashContext<unknown, unknown>, viewError?: unknown) => Document;
33
+ /** Mutable holder for the vnode tree currently mounted in the container.
34
+ * The render frame writes it after every patch; the dispose finalizer, the
35
+ * replay render, and {@link renderCrashView} read it. A plain object rather
36
+ * than a Ref because every reader runs synchronously on the main thread. */
37
+ export type VNodeSlot = {
38
+ maybeCurrentVNode: Option.Option<VNode>;
39
+ };
40
+ export declare const renderCrashView: <Model, Message>(context: CrashContext<Model, Message>, crash: CrashConfig<Model, Message> | undefined, container: HTMLElement, vnodeSlot: VNodeSlot, manageDocument: boolean) => void;
10
41
  //# sourceMappingURL=crashUI.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crashUI.d.ts","sourceRoot":"","sources":["../../src/runtime/crashUI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAE,QAAQ,EAAmB,MAAM,kBAAkB,CAAA;AAE5D,eAAO,MAAM,YAAY;IACvB,aAAa,aAAa,OAAO;IACjC,YAAY,aAAa,OAAO;CACjC,CAAA;AAmBD,eAAO,MAAM,gBAAgB,YAClB,QAAQ,CAAC;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC,cACvB,OAAO,KAClB,QAwLF,CAAA"}
1
+ {"version":3,"file":"crashUI.d.ts","sourceRoot":"","sources":["../../src/runtime/crashUI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEhD,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,KAAK,WAAW,EAKjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,KAAK,EAAgB,MAAM,YAAY,CAAA;AAIhD;;;wCAGwC;AACxC,MAAM,MAAM,YAAY,CAAC,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC;IAClD,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;CAChC,CAAC,CAAA;AAEF;;;iDAGiD;AACjD,MAAM,MAAM,WAAW,CAAC,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC;IACjD,IAAI,CAAC,EAAE,CACL,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,EACrC,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,KAClB,QAAQ,CAAA;IACb,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;CACzD,CAAC,CAAA;AAEF;;yBAEyB;AACzB,MAAM,MAAM,kBAAkB,CAAC,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC;IACxD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,IAAI,CAAA;IAC7E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;CACzD,CAAC,CAAA;AAEF,eAAO,MAAM,YAAY;IACvB,aAAa,aAAa,OAAO;IACjC,YAAY,aAAa,OAAO;CACjC,CAAA;AAmBD,eAAO,MAAM,gBAAgB,YAClB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,cAC3B,OAAO,KAClB,QAwLF,CAAA;AAED;;;6EAG6E;AAC7E,MAAM,MAAM,SAAS,GAAG;IACtB,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;CACxC,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,KAAK,EAAE,OAAO,WACnC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,SAC9B,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,SAAS,aACnC,WAAW,aACX,SAAS,kBACJ,OAAO,KACtB,IA8DF,CAAA"}
@@ -1,5 +1,9 @@
1
- import { Effect } from 'effect';
2
- import { inertHtml as ih } from '../html/index.js';
1
+ import { Context, Effect, Option } from 'effect';
2
+ import { __clearRuntime as clearHtmlRuntime, __htmlBuilder as htmlBuilderFor, inertHtml as ih, __setRuntime as setHtmlRuntime, } from '../html/index.js';
3
+ import { MountTracker } from '../mount/index.js';
4
+ import { __patchVNode } from '../vdom.js';
5
+ import { Dispatch } from './dispatch.js';
6
+ import { applyDocumentMetadata } from './documentMetadata.js';
3
7
  export const noOpDispatch = {
4
8
  dispatchAsync: (_message) => Effect.void,
5
9
  dispatchSync: (_message) => { },
@@ -163,3 +167,52 @@ export const defaultCrashView = (context, viewError) => {
163
167
  ]);
164
168
  return { title: 'Application Crash', body };
165
169
  };
170
+ export const renderCrashView = (context, crash, container, vnodeSlot, manageDocument) => {
171
+ console.error('[foldkit] Application crash:', context.error);
172
+ if (crash?.report) {
173
+ try {
174
+ crash.report(context);
175
+ }
176
+ catch (reportError) {
177
+ console.error('[foldkit] crash.report failed:', reportError);
178
+ }
179
+ }
180
+ const crashContext = Context.make(Dispatch, noOpDispatch).pipe(Context.add(MountTracker, {
181
+ started: () => { },
182
+ ended: () => { },
183
+ }));
184
+ try {
185
+ setHtmlRuntime(noOpDispatch.dispatchSync, crashContext);
186
+ let crashDocument;
187
+ try {
188
+ crashDocument = crash?.view
189
+ ? crash.view(context, htmlBuilderFor())
190
+ : defaultCrashView(context);
191
+ }
192
+ finally {
193
+ clearHtmlRuntime();
194
+ }
195
+ const patchedVNode = __patchVNode(vnodeSlot.maybeCurrentVNode, crashDocument.body, container);
196
+ vnodeSlot.maybeCurrentVNode = Option.some(patchedVNode);
197
+ if (manageDocument) {
198
+ applyDocumentMetadata(crashDocument, patchedVNode.elm);
199
+ }
200
+ }
201
+ catch (viewError) {
202
+ console.error('[foldkit] crash.view failed:', viewError);
203
+ const fallbackViewError = viewError instanceof Error ? viewError : new Error(String(viewError));
204
+ setHtmlRuntime(noOpDispatch.dispatchSync, crashContext);
205
+ let fallbackDocument;
206
+ try {
207
+ fallbackDocument = defaultCrashView(context, fallbackViewError);
208
+ }
209
+ finally {
210
+ clearHtmlRuntime();
211
+ }
212
+ const patchedVNode = __patchVNode(vnodeSlot.maybeCurrentVNode, fallbackDocument.body, container);
213
+ vnodeSlot.maybeCurrentVNode = Option.some(patchedVNode);
214
+ if (manageDocument) {
215
+ applyDocumentMetadata(fallbackDocument, patchedVNode.elm);
216
+ }
217
+ }
218
+ };
@@ -0,0 +1,85 @@
1
+ import { Effect, Option, Schema, Scope } from 'effect';
2
+ import type { DevToolsStore } from '../devTools/store.js';
3
+ import { type Visibility } from './visibility.js';
4
+ /** Position of the DevTools badge and panel on screen. */
5
+ export type DevToolsPosition = 'BottomRight' | 'BottomLeft' | 'TopRight' | 'TopLeft';
6
+ /** Controls DevTools interaction mode.
7
+ *
8
+ * - `'Inspect'`: Messages stream in and clicking a row shows its state snapshot without pausing the rendered view.
9
+ * - `'TimeTravel'`: Clicking a row installs a paused historical view while the live application continues. Resume to patch the latest live view.
10
+ */
11
+ export type DevToolsMode = 'Inspect' | 'TimeTravel';
12
+ /** Mode value for the DevTools panel. Either a single mode used in every
13
+ * environment, or an object selecting different modes for development and
14
+ * production. Use the object form to keep `'TimeTravel'` for local debugging
15
+ * while shipping the safer `'Inspect'` mode to users. `'TimeTravel'` in
16
+ * production pauses the user's rendered view when a history row is clicked. */
17
+ export type DevToolsModeConfig = DevToolsMode | Readonly<{
18
+ development: DevToolsMode;
19
+ production: DevToolsMode;
20
+ }>;
21
+ /**
22
+ * Factory that mounts the in-browser DevTools overlay against a recording
23
+ * store. The runtime keeps the store and the WebSocket bridge (so external
24
+ * tooling like the DevTools MCP server works without an overlay); the visual
25
+ * overlay is injected so it can live in `@foldkit/devtools` and pull in
26
+ * `@foldkit/ui` without coupling the core runtime to either.
27
+ *
28
+ * Foldkit's Vite plugin supplies the installed overlay automatically. It is
29
+ * included in production when `@foldkit/devtools` is a regular dependency.
30
+ */
31
+ export type DevToolsOverlay = (store: DevToolsStore, position: DevToolsPosition, mode: DevToolsMode, maybeBanner: Option.Option<string>) => Effect.Effect<void, never, Scope.Scope>;
32
+ /**
33
+ * DevTools configuration.
34
+ *
35
+ * Pass `false` to disable DevTools entirely.
36
+ *
37
+ * - `show`: `'Development'` (default) enables in dev mode only, `'Always'` enables in all environments including production.
38
+ * - `position`: Where the badge and panel appear. Defaults to `'BottomRight'`.
39
+ * - `mode`: `'TimeTravel'` (default) enables full time-travel debugging by installing a paused historical view while the live application continues. `'Inspect'` allows browsing state snapshots without replacing the live view. Pass `{ development, production }` to use different modes per environment. Useful when DevTools is shown in production (`show: 'Always'`) and you want `'TimeTravel'` only in local development.
40
+ * - `banner`: Optional text shown as a banner at the top of the panel.
41
+ * - `excludeFromHistory`: Message `_tag` values whose dispatches should not be recorded in DevTools history. The Messages still drive `update` and the runtime as usual; they just don't appear in the history panel and don't pay the per-Message diff cost. Use for high-frequency Messages (animation frames, pointer moves, scroll events) that would flood history without adding insight.
42
+ * - `maxEntries`: Maximum number of recorded Messages retained in history before the oldest is evicted. Defaults to 100. Clamped to the range 20-500: smaller values keep the panel snappy under high message rates, larger values give you more scroll-back. Each retained entry stores a full Model snapshot, so memory cost scales linearly with both `maxEntries` and your Model size.
43
+ * - `keyframeInterval`: Number of recorded Messages between full Model snapshots. Defaults to 31. Time-travel to an index replays `update` forward from the nearest earlier keyframe, so this is a memory/time tradeoff: smaller values store more snapshots (more memory) but make each jump cheaper, down to `1` where every jump is a constant-time snapshot lookup with no replay. Reach for a denser interval when the app has a heavy `update` and time-travel jumps feel sluggish. Clamped to a minimum of 1. Forced to 1 automatically when `excludeFromHistory` is active, since excluded Messages are never replayed.
44
+ */
45
+ export type DevToolsConfig = false | Readonly<{
46
+ show?: Visibility;
47
+ position?: DevToolsPosition;
48
+ mode?: DevToolsModeConfig;
49
+ banner?: string;
50
+ excludeFromHistory?: ReadonlyArray<string>;
51
+ maxEntries?: number;
52
+ keyframeInterval?: number;
53
+ /**
54
+ * The application's `Message` Schema. When provided and the running app
55
+ * is connected to the Foldkit DevTools MCP server, AI agents can dispatch
56
+ * Messages into the runtime. The Schema decodes inbound dispatch payloads
57
+ * at the bridge boundary and returns a clean error on mismatch.
58
+ *
59
+ * Without this field, `RequestDispatchMessage` is rejected with an
60
+ * informative error.
61
+ */
62
+ Message?: Schema.Codec<any, any, unknown, unknown>;
63
+ }>;
64
+ /** Registers the overlay supplied by the Foldkit Vite plugin. */
65
+ export declare const __setDevToolsOverlay: (overlay: DevToolsOverlay | undefined) => void;
66
+ /** The DevTools settings a runtime boots with once `show` has been resolved
67
+ * against the environment. */
68
+ export type ResolvedDevToolsConfig = Readonly<{
69
+ position: DevToolsPosition;
70
+ mode: DevToolsMode;
71
+ maybeBanner: Option.Option<string>;
72
+ maybeOverlay: Option.Option<DevToolsOverlay>;
73
+ }>;
74
+ /** Resolves the DevTools config for this boot, or `Option.none()` when
75
+ * DevTools is disabled or hidden. `'Development'` visibility requires Vite
76
+ * HMR and a top-level window, so an app previewed inside an iframe does not
77
+ * stack a second panel over its host's. */
78
+ export declare const resolveDevToolsConfig: (devTools: DevToolsConfig | undefined) => Option.Option<ResolvedDevToolsConfig>;
79
+ /** The Message tags `excludeFromHistory` keeps out of the DevTools history. */
80
+ export declare const resolveExcludeFromHistoryTags: (devTools: DevToolsConfig | undefined) => ReadonlySet<string>;
81
+ /** The configured `maxEntries`, clamped to the supported range. */
82
+ export declare const resolveDevToolsMaxEntries: (devTools: DevToolsConfig | undefined) => number | undefined;
83
+ /** The configured `keyframeInterval`, floored and clamped to its minimum. */
84
+ export declare const resolveDevToolsKeyframeInterval: (devTools: DevToolsConfig | undefined) => number | undefined;
85
+ //# sourceMappingURL=devToolsConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devToolsConfig.d.ts","sourceRoot":"","sources":["../../src/runtime/devToolsConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAQ,MAAM,QAAQ,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,KAAK,UAAU,EAAa,MAAM,iBAAiB,CAAA;AAE5D,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAC1B,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAA;AAEvD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,YAAY,CAAA;AAEnD;;;;gFAIgF;AAChF,MAAM,MAAM,kBAAkB,GAC1B,YAAY,GACZ,QAAQ,CAAC;IAAE,WAAW,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,YAAY,CAAA;CAAE,CAAC,CAAA;AAErE;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;AAE5C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,QAAQ,CAAC;IACP,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kBAAkB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;CACnD,CAAC,CAAA;AAIN,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,YACtB,eAAe,GAAG,SAAS,KACnC,IAEF,CAAA;AAiBD;+BAC+B;AAC/B,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,IAAI,EAAE,YAAY,CAAA;IAClB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAClC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;CAC7C,CAAC,CAAA;AAEF;;;4CAG4C;AAC5C,eAAO,MAAM,qBAAqB,aACtB,cAAc,GAAG,SAAS,KACnC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAmBtC,CAAA;AAED,+EAA+E;AAC/E,eAAO,MAAM,6BAA6B,aAC9B,cAAc,GAAG,SAAS,KACnC,WAAW,CAAC,MAAM,CASlB,CAAA;AAEH,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,aAC1B,cAAc,GAAG,SAAS,KACnC,MAAM,GAAG,SAaT,CAAA;AAEH,6EAA6E;AAC7E,eAAO,MAAM,+BAA+B,aAChC,cAAc,GAAG,SAAS,KACnC,MAAM,GAAG,SAUT,CAAA"}
@@ -0,0 +1,49 @@
1
+ import { Option, pipe } from 'effect';
2
+ import { isVisible } from './visibility.js';
3
+ let registeredDevToolsOverlay;
4
+ /** Registers the overlay supplied by the Foldkit Vite plugin. */
5
+ export const __setDevToolsOverlay = (overlay) => {
6
+ registeredDevToolsOverlay = overlay;
7
+ };
8
+ const DEFAULT_DEV_TOOLS_SHOW = 'Development';
9
+ const DEFAULT_DEV_TOOLS_POSITION = 'BottomRight';
10
+ const DEFAULT_DEV_TOOLS_MODE = 'TimeTravel';
11
+ const resolveDevToolsMode = (config) => {
12
+ if (typeof config === 'string') {
13
+ return config;
14
+ }
15
+ else {
16
+ return import.meta.hot ? config.development : config.production;
17
+ }
18
+ };
19
+ const DEV_TOOLS_MAX_ENTRIES_MIN = 20;
20
+ const DEV_TOOLS_MAX_ENTRIES_MAX = 500;
21
+ const DEV_TOOLS_KEYFRAME_INTERVAL_MIN = 1;
22
+ /** Resolves the DevTools config for this boot, or `Option.none()` when
23
+ * DevTools is disabled or hidden. `'Development'` visibility requires Vite
24
+ * HMR and a top-level window, so an app previewed inside an iframe does not
25
+ * stack a second panel over its host's. */
26
+ export const resolveDevToolsConfig = (devTools) => {
27
+ const isInIframe = window.self !== window.top;
28
+ return pipe(devTools ?? {}, Option.liftPredicate(config => config !== false), Option.filter(config => isVisible(config.show ?? DEFAULT_DEV_TOOLS_SHOW, !!import.meta.hot && !isInIframe)), Option.map(config => ({
29
+ position: config.position ?? DEFAULT_DEV_TOOLS_POSITION,
30
+ mode: resolveDevToolsMode(config.mode ?? DEFAULT_DEV_TOOLS_MODE),
31
+ maybeBanner: Option.fromNullishOr(config.banner),
32
+ maybeOverlay: Option.fromNullishOr(registeredDevToolsOverlay),
33
+ })));
34
+ };
35
+ /** The Message tags `excludeFromHistory` keeps out of the DevTools history. */
36
+ export const resolveExcludeFromHistoryTags = (devTools) => pipe(devTools ?? {}, Option.liftPredicate(config => config !== false), Option.flatMapNullishOr(config => config.excludeFromHistory), Option.match({
37
+ onNone: () => new Set(),
38
+ onSome: tags => new Set(tags),
39
+ }));
40
+ /** The configured `maxEntries`, clamped to the supported range. */
41
+ export const resolveDevToolsMaxEntries = (devTools) => pipe(devTools ?? {}, Option.liftPredicate(config => config !== false), Option.flatMapNullishOr(config => config.maxEntries), Option.match({
42
+ onNone: () => undefined,
43
+ onSome: value => Math.max(DEV_TOOLS_MAX_ENTRIES_MIN, Math.min(DEV_TOOLS_MAX_ENTRIES_MAX, value)),
44
+ }));
45
+ /** The configured `keyframeInterval`, floored and clamped to its minimum. */
46
+ export const resolveDevToolsKeyframeInterval = (devTools) => pipe(devTools ?? {}, Option.liftPredicate(config => config !== false), Option.flatMapNullishOr(config => config.keyframeInterval), Option.match({
47
+ onNone: () => undefined,
48
+ onSome: value => Math.max(DEV_TOOLS_KEYFRAME_INTERVAL_MIN, Math.floor(value)),
49
+ }));
@@ -0,0 +1,56 @@
1
+ import { Effect, type Scope } from 'effect';
2
+ import { type MountRecord } from '../devTools/store.js';
3
+ import { MountRuntime, MountTracker, type ViewState } from '../mount/index.js';
4
+ import { type DevToolsConfig } from './devToolsConfig.js';
5
+ /** The Mount starts and ends recorded since the last drain. */
6
+ export type MountEvents = Readonly<{
7
+ starts: ReadonlyArray<MountRecord>;
8
+ ends: ReadonlyArray<MountRecord>;
9
+ }>;
10
+ /** What the DevTools history keeps about a Command: its name and arguments. */
11
+ export type RecordableCommand = Readonly<{
12
+ name: string;
13
+ args?: Record<string, unknown>;
14
+ }>;
15
+ /** The replay bridge the render side hands the store when it is installed. */
16
+ export type DevToolsRenderBridge = Readonly<{
17
+ renderReplay: (model: unknown) => Effect.Effect<void>;
18
+ markRenderPending: Effect.Effect<void>;
19
+ }>;
20
+ /**
21
+ * Everything the runtime needs from DevTools, whether or not DevTools is
22
+ * configured. The Mount tracker and runtime, the view state, and the pause
23
+ * check exist from boot, and the tracker records only once the store
24
+ * exists. `installDevToolsStore` creates the store, mounts the overlay,
25
+ * and starts the WebSocket bridge once the render side can supply the
26
+ * replay bridge, and does nothing when DevTools is off. The recording
27
+ * helpers no-op until the store exists.
28
+ */
29
+ export type DevToolsIntegration<Model, Message> = Readonly<{
30
+ mountTracker: typeof MountTracker.Service;
31
+ mountRuntime: typeof MountRuntime.Service;
32
+ drainMountEvents: () => MountEvents;
33
+ readViewState: () => ViewState;
34
+ setViewState: (nextViewState: ViewState) => void;
35
+ isPausedNow: () => boolean;
36
+ installDevToolsStore: (bridge: DevToolsRenderBridge) => Effect.Effect<void, never, Scope.Scope>;
37
+ resumeDevTools: Effect.Effect<void>;
38
+ recordInit: (initModel: Model, initCommands: ReadonlyArray<RecordableCommand>) => Effect.Effect<void>;
39
+ recordMessage: (message: Message, currentModel: Model, nextModel: Model, commands: ReadonlyArray<RecordableCommand>) => void;
40
+ attachRenderedMounts: () => void;
41
+ }>;
42
+ /**
43
+ * Builds the DevTools integration for one runtime from the `devTools`
44
+ * config. `update` and `maybeFreezeModel` drive time-travel replay, and
45
+ * `enqueueMessageEffect` is how Messages sent over the WebSocket bridge
46
+ * reach the queue.
47
+ */
48
+ export declare const makeDevToolsIntegration: <Model, Message>({ devTools, update, maybeFreezeModel, enqueueMessageEffect, }: Readonly<{
49
+ devTools: DevToolsConfig | undefined;
50
+ update: (model: Model, message: Message) => Readonly<{
51
+ model: Model;
52
+ }>;
53
+ maybeFreezeModel: (model: Model) => Model;
54
+ enqueueMessageEffect: (message: Message) => Effect.Effect<void>;
55
+ }>) => Effect.Effect<DevToolsIntegration<Model, Message>>;
56
+ //# sourceMappingURL=devToolsIntegration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devToolsIntegration.d.ts","sourceRoot":"","sources":["../../src/runtime/devToolsIntegration.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAIN,KAAK,KAAK,EAGX,MAAM,QAAQ,CAAA;AAEf,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,cAAc,EAKpB,MAAM,qBAAqB,CAAA;AAE5B,+DAA+D;AAC/D,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;IAClC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;CACjC,CAAC,CAAA;AAEF,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B,CAAC,CAAA;AAEF,8EAA8E;AAC9E,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrD,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACvC,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,CAAC,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC;IACzD,YAAY,EAAE,OAAO,YAAY,CAAC,OAAO,CAAA;IACzC,YAAY,EAAE,OAAO,YAAY,CAAC,OAAO,CAAA;IACzC,gBAAgB,EAAE,MAAM,WAAW,CAAA;IACnC,aAAa,EAAE,MAAM,SAAS,CAAA;IAC9B,YAAY,EAAE,CAAC,aAAa,EAAE,SAAS,KAAK,IAAI,CAAA;IAChD,WAAW,EAAE,MAAM,OAAO,CAAA;IAC1B,oBAAoB,EAAE,CACpB,MAAM,EAAE,oBAAoB,KACzB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IAC5C,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACnC,UAAU,EAAE,CACV,SAAS,EAAE,KAAK,EAChB,YAAY,EAAE,aAAa,CAAC,iBAAiB,CAAC,KAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACxB,aAAa,EAAE,CACb,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,KAAK,EACnB,SAAS,EAAE,KAAK,EAChB,QAAQ,EAAE,aAAa,CAAC,iBAAiB,CAAC,KACvC,IAAI,CAAA;IACT,oBAAoB,EAAE,MAAM,IAAI,CAAA;CACjC,CAAC,CAAA;AAOF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,KAAK,EAAE,OAAO,iEAKnD,QAAQ,CAAC;IACV,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAA;IACpC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,KAAK,QAAQ,CAAC;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IACtE,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAA;IACzC,oBAAoB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAChE,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAuOlD,CAAA"}
@@ -0,0 +1,186 @@
1
+ import { Array, Effect, Option, PubSub, Stream, SubscriptionRef, } from 'effect';
2
+ import { createDevToolsStore, } from '../devTools/store.js';
3
+ import { startWebSocketBridge } from '../devTools/webSocketBridge.js';
4
+ import { MountRuntime, liveViewStateChanges, } from '../mount/index.js';
5
+ import { resolveDevToolsConfig, resolveDevToolsKeyframeInterval, resolveDevToolsMaxEntries, resolveExcludeFromHistoryTags, } from './devToolsConfig.js';
6
+ const toCommandRecord = (command) => command.args !== undefined
7
+ ? { name: command.name, args: command.args }
8
+ : { name: command.name };
9
+ /**
10
+ * Builds the DevTools integration for one runtime from the `devTools`
11
+ * config. `update` and `maybeFreezeModel` drive time-travel replay, and
12
+ * `enqueueMessageEffect` is how Messages sent over the WebSocket bridge
13
+ * reach the queue.
14
+ */
15
+ export const makeDevToolsIntegration = ({ devTools, update, maybeFreezeModel, enqueueMessageEffect, }) => Effect.gen(function* () {
16
+ const resolvedDevTools = resolveDevToolsConfig(devTools);
17
+ const excludeFromHistoryTags = resolveExcludeFromHistoryTags(devTools);
18
+ const devToolsMaxEntries = resolveDevToolsMaxEntries(devTools);
19
+ const devToolsKeyframeInterval = resolveDevToolsKeyframeInterval(devTools);
20
+ let currentViewState = 'Live';
21
+ const maybeViewStatePubSub = Option.isSome(resolvedDevTools)
22
+ ? Option.some(yield* PubSub.unbounded({
23
+ replay: 1,
24
+ }))
25
+ : Option.none();
26
+ const viewStateChanges = Option.match(maybeViewStatePubSub, {
27
+ onNone: () => liveViewStateChanges,
28
+ onSome: viewStatePubSub => {
29
+ PubSub.publishUnsafe(viewStatePubSub, currentViewState);
30
+ return Stream.fromPubSub(viewStatePubSub);
31
+ },
32
+ });
33
+ const setViewState = (nextViewState) => {
34
+ if (nextViewState === currentViewState) {
35
+ return;
36
+ }
37
+ currentViewState = nextViewState;
38
+ if (Option.isSome(maybeViewStatePubSub)) {
39
+ PubSub.publishUnsafe(maybeViewStatePubSub.value, nextViewState);
40
+ }
41
+ };
42
+ const readViewState = () => currentViewState;
43
+ // NOTE: the DevTools store is installed at most once during boot and
44
+ // never replaced. Caching it in a closure variable avoids a
45
+ // `Ref.get` on every message and on every render frame (the
46
+ // store powers the pause check). Plain `null` rather than `Option`:
47
+ // the hot path only ever presence-checks it, and the check should
48
+ // stay a bare comparison.
49
+ let devToolsStore = null;
50
+ const mountRuntime = MountRuntime.of({
51
+ captureViewStateChanges: () => Stream.concat(Stream.make(currentViewState), viewStateChanges).pipe(Stream.changes),
52
+ });
53
+ const isPausedNow = () => devToolsStore !== null &&
54
+ SubscriptionRef.getUnsafe(devToolsStore.stateRef).isPaused;
55
+ // NOTE: recording is gated on the DevTools store because the store
56
+ // is the only consumer. Without the gate every Mount start and end
57
+ // in a production frame would allocate a record just to be sliced
58
+ // and dropped.
59
+ const mountStartBuffer = [];
60
+ const mountEndBuffer = [];
61
+ const mountTracker = {
62
+ started: (name, args) => {
63
+ if (devToolsStore === null) {
64
+ return;
65
+ }
66
+ mountStartBuffer.push(args === undefined ? { name } : { name, args });
67
+ },
68
+ ended: (name, args) => {
69
+ if (devToolsStore === null) {
70
+ return;
71
+ }
72
+ mountEndBuffer.push(args === undefined ? { name } : { name, args });
73
+ },
74
+ };
75
+ const drainMountEvents = () => {
76
+ const starts = mountStartBuffer.slice();
77
+ const ends = mountEndBuffer.slice();
78
+ mountStartBuffer.length = 0;
79
+ mountEndBuffer.length = 0;
80
+ return { starts, ends };
81
+ };
82
+ const installDevToolsStore = ({ renderReplay, markRenderPending, }) => Option.match(resolvedDevTools, {
83
+ onNone: () => Effect.void,
84
+ onSome: ({ position, mode, maybeBanner, maybeOverlay }) => Effect.gen(function* () {
85
+ // NOTE: when excludeFromHistory is active, the runtime drops
86
+ // excluded Messages from the recorded history. Replay walks the
87
+ // recorded entries forward from the nearest keyframe. With
88
+ // exclusion, the dropped Messages aren't in that walk, so any
89
+ // cumulative state they would have produced is missing from the
90
+ // replayed model. Setting keyframeInterval to 1 stores a full
91
+ // snapshot on every recorded entry, so time-travel becomes a
92
+ // direct lookup that reflects the real live state at the moment
93
+ // the entry was recorded.
94
+ const isExcludingMessages = excludeFromHistoryTags.size > 0;
95
+ const store = yield* createDevToolsStore({
96
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
97
+ replay: (model, message) => {
98
+ const replayUpdate = update(model, message);
99
+ return maybeFreezeModel(replayUpdate.model);
100
+ },
101
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
102
+ render: renderReplay,
103
+ markRenderPending,
104
+ }, {
105
+ ...(devToolsKeyframeInterval !== undefined && {
106
+ keyframeInterval: devToolsKeyframeInterval,
107
+ }),
108
+ ...(devToolsMaxEntries !== undefined && {
109
+ maxEntries: devToolsMaxEntries,
110
+ }),
111
+ // NOTE: exclusion forces keyframeInterval to 1 regardless of any
112
+ // configured value, since excluded Messages are never replayed
113
+ // and a denser interval would leave gaps in the replayed model.
114
+ // Spread last so it wins over `keyframeInterval` above.
115
+ ...(isExcludingMessages && { keyframeInterval: 1 }),
116
+ });
117
+ devToolsStore = store;
118
+ // NOTE: the boot records init through `recordInit` after the init
119
+ // render, so the mount buffer reflects the Mounts that fired on the
120
+ // first paint.
121
+ yield* Option.match(maybeOverlay, {
122
+ onNone: () => Effect.void,
123
+ onSome: overlay => overlay(store, position, mode, maybeBanner),
124
+ });
125
+ if (import.meta.hot) {
126
+ const maybeMessageSchema = devTools !== undefined && devTools !== false
127
+ ? Option.fromNullishOr(devTools.Message)
128
+ : Option.none();
129
+ yield* startWebSocketBridge(store, import.meta.hot,
130
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
131
+ message => enqueueMessageEffect(message),
132
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
133
+ maybeMessageSchema);
134
+ }
135
+ }),
136
+ });
137
+ const recordInit = (initModel, initCommands) => Effect.gen(function* () {
138
+ const initMountEvents = drainMountEvents();
139
+ if (devToolsStore !== null) {
140
+ yield* devToolsStore.recordInit(initModel, Array.map(initCommands, toCommandRecord), initMountEvents.starts);
141
+ }
142
+ });
143
+ const attachRenderedMounts = () => {
144
+ if (devToolsStore !== null) {
145
+ const mountEvents = drainMountEvents();
146
+ Effect.runFork(devToolsStore.attachRenderedMounts(mountEvents.starts, mountEvents.ends));
147
+ }
148
+ };
149
+ const recordMessage = (message, currentModel, nextModel, commands) => {
150
+ // NOTE: store writes go through `Effect.runFork`, not
151
+ // `Effect.runSync`. Both complete inline when the store's state
152
+ // Ref is uncontended (the always case on this path), but a
153
+ // DevTools fiber holding the Ref's permit across a yield would
154
+ // make `runSync` throw and crash the app; `runFork` parks and
155
+ // finishes the write when the permit frees, and the Ref's FIFO
156
+ // permit queue preserves write order.
157
+ if (devToolsStore !== null) {
158
+ const store = devToolsStore;
159
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
160
+ const tag = message._tag;
161
+ const isModelChanged = currentModel !== nextModel;
162
+ if (!excludeFromHistoryTags.has(tag)) {
163
+ Effect.runFork(store.recordMessage(
164
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
165
+ message, currentModel, nextModel, Array.map(commands, toCommandRecord), isModelChanged));
166
+ }
167
+ else if (isModelChanged) {
168
+ Effect.runFork(store.updateLatestModel(nextModel));
169
+ }
170
+ }
171
+ };
172
+ const resumeDevTools = Effect.suspend(() => devToolsStore !== null ? devToolsStore.resume : Effect.void);
173
+ return {
174
+ mountTracker,
175
+ mountRuntime,
176
+ drainMountEvents,
177
+ readViewState,
178
+ setViewState,
179
+ isPausedNow,
180
+ installDevToolsStore,
181
+ resumeDevTools,
182
+ recordInit,
183
+ recordMessage,
184
+ attachRenderedMounts,
185
+ };
186
+ });
@@ -0,0 +1,10 @@
1
+ import { Context, Effect } from 'effect';
2
+ declare const Dispatch_base: Context.ServiceClass<Dispatch, "@foldkit/Dispatch", {
3
+ readonly dispatchAsync: (message: unknown) => Effect.Effect<void>;
4
+ readonly dispatchSync: (message: unknown) => void;
5
+ }>;
6
+ /** Effect service tag that provides message dispatching to the view layer. */
7
+ export declare class Dispatch extends Dispatch_base {
8
+ }
9
+ export {};
10
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/runtime/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;;4BAMZ,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;2BAC1C,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI;;AALrD,8EAA8E;AAC9E,qBAAa,QAAS,SAAQ,aAMN;CAAG"}
@@ -0,0 +1,4 @@
1
+ import { Context } from 'effect';
2
+ /** Effect service tag that provides message dispatching to the view layer. */
3
+ export class Dispatch extends Context.Service()('@foldkit/Dispatch') {
4
+ }
@@ -0,0 +1,3 @@
1
+ import { Document } from '../html/index.js';
2
+ export declare const applyDocumentMetadata: (nextDocument: Document, mountedRoot: Node | undefined) => void;
3
+ //# sourceMappingURL=documentMetadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentMetadata.d.ts","sourceRoot":"","sources":["../../src/runtime/documentMetadata.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAA4B,MAAM,kBAAkB,CAAA;AAsLrE,eAAO,MAAM,qBAAqB,iBAClB,QAAQ,eACT,IAAI,GAAG,SAAS,KAC5B,IA8CF,CAAA"}