@parlor/agent-ui 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/client/agent-ui-client.d.ts +60 -0
  4. package/dist/client/agent-ui-client.d.ts.map +1 -0
  5. package/dist/client/agent-ui-client.js +407 -0
  6. package/dist/client/agent-ui-client.js.map +1 -0
  7. package/dist/client/confirmations.d.ts +34 -0
  8. package/dist/client/confirmations.d.ts.map +1 -0
  9. package/dist/client/confirmations.js +95 -0
  10. package/dist/client/confirmations.js.map +1 -0
  11. package/dist/client/environment.d.ts +37 -0
  12. package/dist/client/environment.d.ts.map +1 -0
  13. package/dist/client/environment.js +79 -0
  14. package/dist/client/environment.js.map +1 -0
  15. package/dist/client/highlight.d.ts +12 -0
  16. package/dist/client/highlight.d.ts.map +1 -0
  17. package/dist/client/highlight.js +98 -0
  18. package/dist/client/highlight.js.map +1 -0
  19. package/dist/client/index.d.ts +7 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.js +7 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/json-equal.d.ts +2 -0
  24. package/dist/client/json-equal.d.ts.map +1 -0
  25. package/dist/client/json-equal.js +20 -0
  26. package/dist/client/json-equal.js.map +1 -0
  27. package/dist/client/registry.d.ts +54 -0
  28. package/dist/client/registry.d.ts.map +1 -0
  29. package/dist/client/registry.js +280 -0
  30. package/dist/client/registry.js.map +1 -0
  31. package/dist/client/screen.d.ts +20 -0
  32. package/dist/client/screen.d.ts.map +1 -0
  33. package/dist/client/screen.js +196 -0
  34. package/dist/client/screen.js.map +1 -0
  35. package/dist/client/write.d.ts +13 -0
  36. package/dist/client/write.d.ts.map +1 -0
  37. package/dist/client/write.js +101 -0
  38. package/dist/client/write.js.map +1 -0
  39. package/dist/index.d.ts +5 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +5 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/manifest.d.ts +44 -0
  44. package/dist/manifest.d.ts.map +1 -0
  45. package/dist/manifest.js +23 -0
  46. package/dist/manifest.js.map +1 -0
  47. package/dist/pages.d.ts +11 -0
  48. package/dist/pages.d.ts.map +1 -0
  49. package/dist/pages.js +42 -0
  50. package/dist/pages.js.map +1 -0
  51. package/dist/protocol.d.ts +206 -0
  52. package/dist/protocol.d.ts.map +1 -0
  53. package/dist/protocol.js +152 -0
  54. package/dist/protocol.js.map +1 -0
  55. package/dist/server/agent-ui.d.ts +36 -0
  56. package/dist/server/agent-ui.d.ts.map +1 -0
  57. package/dist/server/agent-ui.js +89 -0
  58. package/dist/server/agent-ui.js.map +1 -0
  59. package/dist/server/audience.d.ts +9 -0
  60. package/dist/server/audience.d.ts.map +1 -0
  61. package/dist/server/audience.js +15 -0
  62. package/dist/server/audience.js.map +1 -0
  63. package/dist/server/broker.d.ts +29 -0
  64. package/dist/server/broker.d.ts.map +1 -0
  65. package/dist/server/broker.js +152 -0
  66. package/dist/server/broker.js.map +1 -0
  67. package/dist/server/confirmations.d.ts +40 -0
  68. package/dist/server/confirmations.d.ts.map +1 -0
  69. package/dist/server/confirmations.js +230 -0
  70. package/dist/server/confirmations.js.map +1 -0
  71. package/dist/server/directory.d.ts +41 -0
  72. package/dist/server/directory.d.ts.map +1 -0
  73. package/dist/server/directory.js +193 -0
  74. package/dist/server/directory.js.map +1 -0
  75. package/dist/server/index.d.ts +10 -0
  76. package/dist/server/index.d.ts.map +1 -0
  77. package/dist/server/index.js +10 -0
  78. package/dist/server/index.js.map +1 -0
  79. package/dist/server/manifest-source.d.ts +10 -0
  80. package/dist/server/manifest-source.d.ts.map +1 -0
  81. package/dist/server/manifest-source.js +36 -0
  82. package/dist/server/manifest-source.js.map +1 -0
  83. package/dist/server/routes.d.ts +11 -0
  84. package/dist/server/routes.d.ts.map +1 -0
  85. package/dist/server/routes.js +38 -0
  86. package/dist/server/routes.js.map +1 -0
  87. package/dist/server/schemas.d.ts +69 -0
  88. package/dist/server/schemas.d.ts.map +1 -0
  89. package/dist/server/schemas.js +78 -0
  90. package/dist/server/schemas.js.map +1 -0
  91. package/dist/server/tools.d.ts +49 -0
  92. package/dist/server/tools.d.ts.map +1 -0
  93. package/dist/server/tools.js +312 -0
  94. package/dist/server/tools.js.map +1 -0
  95. package/dist/view.d.ts +54 -0
  96. package/dist/view.d.ts.map +1 -0
  97. package/dist/view.js +15 -0
  98. package/dist/view.js.map +1 -0
  99. package/dist/vite/index.d.ts +3 -0
  100. package/dist/vite/index.d.ts.map +1 -0
  101. package/dist/vite/index.js +3 -0
  102. package/dist/vite/index.js.map +1 -0
  103. package/dist/vite/manifest.d.ts +5 -0
  104. package/dist/vite/manifest.d.ts.map +1 -0
  105. package/dist/vite/manifest.js +107 -0
  106. package/dist/vite/manifest.js.map +1 -0
  107. package/dist/vite/plugin.d.ts +16 -0
  108. package/dist/vite/plugin.d.ts.map +1 -0
  109. package/dist/vite/plugin.js +192 -0
  110. package/dist/vite/plugin.js.map +1 -0
  111. package/package.json +65 -3
