Files
pagerite/AGENTS.md
T
LeoVasanko 512ed91b31 Bottom-anchor banner artwork, instant navigation via in-memory page cache, seed/stars/structure tweaks
- 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.
2026-08-19 01:40:26 +00:00

22 KiB

AGENTS.md

  • Do NOT test, run the server, write tests etc.
  • ESPECIALLY DO NOT make repros, do NOT install Playwright etc.

Please instead ask the user to see from dev tools what you need, e.g. to look up something in DOM or log. Use console.log for debugging where needed (and otherwise for permanently kept useful messages in the app).

What this is

Pagerite: a single-user CMS/blog. FastAPI serves HTML rendered in Python with html5tagger; content is persisted in a kanta database and rendered on the fly per request. Vue is used only for interactive bits (editing tools), not for the public pages. See docs/design-principles.md for the design.

Layout

  • pagerite/ — the Python backend package (hatchling build target).
    • Server run by CLI entry point uv run pagerite (no auto reloads, build needed)
    • Dev mode scripts/devserver.py (which the user mostly uses for auto reloads, no build needed)
    • Avoid running the server yourself, ask the user to test
    • app.py — the FastAPI app. FastAPI's built-in API docs are disabled (docs_url/redoc_url/openapi_url=None) because /docs belongs to our content. Our own routes (content pages, /_api/..., /_f/...) are registered BEFORE frontend.route(app, "/") is called: fastapi-vue inserts its file routes at the position where route() was called (during load() in the lifespan), so anything defined earlier wins. The one exception is the content catch-all /{path:path}, registered AFTER frontend.route() so that built frontend assets still take priority over content slugs. The Frontend is constructed with spa=False explicitly: it only serves the built files without a catch-all. The build mirrors the URL space — hashed immutable assets under /_assets/, favicon.ico at the site root — and an index.html in the build would become a / route, so leave it out of the build to keep / ours.
    • data.py — msgspec Structs for the kanta database. The site structure is a tree: Data.menu maps top-level slugs to Nodes, each with children keyed by slug — the URL path is the slug chain. The front page is whichever top-level node has slug "" (parallel to the other main level pages, not their parent); it cannot have children, and renaming its slug away leaves no front page ("/" redirects to the first nav item). Node.content is the Markdown page, or None for a pure category label whose URL renders a placeholder page (while nav links to it point at its first child); every label's title and slug are editable. Siblings order by the fractional Node.order key: a moved item gets a fresh key relative to its new siblings, all others keep theirs. resolve/find_slot walk the tree by path; moves are slot detach/attach carrying the whole subtree. Legacy flat Data.pages (pre-tree databases) migrates into menu on startup. The app owns the Data object; reads are plain attribute access, writes in kanta.transaction(...). Data.files is a content-addressed store (blake3[:12] + extension) mapping file names to bytes, served at /_f/{name} with immutable caching; pages reference files by absolute /_f/ URLs so hierarchy moves never break them. Node.banner is a raw trusted HTML snippet for the header banner (img, styled div, canvas+script...); empty inherits from the node's ancestors (front page last). It is rendered AFTER the banner design's artwork, so author code (e.g. a <style> override) always wins over the design's own styles. Node.banner_design picks a banner design: a theme folder name whose banner.css styles it and whose banner.html (arbitrary markup: canvas + style + script) or banner.svg supplies the inline artwork (wrapped in div[data-design]); "" = explicitly no design, None = inherit (nearest ancestor, front page last, then the active theme's own design if it ships banner.css/banner.svg/banner.html). The design's banner.css is linked in <head> (id pagerite-banner) between the theme and the custom CSS. Data.version is bumped on every write and embedded in page ETags so nav-affecting changes invalidate caches. Data.brand is the site name (header link + <title> suffix), editable in the site editor via /_api/settings; empty = no header link and no <title> suffix. Data.brand_html is raw trusted HTML replacing the brand link entirely (rendered in a #brand div on top of the banner, next to the nav) — site-wide, not per-page like banners; edited in the site editor with image/video upload into Data.files. Data.theme is the active theme name (empty = none/base only); themes are folders in pagerite/themes/{name} containing theme.css and/or banner.css (+ banner.svg artwork and any extra assets the CSS references, like summer's grass.svg), served by the backend at /_themes/{name}/... — read from disk per request (etag by mtime), never built, so on-disk edits show on the next page load even in prod. The theme selector and banner-design selector enumerate these folders via GET /_api/settings. Data.custom_css is raw trusted CSS injected inline in every page <head> (id pagerite-user) and swapped during fetch-navigation; editable in the site editor. Font picks (heading/body/brand) in the site editor are stored as plain :root rows in custom_css (--font-body: var(--font-source-sans); format — parsed out and rewritten on change, the :root block added/removed as needed), referencing the per-family variables (--font-source-sans etc.) from pagerite.css; the base stylesheet's --font-brand defaults to var(--font-heading). Data.favicon names a file in the content-addressed files store, uploaded/cleared in the site editor via PUT/DELETE /_api/settings/favicon; when set it is linked as <link rel="icon"> on every page, otherwise browsers fall back to the build's /favicon.ico by convention.
    • markdown.py — markdown-it-py renderer (html passthrough + attrs, footnote, deflist, tasklists, admon, gfm_autolink, sub/superscript plugins; typographer + breaks on). Custom image rule: relative srcs resolve against the page path; an image standing alone in its paragraph becomes a figure (captioned when titled), while inline-with-text images and raw HTML stay plain. A {dates} line expands to the article's published/updated dateline (p.dateline, from Node.created/ modified; left literal in previews of unsaved pages).
    • views.py — the shared page layout as an html5tagger Template with placeholders (Title, Brand, Banner, Nav, Sidebar, Main), nav rendering straight from the Data.menu tree (siblings sorted by Node.order; nav links to content-less labels point at their first child via first_leaf, the first published descendant with content), and page/404 rendering. If the markdown contains its own h1, the page title is NOT rendered as an additional h1 (it still supplies