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.
- package/dist/devTools/overlay.d.ts.map +1 -1
- package/dist/devTools/overlay.js +3 -22
- package/dist/devTools/protocol.d.ts +94 -19
- package/dist/devTools/protocol.d.ts.map +1 -1
- package/dist/devTools/protocol.js +40 -11
- package/dist/devTools/public.d.ts +1 -1
- package/dist/devTools/public.d.ts.map +1 -1
- package/dist/devTools/public.js +1 -1
- package/dist/devTools/serialize.d.ts.map +1 -1
- package/dist/devTools/serialize.js +16 -10
- package/dist/devTools/submodelPath.d.ts +27 -0
- package/dist/devTools/submodelPath.d.ts.map +1 -0
- package/dist/devTools/submodelPath.js +36 -0
- package/dist/devTools/summarize.d.ts +46 -0
- package/dist/devTools/summarize.d.ts.map +1 -0
- package/dist/devTools/summarize.js +111 -0
- package/dist/devTools/webSocketBridge.d.ts.map +1 -1
- package/dist/devTools/webSocketBridge.js +38 -14
- package/dist/ui/virtualList/index.d.ts +42 -1
- package/dist/ui/virtualList/index.d.ts.map +1 -1
- package/dist/ui/virtualList/index.js +89 -15
- package/dist/ui/virtualList/public.d.ts +1 -1
- package/dist/ui/virtualList/public.d.ts.map +1 -1
- package/dist/ui/virtualList/public.js +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
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"}
|
package/dist/devTools/overlay.js
CHANGED
|
@@ -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
|
|
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,
|
|
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", {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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", {
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAMpC,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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"}
|
package/dist/devTools/public.js
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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;
|
|
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*
|
|
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 =>
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAKL,MAAM,EACN,MAAM,IAAI,CAAC,
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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({
|
|
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';
|