Files
khosra/docs/state.md
T
bdeshi b14658094e add tag listings, global and section-narrowed
One global namespace (ADR-0018): /tags/{term}/ spans every section and
/{section}/tags/{term}/ narrows it. Listings group by section so one busy term
stays readable, which needed List.Groups alongside Items — list.html renders
whichever is set.

This is Query's second use, so it gained a Tag field rather than being generalised
on speculation: one filter, two callers. Tag slugs lowercase and hyphenate,
preserving script, so "Long Monsoon" and "long monsoon" are one term while Bengali
passes through unchanged. Hand-chosen slugs per term still wait for the type
declaration that owns overrides.

`tags` is reserved at the top level and inside every section, alongside `page` and
the language prefixes. A tag listing redirects to its canonical URL only once it is
known to exist, matching the rule bundles already followed — otherwise a canonical
URL for nothing confirms what is not there.

One stale test expectation fixed rather than worked around: it asserted tags land
in Extra, which stopped being true when tags became a named field.

Evidence: /tags/monsoon/ lists Hello World under posts and First Rain under comics;
/comics/tags/monsoon/ shows one; /tags/monsoon 301s; /tags/nothing/ and /tags/ 404.
2026-08-01 02:23:34 +06:00

4.9 KiB

State

Verified against: 499d107 on 2026-07-30 — update this line every change. If this file disagrees with the code, the code is right and this file is a bug.

Inventory

File Purpose LOC
go.mod module khosra; x/text, yaml.v3 direct 8
internal/content/content.go site root → bundles: os.Root open, walk, frontmatter split, key/lang derivation, NFC, collision drop, key index, language fallback, alias index, URL building 358
internal/render/render.go goldmark, per-kind template sets with site override, Page/List/head 214
internal/render/templates/ reference theme: base.html, page.html, list.html, theme.css (ADR-0026)
internal/web/resolve.go URL → (key, lang) or a canonical redirect: language prefix, /en/… fork guard, trailing slash 56
internal/web/web.go handler: resolve, look up with fallback, listings, /static/, degrade on failure 62
cmd/khosra/main.go flags, wiring, startup — the only place things are assembled 50
*_test.go table-driven; symlink escape, permalink, redirect, 404 245

Serves a bundle at /{section}/{slug}/. This repo holds engine source only — the site root is external and passed with -site (ADR-0011).

Dependencies: none.

Counters — the earn-it authority

Never anticipate a threshold. Increment when the code lands, then check whether the extraction is due this change.

Counter Now Extraction due at What it buys
Render transforms 0 3 Stage pipeline (ordered func(ctx,*Page) error)
Routing cases 5 2 — done Resolver at internal/web/resolve.go: bundle, language prefix, pagination, tag, section-narrowed tag
Collection pages 3 1 — done Query primitive: content.Query{Section, Tag, Lang} + Site.Run
Views / output formats 2 2 — due Two template sets exist (bundle, listing); the View layer is Arc 2's third item
Effects 0 2 Effect runner + trigger wiring (change / schedule / demand)
Extensions 0 3 Extension registry + wire file (extensions.md)
Interface implementations 2 The interface itself
Non-stdlib dependencies 3 direct budget in scripts/budgets.env

Allowlisted, in use: goldmark is not yet imported. Allowlist: goldmark (markdown), golang.org/x/text (NFC, ADR-0015), gopkg.in/yaml.v3 (frontmatter, ADR-0020).

Latent items — known, deliberately unfixed

Do not fix these mid-feature. They become features when the human says so. An arc does not close with an untriaged item: at each arc boundary every row is fixed, scheduled into an arc, or accepted with a stated reason. A list nothing drains is a graveyard of known defects.

Item Why it waits Trigger to fix
No mechanical check that the counters are correct Accepted at the Arc 1 boundary: the coupling gate makes forgetting them impossible, which is the real failure mode, and checking the values needs code to count 3rd transform (queue 12)
No mechanical gate on the untrusted boundary (ADR-0003) Scheduled to Arc 3: nothing untrusted is read yet The comment path — a test that untrusted input reaches no shortcode or template evaluation
Raw HTML in Markdown is currently omitted only because goldmark's default omits it Verified at the Arc 1 boundary, and it is what keeps invariant 2 intact for authored content. html.WithUnsafe() is the obvious move when a shortcode needs to emit HTML, and it silently turns authored Markdown into an injection path Shortcodes (queue 12) — if unsafe rendering is enabled, the trusted/untrusted split must be real code, not a default

Open questions blocking Arc 1

None. Every decision the engine needs before Arc 1 and before the first deploy is recorded.

Every ADR in decisions.md is accepted; none is open or proposed.

Build queue

Working plan lives in .scratch/build-queue.md, which is deliberately not committed — git log is the record of what actually landed. If that file is absent, read the log and rebuild the plan from it.

Arc retro log

One line per completed arc: what it cost, what it taught, what it made unnecessary.

  • Arc 1 — the spine. 619 core lines, 3 dependencies, 4 queue entries. Taught: os.Root makes the path guard a property of the type, so the latent item that shipped with the harness died instead of being implemented; and running the gates against real code found six defects in the gates — an allowlist parser that rejected its own documented format, two advisories that fired only on correct code, a coupling gate that demanded explanations for permission edits, an untidy go.mod hiding a direct dependency, and a nesting check off by one level. Made unnecessary: a hand-rolled traversal cleaner, and a second routing branch — the resolver arrived by counter at exactly the right moment.