Commit Graph
100 Commits
Author SHA1 Message Date
pj 42996804bb feat(appstore): add the listing screenshots
Five 2560x1600 frames. The app window inside each is a CSS rebuild of the real interface rather
than a bitmap, laid out at the app's own 1440x900 and scaled once, so the type is real text at
render time instead of a resampled capture. The only Macs to hand are 1x, and an upscaled screen
capture looks like one.

The website's own AppWindow mockup was the obvious source and turned out to be wrong in several
places: the sidebar is 248px not 196, the proofing marks are straight underlines rather than wavy,
and the trim default differs. The rebuild follows shared/css/tokens.css and src/styles/app.css
instead, so these are more accurate than the marketing imagery.

Sample prose is original and written for this listing, since it goes in a public store page.

Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
2026-08-31 17:26:22 +05:30
pj 6ddb3ac43e feat(appstore): script the provisioning, listing and TestFlight setup
The listing copy lives in text files rather than in a web form, so changing a description is a diff
someone can read and the store listing is reviewable next to the code it describes. Field lengths
are checked before sending, because Apple rejects an over-length field with an error that never
names the limit.

apple-provision.rb drives the Developer Portal through spaceship and is find-or-create throughout.
That matters most for the Developer ID certificate: an account may hold only a handful, they cannot
be un-revoked, and every copy of the app already signed by one stops verifying if it goes away.

Both scripts pin the App Store Connect team. This Apple ID can see more than one, and the other
belongs to somebody else entirely, so letting spaceship choose is how a listing ends up on the
wrong account.

The reviewer phone number and email are deliberately not in here. Apple requires a real number and
this repo is public.

Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
2026-08-31 17:26:14 +05:30
pj 92b446973f feat(appstore): add a Mac App Store build track
Tauri has no App Store target, so mas-package.sh covers the distance between the .app and something
App Store Connect will take. The order is load-bearing: the provisioning profile goes in before
codesign runs because the signature covers it, which is also why Tauri's own signing is switched
off for this build.

The sandbox costs three things, all Apple's rules rather than choices. The updater is gone, and
falls out for free because the plugin was already conditional on the config declaring it; only the
release overlay does. The Check for Updates menu item is gated on the same condition, since a menu
item that errors when clicked is its own rejection risk. The library moves into the container, and
the system spelling dictionary becomes unreadable.

Two things the first upload taught us. The profile is kept owner-only where it lives next to the
signing keys, and cp carried that mode into the bundle; Apple rejects a package containing anything
a non-root user cannot read. And altool exits 0 after printing UPLOAD FAILED, so its exit status
cannot be trusted and the transcript is the only reliable signal.

Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
2026-08-31 17:26:03 +05:30
pj 39d4097773 ci(release): sign and notarize the macOS bundle, and bump the Homebrew cask
An unsigned bundle on a current macOS opens to a malware warning with no obvious way past it, and
the way past it that does exist teaches people to click through exactly the warning worth reading.
The build now signs with a Developer ID certificate and notarizes with an App Store Connect API
key, which is also what does the App Store upload, so there is one credential to rotate.

The verification step is the point. codesign only says a signature is internally consistent;
spctl is what a person double-clicking the file actually meets, and it does not pass until the
notarization ticket is stapled.

A final job rewrites the version and sha256 in the tap's cask, using an SSH deploy key rather than
a token so a leak from a release job cannot reach the app repositories.

Also fixes Cargo.lock drifting a version behind on every release: the bump wrote Cargo.toml but
never staged the lock, so any fresh build dirtied the tree.

Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
2026-08-31 17:25:51 +05:30
pj d8c47bb6f0 chore: add FSL-1.1-MIT licence and fill in package metadata
The repo had no licence at all, which legally means all rights reserved. FSL grants free use for
anything except building a competing product, and each version becomes MIT two years after its
release. AGPL was the other candidate and is ruled out by the App Store, whose terms impose
restrictions the GPL forbids.

