CodeMirror on the left. True paged A4 preview on the right. Every page break updates as you type.
Quick start · Features · Themes · Comparison · Showcase
Pagewright is a local-first book editor for Markdown authors. You
write in CodeMirror; you see your manuscript laid out as A4 pages
in real time via paged.js; your work is saved
atomically into plain .md files in a directory you control.
It is not a notebook. It is not a knowledge graph. It is not a markdown previewer. It is built for one job: writing books, papers, and long-form documents where the printed page matters.
| You are… | Why Pagewright fits |
|---|---|
| Writing a book in Markdown | Multi-chapter project model. Watch your page breaks update as you type. Theme = print CSS. |
| Self-publishing non-fiction or fiction | Local-first. No cloud. Your manuscript is plain text, in git, owned by you. |
| Drafting a long paper or report | A4 preview matches what you'll print. Atomic saves + ETag conflict detection. |
| Building a custom typography system | Themes are just theme.css + an optional opener template. No build step, no preprocessor. |
| A publisher with an in-house design system | Ship your CSS as a theme. Your authors write Markdown; the design stays consistent. |
| You are… | Use this instead |
|---|---|
| Writing notes / building a knowledge graph | Obsidian, Logseq |
| Writing a single-doc blog post | Any markdown editor; Pagewright is overkill. |
| Writing in LaTeX | Overleaf |
| Collaborating in real time with co-authors | Vivliostyle, Ketty |
| Using Word and exporting to PDF | Word, then. |
# Clone the repo to get the bundled sample book
git clone https://github.com/Mavengence/pagewright
cd pagewright
pip install -e .
# Run the editor against the sample
pagewright examples/sample-bookOpen http://127.0.0.1:5566. You're writing.
PyPI publication is on the roadmap. Until then, install from source with
pip install -e .(editable) orpip install git+https://github.com/Mavengence/pagewright.git.
For your own book, create a directory with one pagewright.yaml and
one markdown file per chapter:
# pagewright.yaml
title: "My Book"
author: "Your Name"
theme: default
parts:
- title: "Part I"
chapters:
- file: 01_intro.md
title: "Introduction"
description: "Why this book exists."
- file: 02_chapter.md
title: "First Chapter"Then point Pagewright at it:
pagewright path/to/my-bookThat's the entire setup. No config in your home directory, no global
state, no build step. Quit Pagewright and your book is just a folder
of .md files.
The right pane has two modes. Continuous scrolls the chapter as
one tall HTML document. Paged uses paged.js to lay it out into
discrete A4 pages with running headers, page numbers, and proper
break-inside handling — exactly what your final PDF will produce.
Switch with one click in the preview header. No rebuild.
Edit a paragraph. 1.5 seconds after the last keystroke, Pagewright autosaves your file and re-renders the preview. The heading at the top of your viewport before the reload is at the top after — no scroll-to-top, no animation, no losing your place.
This is what makes the Paged preview usable while writing. paged.js re-paginates in 1–3 seconds; you land back on the same A4 page, not back at page 1.
The left sidebar shows your chapter's outline (h1 / h2 / h3). Click
a heading and the editor cursor jumps to that line and the
preview scrolls to the matching #h-N anchor. Click anywhere in the
editor and the corresponding outline entry highlights. You can
navigate a 30,000-word chapter without using the scrollbar.
⌘K opens the jump-to-chapter palette. ⌘/ focuses the chapter
filter in the sidebar. Both fuzzy-match against book titles and
chapter titles.
Auto / Light / Dark cycle. The preview iframe inherits the
preference; themes opt in to dark via data-color-scheme="dark"
selectors in their CSS.
The bundled default theme — clean modern paper-on-cream with an
ember accent — rendered through both preview modes.
![]() |
![]() |
The renderer is theme-agnostic — default is a reference
implementation, not a built-in. Drop your own under
src/pagewright/themes/<name>/ or pass an absolute path with
--theme. See docs/themes.md for the full
theme-author guide.
- Two preview modes — Continuous (scrolling) and Paged (true A4 via paged.js with running headers + page numbers).
- Position-stable hot reload — your viewport heading stays at the top across re-renders. Works in both modes.
- Outline-driven navigation — click a heading, cursor jumps, preview scrolls. Bidirectional.
- Atomic saves — temp file +
os.replace. A killed process never leaves a half-written file. - ETag conflict detection — if the file changed under you (other
editor,
git pull), the server returns409 Conflictand the editor opens a Reload / Overwrite / Cancel dialog. - 3× retry with exponential backoff — transient save failures retry [0, 400, 1200] ms. On exhaustion: persistent toast with a Retry action; your edits stay in the editor.
- Eight semantic callouts — Note / Tip / Warning / Important / Example / Definition / Quote / Code. Theme-overridable labels (so themes for other languages can ship their own marker words).
- Dark mode — Auto / Light / Dark, persisted, system-aware.
- Zoom + fit-to-width —
⌘+,⌘−,⌘0, or the segmented control in the preview header. - Command palette —
⌘Kto jump between chapters. - No cloud, no auth, no telemetry — runs on
127.0.0.1against your local files.
| Pagewright | pixel-and-paper | Vivliostyle | Quarto / mdBook | Obsidian / Typora | Ketty / Editoria | Overleaf | |
|---|---|---|---|---|---|---|---|
| Markdown source | ✅ | ✅ | ✅ (VFM) | ✅ | ✅ | ❌ (Word) | ❌ (TeX) |
| Multi-chapter project | ✅ | ✅ | ✅ | ✅ | |||
| Live preview | ✅ | ✅ | ✅ | ✅ | ✅ | ||
| True paged A4 preview | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| Built-in editor | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
| Local-first | ✅ | ✅ | ❌ (cloud) | ✅ | ✅ | ❌ (server) | ❌ (cloud) |
| Atomic saves + ETag conflict | ✅ | ❌ | ❌ | — | — | — | — |
| Position-stable hot reload | ✅ | ❌ | — | — | — | — | — |
Pagewright is the only project at the full intersection: a local-first multi-chapter book editor with true paged A4 preview, production-grade save semantics, and an opinionated default theme.
The closest neighbours each give up something Pagewright keeps:
- pixel-and-paper — paged.js + local + markdown, but bring your own editor (the README literally says "use Obsidian or VS Code"). No project model beyond filename ordering.
- Vivliostyle — paged-media engine + an authoring app (the Pub editor was a cloud-hosted, single-document, GitHub-login interface; project is dormant).
- Quarto, mdBook, Bookdown, Honkit — excellent CLI build tools, but no live paged preview. You edit, you re-build, you reload.
- Ketty / Editoria — production-grade book authoring for university presses, but not Markdown (Word import, ProseMirror) and a 10-microservice deploy.
A theme is a directory shipped with Pagewright (or a folder you own) containing one to three files:
my-theme/
├── theme.css # Required. Your print-CSS.
├── theme.yaml # Optional. Callout label overrides.
└── chapter_opener.html # Optional. Per-chapter front-page template.
Resolve a theme by built-in name or by absolute path:
pagewright my-book --theme default
pagewright my-book --theme ./themes/my-house-style/Or set it once in pagewright.yaml:
theme: ./themes/my-house-style/The CSS is your A4 print stylesheet — @page rules, running headers,
page-number counters, the lot. Pagewright wraps it in continuous- or
paged-mode overrides at preview time but never modifies your rules.
The same theme.css drives the live preview and the final PDF
render — there's only one source of truth.
See docs/themes.md for the full theme-author
guide, including the rendered HTML structure, @page recipes, and
the chapter_opener.html placeholder set.
Eight semantic callouts ship in the default theme. Each is an
extended blockquote prefixed with a **Label:** marker:
> **Tip:** Practical, actionable advice the reader can apply right now.
> **Warning:** Real consequences if ignored. Use sparingly.
> **Important:** A non-negotiable point. Higher gravity than a tip.Theme authors override the labels via theme.yaml:
callouts:
- { label: "Astuce:", css_class: "callout-tip", display: "Astuce" }
- { label: "Attention:", css_class: "callout-warning", display: "Attention" }…so a French theme's > **Astuce:** and the default English
> **Tip:** both produce the same <div class="callout callout-tip">.
| Key | Action |
|---|---|
⌘S |
Save now |
⌘B / ⌘I |
Bold / italic |
⌘F |
Find in current file |
⌘K |
Jump-to-chapter palette |
⌘/ |
Focus chapter filter |
⌘\ |
Toggle sidebar |
⌘+ / ⌘− / ⌘0 |
Zoom preview (in/out/fit) |
? |
Show keyboard help |
Esc |
Close palette / dialog |
This isn't a weekend project that happens to work.
- Atomic writes — every save goes through a sibling
.tmpfile andos.replace. A killed process never leaves a half-written file. - ETag-based conflict detection — every load returns
ETag: <mtime_ns>; every save sendsIf-Match. The server returns409 Conflictif the file changed underneath, and the editor opens a Reload / Overwrite / Cancel resolution dialog. - Retry policy — 3× exponential backoff (0 / 400 / 1200 ms) on transient save failures (network, 5xx, 408, 429). Persistent toast with Retry on exhaustion. Your edits stay in the editor.
- No-cache headers — Flask sets
Cache-Control: no-storeon/,/static/*, and/api/*. CSS/JS changes land on next reload, no hard-refresh needed. - Path-traversal safe — every asset access is sandboxed inside
the book directory;
..returns 404. - Throttled side effects — word count + outline rebuild debounce at 200 ms so 50 KB chapters stay snappy under burst typing.
- Resilient layout —
ResizeObserver+fonts.ready+ RAF refreshes catch every layout shift CodeMirror needs to remeasure (split drag, sidebar toggle, full-screen, web-font load).
pagewright/
├── src/pagewright/
│ ├── cli.py ← argparse entrypoint (`pagewright …`)
│ ├── server.py ← Flask app: atomic saves, ETag, no-cache
│ ├── renderer.py ← Markdown → HTML, theme-aware, mode-aware
│ ├── markdown.py ← Self-contained markdown converter
│ ├── theme.py ← Theme loader (built-in + custom paths)
│ ├── config.py ← `pagewright.yaml` schema + loader
│ ├── static/ ← Editor UI (HTML + CSS + JS)
│ └── themes/ ← Built-in themes (default)
├── examples/sample-book/ ← Runnable sample (the book you read above)
├── docs/ ← Theme guide, configuration, architecture
└── tests/ ← Playwright test suite
The editor is a single-page Flask app. The renderer turns one chapter
into a standalone HTML document; the editor iframe loads it from
/api/preview?path=…. Saves go through /api/file with If-Match
ETag headers — the server returns 409 Conflict if the file changed
under you, and the editor opens a resolution dialog.
See docs/architecture.md for the request
flow diagram and extension points.
Pagewright is the editor. To produce a final PDF, point your favourite print-CSS engine at the same theme CSS the preview uses:
# WeasyPrint (Python, recommended)
weasyprint preview.html my-book.pdf
# paged.js-cli (Node — npm install -g pagedjs-cli)
pagedjs-cli preview.html -o my-book.pdf
# Prince (commercial)
prince preview.html -o my-book.pdfAll three engines honour the same @page rules, string-set running
headers, and break-inside hints your theme defines, so the output
matches the on-screen preview.
A pagewright build my-book.pdf command that glues the renderer to
WeasyPrint or paged.js-cli is on the roadmap. Today,
write a 30-line Python script that imports render_chapter_html
and pipes the output to your engine of choice — see
docs/architecture.md for an example.
The shipped editor is feature-complete for single-author book writing. The high-leverage future work, in order:
- PyPI publication —
pip install pagewrightwill Just Work. pagewright build my-book.pdf— single-command full-book PDF build via WeasyPrint or paged.js-cli.- More themes — academic (Latin Modern + footnotes + bibliography), fiction-novel (single column + drop caps + generous leading), technical-reference (multi-column, callout-heavy).
- Per-chapter image references —
resolved againstbook-asset/. (Currently themes manage their own assets.) pagewright init— scaffold a new book directory in one command.
PRs welcome on any of the above.
Beta. The API is stable; the data model (pagewright.yaml,
theme directory shape) is unlikely to change incompatibly. Issues
and PRs welcome.
- Single-chapter preview only. For full-book layout (cover, TOC, back-matter) build the PDF with WeasyPrint or paged.js-cli.
- Single user, single tab. Two browser tabs editing the same file conflict-detect each other on save.
- No inline markdown images. Themes manage their own image assets via theme directories. (Patches welcome.)
- English default callouts only. Other languages need a custom
theme.yaml.
Built by @Mavengence.
Standing on the shoulders of:
- paged.js — the print-CSS polyfill that makes the paged preview real.
- CodeMirror 5 — the editor.
- Lucide — the icon set.
- Inter + JetBrains Mono — the default theme's typography.
PRs welcome. The codebase is small (≈4,000 LOC across Python + JS) and well-commented. 68 tests cover the markdown converter, config loader, theme system, renderer, and every Flask endpoint.
git clone https://github.com/Mavengence/pagewright
cd pagewright
pip install -e ".[dev]"
pytest tests/ -vSee CONTRIBUTING.md for the full guide.
MIT.





