Look at a file without downloading it first #191

Closed
opened 2026-09-02 03:19:06 +00:00 by jcoffey-dev · 0 comments
Owner

Files could only hand you the bytes — double-clicking a picture put it on disk and left you to go find it. The viewer already existed for mail attachments (MessageView.tsx: image, PDF, text); it was wired to one screen. It is now ui/filepreview.tsx, used by both.

What it needed before it was any use on Files

Type detection falls back to the file name. An upload carries whatever the browser guessed, and files.ts stores f.type || "application/octet-stream" — so the old exact-type check saw nothing to show in a .md that had just been uploaded. A specific type still wins over the extension: a .txt served as a zip is a zip.

Text is read with fetch, which ignores Content-Disposition — so Markdown previews even though the server will not serve it inline.

Two questions, kept apart

lib/preview.ts answers them separately, and says why in the file:

  • previewKind — can the app render it?
  • openableInTab — will the server hand it back inline? That mirrors isInlineSafe in server/src/app.ts:700, the actual security boundary. Navigating to a blob the server will not inline just starts a download, so the "open in a new tab" button has to ask this one. A test pins the two together.

SVG stays out of both. It carries script, the server already refuses to inline it, and deciding how to show one safely is its own question — it keeps downloading, exactly as before.

Printing

A picture or a text file prints from the dialog with everything else dropped. A PDF prints itself from its own iframe — the page around it cannot paginate someone else’s document — falling back to opening it in a tab if the viewer refuses.

Hiding .app was not enough: #root kept its height: 100% and printed a blank first page, the same trap as break-inside on a message card in #189.

Verified

Mock stack in a visible Chrome tab, uploading files through the real upload path:

result
notes.md (no MIME type) previews as text — the case that used to fail
shot.png renders in the dialog
context menu Preview above Download, only where there is something to show
mail attachments unchanged; caption and Download still there

Print paths by headless print-to-PDF against the real stylesheet: text preview 1 page, content from the top, no mail or file list behind it; image preview 1 page, unclipped. Before the #root fix it was 2 pages with the first blank.

typecheck clean, 587 web + 109 server tests pass, i18n:check clean, build clean.

Not in this

Rendered Markdown needs a dependency (the runtime list is deliberately 8 packages), so .md shows as source for now. Editing is v2 and deliberately absent — that is where conflict handling and quota churn live.

Merged 2026-09-01 as coffey-labs/ihasmail@c170b8554c

Rebuilt from: git history, session transcript.

Files could only hand you the bytes — double-clicking a picture put it on disk and left you to go find it. The viewer already existed for mail attachments (`MessageView.tsx`: image, PDF, text); it was wired to one screen. It is now `ui/filepreview.tsx`, used by both. ## What it needed before it was any use on Files **Type detection falls back to the file name.** An upload carries whatever the browser guessed, and `files.ts` stores `f.type || "application/octet-stream"` — so the old exact-type check saw nothing to show in a `.md` that had just been uploaded. A specific type still wins over the extension: a `.txt` served as a zip is a zip. **Text is read with `fetch`,** which ignores `Content-Disposition` — so Markdown previews even though the server will not serve it inline. ## Two questions, kept apart `lib/preview.ts` answers them separately, and says why in the file: - `previewKind` — can the app render it? - `openableInTab` — will the *server* hand it back inline? That mirrors `isInlineSafe` in `server/src/app.ts:700`, the actual security boundary. Navigating to a blob the server will not inline just starts a download, so the "open in a new tab" button has to ask this one. A test pins the two together. SVG stays out of both. It carries script, the server already refuses to inline it, and deciding how to show one safely is its own question — it keeps downloading, exactly as before. ## Printing A picture or a text file prints from the dialog with everything else dropped. A PDF prints itself from its own iframe — the page around it cannot paginate someone else’s document — falling back to opening it in a tab if the viewer refuses. Hiding `.app` was not enough: `#root` kept its `height: 100%` and printed a blank first page, the same trap as `break-inside` on a message card in #189. ## Verified Mock stack in a visible Chrome tab, uploading files through the real upload path: | | result | |---|---| | `notes.md` (no MIME type) | previews as text — the case that used to fail | | `shot.png` | renders in the dialog | | context menu | Preview above Download, only where there is something to show | | mail attachments | unchanged; caption and Download still there | Print paths by headless print-to-PDF against the real stylesheet: text preview 1 page, content from the top, no mail or file list behind it; image preview 1 page, unclipped. Before the `#root` fix it was 2 pages with the first blank. `typecheck` clean, 587 web + 109 server tests pass, `i18n:check` clean, build clean. ## Not in this Rendered Markdown needs a dependency (the runtime list is deliberately 8 packages), so `.md` shows as source for now. Editing is v2 and deliberately absent — that is where conflict handling and quota churn live. **Merged** 2026-09-01 as coffey-labs/ihasmail@c170b8554c9e <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.