JS-running crawlers (Googlebot, GoogleOther, Applebot) execute pagerite.js
and send navigation pings, registering as visitors. Pings whose User-Agent
matches _is_bot_ua (any "bot" token plus listed exceptions) are now
ignored, so their document GETs flush to the crawler list as intended. No
source verification: a spoofed bot UA merely lands in the crawler stats,
and path-based abuse classification catches scanners regardless.
Idle-time link preloads from pagerite.js were queued as pending crawler
hits and flushed to the crawler list whenever the user navigated more than
10s later, so real visitors' subpage loads showed up as crawler hits.
Preload fetches now carry an x-pagerite-preload header and the document
GET handler skips tracking for them; the ping sent on actual navigation
does the counting.
load() queried the live document for the pagerite:analytics-src meta,
but the swap never touches <head> — the meta only exists in the fetched
doc, so the app never mounted unless /_a was loaded directly. Also cache
the fetched HTML so the post-swap preload doesn't re-GET the page we
just navigated to.
Add server-side visit analytics collection, a public-page ping endpoint,
and a full-screen AnalyticsView for admins.
Backend:
- Add pagerite/analytics.py: Analytics/Visit model, Store, and persistence
- Wire /_a ping endpoint and GET /_api/analytics into pagerite/app.py
Frontend:
- Add full-screen AnalyticsView with visitor charts and transition map
- Add VisitorCharts and TransitionGraph subcomponents
- Add analytics JS helpers in frontend/src/analytics/
- Send navigation pings from frontend/src/pagerite.js
- Mount AnalyticsView from frontend/src/main.js
- Document the feature in docs/analytics.md and update AGENTS.md
- Fix banner artwork sizing: explicit 100% grid track so children
stretch instead of resolving height:100% against a content-sized row
(SVG intrinsic ratio bloated the row, cropping the artwork's bottom).
Bottom-anchor via object-position, transform-origin and YMax slice.
- pagerite.js: in-memory page cache — preload every visible internal
link once, serve navigation from memory without fetching; editors'
loadPlain keeps the cache in sync (pagerite:page-fetched).
- Structure editor: delete pages directly, no two-step confirmation.
- New 'stars' banner design (drifting starfield) alongside 'eyes'.
- Rewrite seed content: welcome page, three-level docs section covering
all Markdown features (source + rendered), showcase hierarchy with
image positioning and a simple leaf-page banner example.
Paskia does not support being iframed; the login/profile buttons are now
plain anchors, and a pageshow handler re-probes auth when history
navigation restores a cached page. Drops the paskia JS dependency.
- One mounted shell with four kept-alive tabs; pens open/switch tabs,
closing only hides the shell so unsaved page-editor state survives
until a real reload; admin panels never reload the page, regions are
refreshed in place via the shared swapdoc.js helper
- SiteEditor split: site settings vs. the new StructureEditor tree tab;
BannerEditor split out of the old site editor
- Article editor format bar (bold/italic/code/link/table/image) with a
table size picker and Ctrl/Cmd-B/I/S bindings; fenced code block on an
empty line; icon-only save button disabled (saturate(0)) when clean
- Media uploads use icon buttons; favicon upload by clicking the preview
tile; placeholders reserved for actual defaults in effect
- Banner/site pens and auth buttons grouped in an .editor-pens container;
page pen no longer shown to anonymous visitors
- While editing, window scroll is locked, the panel exactly fills the
available window height, and only #main scrolls; editor scroll drives
the article
- Banner design inherit label names the design and its true source;
backend banner_design_source excludes the node's own setting and the
doc payload carries banner_design_inherited
overflow: overlay is dead in current Chromium, so an appearing native
scrollbar shifted the layout again. OverlayScrollbars replaces it with
floating auto-hiding scrollbars that never reserve space (native window
scroll APIs unaffected on a body target). Themed via --os-* variables in
pagerite.css; the html scrollbar-color styling stays as no-JS fallback.
(package-lock.json is gitignored.)
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.
Replace the fixed 100rem two-column breakpoint with 'columns: 30rem' so
CSS fits as many >=30rem columns as the article's width allows, and let
long (.multicol) articles grow past the 78rem cap (4:1 share against the
gutters, keeping them symmetric for the .wide breakout math). Also treat
mid-article h1s as full-width column separators like h2s.
- 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.
- Base stylesheet: per-family font variables (--font-source-sans etc.)
with functional slots referencing them; --font-brand defaults to
var(--font-heading). Restore Fraunces/Literata faces dropped in the
font-set swap
- Site editor: stylized A button (always Fira Code) opens a font panel
with body/heading/brand tabs; options render their own name in the
candidate font at the target's size/weight; clicking the current pick
clears it. Picks are stored as plain :root rows in the custom CSS,
parsed and rewritten on change
- Base banner: drop black text-shadow on light background; legibility
via a bottom scrim fading artwork into --bg. Purple keeps its own nav
shadow
- Headings no longer accent-colored in base (moved to purple theme)
- Keep #pagerite-user custom CSS last in <head> at every mutation point
so its :root rules win equal specificity (dev injects base/theme
styles after the server-rendered tag)
- 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.
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.