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/file-upload.md

6.1 KiB

Audit: file-upload

audit-version: 1 audited-at: 2026-06-26 scope: SCOPE_DRIFT (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/file-upload/file-upload-provider.svelte.ts cleanup-audit (A6/A35/A36): A6 LISTENERS & CLEANUP VERIFIED: $effect (line 99-110): soma.dom.listen(doc, 'dragover') + listen(doc, 'drop') → cleanupDragOver() + cleanupDrop() returned in cleanup function ✓ $effect (line 637-647, ItemPreviewProvider): URL.createObjectURL() → URL.revokeObjectURL(url) returned in cleanup function

Summary

Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.

Findings

MEDIUM: SYS-1: morfo scope must include all layer dirs that exist — file-upload-001

  • dimension: Contract
  • rule: SYS-1: morfo scope must include all layer dirs that exist
  • location: src/uix/morfo/components/file-upload.ts:7
  • evidence: morfo declares scope: ['soma', 'sema'] but src/uix/eidos/components/file-upload/ directory exists with 13 .svelte files (file-upload.svelte, file-upload-dropzone.svelte, file-upload-item.svelte, etc.). Eidos layer is implemented but not declared in morfo scope.
  • impact: Scope mismatch signals incomplete morfo contract. Consumers expect morfo scope to list all layers present. This is a systemic architectural issue affecting component layering.
  • repro: grep -n 'scope:' src/uix/morfo/components/file-upload.ts; ls -d src/uix/eidos/components/file-upload
  • proposed-fix: Update line 7 from scope: ['soma', 'sema'] to scope: ['soma', 'sema', 'eidos']
  • verify: [confirmed] CONFIRMED. morfo src/uix/morfo/components/file-upload.ts:7 declares scope: ['soma', 'sema']. The eidos layer is fully implemented: src/uix/eidos/components/file-upload/ holds 13 .svelte wrappers + file-upload.css (foundation reading --file-upload-* recipe tokens, e.g. --_file-upload-gap: var(--file-upload-gap-md)) + types.ts + README.md, AND a recipe exists at src/uix/eidos/lib/recipes/base.ts:2369 ('file-upload': {...} with per-color solid/track tokens). Eidos is present but omitted from morfo scope → SYS-1 scope-drift. MEDIUM is correct per baseline. Note: the candidate's repro grep -n 'scope:' is fine, but a grep 'file-upload:' on base.ts misses the recipe because the key is tab-indented — the recipe DOES exist.
  • fix-status: open

No-findings dimensions

Behavior (A35/A36 loops: no per-item $effect reading opts.ref.current AND writing provider state; no microtask-mediated async without untrack), Behavior (A6 cleanup: all listeners via soma.dom.listen() return disposers; URL.createObjectURL/revokeObjectURL pair in $effect return), Behavior (A33: no $state(new Map/Set); A31: no O(N²) includes checks), Behavior (A30: hiddenInputId set directly in constructor line 444, not in $effect; hiddenInputRef via onRefChange callback line 440-442), Behavior (A15 GESTURE: no drag-drop/cropper gesture logic; dropzone uses native drag events with proper preventDefault), Behavior (LIVE REGIONS: signal-warn-reject dispatches with message; untilFix persistence documented line 191-194), Contract (2-of-3 rule: morfo + soma + eidos all present; all 13 morfo parts registered via runtime.part()), Contract (data naming: all data-* attributes are kebab-cased; no data-soma-* violations), Contract (morfo validation: 'as const satisfies Morfo' present; all aria and data attrs declared), Frontier (no soma->eidos imports; eidos correctly imports from soma), Frontier (no syncAttrs double-write; parts use renderProps() correctly), Theming (focus rings use --focus-ring-width and --focus-ring-color CSS vars; no hardcoded hex colors or magic px values; no z-index magic), DOM selectors (no querySelector with interpolated consumer values; no direct document/window; uses soma.dom.getDocument()), Passive roles (ItemProgress role='progressbar' declares no component-level events; correct), Archetype validation (Item archetype:'item' is correct; CSS does not assign cursor:pointer or interactive styling to display items), Tests (provider.svelte.test.ts covers rejection logic, maxFiles caps, dropzone interactions, item metadata, removal, progress, clear state; jsdom environment)

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: vitest/jsdom
  • covers: accept/reject logic (FILE_INVALID_TYPE, FILE_TOO_LARGE, FILE_TOO_SMALL, FILE_EXISTS, FILE_CUSTOM_ERROR); validation hook callback; maxFiles cap enforcement; disabled state propagation; required/invalid/empty data attrs; dropzone dragenter/dragover/dragleave/drop handlers; hidden input onchange binding; label for= linking; item name/size/progress display; item removal; clear trigger disabled when empty; progress clamping 0-100
  • untested: signal-warn-reject dispatch with live-region message (signal event routing not tested); preventDocumentDrop listener cleanup on unmount; ItemPreview URL.createObjectURL/revokeObjectURL lifecycle across file changes; paste event on dropzone; keyboard Enter/Space on dropzone (role='button'); multiple=false single-file replacement behavior

Style observations (non-blocking)

  • Signal-warn-reject event (line 210) correctly uses 'post' sequence and passes message for live-region announce per a11ySemantic.requiresPersistentTrace + reducedMotionFallback='text'
  • A30 pattern correctly applied: hiddenInputId set direct in constructor (line 444), hiddenInputRef via onRefChange callback (line 440-442) — no $effect overhead
  • untilFix persistence clearly documented (line 191-194): clearTarget(provider) on each addFiles call resets signal before evaluating new rejections
  • Item archetype correctly declared as 'item' (not 'option'/'interactive'); CSS treats item as display container (no cursor:pointer, interactive children are explicit buttons)

Powered by TurnKey Linux.