Repository navigation
v0.2.3
Adds a feed kit: a site may declare one content stream, served as RSS 2.0,
Atom 1.0 and JSON Feed 1.1 at /rss.xml, /atom.xml and /feed.json.
A feed is not a search ranking signal, and this does not pretend otherwise. What
it buys is real but narrower: Google accepts an RSS or Atom file as a sitemap
and treats it as the recency signal beside the full inventory, and syndication to
readers and aggregators produces inbound links, which are.
Feeds
WebServer::feedis omittable — a site with no stream serves no feed
documents and emits no autodiscovery links, so existing sites are untouched.
Calling it withoutfrontendfails the boot rather than silently serving
nothing.- Entries are supplied, not derived: a blog is a
dynamic_page_group, whose
template data is built per request, so there is nothing to read at boot.
Supply them in any order and any number — sorted newest-first, truncated to 20. - Channel metadata (author, language, icons, copyright, self and alternate
links) is derived from what the site already declares. FeedEntryis a named-field struct, so shipping a teaser feed is something you
type (content_html: None) rather than something you forget.
Correctness
- Content URLs are rewritten, not copied: cache-busted, then absolutised, with a
bare#fragmentresolved against the post. A reader renders your entry on
feedly.com, where every relative URL is broken. Rewriting is confined to real
elements, so a post that shows markup in a code fence is left alone;
srcsetfails the boot rather than being half-rewritten. - Characters outside XML 1.0's
Charproduction are stripped, not escaped — no
escape exists for them, and one0x0Brejects an entire feed on a validating
parser. - Documents are byte-stable: dates come from the newest entry, never the
clock, and ordering ties break on path. A redeploy that changed no post
re-sends nothing to any subscriber.
Serving and discovery
- Feeds bypass the never-cache layer, which strips
ETag/If-None-Matchand
stampsno-store— wrong for the most-polled document a site serves. They
carrypublic, max-age=1800, a boot-computed strongETagand
Last-Modified, and answer a conditional request with a bodiless304. - Three
rel="alternate"links on every page, ordered RSS, Atom, JSON. robots.txtnames only/atom.xml, between the sitemap index and the chunk
lines — Atom has a real per-entry<updated>, RSS carries publication dates
only, and JSON Feed is not a supported sitemap format.
Also
- Every resource and its cache tier is named once at boot, logical path to
hashed path, so a tier that failed to wire up is visible in the first screen
of a deploy's logs. Pages::paths, so a feed advertising an undeclared page fails the boot rather
than linking a 404.bin/,cache-buster.json,static/script/generated/and.DS_Storeare
excluded from the Docker build context, so a host-generated manifest cannot
reach the image and feed the JS build a stale one.
Upgrading
No consuming project needs to change anything. Frontend::build gained a
parameter and FrontendRuntime, WellKnown and TemplateData each gained a
field, so the public surface did move — but all of them are constructed by the
library and read by the layout, and no project builds one. Kept a patch bump
deliberately so existing 0.2.x pins pick the feed kit up without an edit.
The TypeScript library is untouched and stays at 0.1.1.
Full Changelog: v0.2.2...v0.2.3