- 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.
16 KiB
16 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/docsbelongs to our content. Our own routes (content pages,/_api/...,/_f/...) are registered BEFOREfrontend.route(app, "/")is called: fastapi-vue inserts its file routes at the position whereroute()was called (duringload()in the lifespan), so anything defined earlier wins. The one exception is the content catch-all/{path:path}, registered AFTERfrontend.route()so that built frontend assets still take priority over content slugs. TheFrontendis constructed withspa=Falseexplicitly: it only serves the built files without a catch-all. The build mirrors the URL space — hashed immutable assets under/_assets/,favicon.icoat the site root — and anindex.htmlin 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.menumaps top-level slugs toNodes, each withchildrenkeyed 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.contentis 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 fractionalNode.orderkey: a moved item gets a fresh key relative to its new siblings, all others keep theirs.resolve/find_slotwalk the tree by path; moves are slot detach/attach carrying the whole subtree. Legacy flatData.pages(pre-tree databases) migrates intomenuon startup. The app owns theDataobject; reads are plain attribute access, writes inkanta.transaction(...).Data.filesis 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.banneris a raw trusted HTML snippet for the header banner (img, styled div, canvas+script...); empty inherits from the node's ancestors (front page last), then the active theme's banner artwork: an inline SVG frompagerite/themes/{theme}/banner.svg, inlined into#page-bannerby the backend only when no user banner applies (so it is recolorable from the theme CSS viavar(...)and never fights user designs; the base stylesheet falls back to a plain gradient).Data.versionis bumped on every write and embedded in page ETags so nav-affecting changes invalidate caches.Data.brandis the site name (header link +<title>suffix), editable in the site editor via/_api/settings; empty = no header link and no<title>suffix.Data.themeis the active theme name (empty = none/base only); themes live infrontend/src/assets/themes/{theme}.Data.custom_cssis raw trusted CSS injected inline in every page<head>(idpagerite-user) and swapped during fetch-navigation; editable in the site editor. Font picks (heading/body/brand) in the site editor are stored as plain:rootrows incustom_css(--font-body: var(--font-source-sans);format — parsed out and rewritten on change, the:rootblock added/removed as needed), referencing the per-family variables (--font-source-sansetc.) from pagerite.css; the base stylesheet's--font-branddefaults tovar(--font-heading).markdown.py— markdown-it-py renderer (html passthrough + attrs, footnote, deflist, tasklists plugins). Custom image rule: relative srcs resolve against the page path, titled images become figures.views.py— the shared page layout as an html5taggerTemplatewith placeholders (Title,Brand,Banner,Nav,Sidebar,Main), nav rendering straight from theData.menutree (siblings sorted byNode.order; nav links to content-less labels point at their first child viafirst_leaf), and page/404 rendering. If the markdown contains its own h1, the page title is NOT rendered as an additional h1 (it still supplies
- Server run by CLI entry point