One idea at a time: definition, necessity, priority, layer, recommendation. Every row is now shipped, scheduled, parked with a trigger, or dropped with a reason — so both tracking files are gone rather than left as a parallel backlog. Roughly a third already shipped. A quarter needed only a theme fragment or CSS, including several the list assumed were features: arbitrary `theme.*` frontmatter attributes already reach templates through Extra (verified), native lazy loading already ships and is deliberately absent on lead figures, and in-page timelines are a theme-defined container. roadmap.md gains an "Order of work" — nine bodies of work in sequence, because the sections this review added had accumulated without one. Item 0 is a decision, not work: core sits at ~2965 of 3000 and logging alone wants the remainder, so discover.go moves out *and* the ceiling rises with an ADR. feed.go and web/extras.go cannot move; they are resolver cases, and three parked items now wait on that same seam decision. Two ADRs came out of it. ADR-0083: the dependency rule described a practice it forbade. "Stdlib first, always" and "usually 40 lines of stdlib" would, read literally, have argued against all five modules here — every one is a format or algorithm somebody else specified, and their allowlist comments say so. Surfaced when this agent proposed hand-rolling Reed–Solomon for QR encoding. The test is now whether you can verify it, not whether you can write it, with compute-versus-hold as the counterweight. conventions.md and allowed-deps.txt contradicted it and were realigned. ADR-0084: the Effect primitive said every Effect is "idempotent and re-runnable from scratch" — true of derivatives, false of anything outbound, since a sent message cannot be recalled. Split into artifact-producing and outbound, the latter idempotent only against a delivery ledger, at-least-once with a dedupe key, and ledgers in a -state directory distinct from the disposable cache. Asked for as forward-looking design so webmentions need no redesign; recorded as a shape rather than built, the way extensions.md records the Extension struct. Also fixed a STATUS claiming Effects were "not buildable yet" when the derivative pass has inhabited them since ADR-0042. Findings that were defects rather than ideas: the Atom feed emits no <author>, which RFC 4287 requires. HTML comments in content are published verbatim — verified — so anything commented out is already public, and a check warning is scheduled rather than the engine deleting authored bytes. `Page` has no Date, so a theme cannot mark up dt-published or show an article's own date. A frontmatter naming rule after this agent proposed `archive` alongside the accepted `archived`: no near-homograph keys, and name the benefit rather than the vendor. reference/microformats-and-indieweb.md is new and is the reference asked for — microformats2 properties and IndieWeb rel values mapped to where a theme puts them, with spec URLs, marked scheduled. rel="me" is the highest-value lowest-cost item in that space and needs no endpoint at all. Four things this agent got wrong and the human caught: asserting "the engine never fetches" from a sentence scoped to the content repo, twice; inventing a data-sovereignty argument for an item he had filed as "a cool geeky thing", when the raw Markdown in his git already is the sovereignty; the archive naming; and claiming microformats could be fully implemented today. 15 files, +556/-385. No code changed. Nothing was scheduled that this agent could not name a consumer for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15 KiB
Exploration catalog
Status: catalog — the verdicts are inside, one row each
Possible futures. Nothing here is committed. Presence in this list is not permission to build.
★ = highest fit-to-effort for this project's identity and values.
The leaf/trunk test — apply before any of this gets built
Definitions: architecture.md invariant 7. Leaf → buildable when wanted. Trunk → waits for an
explicit human decision, in an ADR, on purpose.
Known trunks: ActivityPub, IPFS, Solid, hosted annotation servers, CRDT-backed editing. Sovereignty test for any of them: if this vanishes, do I lose data, or only convenience?
Use /leaf <topic> to get a verdict written into this file.
Verdicts
The human's 130-item feature list was reviewed one item at a time on 2026-08-02 and the file holding it is
gone: everything is now either shipped, scheduled in harness/roadmap.md, parked below, or dropped below.
Roughly a third already shipped, a quarter needed only a theme fragment or CSS, and nine bodies of work were
scheduled. Two ADRs came out of it — ADR-0083 (dependencies) and ADR-0084 (Effects).
| Body of work | Verdict | Reduces to | Note |
|---|---|---|---|
| Corrections and updates per post | rejected in its cheap form, parked in its real one | Effect | corrections-from-history.md. A hand-maintained frontmatter list misleads where none would not; deriving it from repository history waits on the Effect runner and on whether the engine may read git at all. Per-post edit history is parked in that same file |
| Per-piece template variants | the highest-value item in the list | View | Scheduled, not parked: the second concrete use that earns the View primitive, and roadmap.md closes Arc 2 when it lands |
| Bengali morphology for search | original work, not a sub-task | — | bengali-search-morphology.md. Exact-form search is scheduled without it and documents its own absence |
Parked, with triggers
Each of these was reviewed and deliberately not scheduled. The trigger is what would change that — a condition, not a mood.
| Item | Trigger | What was decided |
|---|---|---|
| Date-grouped listings | wanting any one of: a cross-page timeline, a yearly wrap-up, a date-grouped archive | One partition beside the existing section one serves all three, following ADR-0046's rule that the engine partitions and the theme decides whether it looks grouped. Individually none justifies the work |
| Inline code highlighting | writing enough technical prose to feel the lack | Syntax settled in advance: a trailing attribute block — `code`{lang=python} — never a marker inside the span, since a code span is literal and this engine's recurring defect is new syntax eating prose. Matches Pandoc/Djot and the existing ## Title {#id} form. Wants its own ADR, because authored syntax is a contract with the author's files |
| Ruby annotations for glosses | the free path failing | Works today via raw HTML. The zero-cost path first: a theme rendering existing *[TERM]: abbreviations as <ruby>, since a gloss above versus on hover is presentation over data already collected. Caveat: abbreviations expand at every occurrence. Standing note — the inline delimiter budget is nearly spent (~ ^ = * all taken), which argues against inventing marks |
| Archive.org snapshot links beside external links | something wanting a pass over authored anchors more than this does | Needs a parse-phase walk over link nodes; a theme cannot do it. Modest value. Distinct from submitting links, which is scheduled |
| JSON Feed | a reader or client you actually use asking for it | Atom covers every consumer. /feed.json site-wide is an exact path and could be a feature cheaply; section and tag scopes are resolver cases, so decide it with that seam |
| Content as data / a personal JSON API | wanting it for its own sake | The human's own framing: "a cool geeky thing", may never be used. The sovereignty argument for it is wrong — the raw Markdown in his git already is the sovereignty. /all.json site-wide is an exact-path escape; per-bundle is resolver-gated |
| oEmbed | building the capture feature below | Reframed: not a render-time embed, which ADR-0080 refuses, but capture-time metadata discovery — how a snapshot learns a URL's title, author and thumbnail |
| Static copies of external content | wanting to quote something that may vanish | Engine-produced, not by another tool. Shape decided: a khosra snapshot subcommand following scaffold's precedent for writing to the site root, into the bundle rather than the disposable cache since a snapshot must outlive its source, storing sanitised text, image and attribution — never the remote HTML, which would execute, because site-root content is trusted by location (ADR-0060) |
| Image processing, as one cluster | any one of them mattering | Cropping (a parameter on the derivative pass, folded into the content hash), EXIF display (the engine already inspects every image for dimensions, so this extends a shipped pass), a view-full-resolution link (nearly free — the original is already src), before/after comparison (CSS-only variants exist; the draggable one is JS and refused), and the AVIF gap (no Go decoder, so it passes through unresized). Clustered at the human's request rather than scattered |
| POSSE back-links | wanting a page to link its own copies | Syndication itself is absorbed by the scheduled notify work — n8n posts to platforms. The residual is u-syndication: recording where copies live, which needs somewhere to put the URLs |
| Micropub | wanting to publish from a phone | The endpoint is easy; IndieAuth is the bulk. Persistence is not a blocker and the resolution is recorded: the engine writes through os.Root (scaffold's precedent) and the operator's existing pull/commit picks it up, so the engine still never touches git |
| "View as Markdown" / raw source per post | the comment problem being solved | ?raw on the existing bundle URL avoids inventing a resolver path and reuses the convention extras already use. Blocked on a leak: HTML comments in content are published verbatim today (verified), so serving raw source would expose anything commented out. The scheduled check warning is the prerequisite |
| IndexNow ping on publish | caring about Bing and Yandex | An outbound Effect, nearly free once the dispatcher exists. Honest limit: Google ignores IndexNow, so it is cheap and marginal rather than cheap and valuable |
| Blogroll and OPML export | wanting others to subscribe to your whole list | The blogroll is content and free today. OPML is one marshaller at an exact path, so a feature with no core growth |
| Random post | wanting a discovery affordance | One exact-path route, architecturally free, and honestly a toy. Note: randomness deserves the containment time.Now() already gets — verify.sh confines the clock to clock.go because ambient nondeterminism hides staleness |
Dropped, with reasons
Kept because a one-line reason is the cheapest defence against relitigating a decision in four months.
| Item | Why |
|---|---|
| EPUB generation | Calibre, Pandoc and percollate already convert web to EPUB. The engine's real contribution is structure — sequences already publish reading order — so the revisit trigger is "external tools produce a bad book", not "want EPUB" |
| Gemtext output | Readership is approximately zero, and its only real value was proving a View can target something other than HTML. Not worth carrying for that |
| Static export | Cannot serve Arc 3's write endpoints or in-memory search, so it is a second permanently crippled product rather than a hedge. The hosting insurance it appears to buy already exists: Markdown in git, one portable binary |
| Incremental rebuilds | A whole page renders in ~63µs. This solves a build measured in minutes, a scale this site will notice instantly if it ever reaches |
| Live reload | Buys not pressing refresh, at the price of JS or SSE on every page — and the rebuild already lands within one poll |
| Self-hosted analytics counting | Write-on-read means state and IO on the hot path. Access logs contain every fact a counter would, cost zero engine code, and leak nothing. goaccess answers it in an afternoon |
| Incoming rebuild webhook | Polling already covers it, so this spends a write endpoint, the untrusted boundary and rate limiting to save two seconds |
| Outgoing chat webhooks as a general system | Absorbed: the scheduled notify Effect emits events and n8n owns routing, formatting and per-platform posting. A configurable per-target rule engine in the engine would serve one caller forever |
| Twitter cards, meta description | Theme work once .Summary exists; Twitter reads the OpenGraph already emitted |
| Design tokens, print stylesheet, columnar layout, in-page timeline | All CSS or a theme-defined container, in a file the engine does not own (ADR-0023) |
| Copy-to-clipboard on code blocks, Cmd+K palette | JS on ordinary pages, refused by ADR-0080. The palette also needs search first, and the search page is one keystroke from the address bar |
WebSub (rel="hub") |
Push instead of polling for feeds, at a scale this site does not have |
| Writing stats page | "Just for fun" by the human's own framing, the same standard that dropped readability scoring |
Catalog
IndieWeb — Micropub★, Microsub, Webmention, WebSub, microformats2, IndieAuth, rel=me, PESOS/POSSE, Bridgy and Bridgy Fed, Vouch.
Protocols — Gemini + gemtext★, Gopher, Nostr (NIP-23 long-form), ActivityPub (trunk), AT Protocol, SSB, Solid (trunk), IPFS (trunk), Hypercore, content negotiation (one URL → many formats).
Small web and discovery — webrings, guestbooks, 88×31 buttons, finger, tildeverse,
OPML blogroll★, full-text feeds★ (for indie search indexing: Marginalia, Kagi Small Web,
ooh.directory, indieblog.page, feedle, powRSS), random / "surprise me", /now pages.
Reading and annotation — Web Annotation + Hypothesis (hosted = trunk), reading progress, interlinear and parallel translation.
Permanence — outbound-link archiving★, WARC self-archive, Memento over git, permalink discipline, exposed revision history.
Identity and integrity — rel=me, Keyoxide, signing (age/minisign/PGP), C2PA, DIDs.
Feeds and data — RSS/Atom, JSON Feed, h-feed, OPDS (publication catalog), JSON-LD and schema.org, OpenGraph, Dublin Core, sitemaps, RSS-to-email.
Page as program — Ink★, Twine, Inform 7, PICO-8/TIC-80, Godot HTML5 embeds, explorable explanations, TiddlyWiki and single-file artifacts.
Print — Typst, Pandoc, EPUB and PDF chapbooks, on-demand anthologies.
Client side — View Transitions★ (palette-shift-as-navigation), Houdini, Canvas, WebGL,
WebGPU, modern CSS, WASM, islands, HTMX, Web Components, PWA offline, prefetch,
accessibility and prefers-reduced-motion.
Build time — SQLite FTS5, Pagefind, sqlite-wasm + OPFS, Djot, Templ, libvips, ThumbHash and BlurHash, content-addressed and Merkle builds, Datasette, CRDTs (heavy, trunk).
Aesthetic — dithering and palette Stage★, ANSI/PETSCII, teletext, demoscene, e-ink View.
Values — robots.txt / ai.txt (training control), llms.txt (low proven impact),
sustainable and low-carbon web, privacy-respecting analytics or none at all, sovereignty,
sneakernet and QR offline distribution.
Standing notes
- Bengali tokenisation for search is the one item here nobody else will solve for you. Treat it as original work, not as a checkbox inside "add search."
- Micropub before an admin panel. It buys an ecosystem of existing editors for the price of one endpoint, and it composes with git instead of fighting it.
- Gemini output is nearly free once the View layer exists, and it validates the claim that a View can target something other than HTML. Good early proof, low cost.
Shapes recorded before anything asked for them
Moved out of harness/content-model.md, which now describes only what the parser accepts. Each is
intent, not a plan: the shape is decided by whatever eventually builds it, and being written here is not
permission.
Galleries, collections [spec]
Both are a Query over bundle metadata with a stable sort; neither justifies a new primitive. A gallery is a Query plus an image View. Related posts are a Query with a scoring function, not a stored graph.
Taxonomies [spec]
Two kinds, deliberately. Recorded intent (ideas/deferred-decisions.md), not built.
tags are one global namespace across every type. /tags/{tag}/ lists everything carrying the tag;
/{section}/tags/{tag}/ narrows it; listing views group results by type so a busy tag stays readable.
Cross-type discovery is the point — one tag spanning a comic, a poem and a photo essay is a feature here,
not noise. Term slugs are derived by locale and overridable by hand (ADR-0015).
Declared taxonomies are structural: a fixed term set that changes engine behaviour, declared on the
type — series for ordering, medium for art, genre for writing. These never enter the tag pool. The
test: a tag is free-form and cross-cutting, a declared taxonomy has known terms and drives behaviour.
Feeds follow the same shape: /tags/{tag}/feed.xml comes free from the same Query as the listing.
Time-dependent presentation [spec]
Anything derivable from a page plus the current clock is computed by a Stage, never stored in content:
an "this article is old" banner compares date to now, relative dates likewise. Because renders are
cached (ADR-0005), such a Stage also declares how long its output stays true — a banner until
date+threshold, a relative date for a minute — and the cached entry expires then. Future-dated
publication is the same shape seen from the other side: the page becomes reachable at a moment nobody is
requesting it.
Below the site level [spec]
Section-level and bundle-level settings are not built: a bundle's own frontmatter covers it, and nothing
reads a section override yet. The parked shape is site → section → bundle, nearest explicit value winning
(ideas/deferred-decisions.md).
| Level | Where it lives |
|---|---|
| site | site.yaml at the site root |
| section, and any enclosing section | that directory's _index.<lang>.md frontmatter |
| bundle | the bundle's own frontmatter |
The cascade carries stage toggles, view selection, taxonomy defaults, cache flags, and metadata
defaults — declared keys only, never arbitrary engine internals. Stages run on everything by default and
are switched off by a cascade key, not by a predicate inside the stage: a stage that finds nothing to do
returns early on content shape ("no images here"), which has nothing to do with type.
This is also how a template is chosen: type default, then section override, then the bundle's view
(ADR-0019).