Export
Three formats, one keystroke to repeat the last one. A self-contained HTML report that works on someone else's machine, Markdown for pull requests, and a unified patch straight to the clipboard.
The Export menu sits in the workspace toolbar, and appears once a comparison has finished.
| Format | Where it goes | Best for |
|---|---|---|
| HTML report | A file you choose | Sending a comparison to someone who does not have TwinScope |
| Markdown | A file you choose | Pasting into a pull request, a ticket or a commit message |
| Unified patch | Straight to the clipboard | Dropping a diff into a review comment |
⌘⇧E repeats the last format you used, which is what turns exporting into a reflex rather than a menu hunt. The first time, it produces the HTML report.
The HTML report is self-contained#
One file. Double-click it and it works — on any machine, offline, with nothing installed.
That is a stricter promise than "it opens in a browser", so it is worth listing what it means:
- No scripts. Not a bundle, not an inline handler, nothing.
- No network requests of any kind. No CDN stylesheet, no web font, no remote image, no analytics. The report's own footer says so, and a regression test asserts the file contains no
httporhttpsURL at all. - Styles are inlined, from a copy of the app's dark palette. A standalone file cannot import the app's stylesheet, so the palette is duplicated — and a test reads the app's tokens and fails if the copy ever drifts.
- Everything is escaped. Content from your files cannot become markup in the report.
- Images are embedded as
data:URLs. A blob URL is meaningless outside the window that made it, and this file has to show its images on someone else's machine. - Print styles. Printing or saving to PDF switches to a light background with legible add and delete shading, and avoids breaking a diff block across pages.
Every report explains itself#
Whatever the format, a report opens with the same context the workspace gives you: both paths, which engine ran, when it was generated, the counts — and the list of normalisation rules that were actually applied. The Markdown report also spells out the engine options it ran with, which is what makes it reproducible from the ticket it was pasted into.
That last part is the point of including it. A report that says "3 changes" without saying that array order was ignored and two paths were suppressed is a number without a method.
Each engine contributes its own body#
The header is shared; the body is whatever a reader of that comparison needs.
| Engine | HTML body | Markdown body |
|---|---|---|
| Text and code | Rows with line numbers, add/delete shading and word-level marks as highlighted spans | A fenced diff block, which renders with colour on most forges and pastes back as a patch |
| JSON | A table of path, change, before, after | The same table |
| Folders | A table of file, status, sizes and any rename note | The same table |
| Images | Before, after and the difference heatmap, embedded, plus a table of changed regions | The numbers and the region table — no images, since Markdown has nowhere to put them that does not depend on a file the recipient may not have |
| Binary | None — the report carries the summary and notes and says the engine has no renderer | The same |
The unified patch#
The patch format is a clipboard action rather than a file, because pasting a diff into a review comment is the whole use case. It is a --- / +++ header followed by the diff body, with the word-level markers stripped.
Saving, and finding it afterwards#
Choosing HTML or Markdown opens the system save dialog, pre-filled with a dated name:
twinscope-report-2026-08-13.html
twinscope-report-2026-08-13.mdCancel the dialog and nothing is written — no partial file, no empty file, no toast claiming success.
On a successful save a toast says Report saved with a Reveal button, which opens the containing folder with the file selected. The patch format's toast says it went to the clipboard instead, and offers no Reveal, because there is nothing on disk to reveal.