@reticlehq/browser 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import type { ActionTarget } from '../dom/realm.js';
1
2
  interface ClickGeometry {
2
3
  occluded: boolean;
3
4
  occludedBy: string | null;
@@ -5,7 +6,7 @@ interface ClickGeometry {
5
6
  }
6
7
  export declare const NO_GEOMETRY: ClickGeometry;
7
8
  export declare function mouseEventFor(el: Element, type: string, init?: MouseEventInit): MouseEvent;
8
- export declare function fireClickSequence(el: HTMLElement, hold?: {
9
+ export declare function fireClickSequence(el: ActionTarget, hold?: {
9
10
  ms: number;
10
11
  sleep: (ms: number) => Promise<void>;
11
12
  now: () => number;
@@ -13,11 +14,11 @@ export declare function fireClickSequence(el: HTMLElement, hold?: {
13
14
  prevented: boolean;
14
15
  heldMs: number;
15
16
  }>;
16
- export declare function clickGeometry(el: HTMLElement): ClickGeometry;
17
+ export declare function clickGeometry(el: ActionTarget): ClickGeometry;
17
18
  export declare function firePointer(el: Element, type: string, relatedTarget?: EventTarget | null): void;
18
19
  export declare function firePointerNonBubbling(el: Element, type: string, relatedTarget?: EventTarget | null): void;
19
- export declare function dragElement(source: HTMLElement, target: HTMLElement | null, data: unknown): Promise<boolean>;
20
- export declare function fireTapSequence(el: HTMLElement, hold: {
20
+ export declare function dragElement(source: ActionTarget, target: ActionTarget | null, data: unknown): Promise<boolean>;
21
+ export declare function fireTapSequence(el: ActionTarget, hold: {
21
22
  ms: number;
22
23
  sleep: (ms: number) => Promise<void>;
23
24
  now: () => number;
@@ -1,3 +1,4 @@
1
+ import { type ActionTarget } from '../dom/realm.js';
1
2
  export declare function pressKey(args: Record<string, unknown>): string;
2
3
  export declare function pressModifiers(args: Record<string, unknown>): {
3
4
  metaKey: boolean;
@@ -9,6 +10,5 @@ export declare function pressCode(args: Record<string, unknown>, key: string): s
9
10
  export declare function focusedOrDocument(): HTMLElement;
10
11
  export declare function isReflessDocumentPress(ref: string, action: string, args: Record<string, unknown>): boolean;
11
12
  export declare function pressKeys(args: Record<string, unknown>): string[];
12
- export declare function holdKey(el: HTMLElement, key: string, code: string, mods: Record<string, boolean>, ms: number): Promise<void>;
13
- export declare function pressCombo(el: HTMLElement, keys: readonly string[], mods: Record<string, boolean>, holdMs: number): Promise<boolean>;
14
- export declare const ZOOM_NEEDS_REAL_BROWSER_MSG: string;
13
+ export declare function holdKey(el: ActionTarget, key: string, code: string, mods: Record<string, boolean>, ms: number): Promise<void>;
14
+ export declare function pressCombo(el: ActionTarget, keys: readonly string[], mods: Record<string, boolean>, holdMs: number): Promise<boolean>;
@@ -118,6 +118,3 @@ export async function pressCombo(el, keys, mods, holdMs) {
118
118
  }
119
119
  return prevented;
120
120
  }
121
- export const ZOOM_NEEDS_REAL_BROWSER_MSG = 'cannot zoom from inside the page — CSS zoom changes how it LOOKS without changing the layout ' +
122
- 'viewport, visualViewport or media queries, so a layout that breaks at 200% would be reported as ' +
123
- 'checked and passing. Drive a real browser (reticle drive <url>, or reticle_lease) and zoom there.';
@@ -5,9 +5,10 @@ import { dangerousActionContext, requiresDangerousConfirmation, submitControlFor
5
5
  import { assertEditable, assertNotRichText, setNativeValue } from './value-input.js';
6
6
  import { getAccessibleName, getRole, isVisible, getStates } from '../dom/a11y.js';
7
7
  import { elementHasHoverHandlers, identifyComponent } from '../registry/stores/adapters.js';
8
- import { isForm, isHtmlElement, isInput, isSelect, isTextArea } from '../dom/realm.js';
8
+ import { isActionTarget, isForm, isInput, isSelect, isTextArea, } from '../dom/realm.js';
9
9
  import { nativeSetTimeout, settle } from '../timers/native/native-timers.js';
10
10
  import { AppearedText } from './appeared-text.js';
11
+ import { alreadyAtCheckedState, assertAriaToggleLanded, isAriaToggle } from './check-state.js';
11
12
  import { focusedOrDocument, isReflessDocumentPress, pressCode, pressKey, pressModifiers, pressKeys, holdKey, pressCombo, } from './actions-press.js';
12
13
  function asString(value, fallback = '') {
13
14
  return 'string' === typeof value ? value : fallback;
@@ -16,8 +17,8 @@ function requireElement(ref) {
16
17
  const el = refs.resolve(ref);
17
18
  if (null === el)
18
19
  throw new Error(`ref '${echoRef(ref)}' no longer resolves to an element`);
19
- if (!isHtmlElement(el))
20
- throw new Error(`ref '${echoRef(ref)}' is not an HTMLElement`);
20
+ if (!isActionTarget(el))
21
+ throw new Error(`ref '${echoRef(ref)}' is not an HTML or SVG element`);
21
22
  return el;
22
23
  }
23
24
  function anchorOf(el) {
@@ -76,11 +77,6 @@ const CLICK_LIKE = new Set([
76
77
  ActionType.CHECK,
77
78
  ActionType.UNCHECK,
78
79
  ]);
79
- function alreadyAtCheckedState(el, action) {
80
- if (action !== ActionType.CHECK && action !== ActionType.UNCHECK)
81
- return false;
82
- return isInput(el) && el.checked === (action === ActionType.CHECK);
83
- }
84
80
  function dragTargetRef(args) {
85
81
  const named = asString(args['toRef']);
86
82
  return '' !== named ? named : asString(args['target']);
@@ -116,7 +112,7 @@ function assertActionAllowed(el, action, args) {
116
112
  const sourceDangerous = requiresDangerousConfirmation(dangerousActionContext(el), getRole(el)) ||
117
113
  (submitter !== null &&
118
114
  requiresDangerousConfirmation(dangerousActionContext(submitter), getRole(submitter)));
119
- const targetDangerous = isHtmlElement(dragTarget) &&
115
+ const targetDangerous = isActionTarget(dragTarget) &&
120
116
  requiresDangerousConfirmation(dangerousActionContext(dragTarget), getRole(dragTarget));
121
117
  if (canTrigger &&
122
118
  (sourceDangerous || targetDangerous) &&
@@ -244,11 +240,16 @@ async function dispatchOther(el, action, args) {
244
240
  throw new Error(`cannot select on a <${el.tagName.toLowerCase()}>`);
245
241
  case ActionType.CHECK:
246
242
  case ActionType.UNCHECK: {
247
- if (!isInput(el))
243
+ const aria = isAriaToggle(el);
244
+ if (!isInput(el) && !aria) {
248
245
  throw new Error(`cannot (un)check a <${el.tagName.toLowerCase()}>`);
246
+ }
249
247
  if (!enabledOf(el)) {
250
248
  throw new Error(`cannot ${action} a disabled control — a real user could not, so neither will Reticle`);
251
249
  }
250
+ if (!isInput(el)) {
251
+ return alreadyAtCheckedState(el, action) ? false : (await fireClickSequence(el)).prevented;
252
+ }
252
253
  if ('radio' === el.type && action === ActionType.UNCHECK) {
253
254
  throw new Error('cannot uncheck a radio button — a real user could not; select another radio in the group');
254
255
  }
@@ -324,7 +325,7 @@ async function dispatchOther(el, action, args) {
324
325
  if ('' === toRef)
325
326
  return await dragElement(el, null, args['data']);
326
327
  const resolved = refs.resolve(toRef);
327
- if (!isHtmlElement(resolved)) {
328
+ if (!isActionTarget(resolved)) {
328
329
  throw new Error(`drag target '${echoRef(toRef)}' did not resolve to an element — pass a ref from ` +
329
330
  'reticle_snapshot or reticle_query as args.toRef (alias: args.target)');
330
331
  }
@@ -380,6 +381,7 @@ export async function executeAction(ref, action, args = {}) {
380
381
  settleReason = outcome.settled ? null : SettleReason.TIMEOUT;
381
382
  obs.disconnect();
382
383
  }
384
+ assertAriaToggleLanded(el, action, alreadyAtValue);
383
385
  const valueAfter = valueOf(el);
384
386
  const nextFocus = activeRef(el);
385
387
  const effect = {
@@ -0,0 +1,5 @@
1
+ import { type ActionTarget } from '../dom/realm.js';
2
+ export declare const isAriaToggle: (el: Element) => boolean;
3
+ export declare const ariaChecked: (el: Element) => boolean;
4
+ export declare function assertAriaToggleLanded(el: Element, action: string, skipped: boolean): void;
5
+ export declare function alreadyAtCheckedState(el: ActionTarget, action: string): boolean;
@@ -0,0 +1,24 @@
1
+ import { ActionType } from '@reticlehq/core';
2
+ import { isInput } from '../dom/realm.js';
3
+ const ARIA_TOGGLE_ROLES = new Set(['checkbox', 'switch']);
4
+ const ARIA_CHECKED = 'aria-checked';
5
+ export const isAriaToggle = (el) => !isInput(el) && ARIA_TOGGLE_ROLES.has(el.getAttribute('role') ?? '');
6
+ export const ariaChecked = (el) => 'true' === el.getAttribute(ARIA_CHECKED);
7
+ export function assertAriaToggleLanded(el, action, skipped) {
8
+ if (skipped || !isAriaToggle(el))
9
+ return;
10
+ if (action !== ActionType.CHECK && action !== ActionType.UNCHECK)
11
+ return;
12
+ const wanted = action === ActionType.CHECK;
13
+ if (ariaChecked(el) === wanted)
14
+ return;
15
+ throw new Error(`${action} clicked the ${el.getAttribute('role') ?? ''} but ${ARIA_CHECKED} is still "${String(!wanted)}" — the app did not toggle it`);
16
+ }
17
+ export function alreadyAtCheckedState(el, action) {
18
+ if (action !== ActionType.CHECK && action !== ActionType.UNCHECK)
19
+ return false;
20
+ const wanted = action === ActionType.CHECK;
21
+ if (isInput(el))
22
+ return el.checked === wanted;
23
+ return isAriaToggle(el) && ariaChecked(el) === wanted;
24
+ }
@@ -1,3 +1,4 @@
1
- export declare function dangerousActionContext(el: HTMLElement): string;
2
- export declare function submitControlFor(el: HTMLElement): HTMLElement | null;
1
+ import { type ActionTarget } from '../dom/realm.js';
2
+ export declare function dangerousActionContext(el: ActionTarget): string;
3
+ export declare function submitControlFor(el: ActionTarget): HTMLElement | null;
3
4
  export declare function requiresDangerousConfirmation(text: string, role?: string): boolean;
@@ -1,5 +1,6 @@
1
1
  import { isDangerousActionText } from '@reticlehq/core';
2
2
  import { getAccessibleName } from '../dom/a11y.js';
3
+ import { isHtmlElement } from '../dom/realm.js';
3
4
  const LABEL_VALUED_INPUT_TYPES = new Set([
4
5
  'submit',
5
6
  'button',
@@ -7,7 +8,7 @@ const LABEL_VALUED_INPUT_TYPES = new Set([
7
8
  'image',
8
9
  ]);
9
10
  function holdsUserText(el) {
10
- if (el.isContentEditable)
11
+ if (isHtmlElement(el) && el.isContentEditable)
11
12
  return true;
12
13
  if (el instanceof HTMLTextAreaElement)
13
14
  return true;
@@ -1,3 +1,4 @@
1
+ import { type ActionTarget } from '../dom/realm.js';
1
2
  export declare function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): boolean;
2
- export declare function assertNotRichText(el: HTMLElement, action: string): void;
3
+ export declare function assertNotRichText(el: ActionTarget, action: string): void;
3
4
  export declare function assertEditable(el: HTMLInputElement | HTMLTextAreaElement, action: string): void;
@@ -1,5 +1,5 @@
1
1
  import { captureValueSetter } from '../patching/capture-method.js';
2
- import { valuePrototypeOf } from '../dom/realm.js';
2
+ import { isHtmlElement, valuePrototypeOf } from '../dom/realm.js';
3
3
  export function setNativeValue(el, value) {
4
4
  const proto = valuePrototypeOf(el);
5
5
  const setter = proto === undefined ? undefined : captureValueSetter(proto);
@@ -15,7 +15,7 @@ export function setNativeValue(el, value) {
15
15
  }
16
16
  function isRichText(el) {
17
17
  const flag = el.getAttribute('contenteditable');
18
- if (el.isContentEditable || (null !== flag && 'false' !== flag))
18
+ if ((isHtmlElement(el) && el.isContentEditable) || (null !== flag && 'false' !== flag))
19
19
  return true;
20
20
  if ('textbox' === el.getAttribute('role'))
21
21
  return true;
package/dist/dom/a11y.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { ElementState, REDACTED_VALUE } from '@reticlehq/core';
2
- import { isButton, isHtmlElement, isImage, isInput, isSelect, isTextArea } from './realm.js';
2
+ import { isButton, isHtmlElement, isImage, isInput, isMeter, isOutput, isProgress, isSelect, isTextArea, } from './realm.js';
3
3
  import { refs } from './addressing/refs.js';
4
4
  import { inspectChart } from './chart.js';
5
5
  import { isSensitiveKey } from '../security/serialization.js';
6
6
  import { formatSource, sourceFromDom } from './addressing/source.js';
7
+ const HTML_DETAILS_TAG = 'details';
8
+ const HTML_DETAILS_OPEN_ATTRIBUTE = 'open';
9
+ const HTML_DIRECT_SUMMARY_SELECTOR = ':scope > summary';
7
10
  const NAME_FROM_CONTENT = new Set([
8
11
  'button',
9
12
  'link',
@@ -190,7 +193,14 @@ export function getAccessibleName(el) {
190
193
  if (alt !== null)
191
194
  return alt.trim();
192
195
  }
193
- if (isInput(el) || isTextArea(el) || isSelect(el)) {
196
+ const isLabelable = isInput(el) ||
197
+ isTextArea(el) ||
198
+ isSelect(el) ||
199
+ isButton(el) ||
200
+ isMeter(el) ||
201
+ isOutput(el) ||
202
+ isProgress(el);
203
+ if (isLabelable) {
194
204
  const labels = el.labels;
195
205
  if (labels !== null && labels.length > 0) {
196
206
  const text = [...labels]
@@ -200,6 +210,16 @@ export function getAccessibleName(el) {
200
210
  if (text.length > 0)
201
211
  return text;
202
212
  }
213
+ }
214
+ if ('fieldset' === el.tagName.toLowerCase()) {
215
+ const legend = [...el.children].find((child) => 'legend' === child.tagName.toLowerCase());
216
+ if (legend !== undefined) {
217
+ const text = collapse(textWithoutHidden(legend));
218
+ if (text.length > 0)
219
+ return text;
220
+ }
221
+ }
222
+ if (isInput(el) || isTextArea(el) || isSelect(el)) {
203
223
  if (isInput(el)) {
204
224
  const type = el.type.toLowerCase();
205
225
  if ('submit' === type || 'button' === type || 'reset' === type) {
@@ -241,6 +261,8 @@ export function getStates(el, visible = isVisible(el)) {
241
261
  states.push(ElementState.CHECKED);
242
262
  if (true === ariaBool(el, 'aria-expanded'))
243
263
  states.push(ElementState.EXPANDED);
264
+ if (true === ariaBool(el, 'aria-pressed'))
265
+ states.push(ElementState.PRESSED);
244
266
  if (el.ownerDocument.activeElement === el)
245
267
  states.push(ElementState.FOCUSED);
246
268
  return states;
@@ -270,11 +292,23 @@ export function getValue(el) {
270
292
  const valueNow = el.getAttribute('aria-valuenow');
271
293
  return valueNow ?? undefined;
272
294
  }
295
+ function hiddenInsideClosedDetails(el) {
296
+ const parent = el.parentElement;
297
+ if (null === parent)
298
+ return false;
299
+ const details = parent.closest(HTML_DETAILS_TAG);
300
+ if (null === details || details.hasAttribute(HTML_DETAILS_OPEN_ATTRIBUTE))
301
+ return false;
302
+ const summary = details.querySelector(HTML_DIRECT_SUMMARY_SELECTOR);
303
+ return null === summary || !summary.contains(el);
304
+ }
273
305
  function selfHidden(el) {
274
306
  if ('true' === el.getAttribute('aria-hidden'))
275
307
  return true;
276
308
  if (isHtmlElement(el) && el.hidden)
277
309
  return true;
310
+ if (hiddenInsideClosedDetails(el))
311
+ return true;
278
312
  const view = el.ownerDocument.defaultView;
279
313
  if (view !== null) {
280
314
  const style = view.getComputedStyle(el);
@@ -288,6 +322,12 @@ function selfHidden(el) {
288
322
  }
289
323
  return false;
290
324
  }
325
+ function parentAcrossShadowBoundary(el) {
326
+ if (null !== el.parentElement)
327
+ return el.parentElement;
328
+ const host = el.getRootNode().host;
329
+ return host ?? null;
330
+ }
291
331
  export function isInViewport(el, memo) {
292
332
  if (!isVisible(el, memo))
293
333
  return false;
@@ -305,7 +345,7 @@ export function isVisible(el, memo) {
305
345
  const cached = memo?.get(el);
306
346
  if (cached !== undefined)
307
347
  return cached;
308
- const parent = el.parentElement;
348
+ const parent = parentAcrossShadowBoundary(el);
309
349
  const result = !selfHidden(el) && (null === parent || isVisible(parent, memo));
310
350
  if (memo !== undefined)
311
351
  memo.set(el, result);
@@ -1,10 +1,16 @@
1
1
  export declare const isElement: (n: unknown) => n is Element;
2
2
  export declare const isImage: (n: unknown) => n is HTMLImageElement;
3
3
  export declare const isHtmlElement: (n: unknown) => n is HTMLElement;
4
+ export declare const isSvgElement: (n: unknown) => n is SVGElement;
5
+ export type ActionTarget = HTMLElement | SVGElement;
6
+ export declare const isActionTarget: (n: unknown) => n is ActionTarget;
4
7
  export declare const isInput: (n: unknown) => n is HTMLInputElement;
5
8
  export declare const isTextArea: (n: unknown) => n is HTMLTextAreaElement;
6
9
  export declare const isSelect: (n: unknown) => n is HTMLSelectElement;
7
10
  export declare const isButton: (n: unknown) => n is HTMLButtonElement;
11
+ export declare const isMeter: (n: unknown) => n is HTMLMeterElement;
12
+ export declare const isOutput: (n: unknown) => n is HTMLOutputElement;
13
+ export declare const isProgress: (n: unknown) => n is HTMLProgressElement;
8
14
  export declare const isForm: (n: unknown) => n is HTMLFormElement;
9
15
  export declare const isFrame: (n: unknown) => n is HTMLIFrameElement;
10
16
  export declare function valuePrototypeOf(el: Element): object | undefined;
package/dist/dom/realm.js CHANGED
@@ -14,10 +14,15 @@ function isIn(node, name, ambient) {
14
14
  export const isElement = (n) => isIn(n, 'Element', 'undefined' === typeof Element ? undefined : Element);
15
15
  export const isImage = (n) => isIn(n, 'HTMLImageElement', 'undefined' === typeof HTMLImageElement ? undefined : HTMLImageElement);
16
16
  export const isHtmlElement = (n) => isIn(n, 'HTMLElement', 'undefined' === typeof HTMLElement ? undefined : HTMLElement);
17
+ export const isSvgElement = (n) => isIn(n, 'SVGElement', 'undefined' === typeof SVGElement ? undefined : SVGElement);
18
+ export const isActionTarget = (n) => isHtmlElement(n) || isSvgElement(n);
17
19
  export const isInput = (n) => isIn(n, 'HTMLInputElement', 'undefined' === typeof HTMLInputElement ? undefined : HTMLInputElement);
18
20
  export const isTextArea = (n) => isIn(n, 'HTMLTextAreaElement', 'undefined' === typeof HTMLTextAreaElement ? undefined : HTMLTextAreaElement);
19
21
  export const isSelect = (n) => isIn(n, 'HTMLSelectElement', 'undefined' === typeof HTMLSelectElement ? undefined : HTMLSelectElement);
20
22
  export const isButton = (n) => isIn(n, 'HTMLButtonElement', 'undefined' === typeof HTMLButtonElement ? undefined : HTMLButtonElement);
23
+ export const isMeter = (n) => isIn(n, 'HTMLMeterElement', 'undefined' === typeof HTMLMeterElement ? undefined : HTMLMeterElement);
24
+ export const isOutput = (n) => isIn(n, 'HTMLOutputElement', 'undefined' === typeof HTMLOutputElement ? undefined : HTMLOutputElement);
25
+ export const isProgress = (n) => isIn(n, 'HTMLProgressElement', 'undefined' === typeof HTMLProgressElement ? undefined : HTMLProgressElement);
21
26
  export const isForm = (n) => isIn(n, 'HTMLFormElement', 'undefined' === typeof HTMLFormElement ? undefined : HTMLFormElement);
22
27
  export const isFrame = (n) => isIn(n, 'HTMLIFrameElement', 'undefined' === typeof HTMLIFrameElement ? undefined : HTMLIFrameElement);
23
28
  export function valuePrototypeOf(el) {
@@ -54,6 +54,7 @@ function stateSuffix(el) {
54
54
  const states = getStates(el).filter((s) => s === ElementState.DISABLED ||
55
55
  s === ElementState.CHECKED ||
56
56
  s === ElementState.EXPANDED ||
57
+ s === ElementState.PRESSED ||
57
58
  s === ElementState.FOCUSED);
58
59
  return states.length > 0 ? ` [${states.join(',')}]` : '';
59
60
  }
@@ -142,9 +143,14 @@ function visit(child, depth, ctx, inLive) {
142
143
  }
143
144
  }
144
145
  function walk(parent, depth, ctx, inLive = false) {
145
- if (depth > ctx.maxDepth)
146
- return;
147
146
  const children = pierceChildren(parent);
147
+ if (depth > ctx.maxDepth) {
148
+ if (children.length > 0) {
149
+ ctx.truncated = true;
150
+ recordUnread([parent], ctx);
151
+ }
152
+ return;
153
+ }
148
154
  for (let index = 0; index < children.length; index += 1) {
149
155
  if (ctx.nodes >= ctx.maxNodes) {
150
156
  ctx.truncated = true;
@@ -1,4 +1,4 @@
1
- import { BlindSpotKind, EventType, REQUEST_SHAPE_FIELD, REQUEST_SHAPE_NONE, RETICLE_WS_PATH, StreamTransport, StreamDirection, fnv1a, } from '@reticlehq/core';
1
+ import { BlindSpotKind, EventType, NEXT_ACTION_FIELD, NEXT_ACTION_HEADER, REQUEST_SHAPE_FIELD, REQUEST_SHAPE_NONE, RETICLE_WS_PATH, StreamTransport, StreamDirection, fnv1a, } from '@reticlehq/core';
2
2
  import { captureMethod } from '../patching/capture-method.js';
3
3
  import { observeSafely, observeValue } from './types.js';
4
4
  import { isCapturableType, projectBody, projectErrorBody, shouldCaptureErrorBody, withBodyDeadline, } from './net-detail/network-body.js';
@@ -120,6 +120,15 @@ function methodOf(input, init) {
120
120
  return input.method.toUpperCase();
121
121
  return 'GET';
122
122
  }
123
+ function nextActionOf(input, init) {
124
+ if (init !== undefined && init.headers !== undefined) {
125
+ return observeValue(() => new Headers(init.headers).get(NEXT_ACTION_HEADER)) ?? undefined;
126
+ }
127
+ if (input instanceof Request) {
128
+ return observeValue(() => input.headers.get(NEXT_ACTION_HEADER)) ?? undefined;
129
+ }
130
+ return undefined;
131
+ }
123
132
  const NON_APP_FRAME = /@reticlehq|reticle\.ts|network\.ts|transport\.ts|<anonymous>|new Promise|node:internal/i;
124
133
  const DEPENDENCY_FRAME = /node_modules|\/\.vite\/deps\//i;
125
134
  export function firstAppFrame(stack) {
@@ -191,6 +200,8 @@ export function installNetwork(emit, opts = {}) {
191
200
  const id = nextId();
192
201
  const start = performance.now();
193
202
  const method = methodOf(input, init);
203
+ const nextAction = nextActionOf(input, init);
204
+ const nextActionFields = nextAction === undefined ? {} : { [NEXT_ACTION_FIELD]: nextAction };
194
205
  const urlFields = netUrlFields(rawUrl);
195
206
  const url = urlFields.url;
196
207
  const initiatorStack = observeValue(() => initiatorFrame());
@@ -226,6 +237,7 @@ export function installNetwork(emit, opts = {}) {
226
237
  ...resourceTiming(rawUrl),
227
238
  ...netResponseMeta(res.statusText, contentType, res.headers.get('content-length')),
228
239
  ...projectRequestBody(init?.body, captureBodies),
240
+ ...nextActionFields,
229
241
  ...responseBodyFields,
230
242
  ...(reinterpret?.(url, (name) => res.headers.get(name)) ?? {}),
231
243
  });
@@ -282,6 +294,7 @@ export function installNetwork(emit, opts = {}) {
282
294
  durationMs: Math.round(performance.now() - start),
283
295
  initiator: 'fetch',
284
296
  ...initiatorFields,
297
+ ...nextActionFields,
285
298
  });
286
299
  });
287
300
  throw error;
@@ -54,7 +54,10 @@ export function installStoreState(emit) {
54
54
  for (const change of changes) {
55
55
  const value = project(change.path, change.new);
56
56
  const old = project(change.path, change.old);
57
- if (!isSensitiveKey(change.path) && samePresentedValue(old, value))
57
+ const same = isSensitiveKey(change.path)
58
+ ? samePresentedValue(sanitizeForTransport(change.old), sanitizeForTransport(change.new))
59
+ : samePresentedValue(old, value);
60
+ if (same)
58
61
  continue;
59
62
  observeSafely(() => emit(EventType.STATE_CHANGE, { name, path: change.path, value, old }));
60
63
  }
@@ -1,5 +1,35 @@
1
1
  import { nativeWarn } from '../timers/console/native-console.js';
2
2
  import { markAdapterSource } from './stores.js';
3
+ function snapshotLive(value) {
4
+ try {
5
+ return copyPlain(value, new WeakMap());
6
+ }
7
+ catch {
8
+ return value;
9
+ }
10
+ }
11
+ function copyPlain(value, seen) {
12
+ if ('object' !== typeof value || null === value)
13
+ return value;
14
+ const done = seen.get(value);
15
+ if (done !== undefined)
16
+ return done;
17
+ if (Array.isArray(value)) {
18
+ const out = [];
19
+ seen.set(value, out);
20
+ for (const item of value)
21
+ out.push(copyPlain(item, seen));
22
+ return out;
23
+ }
24
+ const proto = Object.getPrototypeOf(value);
25
+ if (proto !== Object.prototype && proto !== null)
26
+ return value;
27
+ const out = {};
28
+ seen.set(value, out);
29
+ for (const [key, item] of Object.entries(value))
30
+ out[key] = copyPlain(item, seen);
31
+ return out;
32
+ }
3
33
  export function tanstackQueryStore(client) {
4
34
  const store = {
5
35
  getState: () => {
@@ -106,7 +136,7 @@ export function svelteStore(readable, warn = nativeWarn) {
106
136
  let value;
107
137
  let called = false;
108
138
  stopSubscription(readable.subscribe((next) => {
109
- value = next;
139
+ value = snapshotLive(next);
110
140
  called = true;
111
141
  }));
112
142
  if (!called && !warnedAboutLazyStore) {
@@ -132,7 +162,7 @@ export function svelteStore(readable, warn = nativeWarn) {
132
162
  const PINIA_SUBSCRIBE_OPTIONS = { detached: true, flush: 'sync' };
133
163
  export function piniaStore(store) {
134
164
  return {
135
- getState: () => store.$state,
165
+ getState: () => snapshotLive(store.$state),
136
166
  subscribe: (listener) => store.$subscribe(() => listener(), { ...PINIA_SUBSCRIBE_OPTIONS }),
137
167
  };
138
168
  }
@@ -6,8 +6,14 @@ export function registerAdapter(adapter) {
6
6
  }
7
7
  export function identifyComponent(el) {
8
8
  for (const adapter of adapters) {
9
- const info = adapter.identify(el);
10
- if (info !== null)
9
+ let info;
10
+ try {
11
+ info = adapter.identify(el);
12
+ }
13
+ catch {
14
+ continue;
15
+ }
16
+ if (info !== null && info !== undefined)
11
17
  return info;
12
18
  }
13
19
  return null;