Commit Graph
4 Commits
Author SHA1 Message Date
Claude Opus 5andbdeshi 4396303771 add khosra demo, and give the site a front page
The demo writes a whole site root that exercises every feature: two languages with
a fallback, a series with ordered chapters, a gallery, a figure, an include,
extras, tags across sections, a slug with an alias, an undated page, a draft, a
template override, static files and site.yaml. It generates its filler rather than
copying stored files, because nothing in this repository is content (ADR-0011) — and
that makes it a test of the engine rather than a fixture: anything khosra can do
that the demo cannot express is a gap.

Two things found by generating and then serving it, which is the whole point:

`khosra check` reported the demo's own series as mixing ordered and unordered
members. It was right — the chapter bodies *described* `order: 10` while the
frontmatter never carried it. The checker caught its own author.

And `/` was a **404**. ADR-0008 leaves the root engine-owned, which is right, but
"engine-owned" was never given an answer, so a visitor to the site's own address got
nothing. The root now lists every bundle, newest first, paginated like any other
listing, and 404s only when nothing is published. A hand-written home page stays a
separate decision, recorded as such.

Verified end to end: 23 files written, 12 bundles, 12 derivatives, `check` clean,
and every URL the demo promises answers — including the alias redirecting, the draft
hidden, and the front page rendering through the site's *own* template override.
2026-07-31 19:54:23 +06:00
Claude Opus 5andbdeshi 96313e4eda finish the reference theme, and the contract holes it found
The theme was supposed to need no engine work. It needed four things, which is
exactly what the audit said would happen if the contract had gaps rather than the
theme (ADR-0046) — a reader could not reach, from any page: another section, the
tags on the page they were reading, the extras beside it, or the same page in
another language.

So the contract grew three fields, all additive: `.Sections` for navigation,
`.Tags` with each term's listing URL, and `.ExtrasURL`, empty when a bundle has
none so a theme never links a 404. Sections arrive through `Renderer.Navigation`, a
callback, because sections change when content does and a copy would go stale — the
nav updates on a rebuild along with everything else.

One flaw only visible by looking at a rendered page: the language switcher pointed
at the canonical host, because `.Alternates` went absolute for hreflang. Those are
two needs, so an Alternate now carries `.URL` (absolute, for machines) and `.Path`
(relative, for a link a person clicks).

The theme now demonstrates every field it is given, including `.First`/`.Last`,
which existed and were never rendered. Still no JavaScript, still one stylesheet.

A test that asserted a page had no sequence nav was matching the inlined
stylesheet rather than the markup, and now matches the element. That is the third
time a loose assertion has passed for the wrong reason.
2026-07-31 19:48:32 +06:00
Claude Opus 5andbdeshi 669a94a26a publish a bundle's extras as a browsable tree
Re-adopts the parked extras entry as ADR-0047: `extras/` inside a bundle is skipped
by the scanner entirely, so a `.md` in there is an asset with no identity and no URL
of its own. The engine enumerates the tree, sorts it by path, classifies by
extension, renders markdown and text, and offers everything else as bytes. One route
with two behaviours — `…/extras/{path}` selects an entry, `?raw` returns the file.

Almost everything it needed already existed, which is the sign the model was right:
the scanner had a directory exclusion, `Assets()` knew which bundles own a
directory, and `Lookup` already decided visibility — so a draft hides its extras
with no new check. A test proves that, including `?raw`.

Two deviations from the parked shape, both because the shape was written before the
code. The directory name is fixed rather than a cascade key, since nothing reads a
section-level setting yet. And an entry is resolved against the *enumeration* rather
than the filesystem: not being in the listing is a stronger answer than os.Root
refusing a path, and cheaper.

Selecting is a link and a full page. No JavaScript is involved, and a
sidebar-and-pane layout is the theme's business — which is the layer rule applied
before writing the feature rather than after.

Three size warnings fired as a result and were fixed by splitting at seams, not by
sharding: render.go gave up its type declarations to view.go, which is the theme
contract in Go and nothing else; serve() split into a dispatcher and serveBundle;
resolve() gave up its language-prefix step to cutLang.
2026-07-31 13:18:34 +06:00
Claude Opus 5andbdeshi 2edb7ff6e5 localise chrome, smooth prose, in English and Bengali
The engine now owns the words it puts on a page that the author did not write
(ADR-0034). `internal/render/chrome.go` holds the phrase table, Gregorian month
names and decimal digits per language, keyed phrase-then-language so both forms
sit side by side and a half-translated row is visible while reading. Templates
reach it through `t`, `num` and `day`, registered before parsing so a site
override's blocks may call them too.

The reference theme stops hardcoding English: "Newer", "Page 2 of 2" and every
date now come from the table, while `datetime` attributes stay ASCII because a
parser reads them.

Authored text gets goldmark's typographer and nothing else — quotes, dashes and
ellipses smoothed, code spans untouched because it works on the parsed tree. It
is a parser option rather than a function over a page, so the transforms counter
does not move; state.md now says why, so the next reader does not miscount.

Deliberately absent: relative dates, which need a validity window that only the
cache entry will have, and body widow prevention, which cannot be done safely by
a pass over rendered HTML.
2026-07-30 03:24:29 +06:00