@@ -0,0 +1,280 @@
1
+ import { jsonEqual } from "./json-equal.js";
2
+ export const NO_AGENT_WRITES = new Map();
3
+ class Mounted {
4
+ view;
5
+ binding;
6
+ element;
7
+ key;
8
+ writes = NO_AGENT_WRITES;
9
+ writeListeners = new Set();
10
+ constructor(view, binding) {
11
+ this.view = view;
12
+ this.binding = binding;
13
+ }
14
+ }
15
+ export function createViewRegistry(env) {
16
+ const singles = new Map();
17
+ const keyed = new Map();
18
+ const live = new Set();
19
+ const byElement = new WeakMap();
20
+ const visibility = new Map();
21
+ let observer;
22
+ let pending = 0;
23
+ const waiting = [];
24
+ let listening = false;
25
+ let entered;
26
+ let interacted;
27
+ let quiet = 0;
28
+ let agentFocus;
29
+ const settle = () => {
30
+ if (pending > 0)
31
+ return;
32
+ for (const resolve of waiting.splice(0))
33
+ resolve();
34
+ };
35
+ const setVisibility = (mounted, next) => {
36
+ const previous = visibility.get(mounted);
37
+ if (previous === "pending")
38
+ pending--;
39
+ if (next === "pending")
40
+ pending++;
41
+ if (next === undefined)
42
+ visibility.delete(mounted);
43
+ else
44
+ visibility.set(mounted, next);
45
+ return previous;
46
+ };
47
+ const onEntries = (entries) => {
48
+ for (const entry of entries) {
49
+ const mounted = byElement.get(entry.target);
50
+ if (!mounted || !visibility.has(mounted))
51
+ continue;
52
+ const previous = setVisibility(mounted, entry.isIntersecting ? "in" : "out");
53
+ if (entry.isIntersecting && previous === "out" && quiet === 0) {
54
+ entered = { view: mounted, at: env.now() };
55
+ }
56
+ }
57
+ settle();
58
+ };
59
+ const handBack = (event) => {
60
+ if (event.target === agentFocus)
61
+ agentFocus = undefined;
62
+ };
63
+ const onInteraction = (event) => {
64
+ if (quiet > 0 && event.type === "focusin")
65
+ return;
66
+ if (event.type === "focusin")
67
+ agentFocus = undefined;
68
+ else
69
+ handBack(event);
70
+ const mounted = owner(event.target);
71
+ if (mounted)
72
+ interacted = { view: mounted, at: env.now() };
73
+ };
74
+ const listen = () => {
75
+ if (listening || !env.document)
76
+ return;
77
+ listening = true;
78
+ env.document.addEventListener("pointerdown", onInteraction, true);
79
+ env.document.addEventListener("focusin", onInteraction, true);
80
+ env.document.addEventListener("keydown", handBack, true);
81
+ env.document.addEventListener("input", onInput);
82
+ };
83
+ const observe = (mounted) => {
84
+ if (!mounted.element || !live.has(mounted))
85
+ return;
86
+ setVisibility(mounted, "pending");
87
+ observer ??= env.observeIntersections(onEntries);
88
+ observer?.observe(mounted.element);
89
+ };
90
+ const unobserve = (mounted) => {
91
+ if (setVisibility(mounted, undefined) === undefined)
92
+ return;
93
+ if (mounted.element)
94
+ observer?.unobserve(mounted.element);
95
+ settle();
96
+ };
97
+ const stackOf = (mounted, create) => {
98
+ const name = mounted.view.name;
99
+ if (!mounted.view.keyed) {
100
+ let stack = singles.get(name);
101
+ if (!stack && create)
102
+ singles.set(name, (stack = []));
103
+ return stack;
104
+ }
105
+ let byKey = keyed.get(name);
106
+ if (!byKey && create)
107
+ keyed.set(name, (byKey = new Map()));
108
+ const key = mounted.key ?? "";
109
+ let stack = byKey?.get(key);
110
+ if (!stack && byKey && create)
111
+ byKey.set(key, (stack = []));
112
+ return stack;
113
+ };
114
+ const removeFromStack = (mounted) => {
115
+ const stack = stackOf(mounted, false);
116
+ if (!stack)
117
+ return;
118
+ stack.splice(stack.indexOf(mounted), 1);
119
+ if (stack.length > 0)
120
+ return;
121
+ const name = mounted.view.name;
122
+ if (!mounted.view.keyed) {
123
+ singles.delete(name);
124
+ return;
125
+ }
126
+ const byKey = keyed.get(name);
127
+ byKey?.delete(mounted.key ?? "");
128
+ if (byKey?.size === 0)
129
+ keyed.delete(name);
130
+ };
131
+ function owner(node) {
132
+ for (let current = node; current; current = current.parentNode) {
133
+ const mounted = byElement.get(current);
134
+ if (mounted && live.has(mounted))
135
+ return mounted;
136
+ }
137
+ return undefined;
138
+ }
139
+ const replaceWrites = (mounted, writes) => {
140
+ mounted.writes = writes;
141
+ for (const listener of mounted.writeListeners)
142
+ listener();
143
+ };
144
+ const onInput = (event) => {
145
+ handBack(event);
146
+ const target = event.target;
147
+ const mounted = owner(target);
148
+ if (!mounted || mounted.writes.size === 0)
149
+ return;
150
+ const name = target?.getAttribute?.("name");
151
+ const remaining = new Map(mounted.writes);
152
+ if (name)
153
+ remaining.delete(name);
154
+ replaceWrites(mounted, remaining.size === 0 ? NO_AGENT_WRITES : remaining);
155
+ };
156
+ const current = (moment) => moment && live.has(moment.view) ? moment : undefined;
157
+ return {
158
+ track(view, binding) {
159
+ const mounted = new Mounted(view, binding);
160
+ return {
161
+ bind(next) {
162
+ mounted.binding = next;
163
+ },
164
+ ref(element) {
165
+ if (mounted.element === (element ?? undefined))
166
+ return;
167
+ unobserve(mounted);
168
+ if (mounted.element)
169
+ byElement.delete(mounted.element);
170
+ mounted.element = element ?? undefined;
171
+ if (element)
172
+ byElement.set(element, mounted);
173
+ observe(mounted);
174
+ },
175
+ mount(key) {
176
+ mounted.key = key;
177
+ live.add(mounted);
178
+ stackOf(mounted, true).push(mounted);
179
+ listen();
180
+ observe(mounted);
181
+ let mountedNow = true;
182
+ return () => {
183
+ if (!mountedNow)
184
+ return;
185
+ mountedNow = false;
186
+ unobserve(mounted);
187
+ removeFromStack(mounted);
188
+ live.delete(mounted);
189
+ };
190
+ },
191
+ subscribeWrites(listener) {
192
+ mounted.writeListeners.add(listener);
193
+ return () => mounted.writeListeners.delete(listener);
194
+ },
195
+ writes: () => mounted.writes,
196
+ };
197
+ },
198
+ find(ref) {
199
+ return (ref.key === undefined ? singles.get(ref.view) : keyed.get(ref.view)?.get(ref.key))?.at(-1);
200
+ },
201
+ remember(mounted, values) {
202
+ const target = mounted;
203
+ const entries = Object.entries(values).filter(([field, value]) => !target.writes.has(field) || !jsonEqual(target.writes.get(field), value));
204
+ if (entries.length === 0)
205
+ return;
206
+ replaceWrites(target, new Map([...target.writes, ...entries]));
207
+ },
208
+ async quietly(act) {
209
+ quiet++;
210
+ try {
211
+ return await act();
212
+ }
213
+ finally {
214
+ quiet--;
215
+ }
216
+ },
217
+ placeFocus(element) {
218
+ if (element.ownerDocument.activeElement === element)
219
+ return;
220
+ quiet++;
221
+ try {
222
+ element.focus({ preventScroll: true });
223
+ }
224
+ finally {
225
+ quiet--;
226
+ }
227
+ agentFocus = element;
228
+ },
229
+ restore: (mounted, writes) => replaceWrites(mounted, writes),
230
+ *singles() {
231
+ for (const stack of singles.values())
232
+ yield stack.at(-1);
233
+ },
234
+ *onScreenCandidates() {
235
+ for (const [mounted, state] of visibility) {
236
+ if (mounted.view.keyed && state !== "out")
237
+ yield mounted;
238
+ }
239
+ },
240
+ keyedCount: (viewName) => keyed.get(viewName)?.size ?? 0,
241
+ isCurrent: (mounted) => stackOf(mounted, false)?.at(-1) === mounted,
242
+ owner,
243
+ lastEntered: () => current(entered),
244
+ lastInteracted: () => current(interacted),
245
+ agentFocus: () => agentFocus,
246
+ awaitingObserver: () => pending > 0 && observer !== undefined,
247
+ observed() {
248
+ if (pending === 0 || !observer)
249
+ return Promise.resolve();
250
+ return new Promise((resolve) => waiting.push(resolve));
251
+ },
252
+ resume() {
253
+ listen();
254
+ for (const mounted of live)
255
+ if (!visibility.has(mounted))
256
+ observe(mounted);
257
+ },
258
+ dispose() {
259
+ observer?.disconnect();
260
+ observer = undefined;
261
+ visibility.clear();
262
+ agentFocus = undefined;
263
+ pending = 0;
264
+ settle();
265
+ if (listening && env.document) {
266
+ env.document.removeEventListener("pointerdown", onInteraction, true);
267
+ env.document.removeEventListener("focusin", onInteraction, true);
268
+ env.document.removeEventListener("keydown", handBack, true);
269
+ env.document.removeEventListener("input", onInput);
270
+ }
271
+ listening = false;
272
+ },
273
+ };
274
+ }
275
+ export function refOf(mounted) {
276
+ return mounted.key === undefined
277
+ ? { view: mounted.view.name }
278
+ : { view: mounted.view.name, key: mounted.key };
279
+ }
280
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/client/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyAgentView, ViewRef } from \"../view.js\";\nimport type {\n BrowserEnvironment,\n IntersectionEntryLike,\n IntersectionObserverLike,\n} from \"./environment.js\";\nimport { jsonEqual } from \"./json-equal.js\";\n\nexport interface AnyViewBinding {\n readonly key?: string | undefined;\n get(): Readonly<Record<string, unknown>>;\n set?(\n next: Readonly<Record<string, unknown>>,\n changed: readonly string[],\n ): void | Promise<void>;\n}\n\nexport type AgentWrites = ReadonlyMap<string, unknown>;\n\nexport const NO_AGENT_WRITES: AgentWrites = new Map();\n\nexport interface ViewSlot {\n readonly ref: (element: Element | null) => void;\n bind(binding: AnyViewBinding): void;\n mount(key: string | undefined): () => void;\n readonly subscribeWrites: (listener: () => void) => () => void;\n readonly writes: () => AgentWrites;\n}\n\nexport interface ViewRegistry {\n track(view: AnyAgentView, binding: AnyViewBinding): ViewSlot;\n}\n\nexport interface MountedView {\n readonly view: AnyAgentView;\n readonly key: string | undefined;\n readonly binding: AnyViewBinding;\n readonly element: Element | undefined;\n readonly writes: AgentWrites;\n}\n\nexport interface Moment {\n readonly view: MountedView;\n readonly at: number;\n}\n\nexport interface RegistryReader {\n find(ref: ViewRef): MountedView | undefined;\n singles(): Iterable<MountedView>;\n onScreenCandidates(): Iterable<MountedView>;\n keyedCount(viewName: string): number;\n isCurrent(mounted: MountedView): boolean;\n owner(node: Node | null): MountedView | undefined;\n lastEntered(): Moment | undefined;\n lastInteracted(): Moment | undefined;\n agentFocus(): Element | undefined;\n}\n\nexport interface ViewRegistryRuntime extends ViewRegistry, RegistryReader {\n remember(mounted: MountedView, values: Readonly<Record<string, unknown>>): void;\n quietly<T>(act: () => Promise<T>): Promise<T>;\n placeFocus(element: HTMLElement): void;\n restore(mounted: MountedView, writes: AgentWrites): void;\n awaitingObserver(): boolean;\n observed(): Promise<void>;\n resume(): void;\n dispose(): void;\n}\n\nclass Mounted implements MountedView {\n element: Element | undefined;\n key: string | undefined;\n writes: AgentWrites = NO_AGENT_WRITES;\n readonly writeListeners = new Set<() => void>();\n constructor(\n readonly view: AnyAgentView,\n public binding: AnyViewBinding,\n ) {}\n}\n\ntype Visibility = \"pending\" | \"in\" | \"out\";\n\nexport function createViewRegistry(env: BrowserEnvironment): ViewRegistryRuntime {\n const singles = new Map<string, Mounted[]>();\n const keyed = new Map<string, Map<string, Mounted[]>>();\n const live = new Set<Mounted>();\n const byElement = new WeakMap<Element, Mounted>();\n const visibility = new Map<Mounted, Visibility>();\n let observer: IntersectionObserverLike | undefined;\n let pending = 0;\n const waiting: (() => void)[] = [];\n let listening = false;\n let entered: Moment | undefined;\n let interacted: Moment | undefined;\n let quiet = 0;\n let agentFocus: Element | undefined;\n\n const settle = () => {\n if (pending > 0) return;\n for (const resolve of waiting.splice(0)) resolve();\n };\n\n const setVisibility = (mounted: Mounted, next: Visibility | undefined) => {\n const previous = visibility.get(mounted);\n if (previous === \"pending\") pending--;\n if (next === \"pending\") pending++;\n if (next === undefined) visibility.delete(mounted);\n else visibility.set(mounted, next);\n return previous;\n };\n\n const onEntries = (entries: readonly IntersectionEntryLike[]) => {\n for (const entry of entries) {\n const mounted = byElement.get(entry.target);\n if (!mounted || !visibility.has(mounted)) continue;\n const previous = setVisibility(mounted, entry.isIntersecting ? \"in\" : \"out\");\n if (entry.isIntersecting && previous === \"out\" && quiet === 0) {\n entered = { view: mounted, at: env.now() };\n }\n }\n settle();\n };\n\n const handBack = (event: Event) => {\n if (event.target === agentFocus) agentFocus = undefined;\n };\n\n const onInteraction = (event: Event) => {\n if (quiet > 0 && event.type === \"focusin\") return;\n if (event.type === \"focusin\") agentFocus = undefined;\n else handBack(event);\n const mounted = owner(event.target as Node | null);\n if (mounted) interacted = { view: mounted, at: env.now() };\n };\n\n const listen = () => {\n if (listening || !env.document) return;\n listening = true;\n env.document.addEventListener(\"pointerdown\", onInteraction, true);\n env.document.addEventListener(\"focusin\", onInteraction, true);\n env.document.addEventListener(\"keydown\", handBack, true);\n env.document.addEventListener(\"input\", onInput);\n };\n\n const observe = (mounted: Mounted) => {\n if (!mounted.element || !live.has(mounted)) return;\n setVisibility(mounted, \"pending\");\n observer ??= env.observeIntersections(onEntries);\n observer?.observe(mounted.element);\n };\n\n const unobserve = (mounted: Mounted) => {\n if (setVisibility(mounted, undefined) === undefined) return;\n if (mounted.element) observer?.unobserve(mounted.element);\n settle();\n };\n\n const stackOf = (mounted: Mounted, create: boolean): Mounted[] | undefined => {\n const name = mounted.view.name;\n if (!mounted.view.keyed) {\n let stack = singles.get(name);\n if (!stack && create) singles.set(name, (stack = []));\n return stack;\n }\n let byKey = keyed.get(name);\n if (!byKey && create) keyed.set(name, (byKey = new Map()));\n const key = mounted.key ?? \"\";\n let stack = byKey?.get(key);\n if (!stack && byKey && create) byKey.set(key, (stack = []));\n return stack;\n };\n\n const removeFromStack = (mounted: Mounted) => {\n const stack = stackOf(mounted, false);\n if (!stack) return;\n stack.splice(stack.indexOf(mounted), 1);\n if (stack.length > 0) return;\n const name = mounted.view.name;\n if (!mounted.view.keyed) {\n singles.delete(name);\n return;\n }\n const byKey = keyed.get(name);\n byKey?.delete(mounted.key ?? \"\");\n if (byKey?.size === 0) keyed.delete(name);\n };\n\n function owner(node: Node | null): MountedView | undefined {\n for (let current = node; current; current = current.parentNode) {\n const mounted = byElement.get(current as Element);\n if (mounted && live.has(mounted)) return mounted;\n }\n return undefined;\n }\n\n const replaceWrites = (mounted: Mounted, writes: AgentWrites) => {\n mounted.writes = writes;\n for (const listener of mounted.writeListeners) listener();\n };\n\n const onInput = (event: Event) => {\n handBack(event);\n const target = event.target as Element | null;\n const mounted = owner(target) as Mounted | undefined;\n if (!mounted || mounted.writes.size === 0) return;\n const name = target?.getAttribute?.(\"name\");\n const remaining = new Map(mounted.writes);\n if (name) remaining.delete(name);\n replaceWrites(mounted, remaining.size === 0 ? NO_AGENT_WRITES : remaining);\n };\n\n const current = (moment: Moment | undefined) =>\n moment && live.has(moment.view as Mounted) ? moment : undefined;\n\n return {\n track(view, binding) {\n const mounted = new Mounted(view, binding);\n return {\n bind(next) {\n mounted.binding = next;\n },\n ref(element) {\n if (mounted.element === (element ?? undefined)) return;\n unobserve(mounted);\n if (mounted.element) byElement.delete(mounted.element);\n mounted.element = element ?? undefined;\n if (element) byElement.set(element, mounted);\n observe(mounted);\n },\n mount(key) {\n mounted.key = key;\n live.add(mounted);\n stackOf(mounted, true)!.push(mounted);\n listen();\n observe(mounted);\n let mountedNow = true;\n return () => {\n if (!mountedNow) return;\n mountedNow = false;\n unobserve(mounted);\n removeFromStack(mounted);\n live.delete(mounted);\n };\n },\n subscribeWrites(listener) {\n mounted.writeListeners.add(listener);\n return () => mounted.writeListeners.delete(listener);\n },\n writes: () => mounted.writes,\n };\n },\n find(ref) {\n return (\n ref.key === undefined ? singles.get(ref.view) : keyed.get(ref.view)?.get(ref.key)\n )?.at(-1);\n },\n remember(mounted, values) {\n const target = mounted as Mounted;\n const entries = Object.entries(values).filter(\n ([field, value]) =>\n !target.writes.has(field) || !jsonEqual(target.writes.get(field), value),\n );\n if (entries.length === 0) return;\n replaceWrites(target, new Map([...target.writes, ...entries]));\n },\n async quietly(act) {\n quiet++;\n try {\n return await act();\n } finally {\n quiet--;\n }\n },\n placeFocus(element) {\n if (element.ownerDocument.activeElement === element) return;\n quiet++;\n try {\n element.focus({ preventScroll: true });\n } finally {\n quiet--;\n }\n agentFocus = element;\n },\n restore: (mounted, writes) => replaceWrites(mounted as Mounted, writes),\n *singles() {\n for (const stack of singles.values()) yield stack.at(-1)!;\n },\n *onScreenCandidates() {\n for (const [mounted, state] of visibility) {\n if (mounted.view.keyed && state !== \"out\") yield mounted;\n }\n },\n keyedCount: (viewName) => keyed.get(viewName)?.size ?? 0,\n isCurrent: (mounted) => stackOf(mounted as Mounted, false)?.at(-1) === mounted,\n owner,\n lastEntered: () => current(entered),\n lastInteracted: () => current(interacted),\n agentFocus: () => agentFocus,\n awaitingObserver: () => pending > 0 && observer !== undefined,\n observed() {\n if (pending === 0 || !observer) return Promise.resolve();\n return new Promise((resolve) => waiting.push(resolve));\n },\n resume() {\n listen();\n for (const mounted of live) if (!visibility.has(mounted)) observe(mounted);\n },\n dispose() {\n observer?.disconnect();\n observer = undefined;\n visibility.clear();\n agentFocus = undefined;\n pending = 0;\n settle();\n if (listening && env.document) {\n env.document.removeEventListener(\"pointerdown\", onInteraction, true);\n env.document.removeEventListener(\"focusin\", onInteraction, true);\n env.document.removeEventListener(\"keydown\", handBack, true);\n env.document.removeEventListener(\"input\", onInput);\n }\n listening = false;\n },\n };\n}\n\nexport function refOf(mounted: MountedView): ViewRef {\n return mounted.key === undefined\n ? { view: mounted.view.name }\n : { view: mounted.view.name, key: mounted.key };\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { QuerySnapshot, RouteSnapshot, ScreenSnapshot } from "../protocol.js";
2
+ import type { BrowserEnvironment } from "./environment.js";
3
+ import { type RegistryReader } from "./registry.js";
4
+ export interface ScreenLimits {
5
+ readonly totalBytes: number;
6
+ readonly valueBytes: number;
7
+ readonly itemsPerView: number;
8
+ readonly selectionChars: number;
9
+ }
10
+ export declare const DEFAULT_SCREEN_LIMITS: ScreenLimits;
11
+ export interface PageSources {
12
+ route(): RouteSnapshot | null;
13
+ queries(): readonly QuerySnapshot[];
14
+ }
15
+ export declare function measure(element: Element, env: BrowserEnvironment): {
16
+ shown: number;
17
+ score: number;
18
+ };
19
+ export declare function captureScreen(registry: RegistryReader, page: PageSources, env: BrowserEnvironment, limits: ScreenLimits): ScreenSnapshot;
20
+ //# sourceMappingURL=screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/client/screen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,aAAa,EAEb,aAAa,EACb,cAAc,EAKf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAY,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE1F,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,aAAa,GAAG,IAAI,CAAC;IAC9B,OAAO,IAAI,SAAS,aAAa,EAAE,CAAC;CACrC;AAWD,wBAAgB,OAAO,CACrB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,kBAAkB,GACtB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAclC;AA0ED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE,YAAY,GACnB,cAAc,CAmEhB"}
@@ -0,0 +1,196 @@
1
+ import { refOf } from "./registry.js";
2
+ export const DEFAULT_SCREEN_LIMITS = {
3
+ totalBytes: 64 * 1024,
4
+ valueBytes: 8 * 1024,
5
+ itemsPerView: 50,
6
+ selectionChars: 2000,
7
+ };
8
+ const encoder = new TextEncoder();
9
+ const bytesOf = (json) => encoder.encode(json).byteLength;
10
+ export function measure(element, env) {
11
+ const rect = env.rect(element);
12
+ const { width, height } = env.viewport();
13
+ const area = rect.width * rect.height;
14
+ if (area <= 0)
15
+ return { shown: 0, score: 0 };
16
+ const top = Math.max(rect.top, 0);
17
+ const bottom = Math.min(rect.bottom, height);
18
+ const visibleHeight = Math.max(0, bottom - top);
19
+ const visibleWidth = Math.max(0, Math.min(rect.right, width) - Math.max(rect.left, 0));
20
+ const shown = Math.round(((visibleHeight * visibleWidth) / area) * 100) / 100;
21
+ if (shown <= 0)
22
+ return { shown: 0, score: 0 };
23
+ const half = height / 2;
24
+ const distance = Math.abs((top + bottom) / 2 - half);
25
+ return { shown, score: shown * Math.max(0, 1 - distance / half) };
26
+ }
27
+ function valueOf(mounted, limit) {
28
+ const value = mounted.binding.get();
29
+ const bytes = bytesOf(JSON.stringify(value));
30
+ return bytes > limit ? { truncated: true, bytes } : value;
31
+ }
32
+ function documentOrder(a, b) {
33
+ const x = a.mounted.element;
34
+ const y = b.mounted.element;
35
+ if (!x || !y || x === y)
36
+ return 0;
37
+ return x.compareDocumentPosition(y) & 4 ? -1 : 1;
38
+ }
39
+ function labelOf(element) {
40
+ const aria = element.getAttribute("aria-label");
41
+ if (aria)
42
+ return aria;
43
+ const labels = element.labels;
44
+ const text = (labels?.[0] ?? element.closest("label"))?.textContent?.trim();
45
+ return text || undefined;
46
+ }
47
+ function focusOf(registry, env) {
48
+ const active = env.document?.activeElement;
49
+ if (!active ||
50
+ active === env.document?.body ||
51
+ active === env.document?.documentElement) {
52
+ return null;
53
+ }
54
+ const owner = registry.owner(active);
55
+ const field = active.getAttribute("name") ?? undefined;
56
+ const label = labelOf(active);
57
+ return {
58
+ ...(owner ? refOf(owner) : {}),
59
+ ...(field ? { field } : {}),
60
+ ...(label ? { label } : {}),
61
+ };
62
+ }
63
+ function selectionOf(registry, env, limit) {
64
+ const selection = env.document?.getSelection();
65
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0)
66
+ return null;
67
+ const owners = new Set(Array.from({ length: selection.rangeCount }, (_, index) => registry.owner(selection.getRangeAt(index).commonAncestorContainer)));
68
+ const [owner] = owners;
69
+ if (owners.size !== 1 || !owner)
70
+ return null;
71
+ const text = selection.toString();
72
+ if (!text)
73
+ return null;
74
+ return {
75
+ text: text.slice(0, limit),
76
+ within: refOf(owner),
77
+ truncated: text.length > limit,
78
+ };
79
+ }
80
+ function recent(moment, env) {
81
+ return moment
82
+ ? { ...refOf(moment.view), agoMs: Math.max(0, env.now() - moment.at) }
83
+ : null;
84
+ }
85
+ export function captureScreen(registry, page, env, limits) {
86
+ const views = {};
87
+ const singleShown = new Map();
88
+ let focal;
89
+ const consider = (candidate) => {
90
+ if (candidate.score > 0 && (!focal || candidate.score > focal.score))
91
+ focal = candidate;
92
+ };
93
+ for (const mounted of registry.singles()) {
94
+ const snapshot = { value: valueOf(mounted, limits.valueBytes) };
95
+ if (mounted.element) {
96
+ const measured = { mounted, ...measure(mounted.element, env) };
97
+ consider(measured);
98
+ singleShown.set(mounted.view.name, measured.shown);
99
+ views[mounted.view.name] = { ...snapshot, shown: measured.shown };
100
+ }
101
+ else {
102
+ views[mounted.view.name] = snapshot;
103
+ }
104
+ }
105
+ const onScreen = new Map();
106
+ for (const mounted of registry.onScreenCandidates()) {
107
+ if (!mounted.element || !registry.isCurrent(mounted))
108
+ continue;
109
+ const measured = { mounted, ...measure(mounted.element, env) };
110
+ if (measured.shown <= 0)
111
+ continue;
112
+ const list = onScreen.get(mounted.view.name) ?? [];
113
+ list.push(measured);
114
+ onScreen.set(mounted.view.name, list);
115
+ }
116
+ const keyed = new Map();
117
+ for (const [name, measured] of onScreen) {
118
+ measured.sort(documentOrder);
119
+ const listed = measured.slice(0, limits.itemsPerView);
120
+ for (const item of measured)
121
+ consider(item);
122
+ keyed.set(name, listed.map((item) => ({
123
+ key: item.mounted.key ?? "",
124
+ value: valueOf(item.mounted, limits.valueBytes),
125
+ shown: item.shown,
126
+ })));
127
+ }
128
+ const keyedView = (name, items) => ({
129
+ items,
130
+ offscreen: registry.keyedCount(name) - items.length,
131
+ });
132
+ for (const [name, items] of keyed)
133
+ views[name] = keyedView(name, items);
134
+ const attention = {
135
+ focus: focusOf(registry, env),
136
+ selection: selectionOf(registry, env, limits.selectionChars),
137
+ lastInteracted: recent(registry.lastInteracted(), env),
138
+ lastEntered: recent(registry.lastEntered(), env),
139
+ focal: focal ? refOf(focal.mounted) : null,
140
+ };
141
+ const snapshot = {
142
+ route: page.route(),
143
+ queries: page.queries(),
144
+ views,
145
+ attention,
146
+ truncated: false,
147
+ };
148
+ return fitBudget(snapshot, keyed, singleShown, keyedView, limits.totalBytes);
149
+ }
150
+ function fitBudget(snapshot, keyed, singleShown, keyedView, totalBytes) {
151
+ let size = bytesOf(JSON.stringify(snapshot));
152
+ if (size <= totalBytes)
153
+ return snapshot;
154
+ const views = { ...snapshot.views };
155
+ const byShown = [...keyed].flatMap(([name, items]) => items.map((item) => ({ name, item })));
156
+ byShown.sort((a, b) => a.item.shown - b.item.shown);
157
+ const dropped = new Set();
158
+ let next = 0;
159
+ while (size > totalBytes && next < byShown.length) {
160
+ const { item } = byShown[next++];
161
+ dropped.add(item);
162
+ size -= bytesOf(JSON.stringify(item)) + 1;
163
+ }
164
+ const withoutDropped = () => {
165
+ for (const [name, items] of keyed) {
166
+ views[name] = keyedView(name, items.filter((item) => !dropped.has(item)));
167
+ }
168
+ return { ...snapshot, views, truncated: true };
169
+ };
170
+ let fitted = withoutDropped();
171
+ const fits = () => bytesOf(JSON.stringify(fitted)) <= totalBytes;
172
+ while (!fits() && next < byShown.length) {
173
+ dropped.add(byShown[next++].item);
174
+ fitted = withoutDropped();
175
+ }
176
+ if (fits())
177
+ return fitted;
178
+ fitted = { ...fitted, queries: [] };
179
+ if (fits())
180
+ return fitted;
181
+ for (const name of [...singleShown.keys()].sort((a, b) => singleShown.get(a) - singleShown.get(b))) {
182
+ delete views[name];
183
+ if (fits())
184
+ return fitted;
185
+ }
186
+ for (const name of Object.keys(views))
187
+ delete views[name];
188
+ if (fits())
189
+ return fitted;
190
+ return {
191
+ ...fitted,
192
+ route: fitted.route?.notFound === false ? { ...fitted.route, search: {} } : fitted.route,
193
+ attention: { ...fitted.attention, selection: null },
194
+ };
195
+ }
196
+ //# sourceMappingURL=screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/client/screen.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AttentionSnapshot,\n FocusSnapshot,\n ItemSnapshot,\n JsonObject,\n QuerySnapshot,\n RecentRef,\n RouteSnapshot,\n ScreenSnapshot,\n SelectionSnapshot,\n SingleViewSnapshot,\n TruncatedValue,\n ViewSnapshot,\n} from \"../protocol.js\";\nimport type { BrowserEnvironment, RectLike } from \"./environment.js\";\nimport { type Moment, type MountedView, refOf, type RegistryReader } from \"./registry.js\";\n\nexport interface ScreenLimits {\n readonly totalBytes: number;\n readonly valueBytes: number;\n readonly itemsPerView: number;\n readonly selectionChars: number;\n}\n\nexport const DEFAULT_SCREEN_LIMITS: ScreenLimits = {\n totalBytes: 64 * 1024,\n valueBytes: 8 * 1024,\n itemsPerView: 50,\n selectionChars: 2000,\n};\n\nexport interface PageSources {\n route(): RouteSnapshot | null;\n queries(): readonly QuerySnapshot[];\n}\n\ninterface Measured {\n readonly mounted: MountedView;\n readonly shown: number;\n readonly score: number;\n}\n\nconst encoder = new TextEncoder();\nconst bytesOf = (json: string) => encoder.encode(json).byteLength;\n\nexport function measure(\n element: Element,\n env: BrowserEnvironment,\n): { shown: number; score: number } {\n const rect: RectLike = env.rect(element);\n const { width, height } = env.viewport();\n const area = rect.width * rect.height;\n if (area <= 0) return { shown: 0, score: 0 };\n const top = Math.max(rect.top, 0);\n const bottom = Math.min(rect.bottom, height);\n const visibleHeight = Math.max(0, bottom - top);\n const visibleWidth = Math.max(0, Math.min(rect.right, width) - Math.max(rect.left, 0));\n const shown = Math.round(((visibleHeight * visibleWidth) / area) * 100) / 100;\n if (shown <= 0) return { shown: 0, score: 0 };\n const half = height / 2;\n const distance = Math.abs((top + bottom) / 2 - half);\n return { shown, score: shown * Math.max(0, 1 - distance / half) };\n}\n\nfunction valueOf(mounted: MountedView, limit: number): JsonObject | TruncatedValue {\n const value = mounted.binding.get();\n const bytes = bytesOf(JSON.stringify(value));\n return bytes > limit ? { truncated: true, bytes } : value;\n}\n\nfunction documentOrder(a: Measured, b: Measured): number {\n const x = a.mounted.element;\n const y = b.mounted.element;\n if (!x || !y || x === y) return 0;\n return x.compareDocumentPosition(y) & 4 ? -1 : 1;\n}\n\nfunction labelOf(element: Element): string | undefined {\n const aria = element.getAttribute(\"aria-label\");\n if (aria) return aria;\n const labels = (element as HTMLInputElement).labels;\n const text = (labels?.[0] ?? element.closest(\"label\"))?.textContent?.trim();\n return text || undefined;\n}\n\nfunction focusOf(\n registry: RegistryReader,\n env: BrowserEnvironment,\n): FocusSnapshot | null {\n const active = env.document?.activeElement;\n if (\n !active ||\n active === env.document?.body ||\n active === env.document?.documentElement\n ) {\n return null;\n }\n const owner = registry.owner(active);\n const field = active.getAttribute(\"name\") ?? undefined;\n const label = labelOf(active);\n return {\n ...(owner ? refOf(owner) : {}),\n ...(field ? { field } : {}),\n ...(label ? { label } : {}),\n };\n}\n\nfunction selectionOf(\n registry: RegistryReader,\n env: BrowserEnvironment,\n limit: number,\n): SelectionSnapshot | null {\n const selection = env.document?.getSelection();\n if (!selection || selection.isCollapsed || selection.rangeCount === 0) return null;\n const owners = new Set(\n Array.from({ length: selection.rangeCount }, (_, index) =>\n registry.owner(selection.getRangeAt(index).commonAncestorContainer),\n ),\n );\n const [owner] = owners;\n if (owners.size !== 1 || !owner) return null;\n const text = selection.toString();\n if (!text) return null;\n return {\n text: text.slice(0, limit),\n within: refOf(owner),\n truncated: text.length > limit,\n };\n}\n\nfunction recent(moment: Moment | undefined, env: BrowserEnvironment): RecentRef | null {\n return moment\n ? { ...refOf(moment.view), agoMs: Math.max(0, env.now() - moment.at) }\n : null;\n}\n\nexport function captureScreen(\n registry: RegistryReader,\n page: PageSources,\n env: BrowserEnvironment,\n limits: ScreenLimits,\n): ScreenSnapshot {\n const views: Record<string, ViewSnapshot> = {};\n const singleShown = new Map<string, number>();\n let focal: Measured | undefined;\n const consider = (candidate: Measured) => {\n if (candidate.score > 0 && (!focal || candidate.score > focal.score))\n focal = candidate;\n };\n\n for (const mounted of registry.singles()) {\n const snapshot: SingleViewSnapshot = { value: valueOf(mounted, limits.valueBytes) };\n if (mounted.element) {\n const measured = { mounted, ...measure(mounted.element, env) };\n consider(measured);\n singleShown.set(mounted.view.name, measured.shown);\n views[mounted.view.name] = { ...snapshot, shown: measured.shown };\n } else {\n views[mounted.view.name] = snapshot;\n }\n }\n\n const onScreen = new Map<string, Measured[]>();\n for (const mounted of registry.onScreenCandidates()) {\n if (!mounted.element || !registry.isCurrent(mounted)) continue;\n const measured = { mounted, ...measure(mounted.element, env) };\n if (measured.shown <= 0) continue;\n const list = onScreen.get(mounted.view.name) ?? [];\n list.push(measured);\n onScreen.set(mounted.view.name, list);\n }\n const keyed = new Map<string, ItemSnapshot[]>();\n for (const [name, measured] of onScreen) {\n measured.sort(documentOrder);\n const listed = measured.slice(0, limits.itemsPerView);\n for (const item of measured) consider(item);\n keyed.set(\n name,\n listed.map((item) => ({\n key: item.mounted.key ?? \"\",\n value: valueOf(item.mounted, limits.valueBytes),\n shown: item.shown,\n })),\n );\n }\n\n const keyedView = (name: string, items: readonly ItemSnapshot[]) => ({\n items,\n offscreen: registry.keyedCount(name) - items.length,\n });\n for (const [name, items] of keyed) views[name] = keyedView(name, items);\n\n const attention: AttentionSnapshot = {\n focus: focusOf(registry, env),\n selection: selectionOf(registry, env, limits.selectionChars),\n lastInteracted: recent(registry.lastInteracted(), env),\n lastEntered: recent(registry.lastEntered(), env),\n focal: focal ? refOf(focal.mounted) : null,\n };\n\n const snapshot: ScreenSnapshot = {\n route: page.route(),\n queries: page.queries(),\n views,\n attention,\n truncated: false,\n };\n return fitBudget(snapshot, keyed, singleShown, keyedView, limits.totalBytes);\n}\n\nfunction fitBudget(\n snapshot: ScreenSnapshot,\n keyed: Map<string, ItemSnapshot[]>,\n singleShown: ReadonlyMap<string, number>,\n keyedView: (name: string, items: readonly ItemSnapshot[]) => ViewSnapshot,\n totalBytes: number,\n): ScreenSnapshot {\n let size = bytesOf(JSON.stringify(snapshot));\n if (size <= totalBytes) return snapshot;\n\n const views = { ...snapshot.views };\n const byShown = [...keyed].flatMap(([name, items]) =>\n items.map((item) => ({ name, item })),\n );\n byShown.sort((a, b) => a.item.shown - b.item.shown);\n const dropped = new Set<ItemSnapshot>();\n let next = 0;\n while (size > totalBytes && next < byShown.length) {\n const { item } = byShown[next++]!;\n dropped.add(item);\n size -= bytesOf(JSON.stringify(item)) + 1;\n }\n const withoutDropped = (): ScreenSnapshot => {\n for (const [name, items] of keyed) {\n views[name] = keyedView(\n name,\n items.filter((item) => !dropped.has(item)),\n );\n }\n return { ...snapshot, views, truncated: true };\n };\n let fitted = withoutDropped();\n const fits = () => bytesOf(JSON.stringify(fitted)) <= totalBytes;\n while (!fits() && next < byShown.length) {\n dropped.add(byShown[next++]!.item);\n fitted = withoutDropped();\n }\n if (fits()) return fitted;\n\n fitted = { ...fitted, queries: [] };\n if (fits()) return fitted;\n\n for (const name of [...singleShown.keys()].sort(\n (a, b) => singleShown.get(a)! - singleShown.get(b)!,\n )) {\n delete views[name];\n if (fits()) return fitted;\n }\n for (const name of Object.keys(views)) delete views[name];\n if (fits()) return fitted;\n return {\n ...fitted,\n route:\n fitted.route?.notFound === false ? { ...fitted.route, search: {} } : fitted.route,\n attention: { ...fitted.attention, selection: null },\n };\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { UiError, UiOpInput, UiOpResults } from "../protocol.js";
2
+ import type { BrowserEnvironment } from "./environment.js";
3
+ import { jsonEqual } from "./json-equal.js";
4
+ import type { AgentWrites, MountedView, ViewRegistryRuntime } from "./registry.js";
5
+ export { jsonEqual };
6
+ export declare function notMounted(input: {
7
+ view: string;
8
+ key?: string | undefined;
9
+ }): UiError;
10
+ export declare function userEditing(registry: Pick<ViewRegistryRuntime, "owner" | "agentFocus">, target: MountedView, changed: readonly string[], env: Pick<BrowserEnvironment, "document">): readonly string[];
11
+ export declare function agentFieldsOf(writes: AgentWrites, current: Readonly<Record<string, unknown>>): ReadonlySet<string>;
12
+ export declare function setView(registry: ViewRegistryRuntime, input: UiOpInput<"set">, env: Pick<BrowserEnvironment, "document">): Promise<UiOpResults["set"] | UiError>;
13
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/client/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEnF,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,OAAO,CAMrF;AAED,wBAAgB,WAAW,CACzB,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,YAAY,CAAC,EAC3D,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,GACxC,SAAS,MAAM,EAAE,CASnB;AAID,wBAAgB,aAAa,CAC3B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACzC,WAAW,CAAC,MAAM,CAAC,CAKrB;AA+BD,wBAAsB,OAAO,CAC3B,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EACvB,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,GACxC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAqDvC"}