# 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 - 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)