Repository navigation
feat: Copy as Rich Text — the rendered document, ready to paste - #44
Merged
Merged
Conversation
Export writes a file that carries its own stylesheet. A paste carries nothing: every destination throws away <style> blocks and class names, so the formatting has to ride on the elements. ⌥⌘C (File menu, ⌘K) puts the document on the clipboard as HTML with the styles inlined, and the markdown itself as the plain-text flavor. What travels is deliberately narrow — the things a paste target cannot infer. It already knows what a heading, a list, a link, and bold text are, and it will style them like the rest of its own document, which is what anyone pasting wants. It does not know that a table has hairline borders and columns proportioned the way a drag left them, that a fence is a tinted block of mono, that a quote is filled, or what color a keyword is. Those are inlined, from the same tokens the app styles with, resolved through Shiki's indirection to literal colors. Always the light palette, for the reason print gives: paper is paper even from a dark-mode machine, and a paste lands on someone else's white page. Three things a destination would otherwise lose: a disabled checkbox is inert markup most of them drop, so it becomes ☐/☑, which is text and survives anything; a relative image src means nothing once pasted, so local images travel as data URIs under a budget and fall back to file URLs past it; and class names are stripped, because `shiki` landing in a CMS that styles that name would mean something wrong. It works on the rendered HTML rather than the tree, because the highlighter's markup exists only in the output string — a plugin added after it still sees a bare <pre><code>. THE pipeline is untouched, and the golden fixtures with it.
help.md: both the Copy as Rich Text bullet and main's updated Reveal in Finder / path-menu bullet.
seamoss
added a commit
that referenced
this pull request
Sep 29, 2026
⌥⌘C puts the rendered document on the clipboard with its styles inlined and the markdown as the plain-text flavor — a rewrite of the one pipeline's output, never a second parser; each tab now remembers whether it was in preview or the editor, with no source flash on the way in; and foolscap://open?path=… lets other apps open a document, guarded to absolute paths of existing markdown files, with a cold-launch fix so a linked file stays the active tab after the session restores (#44–#46, Brandon Dohman @productengineered).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Export writes a file that carries its own stylesheet. A paste carries nothing — every destination throws away
<style>blocks and class names — so the formatting has to ride on the elements.⌥⌘C(File menu, next to Export; also⌘K) puts the document on the clipboard as HTML with the styles inlined, and the markdown itself as thetext/plainflavor.What travels, and what deliberately doesn't
A paste target already knows what a heading, a list, a link, and bold text are, and it will style them like the rest of its own document — which is what anyone pasting wants, so those carry nothing (headings keep the Fraunces stack and no size). What it cannot infer is inlined: table hairlines and header fill, columns proportioned the way a drag-to-resize left them (
<colgroup>fromcolumnDisplayWidth, the same function the preview sizes with), the fence's tinted mono, the quote's fill, the rule, and Shiki's token colors resolved through their palette indirection to literals.Every value comes from
tokens.css+themes/ledger.css— parsed,var()s resolved (balanced-paren scan, nested references followed), never a hardcoded hex. Always the light palette, for the reason print gives in export.css: paper is paper even from a dark-mode machine, and a paste lands on someone else's white page.Three things a destination would otherwise lose:
<input type="checkbox" disabled>is inert markup most destinations drop, taking the done/not-done with it. It becomes ☐ / ☑, which is text and survives anything.srcmeans nothing once pasted. Local images travel as data URIs (Docs and most CMSes accept and upload them) under an 8 MB budget, falling back to absolutefile:URLs past it.shikiortask-list-itemlanding in a CMS that styles those names would mean something wrong. Ids stay; footnote links are built on them.Why it works on the rendered HTML rather than the tree: the highlighter's markup — the spans this has to recolor — exists only in the output string. A rehype plugin registered after
rehypeShikiFromHighlighterstill sees a bare<pre><code>(verified both ways,lazyon and off). Sosrc/shared/markdown.tsis untouched, and the golden fixtures with it. The markup being rewritten is our own, from one generator, and the tag scan is quote-aware so a>in alt text can't end a tag early.Tests:
pnpm test337 (28 new) /pnpm typecheckclean. Note the unit tests inject the palette read from the sheets on disk, because vitest stubs CSS imports away — a?rawimport arrives as''there. That path is covered instead by a live check in an isolated instance: drive the palette entry, then read the clipboard's flavors back through the main process. 16 checks, all passing — both flavors present, the text flavor byte-identical to the source, novar(left anywhere (so the?rawsheets did reach the packaged build), the colgroup proportioned 22.222/77.778 from the delimiter row, the fence recolored, checkboxes glyphed, the PNG embedded as a data URI. The driver saves the user's clipboard first and puts it back.No
package.json/ version changes, per the usual.