- 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.
Rework the site structure tree's drag-and-drop to match Sortable's native
model instead of mid-drag drop zones:
- Drop the dashed empty-list drop zones that appeared on drag start
(flicker, space reservation, and mis-targeted drops: the last item's
child zone sat exactly where 'end of parent list' was).
- Every non-empty list (and the root) ends with a non-draggable ➕ footer
row (vuedraggable #footer slot): click to start a new page at that
level, and while dragging it is the 'end of this list' drop target.
It renders last even mid-drag via flex order, since Sortable appends
end-of-list previews after it in the DOM.
- Dropping ON the lower part of a row makes the page that row's first
child: the child list's container invisibly overlaps its own row's
bottom (negative margin + equal padding, no layout effect) and becomes
hit-testable only while dragging. A row's exposed top strip remains
the 'sibling before' target. Leaf rows work too, creating a sublist.
- Indentation is structural (each nested list margin-indents itself), so
a dragged row previews its whole subtree at the target list's depth.
Rows get a little vertical padding to widen the drop zones.
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.