Cargo.toml still carried the Tauri scaffold defaults, and that description ends up in the deb and
rpm metadata.

Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
2026-08-31 17:25:42 +05:30
pj d4318554f6 refactor changes 2026-08-30 16:59:00 +05:30
pj bcd5eb9ebc group of bug fixes 2026-08-24 09:17:26 +05:30
pj b954a3135d Straight through shortcut 2026-08-10 18:44:46 +05:30
pj 9e310a34bb feat(macos): trigger Apple Intelligence writing tools from the editor 2026-08-10 18:37:23 +05:30
pj fe39274ed7 feat(nav): peel overlays with esc, close the book on a second esc 2026-08-10 18:37:23 +05:30
pj 2e15dc2dc9 feat(editor): open the link popover with cmd+k 2026-08-10 18:36:52 +05:30
pj dad0ac86d0 feat(chapters): focus the title field when adding a chapter 2026-08-10 18:36:39 +05:30
pj efd45cf61e feat(library): show last edited time on book cards 2026-08-10 18:36:30 +05:30
pj 742e42d65b feat(editor): keep the caret clear of the floating toolbar 2026-08-10 18:36:18 +05:30
pj abcd133b19 chore: sync Cargo.lock with package version 2026-08-10 18:36:18 +05:30
pj acf78dfe29 feat(menu): reorganize native menu bar with discoverable actions
Move Check for Updates into the macOS app menu and add Settings (Cmd+,),
Save (Cmd+S), Find (Cmd+F), chapter navigation, and a Help > Report an
Issue link. Route new menu actions through menu-action events and trim
duplicate editor keybindings.
2026-07-15 09:31:57 +05:30
pj 866a971729 feat(updater): show in-app progress during app update
Replace native update dialogs with an in-app modal that shows a live
download progress bar and installing state, so updates no longer feel
abrupt. Drives the plugin's downloadAndInstall progress events through a
zustand store.
2026-07-15 09:31:57 +05:30
pj a31f99cb76 fix keyboard fixed 2026-07-14 16:07:05 +05:30
pj 20365dd02d fix(proofing): stop flagging bare URLs as misspelled on macOS
NSSpellChecker's implicit URL handling is unreliable for URLs alone on a
line (a bookmark list), flagging the whole URL as one misspelling. Request
the link checking type alongside spelling so the data detector claims URL
ranges, then keep only spelling results. Real misspellings are unaffected.
2026-07-14 16:07:05 +05:30
pj f11ddd290f fix(proofing): use macOS NSSpellChecker for spelling on macOS
The bundled Hunspell dictionary (~49k SCOWL roots) lacked common words like
"cybersecurity", "scalable", and "assistantship", and checked words inside
URLs. On macOS, spelling now uses the system NSSpellChecker (same engine as
TextEdit/Safari): far better vocabulary, skips URLs, and respects words the
user has taught macOS. The custom/tech dictionaries and Harper grammar are
unchanged; non-macOS builds keep the Hunspell fallback.
2026-07-13 20:27:58 +05:30
pj cd41a02737 changes 2026-06-27 09:48:42 -04:00
pj 5a2ada9e9e ci(release): pin macOS runner to macos-26 2026-06-27 09:48:42 -04:00
pj c090fb595f changes 2026-06-27 09:32:22 -04:00
pj 11df8697b1 changes 2026-06-23 11:29:54 -04:00
pj f47e822497 changes 2026-06-22 21:26:03 -04:00
pj cc26e1d51c changes 2026-06-22 17:12:44 -04:00
pj 74a684498a changes 2026-06-22 16:51:39 -04:00
pj 3fccb17060 fix(ci): provision google-credentials.json before release build
include_str! embeds google-credentials.json at compile time, but the
file is gitignored, so CI checkouts had no file and the Rust crate
failed to compile on all three platforms. Write it from the
GOOGLE_CREDENTIALS secret, falling back to the committed example
placeholders so the build always succeeds.
2026-06-22 16:50:28 -04:00
pj 9acb3aae3c changes 2026-06-22 16:42:03 -04:00
pj c79897c3db changes 2026-06-22 16:19:24 -04:00
pj 5cc083d670 changes 2026-06-22 16:11:43 -04:00
pj ecc94e6c31 refactor(fonts): use DEFAULT_FONTS as settings fallback
Avoids leaking a pairing's id/label into the stored book fonts.
2026-06-22 14:34:45 -04:00
pj baf1154683 feat(fonts): embed selected fonts in EPUB export
Embed each bundled font the book uses (fetched in the webview) with its
own @font-face, point body text and headings at the chosen families, and
drop the hardcoded Literata injection in the packager. System fonts are
referenced by name with a serif fallback since they cannot be embedded.
2026-06-22 14:30:36 -04:00
pj 7a2d7c6aff feat(fonts): honor selected fonts in PDF export
Interpolate the book's body and heading families into the Typst
preamble, title page, cover, and contents. Embed the needed bundled
fonts and load any chosen system fonts (via fontdb) into the Typst
engine so PDF preview and export match the on-screen fonts.
2026-06-22 14:27:51 -04:00
pj 9a38976000 feat(fonts): add font picker to book setup
Add a Typography section to book setup with one-click preset pairings,
an advanced body/heading picker, and a live sample. Enumerate installed
fonts via a new list_system_fonts Tauri command (fontdb) so they appear
alongside the bundled set, with a note that system fonts may not embed
in exports.
2026-06-22 14:24:09 -04:00
pj 28367b6670 feat(fonts): apply per-book body and heading fonts on screen
Split heading typography onto a new --font-heading token and set both
--font-book and --font-heading from the active book's fonts when it
opens or its setup changes. Declare @font-face for the new bundled
fonts.
2026-06-22 14:19:01 -04:00
pj 124c1947c1 feat(fonts): add bundled fonts and per-book font model
Add EB Garamond, Lora, Source Serif 4, and Fraunces (OFL variable
fonts) and a font catalog with body/heading pairings. Store the chosen
fonts per book in BookSettings.fonts, defaulting to Literata so existing
books are unchanged.
2026-06-22 14:18:57 -04:00
pj 738eb9c9cf Quality bugfix plan (#2)
* fix(persist): atomic+durable .margin writes

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

* fix(editor): isolate undo history per chapter

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

* feat(editor): persist active chapter per book

Remember which chapter was open per book and restore it on reopen,
falling back to the first chapter when the saved id is no longer valid.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(persist): flush pending save on navigate-away and quit

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

* fix(persist): guard markSaved against in-flight edits

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

* fix(persist): persist new books on create

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

* fix(export): correct list nesting, marks, line-start escaping, svg type

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

* feat(export): surface typst warnings and glyph-coverage notice

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

* fix(ui): handle library-open errors

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

* fix(preview): destroy pdf.js document on recompile

The preview created a new PDF document on every recompile but never
destroyed the old one, leaking the document and its worker transport.
Keep the loading task and destroy it on effect cleanup.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(library): surface unreadable books

list_books silently skipped any .margin file it could not read, parse,
or that lacked an id, so a truncated manuscript just vanished. Emit a
corrupt placeholder entry instead and show it in the library as a
non-openable card with a recovery hint pointing at the .bak backup.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(persist): schema-version guard and editor content check

A book file's declared schema was never read, so a file from a newer
app version would be opened, normalized and autosaved over in the old
format. Refuse to open books whose schema is newer than supported.
Enable the editor content check so chapters with unreadable content
warn the user instead of silently loading stripped content that the
next edit would persist.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(editor): prune chapter positions on delete and guard quota

Saved cursor/scroll and active-chapter entries accumulated per book
forever and were never removed when a book was deleted. Add
clearPositions and call it from the delete flow, and guard localStorage
writes against quota or unavailable storage.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(security): validate book id paths

load/save/delete_book built paths from the raw id, and ids round-trip
from file JSON, so a crafted id could escape the library directory.
Route all three through a book_path helper that rejects anything but
[A-Za-z0-9_-].

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* chore(security): restrictive content security policy

security.csp was null (no CSP). Add a restrictive policy as
defense-in-depth: self-only by default, data:/blob: images for cover
and figure URLs, inline styles for React/inline style attributes, and
blob: workers for pdf.js. Needs verification against a production build
(covers, preview, export, IPC).

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(import): preserve link, underline, strike and code marks

EPUB import only mapped bold and italic; a, u, s/del/strike and code
elements lost their formatting (kept as plain text). Map them to the
matching marks, consistent with what the exporters now render.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(import): keep inline svg and unresolved image alt text

Inline svg elements were dropped entirely and an img whose source could
not be resolved was discarded. Serialize a standalone svg to a
data:image/svg+xml figure, and fall back to an unresolved image's alt
text as a paragraph instead of losing it.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(import): keep table rows together

Tables were flattened to one paragraph per cell, losing row grouping.
Join each row's cells into a single paragraph so the row structure
survives (the model has no table node).

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(import): import the cover image

The EPUB cover was discarded and replaced with the default generated
cover. Locate the cover via the manifest cover-image property or the
meta name="cover" reference, resolve it to a data URI, and import it as
an image cover.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(import): validate ISBN with checksum

The importer accepted any 9-13 digit run as the ISBN, so a UUID or
arbitrary number became a bogus ISBN. Prefer scheme-tagged identifiers
and accept only values that pass the ISBN-10/13 length and checksum
test, leaving it empty otherwise.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* fix(import): isolate bad chapters and bound nesting depth

A deeply nested document could overflow the stack and abort the entire
import. Bound block recursion depth and wrap each chapter build in a
try/catch so one unreadable chapter is skipped instead of failing the
whole import.

Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk

* changes
2026-06-22 13:44:30 -04:00
pj 61bd7cf6e5 changes 2026-06-22 13:03:11 -04:00
pj 72d698e427 changes 2026-06-22 12:40:29 -04:00
pj 8b83f80d52 fix(editor): persist per-chapter cursor and scroll across restarts
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.
2026-06-22 12:09:34 -04:00
pj 39609fbd09 perf(proofing): run proof_text on a blocking thread
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.
2026-06-22 12:01:25 -04:00
pj 3177b2f1eb fix(preview): show render errors instead of a blank pane
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).
2026-06-22 12:01:25 -04:00
pj 19c4823d3d changes 2026-06-22 11:56:31 -04:00
pj 5727592abe ci(release): rebase onto origin/main before pushing version bump 2026-06-22 11:48:49 -04:00
pj 706db33792 changes 2026-06-22 11:45:21 -04:00
pj bd44c0ba7e changes 2026-06-22 11:42:48 -04:00
pj 3c12d9857f action update 2026-06-22 10:26:30 -04:00
pj a9a4245de5 changes 2026-06-22 10:12:35 -04:00
pj 7cc36e3e40 changes 2026-06-22 09:56:20 -04:00
pj 78fff9b6f4 changes 2026-06-21 14:53:08 -04:00
pj fa1c89af63 changes 2026-06-21 14:37:02 -04:00
pj 47710970c2 changes 2026-06-21 14:17:57 -04:00
pj aa44696803 changes 2026-06-21 00:35:08 -04:00
pj 0e0d334f6a changes 2026-06-20 23:46:10 -04:00
pj c86d5a7d5c changes 2026-06-20 19:48:18 -04:00
pj 734ded73ac changes 2026-06-20 19:23:28 -04:00
pj 20ae0b7387 fix 2026-06-20 19:07:57 -04:00
pj be252c6b21 chore: remove in-code tests 2026-06-20 19:06:10 -04:00
pj 7464665d08 test(pdf): cover single-chapter preview compile 2026-06-20 19:03:46 -04:00
pj bfba86a01b perf(preview): typeset only the active chapter in live preview 2026-06-20 19:03:46 -04:00
pj 29e8842847 perf(preview): add per-chapter Typst compile inputs 2026-06-20 19:03:46 -04:00
pj 8b9a7081f7 feat(library): open example as a saved copy, hide it once a book exists 2026-06-20 18:53:36 -04:00
pj 4410b2037f refactor(sidebar): rename back button to All books 2026-06-20 18:46:43 -04:00
pj 9991d318ba feat(theme): persistent light/dark toggle in the titlebar 2026-06-20 18:46:43 -04:00
pj a5b3c0c0e3 feat(theme): monochrome black accent and light/dark surfaces 2026-06-20 18:46:43 -04:00
pj 79f3483b8e feat(epub): add Import EPUB entry to the home library 2026-06-20 18:41:13 -04:00
pj 3d3abd91f2 test(epub): cover unzip_epub reading a file from disk 2026-06-20 18:40:57 -04:00
pj 0d2d66ed6a fix(icons): restore dark-background app icon 2026-06-20 18:40:08 -04:00
pj 406cd1289c feat(epub): parse EPUB files into a Book (importer) 2026-06-20 18:36:18 -04:00
pj 8b9fa7f24e chore(window): blank the native window title 2026-06-20 18:33:43 -04:00
pj 847e912d05 refactor(library): remove brand logo and wordmark from home 2026-06-20 18:33:43 -04:00
pj 1cb738c20c fix(icons): ink mark on paper to match the logo (not inverted) 2026-06-20 18:30:53 -04:00
pj 18c367a82d chore(icons): generate app icon from the margin mark 2026-06-20 18:28:11 -04:00
pj 2997e8b79f feat(epub): add unzip_epub Tauri command for import 2026-06-20 18:24:09 -04:00
pj e09ce5cc92 chore(scripts): add pnpm dmg to bundle a macOS dmg 2026-06-20 18:20:22 -04:00
pj 11b6e81437 feat(app): use logo mark as favicon 2026-06-20 18:20:22 -04:00
pj 40d4c9f279 feat(library): show themed logo mark in the banner 2026-06-20 18:20:22 -04:00
pj 5e7b782f59 chore(assets): add margin logo mark 2026-06-20 18:20:22 -04:00
pj 041619821f feat(settings): draft Book setup, commit changes only on Save 2026-06-20 18:12:15 -04:00
pj 008e22c36c feat(export): apply print bleed and full-bleed full-page images 2026-06-20 18:12:15 -04:00
pj d684abbdef refactor(settings): drop trim field, now set from preview panel 2026-06-20 17:05:55 -04:00
pj 3202f24947 feat(preview): move trim size selector into the preview panel 2026-06-20 17:05:51 -04:00
pj 1d600ccc33 feat(menu): handle native menu actions in the app 2026-06-20 15:13:12 -04:00
pj f4a4d63349 feat(menu): native File menu with New Book and Export 2026-06-20 15:13:12 -04:00
pj f44e38d7fe style: library shelf, book cards, and setup save footer 2026-06-20 15:09:13 -04:00
pj e9669290ec feat(ui): back-to-library, export-only download button, save in book setup 2026-06-20 15:09:13 -04:00
pj 686c7768c7 refactor(app): route between library and editor with autosave 2026-06-20 15:09:13 -04:00
pj cd43f9c513 feat(library): book organizer home screen 2026-06-20 15:09:13 -04:00
pj 14d73df648 feat(library): list/load/save/delete library API 2026-06-20 15:09:13 -04:00
pj a6404847d1 refactor(store): nullable open book with open/close for library nav 2026-06-20 15:09:13 -04:00
pj 4896e10aba feat(model): give each book an id 2026-06-20 15:09:13 -04:00
pj 709dd46f1c feat(library): app-managed book storage commands 2026-06-20 15:09:13 -04:00
pj 7e7df12aa7 chore: add project guidelines 2026-06-20 14:47:30 -04:00
pj b1dac0ca71 fix(editor): remove floated drop cap that broke WebKit text input 2026-06-20 14:47:14 -04:00
pj 5e1582fc39 feat(ui): guard desktop-only actions with a clear browser-preview message 2026-06-20 14:30:07 -04:00
pj f8ddde5e91 fix(epub): embed fonts in the Rust packager instead of fetching in the webview 2026-06-20 14:30:07 -04:00
pj cb506af374 feat(ui): surface save and export errors as toasts; add webkit backdrop-filter 2026-06-20 13:29:52 -04:00
pj c4b69f3641 fix(editor): defer render and load content per chapter for WebKit 2026-06-20 13:28:25 -04:00
pj 0de442eab0 docs: project README 2026-06-20 13:09:40 -04:00