method: adversarially-verified workflow (analyze → refute); HIGH lead-verified by direct read of the cited code. B5 ground-truth: the A31 O(N²) isSelected/isExpanded (.includes from a per-item $derived) is confirmed across listbox/grid-list/tree-view/tree-grid/tag-group (SYS-7); rovingTargetEl is correctly LIFTED everywhere (not A31); virtual-* use SvelteMap (A33-clean).
reactivity (A31/A33/A35): HAZARD A31 FOUND at tree-grid-provider.svelte.ts:578-579. Per-row isSelected and isExpanded $derived call provider.isSelected()/isExpanded() which read opts.value/expanded arrays via .includes(). When arrays change, all N rows re-derive and call O(N) array lookups → O(N²) total. Fix: provider.selectedSet/expandedSet with Set.has().
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 1 · MEDIUM 0 · LOW 0.
## Findings
### HIGH: A31 — per-item/per-row $derived calling provider method that reads global state — tree-grid-001 <!-- id: tree-grid-001 -->
- dimension: B, Reactivity
- rule: A31 — per-item/per-row $derived calling provider method that reads global state
- impact: O(N²) reactivity: when value[] or expanded[] arrays change, every row's isSelected and isExpanded deriveds re-run, each calling provider.isSelected()/isExpanded() which do .includes() lookups on those arrays. With 50+ rows and 10+ selections, this causes quadratic re-derives and array scans, manifesting as lag on selection/expansion.
- proposed-fix: Lift a Set on the provider: `readonly selectedSet = $derived(new Set(this.opts.value.current))` and `readonly expandedSet = $derived(new Set(this.opts.expanded.current))`. Each row then does O(1) `.has()` instead of O(N) `.includes()`. Per-item derived becomes `readonly isSelected = $derived.by(() => this.provider.selectedSet.has(this.opts.value.current))` → O(1) per row.
- verify: [confirmed] CONFIRMED A31 O(N²). tree-grid-provider.svelte.ts:578-579 per-row `readonly isSelected = $derived.by(() => this.provider.isSelected(this.opts.value.current));` and `readonly isExpanded = $derived.by(() => this.provider.isExpanded(this.opts.value.current));`. The provider methods read GLOBAL arrays via O(N) `.includes()`: line 188-190 `isSelected(value){ return this.opts.value.current.includes(value); }` and line 147-149 `isExpanded(value){ return this.opts.expanded.current.includes(value); }`. When the global `value[]` or `expanded[]` array mutates, every row's two deriveds re-run, each scanning the array → quadratic. NOT a lifted Set: the only Set in the file is the single provider-level `rovingTargetEl` (line 141), unrelated to selection/expansion. Matches the documented Listbox/Command A31 shape exactly. HIGH justified (two O(N) deriveds per row; degrades at 30+ rows on each selection/expansion). Fix as proposed: lift `selectedSet`/`expandedSet` `$derived(new Set(...))` on the provider and have rows do O(1) `.has()`.