diff --git a/docs/decisions.md b/docs/decisions.md index 2e45de5..761c4d2 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -367,3 +367,20 @@ Consequence: cheap — the guard cannot be forgotten, and the test is one symlin go through the root handle, so no helper may take a `string` path and open it directly, and the root is held for the life of the process. Revisit if: never. A hand-rolled cleaner is strictly worse. + +## ADR-0032 — Tags: one global namespace, grouped in the listing +Date: 2026-07-30 · Status: accepted (the tag half of withdrawn ADR-0018, now built; the feed half is still +deferred in `ideas/deferred-decisions.md`) +Decision: `tags` is one global namespace across every section. `/tags/{term}/` lists everything carrying a +term, `/{section}/tags/{term}/` narrows it, and listings group by section. The URL form of a term is +lowercased with whitespace hyphenated, preserving script, so case is not a distinction and Bengali passes +through unchanged. Structural metadata with a fixed term set — `series` and its kin — is not a tag. +Why: cross-type discovery is the point of a single-author site; one term spanning a comic, a poem and a +photo essay is the feature, not the noise. Per-section pools would fragment that to solve a readability +problem the View solves by grouping. And a free-form cross-cutting tag behaves nothing like a fixed term +set that drives engine behaviour, so conflating them makes both worse. +Consequence: cheap — one Query with an optional section predicate serves both listings, and the second +caller is what earned `Query.Tag` rather than a guess. Expensive — tag hygiene is the author's discipline +since nothing scopes terms, so `check` owes a near-duplicate report; and `tags` is now reserved at the top +level and inside every section. +Revisit if: the pool becomes unusable in practice — and then the answer is curation, not namespacing. diff --git a/docs/state.md b/docs/state.md index ad5fe74..1ae4e06 100644 --- a/docs/state.md +++ b/docs/state.md @@ -1,6 +1,6 @@ # State -**Verified against:** `449850c` on 2026-07-30 — update this line every change. +**Verified against:** `fbf24f7` 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 diff --git a/ideas/deferred-decisions.md b/ideas/deferred-decisions.md index 5635a1e..dfc2b90 100644 --- a/ideas/deferred-decisions.md +++ b/ideas/deferred-decisions.md @@ -76,26 +76,13 @@ sets a policy once for everything beneath it. Expensive — resolution must be c runs per bundle, and the set of cascadable keys must stay declared or it becomes unbounded config. Revisit if: cascade resolution shows up in a render-path profile. -## Taxonomies and feed shape +## Feed shape -Deferred because no tag pages and no feeds in the MVP. +Deferred because no feeds exist yet. The tag half of this became ADR-0032 when tags were built. -## Global flat tags, declared structural taxonomies -Date: 2026-07-28 · Status: accepted -Decision: `tags` is one global namespace across every type — `/tags/{tag}/` lists everything carrying it, -`/{section}/tags/{tag}/` narrows to a section, and listing views group results by type. Structural -metadata with known terms that drives behaviour — `series`, `medium`, `genre` — is a *declared* taxonomy -on the type (ADR-0014) and never enters the tag pool. Feeds follow the same shape: `/feed.xml` carries -every type declared `primary`, `/{section}/feed.xml` carries a section, `/tags/{tag}/feed.xml` falls out -of the same Query. -Why: cross-type discovery is the point of a single-author site — one tag spanning a comic, a poem and a -photo essay is a feature. Per-section tag pools would fragment that for a readability problem better -solved by grouping in the View. But a tag is free-form and cross-cutting, while a structural taxonomy has -a fixed term set and changes what the engine does; conflating them makes both worse. -Consequence: cheap — one Query with an optional section predicate serves tag pages, section tag pages and -their feeds. Expensive — tag hygiene is now the author's discipline, since nothing scopes them; the -content `check` command should report near-duplicate terms. -Revisit if: the tag pool becomes unusable in practice — and then the answer is curation, not namespacing. +Recorded direction: `/feed.xml` carries every type declared `primary`, `/{section}/feed.xml` carries a +section, and `/tags/{term}/feed.xml` falls out of the same Query. Which types are `primary` is part of the +type declaration, so this and declared types land together. ## Extras