Linear: https://linear.app/engineereddev/issue/ENG-261
What's in this PR (20 files, all under apps/web/; three omp commits):
- Attach: multiple-file picker, drop anywhere on Home with an overlay, paste of files and images; one push per file; the composer text goes with the first file. Attach, drop and paste are unavailable until files can actually be sent (
filesReady). - Size check against
limits.maxFileBytesusingciphertextSize, before any reservation; a rejected selection keeps the draft and names the limit. - Upload:
generateFileKey→POST /api/files→createFileEncryptorinto a Blob → XHR PUT with progress (direct: presigned URL + returned headers; proxy:/api/files/:id/contentwith the bearer) →POST /api/pusheswith the file payload. Cancel aborts before the push is created. - File rows and download: icon by MIME, name, size, Download with byte progress through
createFileDecryptor; any tag failure shows "This file was corrupted or tampered with" and saves nothing. - Harness:
startWebRuntime(prefix, { uploadMode, maxFileBytes })for per-test direct/proxy servers; readiness now waits for the server's "Server listening" event plus one health request within the existing 30 s deadline (no retries or timeout increases). - Tests: vitest (size limit, progress reducer, cancel, readiness gating, same-commit handler race); Playwright in both modes (SHA-256 round trips at 0 B / 1 B / 65,536 B / 5 MiB, over-limit rejected with no
POST /api/files, paste, cancel mid-upload, tamper, blocked-until-ready); axe and screenshots.
Verify (lead, rebased on 7a01948 with ENG-262 merged; conflicts in README.md and e2e/runtime.ts resolved keeping both sides):
mise run check:web→ exit 0 (126 tests, build OK)mise run e2e:web→ exit 0, twice in a row (42 passed, 42 passed)- Implementer: three consecutive
e2e:webruns green before the rebase.
Screenshots (not committed): file-{unavailable,uploading,item,tamper}-{light,dark,mobile}.png.
Deviations: none. Check-fix rounds: (1) the ENG-258 DevicesView fixture after the rebase; (2) the flaky readiness race found by the lead (Attach enabled before files could be sent; harness readiness under load).
🤖 Generated with Claude Code