foldkit 0.77.0 → 0.79.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.
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/devTools/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,MAAM,EAGN,OAAO,EAGP,MAAM,EASP,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAA;AAU9C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAQ3E,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,YAAY,CAAA;AAuOnB,eAAO,MAAM,MAAM;;EAA0C,CAAA;AAC7D,eAAO,MAAM,YAAY;;;;;;EAGxB,CAAA;AACD,eAAO,MAAM,aAAa;;;;;;EAGzB,CAAA;AACD,eAAO,MAAM,MAAM;;EAA4C,CAAA;AAC/D,eAAO,MAAM,KAAK;;EAA0C,CAAA;AAC5D,eAAO,MAAM,UAAU;;EAA6C,CAAA;AACpE,eAAO,MAAM,YAAY;;EAAiD,CAAA;AAC1E,eAAO,MAAM,WAAW;;EAA+C,CAAA;AAkzCvE,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,UAAU,gBAAgB,EAC1B,MAAM,YAAY,EAClB,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,sCAkDhC,CAAA"}
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/devTools/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,MAAM,EAGN,OAAO,EAGP,MAAM,EASP,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAA;AAU9C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAQ3E,OAAO,EAAE,KAAK,aAAa,EAA+B,MAAM,YAAY,CAAA;AA4M5E,eAAO,MAAM,MAAM;;EAA0C,CAAA;AAC7D,eAAO,MAAM,YAAY;;;;;;EAGxB,CAAA;AACD,eAAO,MAAM,aAAa;;;;;;EAGzB,CAAA;AACD,eAAO,MAAM,MAAM;;EAA4C,CAAA;AAC/D,eAAO,MAAM,KAAK;;EAA0C,CAAA;AAC5D,eAAO,MAAM,UAAU;;EAA6C,CAAA;AACpE,eAAO,MAAM,YAAY;;EAAiD,CAAA;AAC1E,eAAO,MAAM,WAAW;;EAA+C,CAAA;AAkzCvE,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,UAAU,gBAAgB,EAC1B,MAAM,YAAY,EAClB,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,sCAkDhC,CAAA"}
@@ -12,7 +12,8 @@ import * as Listbox from '../ui/listbox/public.js';
12
12
  import * as Tabs from '../ui/tabs/public.js';
13
13
  import { overlayStyles } from './overlay-styles.js';
14
14
  import { toInspectableValue } from './serialize.js';
15
- import { INIT_INDEX, } from './store.js';
15
+ import { INIT_INDEX } from './store.js';
16
+ import { GOT_MESSAGE_PATTERN, extractSubmodelInfo, isTagged, } from './submodelPath.js';
16
17
  // MODEL
