Banner designs can now ship arbitrary markup as banner.html (canvas +
style + script), taking precedence over banner.svg; the artwork is
inlined in a div[data-design] wrapper either way, which the editor's
live preview preserves (and never re-runs its scripts).
The bundled 'eyes' design (themes/eyes/banner.html + banner.css sizing
the stage) replaces the eyes canvas script that was embedded in the
notes-on-urls page's own banner — the page now just picks the design.
Seeds set banners only on select sub pages (the-long-read gradient,
canvas-nights stars): the front page no longer gets a banner image and
shows the theme's default design. Seed entries carry a banner_design
field.
Dateline is now '1 Jan 2026', with ' – edited 3 Jan 2026' appended only
when the last edit came at least 24h after publishing.
The Last-Modified header added heuristic browser caching of pages (no
Cache-Control was sent), so the site editor's re-fetch after a theme
change served the cached page with the old stylesheet link. Pages now
send 'Cache-Control: no-cache' — always revalidate, still cheap via the
ETag.
An http middleware sets 'Server: pagerite' (dropping uvicorn's versioned
default) and content pages return Last-Modified from Node.modified. In
markdown, a {dates} line expands to the article's published/updated
dateline (updated shown only when it falls on a later day); the editor
preview resolves it for pages that exist, unsaved pages show it literal.
Demonstrated in the long-read seed page.
With markdown-it breaks:True, the 80ch hard wraps rendered as forced
<br>s: frozen wrapping at the source's line breaks, ragged unjustified
lines and stale manual hyphenation. One line per paragraph lets the
browser reflow, justify and hyphenate normally.
Themes move from Vite-built frontend assets to pagerite/themes/{name}/
folders holding theme.css and/or banner.css (+ banner.svg), served by the
backend at /_themes/{name}/... and re-read from disk per request (etag by
mtime), so on-disk edits show on the next page load even in prod and new
themes need no build or config. The theme and banner-design selectors
enumerate these folders via GET /_api/settings.
Banner designs: Node.banner_design picks a design per page (None inherits
from ancestors, then the front page, then the active theme's own design;
"" = none). The design's banner.css is linked in <head> (id
pagerite-banner, between theme and custom CSS) and its banner.svg inlined
into #page-banner first (marked svg[data-design]); the page's own
Node.banner HTML renders after it, so author code always wins. #page-banner
is now a stacking grid so artwork and author code overlay.
Dev/prod hot loading unified: the backend renders the theme/design links
in both modes; in dev pagerite.js only re-appends them (and the custom
CSS) after the Vite-injected base styles. Theme switches just swap the
link href. The pagerite:theme meta and Vite theme build entries are gone.
Data.favicon names a blob in the content-addressed files store (no
migration: msgspec default). PUT/DELETE /_api/settings/favicon upload and
clear it; when set, every page links it as <link rel="icon">, otherwise
browsers fall back to the build's /favicon.ico. SiteEditor shows a preview
with upload/replace/remove and applies the change to the live page head.
- Two new themes (corporate light/dark, nitro racing) and a purple
retune; theme banner SVGs moved to pagerite/themes/, inlined by the
backend, recolored per scheme via CSS classes, with scroll parallax.
- pygments.css maps tokens onto --code-* variables; light and dark
palette sets in the base stylesheet resolve via light-dark() from the
theme's color-scheme; themes just pick a set or retint --code-bg.
- Sidebar omitted unless the section has at least two published items;
fetch-navigation and the site editor handle it appearing/disappearing.
- Floated figures keep captions below at 30% of the text column; h1/h2
clear floats so images never overflow into the next section.
- Links use --link (color-mix of text and accent), weight 500, no
underlines; hover goes full accent.
- New-page flow: slug placeholder shows the slug derived live from the
title (placeholders keep browser-default styling; slug values no longer
muted). Committing PUTs empty markdown, which now creates an empty page
rendering with its title instead of 404, then hands over to the page
editor (CodeMirror focuses on mount)
- Saving never deletes: REST PUT and WS save store empty content as an
empty page; deletion is the page editor's explicit choice — saving
trimmed-empty text issues a REST DELETE
- Fonts: add Cormorant (variable 300-700, normal+italic) to the serif
picks, replacing single-weight Gloock; Playfair Display files kept on
disk but not offered
- Data.theme/Data.custom_css site settings with /_api/settings support;
theme stylesheet linked per page, custom CSS inline as #pagerite-user,
swapped during fetch-navigation
- Site editor: theme selector, custom CSS CodeMirror field; in-place
stylesheet swap (diff-based, preserves editor stylesheets); dev swaps
Vite-injected style tags since no <link>s exist there
- Dev theme delivery via <meta name="pagerite:theme"> + dynamic import
in pagerite.js instead of a hardcoded purple import
- Fix dead edit pens in prod: Vite app builds strip entry exports, so
the dynamic-imported main.js had no openEditor/closeEditor; build with
preserveEntrySignatures 'exports-only' and log editor load failures
instead of swallowing them
- Editor slide-in is one-shot: cleared on animationend so stylesheet
swaps cannot restart a finished animation
- Replace Fraunces/Literata with Source Sans 3/Serif 4, Inter and
Montserrat variable fonts; font stacks as CSS variables
- Remove the /_admin route, views.render_editor, and standalone mode from
the Vue editor (main.js / PageEditor.vue)
- Remove /_admin dev proxy and preload exclusion
- Set vite appType to 'mpa' and delete frontend/index.html so unknown
/_ paths return 404 instead of an empty Vue shell
- Update AGENTS.md and docs/design-principles.md
- Move API routes from /_/api/ to /_api/
- Move uploaded files from /_/f/ to /_f/
- Move built assets from /_/assets/ to /_assets/
- Move admin shell from /_/admin to /_admin
- Update Vite proxy/build config, frontend fetches/WebSockets, devserver health URL, seed content, and docs
- Fix README database name to pagerite.kantadb
- Move shared structural styles to frontend/src/assets/pagerite.css
with conservative variables, and purple theme overrides to
frontend/src/assets/themes/purple/theme.css.
- Backend now links the base and theme stylesheets as separate files
so they can be swapped or augmented.
- Add blocking="render" to stylesheet links to avoid FOUC.
- Drop redundant defer from module script tags.
- Update related docs and comments.
- Remove the section-header ➕ "add landing page" button; landing pages are
now created via the page editor pen.
- Treat saving a page with empty (after stripping) Markdown as deleting it:
childless nodes are removed, nodes with children become content-less labels.
- Restrict server-side slugs to [a-z0-9_-] with no leading underscore or dot;
drop the reserved-filename blacklist.
- Render placeholder pages for content-less category labels and link them to
their first published child in the navigation.
- Make task-list checkboxes live, non-disabled inputs: toggling updates the
Markdown source, updates the open CodeMirror document when the page editor is
open, and persists to the server when no editor is open. Errors revert the UI.
- Update docs and AGENTS.md conventions accordingly.
Slug inputs in the site editor now filter as you type via slugify.js
(built on the transliteration npm package: unicode incl. asian scripts
folds to ASCII, spaces become hyphens, anything outside [a-z0-9-] is
dropped) instead of erroring on commit. A new page left with an empty
slug gets one derived from its title. Server-side, _check_reserved
additionally rejects reserved root file names (robots.txt, ads.txt,
sitemap.xml, openapi.json, favicon.ico, site.webmanifest) with a
human-readable reason, which the editor surfaces in its error line so
the row can be edited and committed again; those URLs are also never
looked up as content when serving.
Enforced at three layers: the site editor validates slug inputs before
committing (rename and new-page rows), the API rejects such paths in
_check_reserved (page save/delete, structure moves, editor socket), and
the content catch-all 404s them without a tree lookup.
Build with assetsDir: '_/assets' so hashed assets land under
frontend-build/_/assets/ and favicon.ico (from frontend/public) at the
build root, then serve the whole build directory at the site root again
(frontend.route(app, "/"), cached="/_/assets/"). The explicit
favicon route is dropped — the Frontend serves it as an ordinary
unhashed (no-cache) file. Manifest paths now carry the _/assets/
prefix, so views.py only prepends a slash.
Reorganize the URL space so all machinery lives under /_/: the Vite
build (now with assetsDir: '', two ES-module entries and hashed shared
assets: style.css, pygments.css, banner.svg, fonts moved from
pagerite/static to frontend/src/assets) is served at /_/assets via
frontend.route(app, "/_/assets") with cached="/", and the admin
shell moves to /_/admin, leaving only "_" as a reserved top-level
slug. A /favicon.ico route serves the file Vite copies from
frontend/public. In dev, Vite proxies content pages and /_/admin to
the backend.
Editor changes: drop the noisy status line for a save-error indicator,
reconnect the WebSocket with exponential backoff, and re-establish the
connection from send() when it has dropped. The socket connects when
an editor is opened (they mount on pen click), not before.
FastAPI backend rendering HTML with html5tagger, content persisted in a
kanta database and rendered per request. Vue only for the editing tools
(page editor over a WebSocket, site/structure editor); public pages are
plain HTML with fetch navigation. No auth: single trusted author.