# Audit: toast audit-version: 1 audited-at: 2026-06-26 scope: (SCOPE-DRIFT → SYS-1) method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations). provider: src/uix/soma/components/toast/toast-provider.svelte.ts cleanup-audit (A6/A35/A36): TIMERS: - autoDismissTimer (line 216): scheduled via soma.uix.timers.schedule(); cleared by clearTimer() (line 355-358); cleanup hook via $effect (line 328-332) returns disposer calling clearTimer(). LISTENERS (Hotkey — ToastViewportProvider): - keydown listener (line 148): cleanup via cleanupKeyd ## Summary Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0. ## Findings ### MEDIUM: SYS-1 (scope-drift) — toast-001 - dimension: A - rule: SYS-1 (scope-drift) - location: src/uix/morfo/components/toast.ts:8 - evidence: Morfo declares scope: ['soma', 'sema'], but eidos recipe exists at src/uix/eidos/components/toast/. Per framework rules, eidos must be in declared scope when recipe directory is present. - impact: Scope mismatch signals incomplete contract coverage or undeclared visual layer coupling. - proposed-fix: Add 'eidos' to morfo scope: scope: ['soma', 'sema', 'eidos'] - verify: [confirmed] CONFIRMED at src/uix/morfo/components/toast.ts:8 — `scope: ['soma', 'sema']` omits 'eidos' while a full eidos layer exists: recipe `toast: {` at src/uix/eidos/lib/recipes/base.ts:3545, plus src/uix/eidos/components/toast/ with toast.css (11466B), types.ts, and 9 part wrappers. The scope field per types.ts:799 declares 'Layers that implement this component', so omitting 'eidos' is genuine drift. Matches the SYS-1 baseline (MEDIUM). NOTE: confirmed SYSTEMIC — dialog/drawer/popover/toggle all likewise declare `scope: ['soma', 'sema']` while shipping eidos recipes+dirs; this is the documented split where some components (button/checkbox/accordion/context-menu) DO declare 'eidos'. Severity MEDIUM stands; it is a contract-coverage inconsistency, not a behavioral bug. - fix-status: open ## No-findings dimensions B (Behavior: A35/A36 loops clean — untrack present on present/announce trigger; A6 timer cleanup via .cancel() in clearTimer and $effect cleanup; A33 no $state(Map/Set); A31 no O(N²) derived patterns; A30 direct ID registration; A15 gesture properly uses setPointerCapture; A6/A34 hotkey listeners cleaned via this.cleanupHotkey in $effect), C (DOM: no unsafe querySelector interpolation; uses soma.dom.getDocument/getWindow), D (Frontier: soma does not import eidos; syncAttrs: true throughout; no double-write divergence), E (TSC: z-index uses --toast-toaster-z token; opacity literals 0/1 are canonical, no --opacity-* variants exist; focus-ring uses canonical tokens; spacing uses --space-* tokens), E-bis (Theming: only 9 roles used; all 6 intents correctly mapped to palettes; status is 'indicator' archetype, not interactive; no hardcoded :active or focus-ring), F (Tests: hotkey focus, timers with cancel, ARIA projection, promise lifecycle covered in separate test), G (Redundancy: no re-implemented gesture/registry/live-region) ## Theming facts (E-bis) - magic z-index: none - magic literals: closed-scale: 0.98 (defined as token) - undeclared parts: none - roles clean: true · variants clean: true ## Tests (F) - exists: true · env: jsdom - covers: Hotkey composite (AltLeft+F8) via listen cleanup; Per-item runtime ARIA projection (role, aria-live, aria-atomic, aria-labelledby, aria-describedby); Timer scheduling and cancel (pause/resume on pointerenter/leave); Dismiss action routing through runtime.trigger; Promise lifecycle (fulfill/threat paths with restartTimer); Max overflow routing through dismiss state - untested: Swipe gesture end-to-end (pointerdown/move/up sequence with threshold); Title/Description ID registration flow (titleId/descriptionId state tracking); Escape key delegation to runtime.keydown; Loading spinner state transitions ## Style observations (non-blocking) - Viewport uses fixed positioning with --toast-toaster-z (canonical z-scale). Item grid uses 3-column auto-collapse on status/close presence. Swipe state removes transition (none); cancel restores. Loading spinner uses animation: toast-spinner (pure CSS rotate). Intent palettes use color-mix for surface/border blends (90% overlay + intent-track). Focus ring on Action is canonical two-ring model. All spacing, font-size, color, radius route through tokens (no hardcoded px/rem literals except closed-offset: 10px, which is a designed constant). Transition durations and easing use canonical tokens.