You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/audit/components/table.md

39 lines
4.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Audit: table
audit-version: 1
audited-at: 2026-06-26
scope:
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).
provider: src/uix/soma/components/table/table-provider.svelte.ts
reactivity (A31/A33/A35): ">ALERT A31 DUAL HAZARD: (1) Line 296: per-row $derived.by reading global selection state via getIsRowSelected() — O(N) re-runs when selection changes. (2) Line 518: per-row $derived.by calling getVisibleColumns().length — O(N*M) filter ops for N rows and M columns. Both are HIGH severity and should be lifted to provider-level caches with O(1) per-item lookups."
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: A31 (per-item derived calling filtering operation) — table-002 <!-- id: table-002 -->
- dimension: B (Behavior / Reactivity)
- rule: A31 (per-item derived calling filtering operation)
- location: src/uix/soma/components/table/table-provider.svelte.ts:518
- evidence: readonly colSpan = $derived.by(() => this.provider.table.getVisibleColumns().length);
In TableRowDetailProvider (per-row), calling getVisibleColumns() which internally filters resolvedColumns array. This runs on every row.
- impact: For a table with N rows and M columns, showing a row detail calls getVisibleColumns() which is O(M) filter operation. Total complexity becomes O(N*M). On a 50-row table with 10 columns, that's 500 filter operations just to compute colSpan.
- repro: Create a table with visibility toggling. Show row details and toggle column visibility. Monitor performance and observe the getVisibleColumns() is called per row on every visibility toggle.
- proposed-fix: Move `visibleColumns` to a table-level $derived and cache it: `const visibleColumns = $derived.by(() => resolvedColumns.filter(col => getIsColumnVisible(col.id)))`. Each RowDetail then accesses a single cached array and reads .length in O(1).
- verify: [downgraded] Confirmed the A31 pattern but HIGH overstated → MEDIUM. table-provider.svelte.ts:518 `readonly colSpan = $derived.by(() => this.provider.table.getVisibleColumns().length)` is a per-RowDetail derived calling a provider method (table-core.svelte.ts:532-534) `function getVisibleColumns() { return resolvedColumns.filter((col) => getIsColumnVisible(col.id)); }` — O(M) filter reading the global `columnVisibility` $state. It re-runs whenever `columnVisibility` is reassigned (toggleColumnVisibility line 537 `columnVisibility = { ...columnVisibility, [columnId]: ... }`). So the pattern IS real. But the candidate's O(N*M) and 'hang at 30+ rows' framing is wrong on two counts: (1) RowDetailProviders are mounted one-per-rendered-row, and the rendered set is PAGE-BOUNDED (default pageSize=10, table-core.svelte.ts:446) — not total row count N; (2) the trigger is a column-visibility toggle, a rare user action, not a per-keystroke / per-selection mutation. Real cost is O(pageSize × M) per infrequent toggle, with M (column count) small and the filter cheap. That is a legitimate micro-optimization (lift `visibleColumns` to a single table-level $derived so each RowDetail reads `.length` in O(1)) but not a behavior/scaling hazard users hit — MEDIUM, not HIGH.
- fix-status: open
## No-findings dimensions
A (Contract/Morfo), C (DOM-selector safety), D (Frontier / Eidos imports), E (TSC / Theming), A34 (require() topology)
## Theming facts (E-bis)
- magic z-index: Acceptable local z-index: 2 within sticky header subtree (line 190 of table.css) — local stacking context, not global
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: provider props mapping; row selection toggling; row detail open/close; sort state; pinned column styling
- untested: A31 quadratic hazard with large row counts; column visibility changes with row details; multi-row bulk operations (toggleAllSelection); sorting + detail interaction

Powered by TurnKey Linux.