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.
41 lines
4.6 KiB
41 lines
4.6 KiB
# Audit: pagination
|
|
audit-version: 1
|
|
audited-at: 2026-06-26
|
|
scope: (SCOPE-DRIFT → SYS-1)
|
|
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/pagination/pagination-provider.svelte.ts
|
|
reactivity (A31/A33/A35): CLEAN. Per-item isSelected (line 457-459) uses simple comparison operator, no global state read. Per-item isDisabled (line 461) reads provider.opts.disabled.current (direct ref access, O(1)). Per-item ariaLabel (line 463-468) calls soma.langs.t() but with local item value only. No A31 quadratic patterns detected. No A33 $state(Map/Set). No A35 per-item $effect loops. Clamping effect (lines 116-122
|
|
|
|
## Summary
|
|
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
|
|
|
|
## Findings
|
|
### MEDIUM: SYS-1 SCOPE-DRIFT: eidos recipe dir exists but morfo scope omits 'eidos' — pagination-001 <!-- id: pagination-001 -->
|
|
- dimension: Contract (morfo)
|
|
- rule: SYS-1 SCOPE-DRIFT: eidos recipe dir exists but morfo scope omits 'eidos'
|
|
- location: src/uix/morfo/components/pagination.ts:7
|
|
- evidence: Morfo declares scope: ['soma', 'sema'] but eidos directory exists at src/uix/eidos/components/pagination/ with README confirming pagination is 'el wrapper visual de Eidos sobre el primitivo Soma'.
|
|
- impact: Scope mismatch signals incomplete layer declaration. Eidos layer is real and should be listed in morfo scope.
|
|
- proposed-fix: Update morfo scope from ['soma', 'sema'] to ['soma', 'sema', 'eidos'] to match the existing eidos layer.
|
|
- verify: [confirmed] Confirmed SYS-1 scope-drift. src/uix/morfo/components/pagination.ts:7 declares `scope: ['soma', 'sema'],` but a complete eidos layer exists: src/uix/eidos/components/pagination/ contains pagination.css (4255 bytes), 7 Svelte wrappers, index.ts, types.ts, and a README, plus a recipe entry at src/uix/eidos/lib/recipes/base.ts:1715 (`pagination: {`). Peer components with an eidos layer include it in scope (accordion.ts:7 `scope: ['soma', 'sema', 'eidos']`, button.ts:47, checkbox.ts:7). The omission is a real layer-declaration drift. MEDIUM is correct.
|
|
- fix-status: fixed (212624e0)
|
|
|
|
## No-findings dimensions
|
|
Contract parts registration (2-of-3 rule verified: all 7 parts registered), Contract data/aria naming (data-{c}-{part} pattern used, no data-soma-* violations), Contract Morfo as const satisfies, Behavior A31 per-item O(N²) derived reading global state, Behavior A33 $state(new Map/Set) reactivity, Behavior A35/A36 per-item $effect ref-write loops, Behavior A30 id-registration via $effect, Behavior A6 cleanup (no Resource leaks detected), Behavior A18/A10/A14/A12 keyboard navigation (N/A: pagination uses snippet-based rendering, not nav), Behavior A34 require() topology (all triggers/items require parent Provider, DOM descendants), DOM-selector (no querySelector usage), Frontier eidos→soma import (correct), Frontier syncAttrs double-write (Provider and Ellipsis only, no conflicting props), Theming roles/tokens (recipe uses --space-*, --font-size-*, --radius-*, --opacity-*, --color-* role references), Tests environment (jsdom with @vitest-environment directive), Tests coverage (page ranges, clamping, navigation, disabled state all tested)
|
|
|
|
## Theming facts (E-bis)
|
|
- magic z-index: none
|
|
- magic literals: none
|
|
- undeclared parts: none
|
|
- roles clean: true · variants clean: true
|
|
|
|
## Tests (F)
|
|
- exists: true · env: jsdom (via @vitest-environment directive)
|
|
- covers: Page range computation with siblingCount=1, boundaryCount=1; Page clamping when value exceeds totalPages; Array slicing via .slice(); Navigation methods: goToNextPage, goToPrevPage, goToFirstPage, goToLastPage; Provider/trigger/item/ellipsis attrs through runtime; aria-current and aria-label projection; disabled state propagation (provider + item + trigger); onclick handlers for all triggers and items
|
|
- untested: Edge case: siblingCount/boundaryCount extremes (e.g., siblingCount > totalPages); Dynamic count/pageSize/siblingCount/boundaryCount changes post-init; onPageChange callback firing on page mutation
|
|
|
|
## Style observations (non-blocking)
|
|
- FirstTrigger and LastTrigger disable when already at boundaries (page 1 / last page), consistent with Ark UI pattern; intentional UX choice not a bug
|
|
- Page range algorithm uses Set deduplication before sort, efficient O(n log n) approach
|
|
- Comments accurately describe behavior (e.g., line 222 mentions hasPrevPage check is intentional)
|