Skip to content

feat: the HTML book installs and reads offline - #16

Merged
apiad merged 20 commits into
mainfrom
html-book-pwa
Oct 20, 2026
Merged

apiad merged 20 commits into
mainfrom
html-book-pwa

Conversation

@apiad

@apiad apiad commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Part 3 of 3 for #13, stacked on #15. Plan tasks 8-9.

What changed

  • book.py: make_icons() (the cover cropped from its top, or the title's first letter on the brand colour, plus a maskable icon), write_manifest() (start_url and scope ./, standalone, the book's lang), site_version() and write_service_worker(). The version is the first 12 hex digits of sha256 over every file but sw.js.
  • themes/base/reader/sw.js: precaches every file of the book under book:<path>:<version> and deletes only caches with its own path prefix. It answers navigations with the cached ./ and everything else from the cache.
  • reader.js: registers the worker with updateViaCache: 'none' and checks for updates when the tab becomes visible. On a new edition it shows a notice with an Update button that reloads at the same place. It also shows "✓ offline" once cached, an Install button on beforeinstallprompt, and a hint on iOS.
  • Docs: README "The HTML book" and "Publishing an HTML book" (HTTPS, sw.js caching, manifest MIME type, a Caddy example with exclusive matchers), know-how/the-html-book.md, AGENTS.md entries, CHANGELOG, and a "superseded" line on the 2026-08-14 spec and plan.

Measured

  • tests/test_book_pwa.py (5): sw.js lists exactly the site's files plus ./; two builds are byte-identical and changing one word changes the version; the manifest fields; a book with no cover and no images builds with letter icons.
  • Browser tests, 3 new (13 in all, pass): an offline reload shows the book with every image decoded; rebuilding with one word changed shows the notice in the book's language, and Update shows the new word; with two copies at /a/ and /b/, updating /a/ replaces its cache and leaves /b/'s.
  • The README's outputs: example loads as written.

Fixes found on CI after #15 merged

  • Page counts no longer depend on fonts or images having loaded. Every declared face loads before the first layout, and image boxes take the column width and the attributes' ratio instead of the loaded image's size. On a first visit, a chapter counted in the hidden strip before its image arrived got another page count than the stage showed. A browser test delays images and fonts and checks that the count holds.
  • The update tests call registration.update() until the new edition lands: Chrome on the runner sometimes ignored a single call made right after the first install.
  • know-how/the-html-book.md describes the per-chapter reader and its measurements.

🤖 Generated with Claude Code

apiad and others added 17 commits October 20, 2026 10:03
Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Themes declare the strings under html-refs:, merged through extends like page:.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pillow becomes a direct dependency: the book needs it without the pdf extra.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No inline TOC, and page-floated notes end their chapter instead.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
render_book writes a static directory: the theme CSS filtered for a browser,
font sizes as shares of the reader's size, images as WebP, and a reader that
pages with CSS columns, swipes, keeps rectos in a spread, follows in-book links
and remembers the place by block. examples/html-book builds it next to two PDFs.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A manifest and icons (from the cover, or the title's letter), and a service
worker that precaches every file under a cache named by the book's path and
the hash of its content. An open reader is told when a new edition arrives.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	docs/superpowers/plans/2026-10-20-html-book.html
#	scriptorium/cli.py
#	scriptorium/render.py
Each chapter is its own file and its own column strip. index.html carries the
TOC, which chapter holds each id and each chapter's length; the reader loads
the chapter it needs, counts the others' pages out of sight in idle time
(cached per geometry, font size and edition), and estimates until then. Each
section restarts the theme's CSS counters where the previous chapters left them.

On an 861-page book at a phone size: ready in 0.44 s (0.70 s at CPU x4), a font
change re-lays out in 28 ms (66 ms), crossing into a chapter takes 8-21 ms
(35-110 ms), and all pages are counted 2.7 s (7.7 s) after opening.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	scriptorium/book.py
@apiad
apiad changed the base branch from html-book to main October 20, 2026 15:00
apiad and others added 3 commits October 20, 2026 11:01
# Conflicts:
#	scriptorium/book.py
#	tests/test_reader_browser.py
#	themes/base/reader/reader.js
Every declared face loads before the first layout, and image boxes take the
column width and the attributes' ratio instead of the loaded image's size. A
chapter counted in the hidden strip before its image or face arrived got
another page count than the one the stage showed. A browser test now delays
images and fonts and checks the count holds.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chrome on CI sometimes ignored one update() right after the first install.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apiad
apiad merged commit 8b2ebc6 into main Oct 20, 2026
2 checks passed
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