Files
coder/site
Michael Suchacz ac11ae52d1 fix(site/src/pages/AgentsPage): stop attachment downloads from trapping iOS PWAs (#27853)
On iOS, tapping an attachment download link inside the installed
(standalone) PWA hands the file to QuickLook, which renders on top of
the app with no reliable way back. Users had to force-quit the PWA to
recover.

## Approach

In iOS standalone mode, when the device supports file sharing, the
download click is intercepted and the fetched attachment is handed to
the native share sheet (`navigator.share` with a `File`), where the user
can pick Save to Files or any other target. This is the
platform-intended path for saving files from a PWA; no library covers it
(FileSaver.js is broken in iOS PWAs, and browser-fs-access falls back to
the same broken anchor path).

Everything else keeps native behavior:

- Non-iOS platforms and regular iOS Safari tabs keep the plain `<a
download>` anchor.
- iOS standalone devices without file sharing keep the anchor too.
- Inline `data:` attachments are decoded locally because the production
CSP (`connect-src 'self'`) blocks fetching them.

## Error model

Deliberately small: a share-sheet dismissal (`AbortError`) is silent, an
expired click gesture (`NotAllowedError`, which happens when the fetch
outlasts transient activation) shows a toast whose Save action retries
with a fresh gesture, and any other failure shows a plain error toast.

An earlier revision carried pending-state tracking, abort-signal
threading through unmount, and a blob-URL tab fallback; those guards
were removed on purpose to keep the change proportionate to the defect.

Also hardens chat file uploads slightly: filenames are sanitized before
upload and the file-picker `accept` attribute mirrors the server's
attachment allowlist.

## Testing

- Storybook interaction tests cover the share happy path and
native-anchor behavior; unit tests cover the intercept gate (including
iPadOS detection), failure toasts, retry action, and inline-data decode.
- Validated on an iPhone against a dev deployment: share sheet opens,
Save to Files works, and the app remains usable afterwards.

> Mux implemented this on Mike's behalf.

<!-- mux-attribution: model=claude-sonnet-4-5 thinking=high -->
2026-08-12 08:31:26 +02:00
..
…
…
…
…