Skip to content

fix(print): make an editor-mode PDF export contain the document - #407

Merged
PathGao merged 1 commit into
fix/checked-reads-and-autosave-toastfrom
fix/editor-mode-pdf-export
Aug 3, 2026
Merged

fix(print): make an editor-mode PDF export contain the document#407
PathGao merged 1 commit into
fix/checked-reads-and-autosave-toastfrom
fix/editor-mode-pdf-export

Conversation

@PathGao

@PathGao PathGao commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

3 / 5 of a stack. Base: #406.

Exporting to PDF from plain edit mode produced a blank page. Two independent causes — and fixing either one alone is worse than fixing neither.

Cause 1: a specificity fight the print sheet cannot win

The print sheet hides the editor pane and restores the preview with .pane.viewer-pane { width: 100% !important }two classes. Svelte scopes the edit-mode collapse to:

.layout-container:not(.split).editing.svelte-xxx .viewer-pane:where(.svelte-xxx)

five. Both carry !important, so specificity decides and width: 0 wins. And opacity: 0 had no print reset at all, so even a width-only fix prints a correctly sized blank rectangle.

#app (the root in app.html) is the only handle a global sheet has that outranks a component's scoped selector.

Side effect worth noting: the old .pane.viewer-pane { flex: none !important } was also losing in reading mode, beaten by the same five-class flex: 1 !important. Harmless there, now correct everywhere.

Cause 2: the preview content is stale in edit mode

The preview-render effect runs on tab.isSplit || (isEditing && settings.showToc), so in plain edit mode with the outline closed, tab.content never updates.

Showing the pane without fixing this yields the document as it was before the edits — present, stale, and looking like it worked. That is worse than a blank page, which at least announces the failure.

syncPreviewForPrint() renders and awaits renderRichContent before printing. Skipped in reading mode — that DOM already came from loadMarkdown on the same buffer, and re-rendering would discard scroll, fold and find state — and skipped when the rendered content already matches. A failure toasts rather than silently exporting a stale document.

#382's print work is intact; exportFoldParity.test.ts still green.

Tests

scripts/editorPdfExport.test.ts resolves the cascade over the real compiled component CSS (svelte/compiler) plus styles.css, modelling #app, :where()/:not() specificity, and the pane's inline flex.

vs #406 7 red / 3 green → 10 / 10

The three that pass by design are premise anchors: edit mode still hides the pane on screen, the editor still stays off the page, and the effect condition is what the fix assumes.

npm run check   0 errors
npm test        452 / 452
cargo test      131 / 131

Not covered

  • Whether the printed page is legible — the test proves the pane is laid out and filled, not typeset well.
  • .markdown-container carries zoom: 1 in edit mode but zoomLevel/100 in reading mode, so a PDF from the editor is always 100% while one from reading mode inherits the user's zoom. Left alone.

🤖 Generated with Claude Code

Exporting to PDF from plain edit mode produced a blank page, from two
independent causes - and fixing either one alone is worse than fixing
neither.

The print sheet hides the editor pane and restores the preview with
`.pane.viewer-pane { width: 100% !important }`, two classes. Svelte scopes
the edit-mode collapse to
`.layout-container:not(.split).editing.svelte-xxx .viewer-pane:where(.svelte-xxx)`,
five. Both carry `!important`, so specificity decides and `width: 0` wins;
`opacity: 0` had no print reset at all, so even a width-only fix prints a
correctly sized blank rectangle. `#app` is the only handle a global sheet
has that outranks a component's scoped selector.

The preview-render effect runs on `tab.isSplit || (isEditing && showToc)`,
so in plain edit mode with the outline closed `tab.content` never updates.
Showing the pane without this yields the document as it was before the
edits - present, stale, and looking like it worked, which is worse than a
blank page.

`syncPreviewForPrint` renders and awaits `renderRichContent` before
printing, skipped in reading mode (that DOM already came from the same
buffer, and re-rendering would discard scroll, fold and find state) and
skipped when the rendered content already matches. A failure toasts rather
than silently exporting a stale document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao force-pushed the fix/editor-mode-pdf-export branch from e045222 to f8f8409 Compare August 2, 2026 23:11
@PathGao
PathGao merged commit 68feee2 into master Aug 3, 2026
5 checks passed
@PathGao
PathGao deleted the fix/editor-mode-pdf-export branch August 3, 2026 02:05
PathGao added a commit that referenced this pull request Aug 3, 2026
Leaving the editor re-read the file from disk, so a dirty tab had to
resolve a save decision - silently write, or answer a modal - before the
rendered view could appear. dayeggpi reported this in #168: "no way to
see rendered view until file is saved."

The prompt was never about losing data. Nothing is lost by switching
view mode; the buffer stays in memory either way. It existed because the
exit path called `loadMarkdown(tab.path)`, and reading the disk on a
dirty tab would show the wrong text. The untitled branch two lines below
already rendered the buffer instead - and #407 extracted
`renderTabPreviewFromRaw` for the print path, which is exactly the same
operation with a real path. Both exits now use it.

That also removes three problems the disk read carried: the exit took
the 50KB preview branch, so leaving the editor on a large file
re-truncated a complete buffer and refused saves until the background
read finished; `loadMarkdown` writes into the *active* tab, so
`toggleSplitView(tabId)` on a background tab would have yanked the
active one; and `if (!success) return` kept the tab in edit mode when
the write failed - which for a read-only file or a lossily decoded
buffer meant reading mode was permanently unreachable.

One segment of that block is kept, narrowed to `autoSave &&
!confirmBeforeSave`: the auto-save effect treats a tab as writable only
while `isEditing || isSplit`, and clears its pending timer otherwise, so
leaving edit mode drops the scheduled write. That flush is the last
chance before the window closes, not a condition of the switch.

Closing a tab and closing a window still ask. Those buffers are about to
cease to exist; this one is not. VS Code, Obsidian and Typora all render
the buffer, and none of them asks.

Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant