- pagerite/masking.py: technical spans (code, URLs, {placeholders}, attrs,
footnote/link labels, container names, HTML tags) become numbered sentinels
for the LLM round trip; results are restored by number and rejected when a
sentinel is mangled (skipped for the rest of the run, stays pending).
Chunks with no prose left after masking are never dispatched.
- Data.translate_key -> translate_keys dict (key -> name); the first key is
generated at bootstrap, result transactions record the key as user=, and
startup logs the service URL(s) via translate.log_service_urls.
- Fix /_translate proxying through the Vite dev server (missing slash).
7.1 KiB
7.1 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).
Layout
Pagerite is a CMS. See docs for the full design and implementation details. Key files for code changes:
pagerite/— Python backend package (hatchling build target).app.py— FastAPI app and route registration.data.py— msgspec Structs for the kanta database.chunks.py— block-level Markdown chunking and content-hash keys for the chunk stores (docs/migrate.md).i18n.py— language selection, translation assembly (chunks + patches).translate.py— translator service protocol (msgspec structs) and pending/store core for the/_translate/{key}WebSocket (docs/localization.md).migrations.py— kanta migrations (migrate_vN); ALL schema/storage upgrades live here (raw state dict before struct decoding), never in the app lifespan: v1 moves legacy in-db file blobs to the on-disk store and rebuilds the legacy flatpagesas the menu tree, v2 rewrites/_f/{hash}.extimage links to the extension-less form, backfills AVIF/WebP/JPEG derivatives on disk and drops the obsoleteversionfield.markdown.py— markdown-it-py renderer.views.py— shared page layout and rendering; theme/user-font resolution acrossTHEME_DIRS/FONT_DIRS(cwd, site, platform data roots, then built-inpagerite/themes/, seedocs/themes-and-assets.md).seed.py— demo content, written only on first database creation.analytics.py— visit analytics collection (seedocs/analytics.md).
frontend/src/— Vue editor and public-page JS entries.main.js— Vue editor app entry.analytics-main.js— analytics page entry (mountsAnalyticsViewat/_a).pagerite.js— public page entry.assets/— base CSS, Pygments styles, fonts.
scripts/devserver.py— dev server with auto reload (the user mostly uses this; avoid running the server yourself, ask the user to test).scripts/translator.py— Seed-X translator service client for the/_translate/{key}socket (reference client, runs in its own uv env via PEP 723).
Server run by CLI entry point uv run pagerite (no auto reloads, build needed). Dev mode is scripts/devserver.py (auto reloads, no build needed).
Toolchain
- Python >= 3.14, managed with uv. Dependencies:
fastapi[standard],fastapi-vue,html5tagger,kanta,markdown-it-py,mdit-py-plugins,platformdirs,pygments,tracerite; dev group hashttpx. Run anything viauv run ...(the venv is.venv). - Key libraries:
- html5tagger — all HTML generation (
E,Document,Template,HTMLfor trusted/raw HTML).- To create stand alone pages, begin with
doc = Document(...)that gives a HTML5 page header - Chain with
doc.p("text").br: every attribute access creates element to doc (returning self), calls add content to current element. - Closing tags are not used where optional, e.g. no
</p>or</li>is ever included in output. Due to this proper "nesting" of content is NOT required and should be avoided. Where needed, () directly after tag define attributes and content INSIDE the element, then close the element.with doc.ul:and such may be used for larger chunks. - Prefer building directly on one builder with
withblocks (recursing inside a with block for hierarchies) over preparingE.snippets into variables and composing them. Notewith doc.li:alone fails (lihas an optional end tag) — usewith doc.li.ul:style chains, ordoc.li.a(...)followed by a nestedwith doc.ul:block. Template(builder)freezes a builder with Capitalized attribute placeholders (e.g.E.Title,doc.main(E.Main, id="main")); calling it fills the slots with escaping — passHTML(...)for raw HTML. Passing a list to a template slot expands it; passing a list to a normal builder call does NOT (spread it:E.ul(*items)).- To create plain HTML snippets use
E.div(E.p("content"))etc using theEempty builder.
- To create stand alone pages, begin with
- kanta — asyncio-native embedded database:
Kanta(filename, data)root object,transaction,flush, snapshot/replay-log persistence.async with Kanta(Data(),...) as kanta:(or await kanta.open/close)with kanta.transaction(...) as data:- transactions only for writesdatamay be referenced directly to read anywhere and to modify in transactions (as datais just a shorthand access)- Data structures should be msgspec.Structs, where JSON restrictions do not apply (we can use
bytes,UUID,datetimeetc. even as dict keys)- We prefer objects rather than lists, as this works better in change diffs. E.g.
dict[str, True]where the keys indicate presence and always have valueTrue.
- We prefer objects rather than lists, as this works better in change diffs. E.g.
- Maintaining and owning the app's own
Dataobject is preferable; Kanta never copies this, only edits in place - Note: besides opening it every access is immediate direct variable access: no
await, no locks, no delays
- fastapi-vue — template glue for serving/building the Vue frontend; keep its integration points (
Frontend, build hook) intact. - platformdirs — platform user/system data dirs for the theme and font search roots (
views.THEME_DIRS/views.FONT_DIRS; usesite_data_dir(..., multipath=True), notsite_data_path, which collapses multipath). - markdown-it-py — Markdown rendering with
html=Trueraw passthrough; mdit-py-plugins for footnote/deflist/tasklists/attrs; in-body h1/h2 headings get auto slug ids + self-links when the body has 3+ of them (python-slugify, mirroringslugify.js); Pygments for server-side code highlighting (nowrapspans, styled byfrontend/src/assets/pygments.csswhich maps token classes 1:1 onto the--code-*variables; light/dark palette sets live inpagerite.cssand resolve vialight-dark()from the theme'scolor-scheme— themes pick a set, not individual colors).
- html5tagger — all HTML generation (
Conventions
- Keep dependencies minimal; add via
uv addand mention it. - The public URL space belongs to content (pretty slugs at root). Reserve only
/_for the machinery (/_api/,/_f/,/_assets/), plus/favicon.icofrom the build. Slugs are lowercase ASCII letters, digits, hyphens and underscores[a-z0-9_-](the site editor filters input live viaslugify.js, built on thetransliterationnpm package — unicode folds to ASCII, spaces become hyphens; an empty slug on a new page is derived from its title), may not begin with_or., and such URLs are never looked up as content. - No auth in core code; the SSO/reverse proxy gates all of
/_api(forward-auth) and owns/auth/(login/logout, session validation). Pages render identically for everyone; pagerite.js adds the editing UI only after the auth server validates the session. The one keyed exception is/_translate/{key}(translator service;Data.translate_keys, see docs/localization.md). - Update the relevant MarkDown files when architecture, tooling, or conventions change.