From 486bc7c3d3caa5f3aa411895ff6301bd2e651b0c Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Thu, 30 Jul 2026 03:03:06 +0600 Subject: [PATCH] record ADR-0033: series membership is structural MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers the question that blocked sequences. A chapter belongs to the series it is nested under, so the engine reads no `series` field; `order` stays, optional, and a member without one sorts by name after every member carrying one. This supersedes ADR-0016's membership clause and keeps its position rules — the point of that ADR was that position never reaches a URL, which still holds. The directory already states membership unambiguously; a second statement in frontmatter can only agree or be a typo that silently orphans a chapter. Cost, stated in the ADR: a series can never span directories, and membership now rides on the bundle key, which ADR-0008 makes permanent. --- docs/content-model.md | 40 ++++++++++++++++++++++++++++------------ docs/decisions.md | 20 +++++++++++++++++++- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/docs/content-model.md b/docs/content-model.md index bc71e3a..8812cf8 100644 --- a/docs/content-model.md +++ b/docs/content-model.md @@ -31,7 +31,7 @@ overrides the defaults the binary embeds, so a bare root still renders. comics/ # webcomics [spec] the-long-monsoon/ _index.en.md # the series bundle; also the cascade point for it - first-rain/ # slug is the name, never the position (ADR-0016) + first-rain/ # a chapter because it is nested here (ADR-0033); slug is never the position index.en.md page.png art/ # single images or sets [spec] @@ -78,7 +78,7 @@ readable by templates (ADR-0002). Never add a required field. | `nocache` | bool | Never cache this bundle's render. Named so absence means cacheable, per ADR-0002 | | `summary` | string | Explicit summary; otherwise derived | | `tags` | []string | Flat, case- and script-preserved as written. A scalar or a list. The URL form is lowercased with spaces hyphenated, so `Long Monsoon` and `long monsoon` are one term; scripts without case pass through unchanged (ADR-0018) | -| `series` / `order` | string / int | Series membership and position. Sparse by convention (10, 20, 30) so insertion is one edit; never appears in a URL (ADR-0016) | +| `order` | int | Position within the series this bundle is nested in (ADR-0033). Sparse by convention (10, 20, 30) so insertion is one edit; never appears in a URL (ADR-0016). Absent, the bundle orders by name, after every sibling carrying one | | `cover` | string | Relative path to the lead image | | `view` | string | Per-bundle View override (Arc 2) | | `styles` / `scripts` | []string | Page-specific assets, relative to the bundle | @@ -152,19 +152,35 @@ path — `/pages/about/` and `/bn/pages/about/`. `/en/…` permanently redirects never live. Emit `hreflang` and `canonical` from the variants that actually exist. -## Sequences, galleries, collections `[spec]` +## Sequences -All of these are a Query over bundle metadata with a stable sort; none justifies a new primitive. +Comics, serial fiction and multi-part essays all reduce to a sequence, which is why it is the one +grouping with a name (ADR-0016). -A **sequence** is the one that needs naming, because comics, serial fiction and multi-part essays all -reduce to it (ADR-0016). Membership is `series`; position is whatever the type declares as its `order` -rule — date, sequence, or manual. Position lives in frontmatter, sparse, and never in the path, so -inserting a chapter between two others is a single edit with no renames, no changed bundle keys and no -aliases. Resolution — first, prev, next, last, index, count — is defined once, honours `draft`, and -respects the language fallback chain, so a missing Bengali chapter does not break Bengali prev/next. +**Membership is structural** (ADR-0033): a bundle belongs to the series it is nested under, so +`comics/the-long-monsoon/first-rain` is a chapter of `comics/the-long-monsoon`. The engine reads no +`series` field. A bundle with bundles nested under it is a landing page and they are its members; a +landing page nested inside another series reports its own members rather than its siblings, since the +deeper series is what the page is about. Membership therefore rides on the bundle key, which is permanent +— re-parenting a chapter changes its URL and needs an alias like any other move. -A gallery is a Query plus an image View. Related posts are a Query with a scoring function, not a stored -graph. Pagination is a Query parameter plus a permalink rule for page 2+, still undecided. +**Order is `order` ascending where set, then by name.** A member without `order` sorts after every member +carrying one, the same way an undated bundle sorts after dated ones. Position lives in frontmatter, sparse +by convention, and never in the path, so inserting a chapter between two others is a single edit with no +renames, no changed keys and no aliases. + +Resolution — first, prev, next, last, index, count — is defined once, and members resolve through the +language fallback chain, so a chapter missing in Bengali still appears in Bengali reading order rather +than breaking prev/next. Reading order is *ascending*, the opposite of a dated listing: a sequence's +"prev" is the earlier entry. + +`draft` is not honoured yet, because no bundle carries it and nothing else excludes drafts either; the +feature that adds the field adds it here in the same change. + +## 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]` diff --git a/docs/decisions.md b/docs/decisions.md index 761c4d2..4c13a42 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -161,7 +161,7 @@ boundary must route through the normaliser rather than accepting a raw string. Revisit if: never for normalisation. Slug derivation rules change with the default locale. ## ADR-0016 — Sequence position is metadata and never appears in a URL -Date: 2026-07-28 · Status: accepted +Date: 2026-07-28 · Status: accepted; its membership clause is superseded by ADR-0033 (position rules stand) Decision: a bundle's slug is its name, never its position — `comics/the-long-monsoon/the-flood/`, not `.../02-the-flood/`. Position comes from a single declared source per type (`order` in the type declaration: date, sequence, or manual), sparse by convention (10, 20, 30) so inserting between two @@ -384,3 +384,21 @@ caller is what earned `Query.Tag` rather than a guess. Expensive — tag hygiene 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. + +## ADR-0033 — Series membership is structural; `order` is optional +Date: 2026-07-30 · Status: accepted (supersedes ADR-0016's membership clause, not its position rules) +Decision: a bundle is a member of a series because it is nested under the series' landing bundle — +`comics/the-long-monsoon/first-rain` under `comics/the-long-monsoon` — so the engine reads no `series` +field. Reading order is `order` ascending where it is set, then by key; a member without `order` sorts +after every member that has one. A bundle with bundles nested under it is a series landing page, and one +nested inside another series reports its own members rather than its siblings. +Why: the directory already states membership unambiguously, and a second statement of it in frontmatter +can only agree or be a bug — a typo in `series` silently orphans a chapter, which is precisely the class +of error a single source of truth removes. Making `order` optional keeps the cheap case cheap: a series +whose filenames already sort correctly needs no frontmatter at all, and `order` is for when they do not. +Consequence: cheap — nothing to declare, nothing to keep in sync, and moving a series moves its +membership with it. Expensive — a series can never span directories, so a cross-directory collection +needs a Query over some other field, not this; and membership now depends on the bundle key, which +ADR-0008 makes permanent, so re-parenting a chapter is a permalink event with an alias. +Revisit if: a real collection must span sections. Then it is a declared taxonomy (ADR-0032's second +half), not a change to how a series is joined.