A .md previewed as its own source, which is reading the punctuation rather than the notes. It opens rendered now, with Rendered | Source in the dialog footer for anyone who wants what the file actually says. Markdown only — a .txt has nothing to toggle between.
Rendering is marked (a 9th runtime dependency, which is why I asked first), sanitised by DOMPurify — the one already here for mail. Markdown is not a safe subset of anything: raw HTML passes through it by design, so a <script> in a file someone uploaded or shared into the account is a script tag unless something takes it out. Tests cover the script, onerror, iframe and javascript: cases.
Images become links, not pictures. An image in a Markdown file is either a relative path with no base to resolve against here, or a URL somewhere else that fetches on open and tells that server the file was read — the tracking pixel this app already blocks in mail. The link keeps the alt text and the address visible, so nothing vanishes silently and the reader chooses.
Also: the PDF preview never worked
Found while testing this. securityHeaders sets X-Frame-Options: DENY on every response, the blob route included, so the PDF iframe rendered Chrome’s "refused to connect" instead of the file — in Files today, and in mail attachments since long before #191.
The middleware now leaves a header a route has already set (the pattern it already uses for Cache-Control), and a PDF served inline sets SAMEORIGIN. Nothing else on the server is framable, and securityHeadersFor is exported so the rule is pinned by a test in both directions rather than only the happy one.
A `.md` previewed as its own source, which is reading the punctuation rather than the notes. It opens rendered now, with **Rendered | Source** in the dialog footer for anyone who wants what the file actually says. Markdown only — a `.txt` has nothing to toggle between.
Rendering is `marked` (a 9th runtime dependency, which is why I asked first), sanitised by DOMPurify — the one already here for mail. Markdown is not a safe subset of anything: raw HTML passes through it by design, so a `<script>` in a file someone uploaded or shared into the account is a script tag unless something takes it out. Tests cover the script, `onerror`, `iframe` and `javascript:` cases.
**Images become links, not pictures.** An image in a Markdown file is either a relative path with no base to resolve against here, or a URL somewhere else that fetches on open and tells that server the file was read — the tracking pixel this app already blocks in mail. The link keeps the alt text and the address visible, so nothing vanishes silently and the reader chooses.
## Also: the PDF preview never worked
Found while testing this. `securityHeaders` sets `X-Frame-Options: DENY` on **every** response, the blob route included, so the PDF iframe rendered Chrome’s "refused to connect" instead of the file — in Files today, and in mail attachments since long before #191.
The middleware now leaves a header a route has already set (the pattern it already uses for `Cache-Control`), and a PDF served inline sets `SAMEORIGIN`. Nothing else on the server is framable, and `securityHeadersFor` is exported so the rule is pinned by a test in both directions rather than only the happy one.
## Verified
Mock stack in a visible Chrome tab:
| | result |
|---|---|
| `rich.md` | opens rendered — headings, bold, inline code, table, blockquote, fenced code, nested lists |
| image in it | shows as a link, never fetched |
| Source toggle | flips to the raw file and back |
| a real PDF | renders in the viewer with thumbnails and zoom — this is the framing fix |
| the mock `report.pdf` | loads the viewer, which then rejects it: it is 14 bytes of fixture, not a PDF |
Print of rendered Markdown by headless print-to-PDF: 1 page, styling intact, nothing of the app around it.
`typecheck` clean, 596 web + 110 server tests, `i18n:check` clean, build clean.
**Merged** 2026-09-01 as coffey-labs/ihasmail@8d562628ff4e
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
A
.mdpreviewed as its own source, which is reading the punctuation rather than the notes. It opens rendered now, with Rendered | Source in the dialog footer for anyone who wants what the file actually says. Markdown only — a.txthas nothing to toggle between.Rendering is
marked(a 9th runtime dependency, which is why I asked first), sanitised by DOMPurify — the one already here for mail. Markdown is not a safe subset of anything: raw HTML passes through it by design, so a<script>in a file someone uploaded or shared into the account is a script tag unless something takes it out. Tests cover the script,onerror,iframeandjavascript:cases.Images become links, not pictures. An image in a Markdown file is either a relative path with no base to resolve against here, or a URL somewhere else that fetches on open and tells that server the file was read — the tracking pixel this app already blocks in mail. The link keeps the alt text and the address visible, so nothing vanishes silently and the reader chooses.
Also: the PDF preview never worked
Found while testing this.
securityHeaderssetsX-Frame-Options: DENYon every response, the blob route included, so the PDF iframe rendered Chrome’s "refused to connect" instead of the file — in Files today, and in mail attachments since long before #191.The middleware now leaves a header a route has already set (the pattern it already uses for
Cache-Control), and a PDF served inline setsSAMEORIGIN. Nothing else on the server is framable, andsecurityHeadersForis exported so the rule is pinned by a test in both directions rather than only the happy one.Verified
Mock stack in a visible Chrome tab:
rich.mdreport.pdfPrint of rendered Markdown by headless print-to-PDF: 1 page, styling intact, nothing of the app around it.
typecheckclean, 596 web + 110 server tests,i18n:checkclean, build clean.Merged 2026-09-01 as coffey-labs/ihasmail@8d562628ff
Rebuilt from: git history, session transcript.