From 15f8bd7e2656bfc262d48d9a16dcf8f0f0ec40d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20M=C3=A4hr?= <14755525+maehr@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:03:16 +0200 Subject: [PATCH 1/4] fix(ci): repair the link-check URL extraction (#3) The extractor trimmed trailing markup in a fixed order: strip one unbalanced `)`, then strip trailing punctuation. A URL that ends a markdown link inside a sentence or a bold run carries both at once, so the `)` survived and lychee checked a truncated URL. Twelve of the seventeen errors in the report were this, not a broken link. Trim in a loop instead. Each pass strips trailing punctuation, then one unbalanced `)`, until the string stops changing. Backticks join the punctuation set, so a URL inside an inline code span is trimmed too. Also drop percent-encoded resolver templates (`%7B`/`%7D`), which the literal `{}` filter missed, and skip `node_modules` so a local `data/node_modules` cannot pull dependency READMEs into the list. Exclude `iso.org` and `loc.gov` in lychee.toml: both answer a bot with 403 and neither link is broken. The report drops from 17 errors to 3. The remaining three are `blob/main` links to ADR-0001, ADR-0002 and ADR-0006, which `main` does not carry yet. Merging staging into main for v0.1.0 resolves them. Closes #3 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0148cB4MGy2zPeuhemebM38h --- .github/workflows/links.yml | 35 ++++++++++++++++++++++++++++------- lychee.toml | 2 ++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 5cb72a6..bd534eb 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -28,15 +28,36 @@ jobs: - name: Collect external URLs from sources run: | mkdir -p lychee - # Allow `)` inside URLs (Wikipedia disambiguation titles), then strip - # a single trailing `)` only when unbalanced (handles markdown - # `[text](url)`). Drop resolver templates containing `{placeholder}`. - # Restrict to YAML/MD so lockfiles inside data/ are not scanned. + # A URL may legitimately contain `(` and `)`, because Wikipedia and + # Wikisource put disambiguation titles there. The match therefore + # keeps parentheses and the trailing markup is trimmed afterwards. + # + # The trim runs as one loop, not as a fixed sequence. A URL at the + # end of a markdown link, a bold run, or a sentence carries several + # layers of trailing markup at once — `…/4.0/).`, `…/arks.org/)**`, + # `…/rfc3987))` — so each pass strips punctuation, then one + # unbalanced `)`, until the string stops changing. Doing this in a + # single pass leaves a `)` behind whenever punctuation follows it, + # which is what produced the false 404s in #3. + # + # Resolver templates are dropped: `{placeholder}` literally, and + # `%7B`/`%7D` for the percent-encoded spelling. + # + # Restrict to YAML/MD, and skip node_modules, so lockfiles and + # dependency READMEs inside data/ are not scanned. grep -rhoE --include='*.yaml' --include='*.yml' --include='*.md' --include='*.mdx' \ + --exclude-dir=node_modules \ 'https?://[^]["'\''<>[:space:]]+' data/ src/content/docs/ \ - | awk '{ n=gsub(/\(/,"&"); m=gsub(/\)/,"&"); if (m>n) sub(/\)$/,""); print }' \ - | grep -v '[{}]' \ - | sed -E 's/[.,;:]+$//' \ + | awk '{ + do { + changed = 0 + if (sub(/[.,;:!?*_`]+$/, "")) changed = 1 + n = gsub(/\(/, "&"); m = gsub(/\)/, "&") + if (m > n && sub(/\)$/, "")) changed = 1 + } while (changed) + if (length($0) > 0) print + }' \ + | grep -viE '[{}]|%7b|%7d' \ | sort -u > lychee/urls.txt echo "Collected $(wc -l < lychee/urls.txt) unique URLs" diff --git a/lychee.toml b/lychee.toml index f2f8688..ff0f57f 100644 --- a/lychee.toml +++ b/lychee.toml @@ -34,6 +34,8 @@ exclude = [ "^https?://([a-z0-9-]+\\.)*twitter\\.com", "^https?://([a-z0-9-]+\\.)*x\\.com", "^https?://([a-z0-9-]+\\.)*opencollective\\.com", + "^https?://([a-z0-9-]+\\.)*iso\\.org", + "^https?://([a-z0-9-]+\\.)*loc\\.gov", "^https?://localhost(:\\d+)?(/|$)", ] From 9da2e7ff8f7756175743d54120a04bca417e3ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20M=C3=A4hr?= <14755525+maehr@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:03:28 +0200 Subject: [PATCH 2/4] fix(site): render a deprecated record as a tombstone (#99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `deprecated` is one of the three tombstone statuses in the model, but no record template treated it as one. ADR-0004, `specification.md:284` and the `Status` enum all say so, and `RETIRED_STATUSES` in the compiler agrees, yet both derivation sites tested `withdrawn` and `blocked` only. A `deprecated` record therefore rendered with no banner and dropped its `superseded_by` link, while the `.json` sibling published it. Decision: the three statuses stay one tombstone category with one presentation. They differ in reach, not in kind — a `deprecated` record is retained and still resolves, while a `withdrawn` or `blocked` record is additionally excluded from `Work` mapping projections — so the user-facing copy distinguishes them and the markup does not. The `superseded_by` treatment is the normal one. This is latent, not live: no record in the registry is `deprecated` today. The cost lands the first time a promoted record is corrected. The copy still lives in two templates. #100 single-sources it, and this decision is what its shared status module has to encode. Closes #99 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0148cB4MGy2zPeuhemebM38h --- src/components/Tombstone.astro | 18 ++++++++++++++++-- src/pages/id/ref/[uuid]/index.astro | 22 ++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/Tombstone.astro b/src/components/Tombstone.astro index 0a3cd14..a51c744 100644 --- a/src/components/Tombstone.astro +++ b/src/components/Tombstone.astro @@ -6,8 +6,15 @@ interface Props { const { status, supersededBy } = Astro.props; +// ADR-0004 and specification.md §12 make `deprecated`, `withdrawn` and `blocked` +// one tombstone category, and the compiler agrees (`RETIRED_STATUSES` in +// scripts/compile.ts). The three differ in reach, not in kind: a `deprecated` +// record is retained and still resolves, while a `withdrawn` or `blocked` record +// is additionally excluded from `Work` mapping projections. The banner therefore +// stays one presentation, and the copy below states which of the three applies. const isDraft = status === 'draft'; -const isTombstone = status === 'withdrawn' || status === 'blocked'; +const isTombstone = + status === 'deprecated' || status === 'withdrawn' || status === 'blocked'; --- { @@ -33,9 +40,16 @@ const isTombstone = status === 'withdrawn' || status === 'blocked'; {status === 'blocked' ? 'This record is blocked by a rights, trust, or policy dispute.' - : 'This record has been withdrawn.'} + : status === 'deprecated' + ? 'This record is deprecated.' + : 'This record has been withdrawn.'}

+ {status === 'deprecated' && ( +

+ It is retained and it still resolves, but it is no longer recommended. +

+ )} {supersededBy ? (

Superseded by {supersededBy}. diff --git a/src/pages/id/ref/[uuid]/index.astro b/src/pages/id/ref/[uuid]/index.astro index 1a55f99..d7a47cd 100644 --- a/src/pages/id/ref/[uuid]/index.astro +++ b/src/pages/id/ref/[uuid]/index.astro @@ -81,8 +81,18 @@ const escapeHtml = (s: string) => .replace(/"/g, '"'); const chicagoHtml = escapeHtml(chicago).replace(/\*([^*]+)\*/g, '$1'); +// ADR-0004 and specification.md §12 make `deprecated`, `withdrawn` and `blocked` +// one tombstone category, and the compiler agrees (`RETIRED_STATUSES` in +// scripts/compile.ts). The three differ in reach, not in kind: a `deprecated` +// record is retained and still resolves, while a `withdrawn` or `blocked` record +// is additionally excluded from `Work` mapping projections. The banner therefore +// stays one presentation, and the copy below states which of the three applies. +// Kept in step with src/components/Tombstone.astro by hand until #100 lands. const isDraft = ref.status === 'draft'; -const isTombstone = ref.status === 'withdrawn' || ref.status === 'blocked'; +const isTombstone = + ref.status === 'deprecated' || + ref.status === 'withdrawn' || + ref.status === 'blocked'; --- {ref.status === 'blocked' ? 'This record is blocked by a rights, trust, or policy dispute.' - : 'This record has been withdrawn.'} + : ref.status === 'deprecated' + ? 'This record is deprecated.' + : 'This record has been withdrawn.'}

+ {ref.status === 'deprecated' && ( +

+ It is retained and it still resolves, but it is no longer + recommended. +

+ )} {ref.superseded_by ? (

Superseded by {ref.superseded_by}. From 0bb63f98decb7cc8179d52bdb8f601ad4cfd2043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20M=C3=A4hr?= <14755525+maehr@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:03:29 +0200 Subject: [PATCH 3/4] refactor(site): delete the orphaned CitationBox component (#48) `src/components/CitationBox.astro` has no consumers. The reference page was the only one, and #30 replaced it with inlined Chicago and COinS rendering against `src/lib/citation.ts`. `git grep CitationBox` now returns nothing, and `npm run build:fast` produces identical `dist/` output. Closes #48 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0148cB4MGy2zPeuhemebM38h --- src/components/CitationBox.astro | 134 ------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 src/components/CitationBox.astro diff --git a/src/components/CitationBox.astro b/src/components/CitationBox.astro deleted file mode 100644 index ec1a9a7..0000000 --- a/src/components/CitationBox.astro +++ /dev/null @@ -1,134 +0,0 @@ ---- -import type { - Work, - CanonicalReference, - CitationSystem, -} from '../../standard/schema/index.js'; -import { toCSL, formatChicago, toCOinS } from '../lib/citation.ts'; - -interface Props { - work: Work | undefined; - ref: CanonicalReference; - system: CitationSystem | undefined; -} - -const { work, ref, system } = Astro.props; -const csl = toCSL(work, ref, system); -const chicago = formatChicago(csl); -const coins = toCOinS(csl); -const cslJson = JSON.stringify(csl, null, 2); - -const escapeHtml = (s: string) => - s - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); - -const chicagoHtml = escapeHtml(chicago).replace(/\*([^*]+)\*/g, '$1'); ---- - -

-

Cite this reference

-

Chicago (notes-bibliography)

-
- -
- - -
- - - From e4e3fb88b7941ef9410f7029bea62beaa9e6891f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20M=C3=A4hr?= <14755525+maehr@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:21:37 +0200 Subject: [PATCH 4/4] fix(github): label the docs issue template with a label that exists `docs.yml` asked for `docs`. The repository has no such label; it has `documentation`, which is what #68, #79 and #81 already carry. GitHub applies the labels an issue form names only when they exist, and it does not create the missing one, so every issue filed through this template would arrive with `triage` alone and miss a `label:documentation` query. Every other template in the directory names a label that exists. This is the one that does not. Latent so far: no issue in the repository carries `triage`, so no docs issue has been filed through the form yet. The tag freezes `.github/`, which makes a one-line fix cheaper now than after. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0148cB4MGy2zPeuhemebM38h --- .github/ISSUE_TEMPLATE/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml index 5bc9820..3a99de4 100644 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -1,7 +1,7 @@ name: 📚 Documentation issue description: Something on the site or in the README is missing, wrong, or unclear. title: 'docs: ' -labels: [docs, triage] +labels: [documentation, triage] body: - type: input id: url