17
18
  const DisplayEntry = S.Struct({
18
19
  tag: S.String,
@@ -107,26 +108,8 @@ const ALL_MESSAGES_VALUE = '';
107
108
  const formatTimeDelta = (deltaMs) => M.value(deltaMs).pipe(M.when(0, () => '0ms'), M.when(Number_.lessThan(MILLIS_PER_SECOND), ms => `+${Math.round(ms)}ms`), M.orElse(ms => `+${(ms / MILLIS_PER_SECOND).toFixed(1)}s`));
108
109
  const MESSAGE_LIST_SELECTOR = '.message-list';
109
110
  const computeSubmodelTags = (entries) => pipe(entries, Array_.flatMap(({ submodelPath }) => submodelPath), Array_.dedupe, Array_.sort(Order.string));
110
- const GOT_MESSAGE_PATTERN = /^Got.+Message$/;
111
- const extractSubmodelInfo = (entry) => {
112
- if (!GOT_MESSAGE_PATTERN.test(entry.tag)) {
113
- return { submodelPath: [], maybeLeafTag: Option.none() };
114
- }
115
- const path = [entry.tag];
116
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
117
- let current = entry.message?.['message'];
118
- while (isTagged(current) && GOT_MESSAGE_PATTERN.test(current._tag)) {
119
- path.push(current._tag);
120
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
121
- current = current?.['message'];
122
- }
123
- return {
124
- submodelPath: path,
125
- maybeLeafTag: pipe(current, Option.liftPredicate(isTagged), Option.map(({ _tag }) => _tag)),
126
- };
127
- };
128
111
  const toDisplayEntries = ({ entries }) => Array_.map(entries, entry => {
129
- const { submodelPath, maybeLeafTag } = extractSubmodelInfo(entry);
112
+ const { submodelPath, maybeLeafTag } = extractSubmodelInfo(entry.tag, entry.message);
130
113
  return {
131
114
  tag: entry.tag,
132
115
  submodelPath,
@@ -144,8 +127,6 @@ const toDisplayState = (state) => ({
144
127
  pausedAtIndex: state.pausedAtIndex,
145
128
  });
146
129
  const isExpandable = (value) => Predicate.isObject(value);
147
- const Tagged = S.Struct({ _tag: S.String });
148
- const isTagged = S.is(Tagged);
149
130
  const objectPreview = (value) => pipe(value, Record.keys, Array_.filter(key => key !== '_tag'), Array_.match({
150
131
  onEmpty: () => '{}',
151
132
  onNonEmpty: keys => {
@@ -1,5 +1,5 @@
1
1
  import { Schema as S } from 'effect';
2
- /** A serialized history entry as it appears on the wire. */
2
+ /** A serialized history entry as it appears on the wire. `submodelPath` lists `Got<Child>Message` wrapper tags from outer to inner when the entry came up through a Submodel chain; `maybeLeafTag` is `Some` with the innermost child Message tag when one exists. */
3
3
  export declare const SerializedEntry: S.Struct<{
4
4
  index: typeof S.Number;
5
5
  tag: typeof S.String;
@@ -9,6 +9,8 @@ export declare const SerializedEntry: S.Struct<{
9
9
  isModelChanged: typeof S.Boolean;
10
10
  changedPaths: S.Array$<typeof S.String>;
11
11
  affectedPaths: S.Array$<typeof S.String>;
12
+ submodelPath: S.Array$<typeof S.String>;
13
+ maybeLeafTag: S.Option<typeof S.String>;
12
14
  }>;
13
15
  /** A serialized history entry suitable for transmission over the WS protocol. */
14
16
  export type SerializedEntry = typeof SerializedEntry.Type;
@@ -26,14 +28,23 @@ export declare const RuntimeInfo: S.Struct<{
26
28
  }>;
27
29
  /** Metadata about a connected browser runtime. */
28
30
  export type RuntimeInfo = typeof RuntimeInfo.Type;
29
- /** Request the current Model snapshot. */
30
- export declare const RequestGetModel: import("../schema/index.js").CallableTaggedStruct<"RequestGetModel", {}>;
31
+ /** Request the current Model snapshot, optionally narrowed to a path and/or expanded. */
32
+ export declare const RequestGetModel: import("../schema/index.js").CallableTaggedStruct<"RequestGetModel", {
33
+ maybePath: S.Option<typeof S.String>;
34
+ expand: typeof S.Boolean;
35
+ }>;
36
+ /** Request a historical Model snapshot at an absolute history index, optionally narrowed to a path and/or expanded. Use `index: -1` for the initial Model. */
37
+ export declare const RequestGetModelAt: import("../schema/index.js").CallableTaggedStruct<"RequestGetModelAt", {
38
+ index: typeof S.Number;
39
+ maybePath: S.Option<typeof S.String>;
40
+ expand: typeof S.Boolean;
41
+ }>;
31
42
  /** Request recent history entries, optionally starting from a given index. */
32
43
  export declare const RequestListMessages: import("../schema/index.js").CallableTaggedStruct<"RequestListMessages", {
33
44
  limit: typeof S.Number;
34
45
  maybeSinceIndex: S.Option<typeof S.Number>;
35
46
  }>;
36
- /** Request a single history entry by index, including before/after Model snapshots. */
47
+ /** Request a single history entry by index. To inspect the Model around the entry, call `RequestGetModelAt` with `index - 1` (before) and `index` (after). */
37
48
  export declare const RequestGetMessage: import("../schema/index.js").CallableTaggedStruct<"RequestGetMessage", {
38
49
  index: typeof S.Number;
39
50
  }>;
@@ -45,6 +56,10 @@ export declare const RequestReplayToKeyframe: import("../schema/index.js").Calla
45
56
  }>;
46
57
  /** Request the runtime resume normal execution from a paused state. */
47
58
  export declare const RequestResume: import("../schema/index.js").CallableTaggedStruct<"RequestResume", {}>;
59
+ /** Request the recorded init data: the initial Model and the names of Commands returned from `init`. */
60
+ export declare const RequestGetInit: import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>;
61
+ /** Request a snapshot of the runtime's DevTools state: history bounds, current paused/live status, and whether init is recorded. */
62
+ export declare const RequestGetRuntimeState: import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>;
48
63
  /** Request the runtime dispatch a Message at the current state. The payload is opaque to the protocol; the runtime validates against the app's Message Schema. */
49
64
  export declare const RequestDispatchMessage: import("../schema/index.js").CallableTaggedStruct<"RequestDispatchMessage", {
50
65
  message: typeof S.Unknown;
@@ -52,7 +67,14 @@ export declare const RequestDispatchMessage: import("../schema/index.js").Callab
52
67
  /** Request the list of currently connected browser runtimes. Handled by the Vite plugin, not forwarded to a runtime. */
53
68
  export declare const RequestListRuntimes: import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>;
54
69
  /** A request from the MCP server. RequestListRuntimes is handled at the Vite plugin layer; all other requests are routed to a browser runtime. */
55
- export declare const Request: S.Union<[import("../schema/index.js").CallableTaggedStruct<"RequestGetModel", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestListMessages", {
70
+ export declare const Request: S.Union<[import("../schema/index.js").CallableTaggedStruct<"RequestGetModel", {
71
+ maybePath: S.Option<typeof S.String>;
72
+ expand: typeof S.Boolean;
73
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestGetModelAt", {
74
+ index: typeof S.Number;
75
+ maybePath: S.Option<typeof S.String>;
76
+ expand: typeof S.Boolean;
77
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestListMessages", {
56
78
  limit: typeof S.Number;
57
79
  maybeSinceIndex: S.Option<typeof S.Number>;
58
80
  }>, import("../schema/index.js").CallableTaggedStruct<"RequestGetMessage", {
@@ -61,12 +83,14 @@ export declare const Request: S.Union<[import("../schema/index.js").CallableTagg
61
83
  keyframeIndex: typeof S.Number;
62
84
  }>, import("../schema/index.js").CallableTaggedStruct<"RequestResume", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestDispatchMessage", {
63
85
  message: typeof S.Unknown;
64
- }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>]>;
86
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>]>;
65
87
  /** A request from the MCP server. */
66
88
  export type Request = typeof Request.Type;
67
- /** Response carrying the current Model snapshot. */
89
+ /** Response carrying a Model snapshot. The `value` is the resolved subtree at `atPath` (or the whole Model when no path was supplied). When `summarized` is true, large arrays/records/strings have been collapsed to `_summary` placeholders to keep payloads small for AI agents; pass `expand: true` on the Request to receive the literal value. */
68
90
  export declare const ResponseModel: import("../schema/index.js").CallableTaggedStruct<"ResponseModel", {
69
- model: typeof S.Unknown;
91
+ value: typeof S.Unknown;
92
+ atPath: typeof S.String;
93
+ summarized: typeof S.Boolean;
70
94
  }>;
71
95
  /** Response carrying a page of history entries. `maybeNextIndex` is `Some` when more entries are available beyond this page (pass it as `RequestListMessages.maybeSinceIndex` to fetch the next page) and `None` when this page reaches the current end of history. */
72
96
  export declare const ResponseMessages: import("../schema/index.js").CallableTaggedStruct<"ResponseMessages", {
@@ -79,10 +103,12 @@ export declare const ResponseMessages: import("../schema/index.js").CallableTagg
79
103
  isModelChanged: typeof S.Boolean;
80
104
  changedPaths: S.Array$<typeof S.String>;
81
105
  affectedPaths: S.Array$<typeof S.String>;
106
+ submodelPath: S.Array$<typeof S.String>;
107
+ maybeLeafTag: S.Option<typeof S.String>;
82
108
  }>>;
83
109
  maybeNextIndex: S.Option<typeof S.Number>;
84
110
  }>;
85
- /** Response carrying a single history entry with surrounding Model snapshots. */
111
+ /** Response carrying a single history entry. Model snapshots are not included; use `RequestGetModelAt` with `index - 1` and `index` to inspect Model state around the entry. */
86
112
  export declare const ResponseMessage: import("../schema/index.js").CallableTaggedStruct<"ResponseMessage", {
87
113
  entry: S.Struct<{
88
114
  index: typeof S.Number;
@@ -93,9 +119,9 @@ export declare const ResponseMessage: import("../schema/index.js").CallableTagge
93
119
  isModelChanged: typeof S.Boolean;
94
120
  changedPaths: S.Array$<typeof S.String>;
95
121
  affectedPaths: S.Array$<typeof S.String>;
122
+ submodelPath: S.Array$<typeof S.String>;
123
+ maybeLeafTag: S.Option<typeof S.String>;
96
124
  }>;
97
- modelBefore: typeof S.Unknown;
98
- modelAfter: typeof S.Unknown;
99
125
  }>;
100
126
  /** Response carrying the list of available keyframes. */
101
127
  export declare const ResponseKeyframes: import("../schema/index.js").CallableTaggedStruct<"ResponseKeyframes", {
@@ -121,13 +147,29 @@ export declare const ResponseRuntimes: import("../schema/index.js").CallableTagg
121
147
  title: typeof S.String;
122
148
  }>>;
123
149
  }>;
150
+ /** Response carrying the recorded init data. `maybeModel` is `None` until the runtime has finished its first render and recorded init; once set it stays set for the rest of the runtime's life. `commandNames` lists the Commands returned from the application's `init` function in the order they were produced. */
151
+ export declare const ResponseInit: import("../schema/index.js").CallableTaggedStruct<"ResponseInit", {
152
+ maybeModel: S.Option<typeof S.Unknown>;
153
+ commandNames: S.Array$<typeof S.String>;
154
+ }>;
155
+ /** Response carrying a snapshot of the runtime's DevTools state. `currentIndex` is the absolute index of the most recently recorded Message, or -1 when no Messages have been recorded yet. `startIndex` is the earliest absolute index still retained in the rolling buffer (older entries are evicted past `maxEntries`). `totalEntries` is the number of retained entries. `isPaused` is true while the runtime is paused at a replayed snapshot; `maybePausedAtIndex` is `Some(index)` then and `None` otherwise. `hasInitModel` is true once the runtime has finished initialising. */
156
+ export declare const ResponseRuntimeState: import("../schema/index.js").CallableTaggedStruct<"ResponseRuntimeState", {
157
+ currentIndex: typeof S.Number;
158
+ startIndex: typeof S.Number;
159
+ totalEntries: typeof S.Number;
160
+ isPaused: typeof S.Boolean;
161
+ maybePausedAtIndex: S.Option<typeof S.Number>;
162
+ hasInitModel: typeof S.Boolean;
163
+ }>;
124
164
  /** Response carrying an error reason for a failed Request. */
125
165
  export declare const ResponseError: import("../schema/index.js").CallableTaggedStruct<"ResponseError", {
126
166
  reason: typeof S.String;
127
167
  }>;
128
168
  /** A response replying to a Request. */
129
169
  export declare const Response: S.Union<[import("../schema/index.js").CallableTaggedStruct<"ResponseModel", {
130
- model: typeof S.Unknown;
170
+ value: typeof S.Unknown;
171
+ atPath: typeof S.String;
172
+ summarized: typeof S.Boolean;
131
173
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseMessages", {
132
174
  entries: S.Array$<S.Struct<{
133
175
  index: typeof S.Number;
@@ -138,6 +180,8 @@ export declare const Response: S.Union<[import("../schema/index.js").CallableTag
138
180
  isModelChanged: typeof S.Boolean;
139
181
  changedPaths: S.Array$<typeof S.String>;
140
182
  affectedPaths: S.Array$<typeof S.String>;
183
+ submodelPath: S.Array$<typeof S.String>;
184
+ maybeLeafTag: S.Option<typeof S.String>;
141
185
  }>>;
142
186
  maybeNextIndex: S.Option<typeof S.Number>;
143
187
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseMessage", {
@@ -150,9 +194,9 @@ export declare const Response: S.Union<[import("../schema/index.js").CallableTag
150
194
  isModelChanged: typeof S.Boolean;
151
195
  changedPaths: S.Array$<typeof S.String>;
152
196
  affectedPaths: S.Array$<typeof S.String>;
197
+ submodelPath: S.Array$<typeof S.String>;
198
+ maybeLeafTag: S.Option<typeof S.String>;
153
199
  }>;
154
- modelBefore: typeof S.Unknown;
155
- modelAfter: typeof S.Unknown;
156
200
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseKeyframes", {
157
201
  keyframes: S.Array$<S.Struct<{
158
202
  index: typeof S.Number;
@@ -167,6 +211,16 @@ export declare const Response: S.Union<[import("../schema/index.js").CallableTag
167
211
  url: typeof S.String;
168
212
  title: typeof S.String;
169
213
  }>>;
214
+ }>, import("../schema/index.js").CallableTaggedStruct<"ResponseInit", {
215
+ maybeModel: S.Option<typeof S.Unknown>;
216
+ commandNames: S.Array$<typeof S.String>;
217
+ }>, import("../schema/index.js").CallableTaggedStruct<"ResponseRuntimeState", {
218
+ currentIndex: typeof S.Number;
219
+ startIndex: typeof S.Number;
220
+ totalEntries: typeof S.Number;
221
+ isPaused: typeof S.Boolean;
222
+ maybePausedAtIndex: S.Option<typeof S.Number>;
223
+ hasInitModel: typeof S.Boolean;
170
224
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseError", {
171
225
  reason: typeof S.String;
172
226
  }>]>;
@@ -200,7 +254,14 @@ export type Event = typeof Event.Type;
200
254
  export declare const RequestFrame: S.Struct<{
201
255
  id: typeof S.String;
202
256
  maybeConnectionId: S.Option<typeof S.String>;
203
- request: S.Union<[import("../schema/index.js").CallableTaggedStruct<"RequestGetModel", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestListMessages", {
257
+ request: S.Union<[import("../schema/index.js").CallableTaggedStruct<"RequestGetModel", {
258
+ maybePath: S.Option<typeof S.String>;
259
+ expand: typeof S.Boolean;
260
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestGetModelAt", {
261
+ index: typeof S.Number;
262
+ maybePath: S.Option<typeof S.String>;
263
+ expand: typeof S.Boolean;
264
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestListMessages", {
204
265
  limit: typeof S.Number;
205
266
  maybeSinceIndex: S.Option<typeof S.Number>;
206
267
  }>, import("../schema/index.js").CallableTaggedStruct<"RequestGetMessage", {
@@ -209,7 +270,7 @@ export declare const RequestFrame: S.Struct<{
209
270
  keyframeIndex: typeof S.Number;
210
271
  }>, import("../schema/index.js").CallableTaggedStruct<"RequestResume", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestDispatchMessage", {
211
272
  message: typeof S.Unknown;
212
- }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>]>;
273
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>]>;
213
274
  }>;
214
275
  /** A wire frame carrying a Request from the MCP server. */
215
276
  export type RequestFrame = typeof RequestFrame.Type;
@@ -217,7 +278,9 @@ export type RequestFrame = typeof RequestFrame.Type;
217
278
  export declare const ResponseFrame: S.Struct<{
218
279
  id: typeof S.String;
219
280
  response: S.Union<[import("../schema/index.js").CallableTaggedStruct<"ResponseModel", {
220
- model: typeof S.Unknown;
281
+ value: typeof S.Unknown;
282
+ atPath: typeof S.String;
283
+ summarized: typeof S.Boolean;
221
284
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseMessages", {
222
285
  entries: S.Array$<S.Struct<{
223
286
  index: typeof S.Number;
@@ -228,6 +291,8 @@ export declare const ResponseFrame: S.Struct<{
228
291
  isModelChanged: typeof S.Boolean;
229
292
  changedPaths: S.Array$<typeof S.String>;
230
293
  affectedPaths: S.Array$<typeof S.String>;
294
+ submodelPath: S.Array$<typeof S.String>;
295
+ maybeLeafTag: S.Option<typeof S.String>;
231
296
  }>>;
232
297
  maybeNextIndex: S.Option<typeof S.Number>;
233
298
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseMessage", {
@@ -240,9 +305,9 @@ export declare const ResponseFrame: S.Struct<{
240
305
  isModelChanged: typeof S.Boolean;
241
306
  changedPaths: S.Array$<typeof S.String>;
242
307
  affectedPaths: S.Array$<typeof S.String>;
308
+ submodelPath: S.Array$<typeof S.String>;
309
+ maybeLeafTag: S.Option<typeof S.String>;
243
310
  }>;
244
- modelBefore: typeof S.Unknown;
245
- modelAfter: typeof S.Unknown;
246
311
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseKeyframes", {
247
312
  keyframes: S.Array$<S.Struct<{
248
313
  index: typeof S.Number;
@@ -257,6 +322,16 @@ export declare const ResponseFrame: S.Struct<{
257
322
  url: typeof S.String;
258
323
  title: typeof S.String;
259
324
  }>>;
325
+ }>, import("../schema/index.js").CallableTaggedStruct<"ResponseInit", {
326
+ maybeModel: S.Option<typeof S.Unknown>;
327
+ commandNames: S.Array$<typeof S.String>;
328
+ }>, import("../schema/index.js").CallableTaggedStruct<"ResponseRuntimeState", {
329
+ currentIndex: typeof S.Number;
330
+ startIndex: typeof S.Number;
331
+ totalEntries: typeof S.Number;
332
+ isPaused: typeof S.Boolean;
333
+ maybePausedAtIndex: S.Option<typeof S.Number>;
334
+ hasInitModel: typeof S.Boolean;
260
335
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseError", {
261
336
  reason: typeof S.String;
262
337
  }>]>;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/devTools/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/devTools/protocol.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { Schema as S } from 'effect';
2
2
  import { ts } from '../schema/index.js';
3
3
  // SHARED
4
- /** A serialized history entry as it appears on the wire. */
4
+ /** A serialized history entry as it appears on the wire. `submodelPath` lists `Got<Child>Message` wrapper tags from outer to inner when the entry came up through a Submodel chain; `maybeLeafTag` is `Some` with the innermost child Message tag when one exists. */
5
5
  export const SerializedEntry = S.Struct({
6
6
  index: S.Number,
7
7
  tag: S.String,
@@ -11,6 +11,8 @@ export const SerializedEntry = S.Struct({
11
11
  isModelChanged: S.Boolean,
12
12
  changedPaths: S.Array(S.String),
13
13
  affectedPaths: S.Array(S.String),
14
+ submodelPath: S.Array(S.String),
15
+ maybeLeafTag: S.Option(S.String),
14
16
  });
15
17
  /** Metadata about a single keyframe. The index identifies the point in history where the runtime can replay back to. */
16
18
  export const KeyframeInfo = S.Struct({
@@ -23,14 +25,23 @@ export const RuntimeInfo = S.Struct({
23
25
  title: S.String,
24
26
  });
25
27
  // REQUEST
26
- /** Request the current Model snapshot. */
27
- export const RequestGetModel = ts('RequestGetModel');
28
+ /** Request the current Model snapshot, optionally narrowed to a path and/or expanded. */
29
+ export const RequestGetModel = ts('RequestGetModel', {
30
+ maybePath: S.Option(S.String),
31
+ expand: S.Boolean,
32
+ });
33
+ /** Request a historical Model snapshot at an absolute history index, optionally narrowed to a path and/or expanded. Use `index: -1` for the initial Model. */
34
+ export const RequestGetModelAt = ts('RequestGetModelAt', {
35
+ index: S.Number,
36
+ maybePath: S.Option(S.String),
37
+ expand: S.Boolean,
38
+ });
28
39
  /** Request recent history entries, optionally starting from a given index. */
29
40
  export const RequestListMessages = ts('RequestListMessages', {
30
41
  limit: S.Number,
31
42
  maybeSinceIndex: S.Option(S.Number),
32
43
  });
33
- /** Request a single history entry by index, including before/after Model snapshots. */
44
+ /** Request a single history entry by index. To inspect the Model around the entry, call `RequestGetModelAt` with `index - 1` (before) and `index` (after). */
34
45
  export const RequestGetMessage = ts('RequestGetMessage', {
35
46
  index: S.Number,
36
47
  });
@@ -42,6 +53,10 @@ export const RequestReplayToKeyframe = ts('RequestReplayToKeyframe', {
42
53
  });
43
54
  /** Request the runtime resume normal execution from a paused state. */
44
55
  export const RequestResume = ts('RequestResume');
56
+ /** Request the recorded init data: the initial Model and the names of Commands returned from `init`. */
57
+ export const RequestGetInit = ts('RequestGetInit');
58
+ /** Request a snapshot of the runtime's DevTools state: history bounds, current paused/live status, and whether init is recorded. */
59
+ export const RequestGetRuntimeState = ts('RequestGetRuntimeState');
45
60
  /** Request the runtime dispatch a Message at the current state. The payload is opaque to the protocol; the runtime validates against the app's Message Schema. */
46
61
  export const RequestDispatchMessage = ts('RequestDispatchMessage', {
47
62
  message: S.Unknown,
@@ -49,22 +64,22 @@ export const RequestDispatchMessage = ts('RequestDispatchMessage', {
49
64
  /** Request the list of currently connected browser runtimes. Handled by the Vite plugin, not forwarded to a runtime. */
50
65
  export const RequestListRuntimes = ts('RequestListRuntimes');
51
66
  /** A request from the MCP server. RequestListRuntimes is handled at the Vite plugin layer; all other requests are routed to a browser runtime. */
52
- export const Request = S.Union(RequestGetModel, RequestListMessages, RequestGetMessage, RequestListKeyframes, RequestReplayToKeyframe, RequestResume, RequestDispatchMessage, RequestListRuntimes);
67
+ export const Request = S.Union(RequestGetModel, RequestGetModelAt, RequestListMessages, RequestGetMessage, RequestListKeyframes, RequestReplayToKeyframe, RequestResume, RequestDispatchMessage, RequestListRuntimes, RequestGetInit, RequestGetRuntimeState);
53
68
  // RESPONSE
54
- /** Response carrying the current Model snapshot. */
69
+ /** Response carrying a Model snapshot. The `value` is the resolved subtree at `atPath` (or the whole Model when no path was supplied). When `summarized` is true, large arrays/records/strings have been collapsed to `_summary` placeholders to keep payloads small for AI agents; pass `expand: true` on the Request to receive the literal value. */
55
70
  export const ResponseModel = ts('ResponseModel', {
56
- model: S.Unknown,
71
+ value: S.Unknown,
72
+ atPath: S.String,
73
+ summarized: S.Boolean,
57
74
  });
58
75
  /** Response carrying a page of history entries. `maybeNextIndex` is `Some` when more entries are available beyond this page (pass it as `RequestListMessages.maybeSinceIndex` to fetch the next page) and `None` when this page reaches the current end of history. */
59
76
  export const ResponseMessages = ts('ResponseMessages', {
60
77
  entries: S.Array(SerializedEntry),
61
78
  maybeNextIndex: S.Option(S.Number),
62
79
  });
63
- /** Response carrying a single history entry with surrounding Model snapshots. */
80
+ /** Response carrying a single history entry. Model snapshots are not included; use `RequestGetModelAt` with `index - 1` and `index` to inspect Model state around the entry. */
64
81
  export const ResponseMessage = ts('ResponseMessage', {
65
82
  entry: SerializedEntry,
66
- modelBefore: S.Unknown,
67
- modelAfter: S.Unknown,
68
83
  });
69
84
  /** Response carrying the list of available keyframes. */
70
85
  export const ResponseKeyframes = ts('ResponseKeyframes', {
@@ -84,12 +99,26 @@ export const ResponseDispatched = ts('ResponseDispatched', {
84
99
  export const ResponseRuntimes = ts('ResponseRuntimes', {
85
100
  runtimes: S.Array(RuntimeInfo),
86
101
  });
102
+ /** Response carrying the recorded init data. `maybeModel` is `None` until the runtime has finished its first render and recorded init; once set it stays set for the rest of the runtime's life. `commandNames` lists the Commands returned from the application's `init` function in the order they were produced. */
103
+ export const ResponseInit = ts('ResponseInit', {
104
+ maybeModel: S.Option(S.Unknown),
105
+ commandNames: S.Array(S.String),
106
+ });
107
+ /** Response carrying a snapshot of the runtime's DevTools state. `currentIndex` is the absolute index of the most recently recorded Message, or -1 when no Messages have been recorded yet. `startIndex` is the earliest absolute index still retained in the rolling buffer (older entries are evicted past `maxEntries`). `totalEntries` is the number of retained entries. `isPaused` is true while the runtime is paused at a replayed snapshot; `maybePausedAtIndex` is `Some(index)` then and `None` otherwise. `hasInitModel` is true once the runtime has finished initialising. */
108
+ export const ResponseRuntimeState = ts('ResponseRuntimeState', {
109
+ currentIndex: S.Number,
110
+ startIndex: S.Number,
111
+ totalEntries: S.Number,
112
+ isPaused: S.Boolean,
113
+ maybePausedAtIndex: S.Option(S.Number),
114
+ hasInitModel: S.Boolean,
115
+ });
87
116
  /** Response carrying an error reason for a failed Request. */
88
117
  export const ResponseError = ts('ResponseError', {
89
118
  reason: S.String,
90
119
  });
91
120
  /** A response replying to a Request. */
92
- export const Response = S.Union(ResponseModel, ResponseMessages, ResponseMessage, ResponseKeyframes, ResponseReplayed, ResponseResumed, ResponseDispatched, ResponseRuntimes, ResponseError);
121
+ export const Response = S.Union(ResponseModel, ResponseMessages, ResponseMessage, ResponseKeyframes, ResponseReplayed, ResponseResumed, ResponseDispatched, ResponseRuntimes, ResponseInit, ResponseRuntimeState, ResponseError);
93
122
  // EVENT
94
123
  /** A new browser runtime connected. */
95
124
  export const EventConnected = ts('EventConnected', {
@@ -1,2 +1,2 @@
1
- export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetMessage, RequestGetModel, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
1
+ export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetInit, RequestGetMessage, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, ResponseRuntimeState, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
2
2
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/devTools/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,YAAY,EACZ,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,eAAe,GAChB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/devTools/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,YAAY,EACZ,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,eAAe,GAChB,MAAM,eAAe,CAAA"}
@@ -1 +1 @@
1
- export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetMessage, RequestGetModel, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
1
+ export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetInit, RequestGetMessage, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, ResponseRuntimeState, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/devTools/serialize.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,OAiBjD,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,YAAY,EACnB,OAAO,MAAM,KACZ,eASD,CAAA"}
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/devTools/serialize.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,OAiBjD,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,YAAY,EACnB,OAAO,MAAM,KACZ,eAkBF,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import { Array as Array_, Function, HashSet, Match as M, Predicate, Record, } from 'effect';
2
+ import { extractSubmodelInfo } from './submodelPath.js';
2
3
  /**
3
4
  * Convert DOM-class instances (File, Blob, Date, URL) to plain-object
4
5
  * representations so the tree renderer's key-enumeration walk can see their
@@ -22,13 +23,18 @@ export const toInspectableValue = (value) => M.value(value).pipe(M.when(M.instan
22
23
  * plain string arrays for JSON transmission and runs the message body through
23
24
  * `toInspectableValue` so DOM-class instances become inspectable objects.
24
25
  */
25
- export const toSerializedEntry = (entry, index) => ({
26
- index,
27
- tag: entry.tag,
28
- message: toInspectableValue(entry.message),
29
- commandNames: entry.commandNames,
30
- timestamp: entry.timestamp,
31
- isModelChanged: entry.isModelChanged,
32
- changedPaths: HashSet.toValues(entry.diff.changedPaths),
33
- affectedPaths: HashSet.toValues(entry.diff.affectedPaths),
34
- });
26
+ export const toSerializedEntry = (entry, index) => {
27
+ const { submodelPath, maybeLeafTag } = extractSubmodelInfo(entry.tag, entry.message);
28
+ return {
29
+ index,
30
+ tag: entry.tag,
31
+ message: toInspectableValue(entry.message),
32
+ commandNames: entry.commandNames,
33
+ timestamp: entry.timestamp,
34
+ isModelChanged: entry.isModelChanged,
35
+ changedPaths: HashSet.toValues(entry.diff.changedPaths),
36
+ affectedPaths: HashSet.toValues(entry.diff.affectedPaths),
37
+ submodelPath,
38
+ maybeLeafTag,
39
+ };
40
+ };
@@ -0,0 +1,27 @@
1
+ import { Option } from 'effect';
2
+ export declare const GOT_MESSAGE_PATTERN: RegExp;
3
+ export declare const isTagged: (u: unknown, overrideOptions?: import("effect/SchemaAST").ParseOptions | number) => u is {
4
+ readonly _tag: string;
5
+ };
6
+ /** Submodel chain information extracted from a recorded Message. */
7
+ export type SubmodelInfo = Readonly<{
8
+ submodelPath: ReadonlyArray<string>;
9
+ maybeLeafTag: Option.Option<string>;
10
+ }>;
11
+ /**
12
+ * Walk a chain of `Got<Submodel>Message` wrappers in a recorded Message and
13
+ * return the wrapper tags plus the innermost leaf tag.
14
+ *
15
+ * The Submodel pattern propagates child Messages up to a parent by wrapping
16
+ * them in `GotChildMessage({ message: childMessage })`. Nested submodels stack
17
+ * those wrappers. Walking the chain reveals the parent → child → grandchild
18
+ * dispatch path that produced the entry.
19
+ *
20
+ * Returns an empty path and `Option.none` for top-level Messages whose tag
21
+ * doesn't match the `Got*Message` pattern. Otherwise the path lists wrapper
22
+ * tags from outer to inner, and `maybeLeafTag` is `Some` when the innermost
23
+ * wrapped value is itself a tagged Message (the underlying child Message that
24
+ * originated the chain).
25
+ */
26
+ export declare const extractSubmodelInfo: (tag: string, message: unknown) => SubmodelInfo;
27
+ //# sourceMappingURL=submodelPath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submodelPath.d.ts","sourceRoot":"","sources":["../../src/devTools/submodelPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,QAAQ,CAAA;AAElD,eAAO,MAAM,mBAAmB,QAAmB,CAAA;AAGnD,eAAO,MAAM,QAAQ;;CAAe,CAAA;AAEpC,oEAAoE;AACpE,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAClC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACnC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;CACpC,CAAC,CAAA;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC9B,KAAK,MAAM,EACX,SAAS,OAAO,KACf,YAuBF,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { Option, Schema as S, pipe } from 'effect';
2
+ export const GOT_MESSAGE_PATTERN = /^Got.+Message$/;
3
+ const Tagged = S.Struct({ _tag: S.String });
4
+ export const isTagged = S.is(Tagged);
5
+ /**
6
+ * Walk a chain of `Got<Submodel>Message` wrappers in a recorded Message and
7
+ * return the wrapper tags plus the innermost leaf tag.
8
+ *
9
+ * The Submodel pattern propagates child Messages up to a parent by wrapping
10
+ * them in `GotChildMessage({ message: childMessage })`. Nested submodels stack
11
+ * those wrappers. Walking the chain reveals the parent → child → grandchild
12
+ * dispatch path that produced the entry.
13
+ *
14
+ * Returns an empty path and `Option.none` for top-level Messages whose tag
15
+ * doesn't match the `Got*Message` pattern. Otherwise the path lists wrapper
16
+ * tags from outer to inner, and `maybeLeafTag` is `Some` when the innermost
17
+ * wrapped value is itself a tagged Message (the underlying child Message that
18
+ * originated the chain).
19
+ */
20
+ export const extractSubmodelInfo = (tag, message) => {
21
+ if (!GOT_MESSAGE_PATTERN.test(tag)) {
22
+ return { submodelPath: [], maybeLeafTag: Option.none() };
23
+ }
24
+ const path = [tag];
25
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
26
+ let current = message?.['message'];
27
+ while (isTagged(current) && GOT_MESSAGE_PATTERN.test(current._tag)) {
28
+ path.push(current._tag);
29
+ current = current?.['message'];
30
+ }
31
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
32
+ return {
33
+ submodelPath: path,
34
+ maybeLeafTag: pipe(current, Option.liftPredicate(isTagged), Option.map(({ _tag }) => _tag)),
35
+ };
36
+ };
@@ -0,0 +1,46 @@
1
+ import { Schema as S } from 'effect';
2
+ declare const PathResolution: S.Union<[import("../schema/index.js").CallableTaggedStruct<"Found", {
3
+ value: typeof S.Unknown;
4
+ atPath: typeof S.String;
5
+ }>, import("../schema/index.js").CallableTaggedStruct<"NotFound", {
6
+ failedAt: typeof S.String;
7
+ reason: typeof S.String;
8
+ availableKeys: S.Array$<typeof S.String>;
9
+ }>]>;
10
+ /**
11
+ * Result of resolving a dot-string path against a Model snapshot.
12
+ *
13
+ * The path representation matches `SerializedEntry.changedPaths` exactly:
14
+ * dot-separated, anchored at the literal segment `root`. `Found.atPath`
15
+ * echoes the canonicalized path; `NotFound.availableKeys` lists the keys
16
+ * present at the deepest segment that resolved, so an agent can recover with
17
+ * one follow-up call.
18
+ */
19
+ export type PathResolution = typeof PathResolution.Type;
20
+ /**
21
+ * Walk a dot-string path against a Model snapshot. Returns the resolved value
22
+ * on success, or a structured `NotFound` describing the deepest segment that
23
+ * resolved plus its available keys so an agent can refine.
24
+ */
25
+ export declare const resolvePath: (root: unknown, path: string) => PathResolution;
26
+ /**
27
+ * Apply structural summarization rules to a value:
28
+ * - Arrays collapse to `{ _summary, length, sample: [head, last] }` at every depth.
29
+ * - Records walk to a depth of 3, then collapse to `{ _summary, keys }`.
30
+ * - Long strings collapse to `{ _summary, length, head }`.
31
+ * - Tagged values (`{ _tag, ... }`) keep their `_tag` since it's a record key.
32
+ *
33
+ * The result is JSON-serializable and intended for transmission to MCP clients
34
+ * with `expand: false`. Use raw values directly when `expand: true`.
35
+ */
36
+ export declare const summarizeValue: (value: unknown) => unknown;
37
+ /**
38
+ * Format a `NotFound` resolution as a single human-readable line for the
39
+ * `ResponseError.reason` channel. Includes the available keys at the failure
40
+ * point so an agent can refine the path on the next call.
41
+ */
42
+ export declare const formatPathNotFound: (notFound: Extract<PathResolution, {
43
+ _tag: "NotFound";
44
+ }>) => string;
45
+ export {};
46
+ //# sourceMappingURL=summarize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/devTools/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,MAAM,IAAI,CAAC,EACZ,MAAM,QAAQ,CAAA;AAwBf,QAAA,MAAM,cAAc;;;;;;;IAA2B,CAAA;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAiCvD;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAG,cAmCzD,CAAA;AA2DD;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,OAAgC,CAAA;AAYhF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAU,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,KACtD,MAIC,CAAA"}
@@ -0,0 +1,111 @@
1
+ import { Array as Array_, Function, Match as M, Option, Predicate, Record, Schema as S, } from 'effect';
2
+ import { OptionExt } from '../effectExtensions/index.js';
3
+ import { ts } from '../schema/index.js';
4
+ const ROOT = 'root';
5
+ const PATH_SEPARATOR = '.';
6
+ const MAX_DEPTH = 3;
7
+ const STRING_TRUNCATE = 200;
8
+ const ARRAY_SAMPLE = 2;
9
+ // PATH
10
+ const Found = ts('Found', {
11
+ value: S.Unknown,
12
+ atPath: S.String,
13
+ });
14
+ const NotFound = ts('NotFound', {
15
+ failedAt: S.String,
16
+ reason: S.String,
17
+ availableKeys: S.Array(S.String),
18
+ });
19
+ const PathResolution = S.Union(Found, NotFound);
20
+ const isExpandable = (value) => Predicate.isReadonlyRecord(value) || Array.isArray(value);
21
+ const keysOf = (value) => M.value(value).pipe(M.when(Array.isArray, items => Array_.makeBy(items.length, index => index.toString())), M.when(Predicate.isReadonlyRecord, Record.keys), M.orElse(() => []));
22
+ const segmentsOf = (path) => path === ROOT ? [] : path.split(PATH_SEPARATOR).slice(1);
23
+ const isRootAnchored = (path) => path === ROOT || path.startsWith(`${ROOT}${PATH_SEPARATOR}`);
24
+ const descend = (parent, segment) => M.value(parent).pipe(M.when(Array.isArray, array => Option.liftPredicate(Number(segment), Number.isInteger).pipe(Option.flatMap(index => Array_.get(array, index)))), M.when(Predicate.isReadonlyRecord, record => Record.get(record, segment)), M.orElse(() => Option.none()));
25
+ /**
26
+ * Walk a dot-string path against a Model snapshot. Returns the resolved value
27
+ * on success, or a structured `NotFound` describing the deepest segment that
28
+ * resolved plus its available keys so an agent can refine.
29
+ */
30
+ export const resolvePath = (root, path) => {
31
+ if (!isRootAnchored(path)) {
32
+ return NotFound({
33
+ failedAt: '',
34
+ reason: `Path must start with '${ROOT}'. Received: '${path}'.`,
35
+ availableKeys: [],
36
+ });
37
+ }
38
+ const initial = Found({ value: root, atPath: ROOT });
39
+ return Array_.reduce(segmentsOf(path), initial, (resolution, segment) => {
40
+ if (resolution._tag === 'NotFound') {
41
+ return resolution;
42
+ }
43
+ return Option.match(descend(resolution.value, segment), {
44
+ onNone: () => NotFound({
45
+ failedAt: resolution.atPath,
46
+ reason: isExpandable(resolution.value)
47
+ ? `No '${segment}' at '${resolution.atPath}'.`
48
+ : `Cannot descend into a primitive at '${resolution.atPath}' (looking for '${segment}').`,
49
+ availableKeys: keysOf(resolution.value),
50
+ }),
51
+ onSome: descended => Found({
52
+ value: descended,
53
+ atPath: `${resolution.atPath}${PATH_SEPARATOR}${segment}`,
54
+ }),
55
+ });
56
+ });
57
+ };
58
+ // SUMMARIZE
59
+ const truncateString = (value) => value.length <= STRING_TRUNCATE
60
+ ? value
61
+ : {
62
+ _summary: 'string',
63
+ length: value.length,
64
+ head: value.slice(0, STRING_TRUNCATE),
65
+ };
66
+ const sampleArray = (items, depth) => {
67
+ const sample = items.length <= ARRAY_SAMPLE
68
+ ? items
69
+ : [
70
+ ...Array_.take(items, ARRAY_SAMPLE - 1),
71
+ ...Option.toArray(Array_.last(items)),
72
+ ];
73
+ return Array_.map(sample, item => summarizeAt(item, depth + 1));
74
+ };
75
+ const summarizeArray = (items, depth) => ({
76
+ _summary: 'array',
77
+ length: items.length,
78
+ sample: sampleArray(items, depth),
79
+ });
80
+ const summarizeRecord = (value, depth) => {
81
+ if (depth >= MAX_DEPTH) {
82
+ return {
83
+ _summary: 'record',
84
+ keys: Record.keys(value),
85
+ };
86
+ }
87
+ return Record.map(value, child => summarizeAt(child, depth + 1));
88
+ };
89
+ const summarizeAt = (value, depth) => M.value(value).pipe(M.when(Predicate.isString, truncateString), M.when(Array.isArray, items => summarizeArray(items, depth)), M.when(Predicate.isReadonlyRecord, record => summarizeRecord(record, depth)), M.orElse(Function.identity));
90
+ /**
91
+ * Apply structural summarization rules to a value:
92
+ * - Arrays collapse to `{ _summary, length, sample: [head, last] }` at every depth.
93
+ * - Records walk to a depth of 3, then collapse to `{ _summary, keys }`.
94
+ * - Long strings collapse to `{ _summary, length, head }`.
95
+ * - Tagged values (`{ _tag, ... }`) keep their `_tag` since it's a record key.
96
+ *
97
+ * The result is JSON-serializable and intended for transmission to MCP clients
98
+ * with `expand: false`. Use raw values directly when `expand: true`.
99
+ */
100
+ export const summarizeValue = (value) => summarizeAt(value, 0);
101
+ // FORMAT
102
+ const formatAvailableKeys = (keys) => OptionExt.when(Array_.isNonEmptyReadonlyArray(keys), `Available keys: ${keys.join(', ')}.`);
103
+ /**
104
+ * Format a `NotFound` resolution as a single human-readable line for the
105
+ * `ResponseError.reason` channel. Includes the available keys at the failure
106
+ * point so an agent can refine the path on the next call.
107
+ */
108
+ export const formatPathNotFound = (notFound) => Option.match(formatAvailableKeys(notFound.availableKeys), {
109
+ onNone: () => notFound.reason,
110
+ onSome: hint => `${notFound.reason} ${hint}`,
111
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"webSocketBridge.d.ts","sourceRoot":"","sources":["../../src/devTools/webSocketBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EAIN,MAAM,EAGN,MAAM,IAAI,CAAC,EAGZ,MAAM,QAAQ,CAAA;AAuBf,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,YAAY,CAAA;AAE3D,KAAK,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;AAczC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,aAAa,EACpB,KAAK,GAAG,EACR,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EACnD,oBAAoB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,KAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,CA0EjB,CAAA"}
1
+ {"version":3,"file":"webSocketBridge.d.ts","sourceRoot":"","sources":["../../src/devTools/webSocketBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EAIN,MAAM,EAGN,MAAM,IAAI,CAAC,EAGZ,MAAM,QAAQ,CAAA;AAyBf,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,YAAY,CAAA;AAQ3D,KAAK,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;AAczC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,aAAa,EACpB,KAAK,GAAG,EACR,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EACnD,oBAAoB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,KAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,CA0EjB,CAAA"}
@@ -1,8 +1,9 @@
1
1
  import { Array, Cause, Effect, Either, HashMap, Match, Option, Order, Runtime, Schema as S, SubscriptionRef, pipe, } from 'effect';
2
2
  import { OptionExt } from '../effectExtensions/index.js';
3
- import { EventConnected, EventDisconnected, KeyframeInfo, RequestFrame, ResponseDispatched, ResponseError, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, RuntimeInfo, } from './protocol.js';
3
+ import { EventConnected, EventDisconnected, KeyframeInfo, RequestFrame, ResponseDispatched, ResponseError, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimeState, RuntimeInfo, } from './protocol.js';
4
4
  import { toInspectableValue, toSerializedEntry } from './serialize.js';
5
5
  import { INIT_INDEX } from './store.js';
6
+ import { formatPathNotFound, resolvePath, summarizeValue, } from './summarize.js';
6
7
  const REQUEST_CHANNEL = 'foldkit:devTools:request';
7
8
  const RESPONSE_CHANNEL = 'foldkit:devTools:response';
8
9
  const EVENT_CHANNEL = 'foldkit:devTools:event';
@@ -80,14 +81,25 @@ export const startWebSocketBridge = (store, hot, dispatch, maybeMessageSchema) =
80
81
  });
81
82
  window.addEventListener('beforeunload', emitDisconnect, { once: true });
82
83
  });
84
+ const presentResolution = (resolution, expand) => Match.value(resolution).pipe(Match.tag('Found', ({ value, atPath }) => ResponseModel({
85
+ value: expand ? value : summarizeValue(value),
86
+ atPath,
87
+ summarized: !expand,
88
+ })), Match.orElse(notFound => ResponseError({ reason: formatPathNotFound(notFound) })));
89
+ const readModelResponse = (store, index, maybePath, expand) => Effect.gen(function* () {
90
+ const model = yield* store.getModelAtIndex(index);
91
+ const path = Option.getOrElse(maybePath, () => 'root');
92
+ return presentResolution(resolvePath(toInspectableValue(model), path), expand);
93
+ }).pipe(Effect.catchAllCause(cause => Effect.succeed(ResponseError({
94
+ reason: `Failed to read Model at index ${index}: ${Cause.pretty(cause)}`,
95
+ }))));
83
96
  const dispatchRequest = (store, dispatch, maybeMessageSchema, request) => Match.value(request).pipe(Match.tagsExhaustive({
84
- RequestGetModel: () => Effect.gen(function* () {
97
+ RequestGetModel: ({ maybePath, expand }) => Effect.gen(function* () {
85
98
  const state = yield* SubscriptionRef.get(store.stateRef);
86
99
  const index = currentAbsoluteIndex(state.entries.length, state.startIndex);
87
- return yield* pipe(index, store.getModelAtIndex, Effect.map(model => ResponseModel({ model: toInspectableValue(model) })), Effect.catchAllCause(cause => Effect.succeed(ResponseError({
88
- reason: `Failed to read current Model: ${Cause.pretty(cause)}`,
89
- }))));
100
+ return yield* readModelResponse(store, index, maybePath, expand);
90
101
  }),
102
+ RequestGetModelAt: ({ index, maybePath, expand }) => readModelResponse(store, index, maybePath, expand),
91
103
  RequestListMessages: ({ limit, maybeSinceIndex }) => Effect.gen(function* () {
92
104
  const state = yield* SubscriptionRef.get(store.stateRef);
93
105
  const startAbsolute = Option.getOrElse(maybeSinceIndex, () => state.startIndex);
@@ -106,16 +118,9 @@ const dispatchRequest = (store, dispatch, maybeMessageSchema, request) => Match.
106
118
  onNone: () => Effect.succeed(ResponseError({
107
119
  reason: `No entry at index ${index} (have ${state.startIndex} to ${state.startIndex + state.entries.length - 1})`,
108
120
  })),
109
- onSome: entry => pipe({
110
- modelBefore: store.getModelAtIndex(index - 1),
111
- modelAfter: store.getModelAtIndex(index),
112
- }, Effect.all, Effect.map(({ modelBefore, modelAfter }) => ResponseMessage({
121
+ onSome: entry => Effect.succeed(ResponseMessage({
113
122
  entry: toSerializedEntry(entry, index),
114
- modelBefore: toInspectableValue(modelBefore),
115
- modelAfter: toInspectableValue(modelAfter),
116
- })), Effect.catchAllCause(cause => Effect.succeed(ResponseError({
117
- reason: `Failed to read Models around index ${index}: ${Cause.pretty(cause)}`,
118
- })))),
123
+ })),
119
124
  });
120
125
  }),
121
126
  RequestListKeyframes: () => Effect.gen(function* () {
@@ -158,4 +163,23 @@ const dispatchRequest = (store, dispatch, maybeMessageSchema, request) => Match.
158
163
  RequestListRuntimes: () => Effect.succeed(ResponseError({
159
164
  reason: 'RequestListRuntimes is plugin-handled and should not reach the runtime bridge',
160
165
  })),
166
+ RequestGetInit: () => Effect.gen(function* () {
167
+ const state = yield* SubscriptionRef.get(store.stateRef);
168
+ return ResponseInit({
169
+ maybeModel: Option.map(state.maybeInitModel, toInspectableValue),
170
+ commandNames: state.initCommandNames,
171
+ });
172
+ }),
173
+ RequestGetRuntimeState: () => Effect.gen(function* () {
174
+ const state = yield* SubscriptionRef.get(store.stateRef);
175
+ const currentIndex = currentAbsoluteIndex(state.entries.length, state.startIndex);
176
+ return ResponseRuntimeState({
177
+ currentIndex,
178
+ startIndex: state.startIndex,
179
+ totalEntries: state.entries.length,
180
+ isPaused: state.isPaused,
181
+ maybePausedAtIndex: OptionExt.when(state.isPaused, state.pausedAtIndex),
182
+ hasInitModel: Option.isSome(state.maybeInitModel),
183
+ });
184
+ }),
161
185
  }));
@@ -68,8 +68,26 @@ export declare const update: (model: Model, message: Message) => readonly [Model
68
68
  *
69
69
  * Should be called after the container has rendered. If the container is not
70
70
  * yet in the DOM the Command silently no-ops (the model still transitions
71
- * through `ScrollingToIndex` → `Idle` via the version-matched completion). */
71
+ * through `ScrollingToIndex` → `Idle` via the version-matched completion).
72
+ *
73
+ * Assumes uniform row heights: target scroll position is computed as
74
+ * `index * model.rowHeightPx`. For variable-height rows, use
75
+ * `scrollToIndexVariable`. */
72
76
  export declare const scrollToIndex: (model: Model, index: number) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
77
+ /** Variable-height counterpart of `scrollToIndex`. Walks the heights of items
78
+ * before `index` to compute the target `scrollTop`. Use this when rendering
79
+ * the list with `itemToRowHeightPx`; use `scrollToIndex` for uniform heights.
80
+ *
81
+ * Out-of-range indices clamp to the corresponding edge: negative or zero
82
+ * scrolls to the top, indices past the end scroll past the last row.
83
+ *
84
+ * Note: when restoring `initialScrollTop` on the first measurement of a
85
+ * variable-height list, the runtime falls back to uniform-height math (using
86
+ * `model.rowHeightPx`) because items aren't reachable from the `update`
87
+ * function. Consumers who need an accurate initial scroll on a
88
+ * variable-height list should call `scrollToIndexVariable` after the first
89
+ * `MeasuredContainer` arrives. */
90
+ export declare const scrollToIndexVariable: <Item>(model: Model, items: ReadonlyArray<Item>, itemToRowHeightPx: (item: Item, index: number) => number, index: number) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
73
91
  /** Slice of the data array that the view should render, plus the spacer
74
92
  * heights that keep the scrollbar physically correct. The first row in the
75
93
  * slice corresponds to data index `startIndex`. */
@@ -82,10 +100,25 @@ export type VisibleWindow = Readonly<{
82
100
  /** Computes the visible slice of a data array given the current scroll
83
101
  * position, container height, row height, and an overscan buffer.
84
102
  *
103
+ * Assumes uniform row heights via `model.rowHeightPx`. For variable-height
104
+ * rows, use `visibleWindowVariable`.
105
+ *
85
106
  * Returns `Option.none()` when the container has not yet been measured;
86
107
  * callers should render a placeholder (or `Html.empty`) and wait for the
87
108
  * first `MeasuredContainer` message. */
88
109
  export declare const visibleWindow: (model: Model, itemCount: number, overscan: number) => Option.Option<VisibleWindow>;
110
+ /** Variable-height counterpart of `visibleWindow`. Walks the heights of every
111
+ * item to build a prefix-sum array, then locates the visible slice with two
112
+ * linear searches.
113
+ *
114
+ * Cost is O(N) per call, walking the whole `items` array once to build the
115
+ * prefix sums. For lists in the 10k-item range, this comfortably fits inside
116
+ * a 60Hz scroll budget. Larger lists or hotter scroll paths can layer a
117
+ * prefix-sum cache invalidated when items change; that lives behind the same
118
+ * return shape so consumers don't have to know.
119
+ *
120
+ * Returns `Option.none()` when the container has not yet been measured. */
121
+ export declare const visibleWindowVariable: <Item>(model: Model, items: ReadonlyArray<Item>, itemToRowHeightPx: (item: Item, index: number) => number, overscan: number) => Option.Option<VisibleWindow>;
89
122
  /** Schema describing the subscription dependencies for container scroll and
90
123
  * resize tracking. */
91
124
  export declare const SubscriptionDeps: S.Struct<{
@@ -150,6 +183,14 @@ export type ViewConfig<Message, Item> = Readonly<{
150
183
  items: ReadonlyArray<Item>;
151
184
  itemToKey: (item: Item, index: number) => string;
152
185
  itemToView: (item: Item, index: number) => Html;
186
+ /** Optional per-item row height in pixels. When provided, the list renders
187
+ * with variable-height rows: each row's wrapper takes the height returned
188
+ * by this callback, and scroll math walks the items to compute the visible
189
+ * slice and spacers. When absent, all rows use `model.rowHeightPx`. Use
190
+ * this for tables with wrapping cells, taller detail rows, or any list
191
+ * where rows differ. Prefer the uniform `rowHeightPx` path when row
192
+ * heights are stable: it avoids the per-render walk over `items`. */
193
+ itemToRowHeightPx?: (item: Item, index: number) => number;
153
194
  /** Number of rows rendered above and below the visible viewport. Higher
154
195
  * values smooth out fast scroll at the cost of mounting more DOM. Default
155
196
  * is 5; react-window uses 1 and react-virtualized uses 3. Pick a value
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/index.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Array, Effect, Match as M, Number, Option, Schema as S, Stream, } from 'effect';
1
+ import { Array, Effect, Match as M, Number, Option, Schema as S, Stream, pipe, } from 'effect';
2
2
  import * as Command from '../../command/index.js';
3
3
  import { createLazy, html, } from '../../html/index.js';
4
4
  import { m } from '../../message/index.js';
@@ -110,6 +110,16 @@ export const update = (model, message) => M.value(message).pipe(M.withReturnType
110
110
  }
111
111
  },
112
112
  }));
113
+ const buildScrollToIndex = (model, index, targetScrollTop) => {
114
+ const nextVersion = Number.increment(model.pendingScrollVersion);
115
+ return [
116
+ evo(model, {
117
+ pendingScrollVersion: () => nextVersion,
118
+ pendingScroll: () => ScrollingToIndex({ index, version: nextVersion }),
119
+ }),
120
+ [applyScroll(model.id, targetScrollTop, nextVersion)],
121
+ ];
122
+ };
113
123
  /** Programmatically scrolls the container so the row at `index` is visible.
114
124
  * Returns the next model and a Command that mutates `element.scrollTop`. The
115
125
  * natural scroll event then flows back through `ScrolledContainer` and the
@@ -121,22 +131,42 @@ export const update = (model, message) => M.value(message).pipe(M.withReturnType
121
131
  *
122
132
  * Should be called after the container has rendered. If the container is not
123
133
  * yet in the DOM the Command silently no-ops (the model still transitions
124
- * through `ScrollingToIndex` → `Idle` via the version-matched completion). */
125
- export const scrollToIndex = (model, index) => {
126
- const nextVersion = Number.increment(model.pendingScrollVersion);
127
- const targetScrollTop = index * model.rowHeightPx;
128
- return [
129
- evo(model, {
130
- pendingScrollVersion: () => nextVersion,
131
- pendingScroll: () => ScrollingToIndex({ index, version: nextVersion }),
132
- }),
133
- [applyScroll(model.id, targetScrollTop, nextVersion)],
134
- ];
134
+ * through `ScrollingToIndex` → `Idle` via the version-matched completion).
135
+ *
136
+ * Assumes uniform row heights: target scroll position is computed as
137
+ * `index * model.rowHeightPx`. For variable-height rows, use
138
+ * `scrollToIndexVariable`. */
139
+ export const scrollToIndex = (model, index) => buildScrollToIndex(model, index, index * model.rowHeightPx);
140
+ /** Variable-height counterpart of `scrollToIndex`. Walks the heights of items
141
+ * before `index` to compute the target `scrollTop`. Use this when rendering
142
+ * the list with `itemToRowHeightPx`; use `scrollToIndex` for uniform heights.
143
+ *
144
+ * Out-of-range indices clamp to the corresponding edge: negative or zero
145
+ * scrolls to the top, indices past the end scroll past the last row.
146
+ *
147
+ * Note: when restoring `initialScrollTop` on the first measurement of a
148
+ * variable-height list, the runtime falls back to uniform-height math (using
149
+ * `model.rowHeightPx`) because items aren't reachable from the `update`
150
+ * function. Consumers who need an accurate initial scroll on a
151
+ * variable-height list should call `scrollToIndexVariable` after the first
152
+ * `MeasuredContainer` arrives. */
153
+ export const scrollToIndexVariable = (model, items, itemToRowHeightPx, index) => {
154
+ const cumulativeOffsets = prefixSum(items, itemToRowHeightPx);
155
+ const targetScrollTop = pipe(cumulativeOffsets, Array.get(Math.max(0, index)), Option.getOrElse(() => lastOrZero(cumulativeOffsets)));
156
+ return buildScrollToIndex(model, index, targetScrollTop);
135
157
  };
136
158
  const clampIndex = (index, itemCount) => Math.max(0, Math.min(index, itemCount));
159
+ const prefixSum = (items, itemToRowHeightPx) => {
160
+ const heights = Array.map(items, itemToRowHeightPx);
161
+ return Array.scan(heights, 0, (cumulative, height) => cumulative + height);
162
+ };
163
+ const lastOrZero = (values) => pipe(values, Array.last, Option.getOrElse(() => 0));
137
164
  /** Computes the visible slice of a data array given the current scroll
138
165
  * position, container height, row height, and an overscan buffer.
139
166
  *
167
+ * Assumes uniform row heights via `model.rowHeightPx`. For variable-height
168
+ * rows, use `visibleWindowVariable`.
169
+ *
140
170
  * Returns `Option.none()` when the container has not yet been measured;
141
171
  * callers should render a placeholder (or `Html.empty`) and wait for the
142
172
  * first `MeasuredContainer` message. */
@@ -157,6 +187,41 @@ export const visibleWindow = (model, itemCount, overscan) => M.value(model.measu
157
187
  });
158
188
  },
159
189
  }));
190
+ /** Variable-height counterpart of `visibleWindow`. Walks the heights of every
191
+ * item to build a prefix-sum array, then locates the visible slice with two
192
+ * linear searches.
193
+ *
194
+ * Cost is O(N) per call, walking the whole `items` array once to build the
195
+ * prefix sums. For lists in the 10k-item range, this comfortably fits inside
196
+ * a 60Hz scroll budget. Larger lists or hotter scroll paths can layer a
197
+ * prefix-sum cache invalidated when items change; that lives behind the same
198
+ * return shape so consumers don't have to know.
199
+ *
200
+ * Returns `Option.none()` when the container has not yet been measured. */
201
+ export const visibleWindowVariable = (model, items, itemToRowHeightPx, overscan) => M.value(model.measurement).pipe(M.withReturnType(), M.tagsExhaustive({
202
+ Unmeasured: () => Option.none(),
203
+ Measured: ({ containerHeight }) => {
204
+ const itemCount = items.length;
205
+ const cumulativeOffsets = prefixSum(items, itemToRowHeightPx);
206
+ const totalHeight = lastOrZero(cumulativeOffsets);
207
+ const firstVisibleIndex = pipe(cumulativeOffsets, Array.findFirstIndex(Number.greaterThan(model.scrollTop)), Option.match({
208
+ onNone: () => itemCount,
209
+ onSome: index => Math.max(0, index - 1),
210
+ }));
211
+ const lastVisibleIndex = pipe(cumulativeOffsets, Array.findFirstIndex(Number.greaterThanOrEqualTo(model.scrollTop + containerHeight)), Option.getOrElse(() => itemCount));
212
+ const startIndex = clampIndex(firstVisibleIndex - overscan, itemCount);
213
+ const endIndex = clampIndex(lastVisibleIndex + overscan, itemCount);
214
+ const topSpacerHeight = pipe(cumulativeOffsets, Array.get(startIndex), Option.getOrElse(() => 0));
215
+ const offsetAtEnd = pipe(cumulativeOffsets, Array.get(endIndex), Option.getOrElse(() => totalHeight));
216
+ const bottomSpacerHeight = totalHeight - offsetAtEnd;
217
+ return Option.some({
218
+ startIndex,
219
+ endIndex,
220
+ topSpacerHeight,
221
+ bottomSpacerHeight,
222
+ });
223
+ },
224
+ }));
160
225
  // SUBSCRIPTION
161
226
  const containerElement = (id) => Option.fromNullable(document.getElementById(id));
162
227
  /** Schema describing the subscription dependencies for container scroll and
@@ -278,7 +343,7 @@ const DEFAULT_OVERSCAN = 5;
278
343
  * count of currently mounted rows. */
279
344
  export const view = (config) => {
280
345
  const { AriaPosinset, AriaSetsize, Class, DataAttribute, Id, Role, Style, keyed, } = html();
281
- const { model, items, itemToKey, itemToView, overscan = DEFAULT_OVERSCAN, rowElement = 'li', className, attributes = [], } = config;
346
+ const { model, items, itemToKey, itemToView, itemToRowHeightPx, overscan = DEFAULT_OVERSCAN, rowElement = 'li', className, attributes = [], } = config;
282
347
  const containerAttributes = [
283
348
  Id(model.id),
284
349
  Role('list'),
@@ -293,7 +358,13 @@ export const view = (config) => {
293
358
  ...attributes,
294
359
  ];
295
360
  const renderContainer = (children) => keyed('ul')(model.id, containerAttributes, children);
296
- return Option.match(visibleWindow(model, items.length, overscan), {
361
+ const maybeWindow = itemToRowHeightPx !== undefined
362
+ ? visibleWindowVariable(model, items, itemToRowHeightPx, overscan)
363
+ : visibleWindow(model, items.length, overscan);
364
+ const rowHeightFor = (item, dataIndex) => itemToRowHeightPx !== undefined
365
+ ? itemToRowHeightPx(item, dataIndex)
366
+ : model.rowHeightPx;
367
+ return Option.match(maybeWindow, {
297
368
  onNone: () => renderContainer([]),
298
369
  onSome: ({ startIndex, endIndex, topSpacerHeight, bottomSpacerHeight }) => {
299
370
  const visibleItems = items.slice(startIndex, endIndex);
@@ -306,7 +377,10 @@ export const view = (config) => {
306
377
  DataAttribute('virtual-list-item-index', String(dataIndex)),
307
378
  AriaSetsize(items.length),
308
379
  AriaPosinset(dataIndex + 1),
309
- Style({ height: `${model.rowHeightPx}px`, display: 'grid' }),
380
+ Style({
381
+ height: `${rowHeightFor(item, dataIndex)}px`,
382
+ display: 'grid',
383
+ }),
310
384
  ], [itemToView(item, dataIndex)]);
311
385
  });
312
386
  return renderContainer([topSpacer, ...renderedRows, bottomSpacer]);
@@ -1,3 +1,3 @@
1
- export { init, update, scrollToIndex, view, lazy, subscriptions, visibleWindow, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, SubscriptionDeps, } from './index.js';
1
+ export { init, update, scrollToIndex, scrollToIndexVariable, view, lazy, subscriptions, visibleWindow, visibleWindowVariable, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, SubscriptionDeps, } from './index.js';
2
2
  export type { InitConfig, ViewConfig, VisibleWindow } from './index.js';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,IAAI,EACJ,IAAI,EACJ,aAAa,EACb,aAAa,EACb,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,qBAAqB,EACrB,IAAI,EACJ,IAAI,EACJ,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
@@ -1 +1 @@
1
- export { init, update, scrollToIndex, view, lazy, subscriptions, visibleWindow, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, SubscriptionDeps, } from './index.js';
1
+ export { init, update, scrollToIndex, scrollToIndexVariable, view, lazy, subscriptions, visibleWindow, visibleWindowVariable, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, SubscriptionDeps, } from './index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.77.0",
3
+ "version": "0.79.0",
4
4
  "description": "A frontend framework for TypeScript, built on Effect, using The Elm Architecture",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",