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.
This commit is contained in:
2026-07-30 02:17:05 +06:00
parent 499d107314
commit 449850c83d
10 changed files with 320 additions and 28 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
# State
**Verified against:** `85a4906` on 2026-07-30 — update this line every change.
**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
@@ -29,8 +29,8 @@ this change*.
| Counter | Now | Extraction due at | What it buys |
|---|---|---|---|
| Render transforms | 0 | **3** | Stage pipeline (ordered `func(ctx,*Page) error`) |
| Routing cases | 3 | **2** — done | Resolver at `internal/web/resolve.go`: bundle, language prefix, pagination |
| Collection pages | 1 | **1** — done | Query primitive: `content.Query` + `Site.Run` |
| 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`) |