explorbot 0.1.28 → 0.1.29

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/src/utils/aria.ts CHANGED
@@ -334,6 +334,54 @@ const detectRenames = (prev: FlatEntry[], curr: FlatEntry[], prevTotals: Map<str
334
334
  return { added, removed };
335
335
  };
336
336
 
337
+ // Interactive controls keep a stable role+name across a state flip; only an ARIA state
338
+ // attribute changes. Report those flips on their own line so the model always sees
339
+ // "now checked / now collapsed", in both directions, regardless of other page churn.
340
+ const STATE_WORDS: Record<string, { on: string; off: string }> = {
341
+ checked: { on: 'checked', off: 'unchecked' },
342
+ selected: { on: 'selected', off: 'unselected' },
343
+ pressed: { on: 'pressed', off: 'unpressed' },
344
+ expanded: { on: 'expanded', off: 'collapsed' },
345
+ };
346
+ const STATE_ATTRS = Object.keys(STATE_WORDS);
347
+
348
+ const stateWord = (attr: string, value: unknown): string => {
349
+ if (attr === 'checked' && value === 'mixed') return 'partially checked';
350
+ const words = STATE_WORDS[attr];
351
+ if (value === true || value === 'true') return words.on;
352
+ return words.off;
353
+ };
354
+
355
+ // Pair entries by path; when role and name match but a state attr differs, it's a toggle.
356
+ const detectToggles = (prev: FlatEntry[], curr: FlatEntry[]): { toggled: string[]; togglePaths: Set<string> } => {
357
+ const toggled: string[] = [];
358
+ const togglePaths = new Set<string>();
359
+ const currByPath = new Map(curr.map((e) => [e.path, e]));
360
+
361
+ for (const before of prev) {
362
+ const after = currByPath.get(before.path);
363
+ if (!after) continue;
364
+ if (before.entry.role !== after.entry.role) continue;
365
+ if (before.entry.name !== after.entry.name) continue;
366
+
367
+ const transitions: string[] = [];
368
+ for (const attr of STATE_ATTRS) {
369
+ const was = stateWord(attr, before.entry[attr]);
370
+ const now = stateWord(attr, after.entry[attr]);
371
+ if (was === now) continue;
372
+ transitions.push(`${was} -> ${now}`);
373
+ }
374
+ if (transitions.length === 0) continue;
375
+
376
+ togglePaths.add(before.path);
377
+ let label = String(after.entry.role);
378
+ const name = after.entry.name;
379
+ if (typeof name === 'string' && name.trim()) label += ` "${name.trim()}"`;
380
+ toggled.push(`${label}: ${transitions.join(', ')}`);
381
+ }
382
+ return { toggled, togglePaths };
383
+ };
384
+
337
385
  const TOP_DIFF_ITEMS = 10;
338
386
 
339
387
  const formatDiffSection = (label: string, items: string[]): string[] => {
@@ -358,9 +406,15 @@ const formatDiffSection = (label: string, items: string[]): string[] => {
358
406
  return lines;
359
407
  };
360
408
 
361
- const formatDiff = (added: string[], removed: string[]): string | null => {
362
- if (added.length === 0 && removed.length === 0) return null;
363
- return ['ariaDiff:', ...formatDiffSection('added', added), ...formatDiffSection('removed', removed)].join('\n');
409
+ const formatDiff = (added: string[], removed: string[], toggled: string[]): string | null => {
410
+ if (added.length === 0 && removed.length === 0 && toggled.length === 0) return null;
411
+ const sections = ['ariaDiff:'];
412
+ if (toggled.length > 0) {
413
+ sections.push(' toggled:');
414
+ for (const line of toggled) sections.push(` - ${line}`);
415
+ }
416
+ sections.push(...formatDiffSection('added', added), ...formatDiffSection('removed', removed));
417
+ return sections.join('\n');
364
418
  };
365
419
 
366
420
  // ─────────────────────────────────────────────────────────────────
@@ -437,13 +491,16 @@ export const diffAriaSnapshots = (previous: string | null, current: string | nul
437
491
  tree = dropEmpty(tree);
438
492
  return flatten(tree);
439
493
  };
440
- const prev = flat(previous);
441
- const curr = flat(current);
494
+ const prevAll = flat(previous);
495
+ const currAll = flat(current);
496
+ const { toggled, togglePaths } = detectToggles(prevAll, currAll);
497
+ const prev = prevAll.filter((e) => !togglePaths.has(e.path));
498
+ const curr = currAll.filter((e) => !togglePaths.has(e.path));
442
499
  const prevTotals = countBy(prev.map((e) => e.summary));
443
500
  const currTotals = countBy(curr.map((e) => e.summary));
444
501
  const byCount = diffByCount(prevTotals, currTotals);
445
502
  const renames = detectRenames(prev, curr, prevTotals, currTotals);
446
- return formatDiff([...byCount.added, ...renames.added], [...byCount.removed, ...renames.removed]);
503
+ return formatDiff([...byCount.added, ...renames.added], [...byCount.removed, ...renames.removed], toggled);
447
504
  };
448
505
 
449
506
  export const detectFocusArea = (snapshot: string | null): FocusAreaResult => {