Opening a book card swallowed failures: a corrupt or unreadable file
failed silently and normalizeBook could throw on a missing chapters
array. Surface load errors as a toast, throw a clear message on
unparseable JSON, and make normalizeBook tolerant of missing chapters,
metadata and settings.
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
Typst compile warnings were discarded. Capture them via the Warned
result and emit a pdf-warnings event (only from the export path, not
the live preview) that App shows as a non-fatal toast; the export still
succeeds. Also scan book text before reporting success and warn when it
contains scripts the embedded Latin fonts cannot render (CJK, Arabic,
Hebrew, Devanagari, Thai, emoji).
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
Four export-correctness fixes intermingled across the Typst and EPUB
renderers:
- H7: neutralize newlines in escaped text and apply leading-marker
escaping (= - + / and N.) to the first text node of every block
(paragraph, heading, list item, blockquote) and figure captions, even
when that run carries a mark, so author text can never inject Typst
markup or phantom headings/TOC entries.
- H9: render lists recursively so nested lists survive and multi-
paragraph list items keep their paragraph breaks (Typst functional
list()/enum(); EPUB nested <ul>/<ol> and multiple <p> in <li>).
- H10: render strike, underline and inline code marks in both exporters
(Typst #strike/#underline/#raw; EPUB <s>/<u>/<code> with css).
- H8b: normalize +-bearing image subtypes so an imported svg+xml cover
becomes cover.svg with the correct media type.
Validated the generated Typst constructs compile with typst 0.15.
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
A freshly created book lived only in memory until the first edit, so
quitting beforehand lost it and it never appeared in the library. Save
it to disk on creation via a shared createAndOpenBook used by both the
Library card and the New Book menu action.
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
markSaved cleared dirty unconditionally, so an edit made while a save
was in flight had its dirty flag wiped and its follow-up save dropped.
Only clear dirty when the book reference is unchanged since the save
started (immutable updates give a fresh reference per edit).
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
Edits are saved on an 800ms debounce, so leaving a book within that
window lost the latest changes. Flush a dirty book before openBook /
closeBook swap state (covers All books, open-another, Cmd+N), and add a
window close-request handler that saves before destroying on quit.
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
Key the editor by chapter id so each chapter gets a fresh TipTap
instance with its own history. Previously a single instance was reused
across chapters, so the content swap entered the undo stack and Ctrl+Z
after switching restored the prior chapter's doc into the current one,
which autosave then persisted.
With remount, save the cursor/scroll position on unmount (from a ref,
not the editor, which is destroyed by then) and load it on mount,
replacing the in-place chapter-switch bookkeeping.
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
Route save_book, write_file, write_bytes through a shared atomic_write
helper: write to a temp file, fsync, then rename into place so a crash
mid-write can never leave a truncated or empty manuscript. Rotate the
prior copy to .bak for manuscripts (not exports) so one good copy
always survives.
Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
Positions were kept in an in-memory Map, so they were lost on quit and
the cursor jumped to the top on reopen. Store them in localStorage
(keyed by book + chapter, like theme/width) and restore on app open,
not just on in-session chapter switches. Also save the current chapter
continuously (debounced on selection + scroll) so closing the app
records where you left off, and re-apply scroll after fonts load.
proof_text loaded the dictionary and ran spell/grammar checks
synchronously inside an async command, blocking an async-runtime worker
and holding the engine mutex for the whole call. Move the work to
spawn_blocking so IPC stays responsive under load.
The render loop swallowed getDocument/render rejections, leaving the
preview silently blank with no signal about what failed. Surface the
error in the pane and recover on the next compile. Also drop the
redundant canvasContext arg (pdf.js v6 renders via the canvas itself).