From 0616467d17c61478acf79e2aae7005289bf6cfe8 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 21 Jun 2026 17:32:03 +0200 Subject: [PATCH] docs: fix stale heading-id + footnote facts (clarity audit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A docs-wide clarity pass turned up factual drift between the case-study narrative and the current grammar/corpus: - case-study/syntax.md (heading ids): the algorithm + examples said ids are NFC-normalized and lowercased by default and "deliberately diverge from djot". The grammar (PART 9, jgm/djot#393) and corpus are the opposite: ids are CASE-PRESERVING with NO Unicode normalization, cross-refs resolve case-insensitively, and lowercasing is an opt-in transform. Rewrite the steps, the example table (Getting-Started, Café-Crème, Über-uns, s-2024-Recap, …), and the framing to match. (comparison.md / divergence-from-djot.md were already correct; syntax.md was the lone stale holdout.) - extensions.md: the footnote note implied inline footnotes are deferred. Inline `^[content]` is implemented Tier-1 core; only the sidenote `[>content]` form is deferred. Clarify. - native-features-analysis.md: label inline footnotes Tier-1 core (a carve addition), not "extension". --- docs/case-study/syntax.md | 47 ++++++++++++++++---------------- docs/extensions.md | 10 ++++--- docs/native-features-analysis.md | 2 +- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/docs/case-study/syntax.md b/docs/case-study/syntax.md index 5c6f9841..cd0bb28a 100644 --- a/docs/case-study/syntax.md +++ b/docs/case-study/syntax.md @@ -94,14 +94,16 @@ heading text by the following algorithm, applied in order: 1. Take the heading's rendered plain text (inline markup removed; symbols `:name:` and footnote references excluded): `# *Setup* guide` yields `Setup guide`. -2. NFC-normalize. +2. **No normalization.** Carve applies no Unicode (NFC) normalization, so a + slug needs no Unicode tables and is byte-identical across implementations. 3. Replace each maximal run of **non-alphanumeric ASCII** characters (spaces, punctuation, `_`, and runs of `-`) with a single `-`. 4. Trim leading and trailing `-`. -5. **Lowercase** it (Unicode-aware): non-ASCII characters are preserved, - only their case is folded. `Über café` → `über-café`, `日本語` stays - `日本語`. (GitHub/SSG style — makes ids and the common cross-reference - case-insensitive.) +5. **Preserve case and non-ASCII characters** — the slug keeps the heading's + original letter case and any non-ASCII characters verbatim + (`Über café` → `Über-café`, `日本語` stays `日本語`). This matches + djot.js / djot-php. Cross-references resolve **case-insensitively**, so a + lowercase `` still finds it. 6. If the result starts with a digit, prefix `s-` (a bare leading digit is a valid HTML id but an invalid CSS selector). If the result is empty, the identifier is `s`. @@ -113,28 +115,25 @@ heading text by the following algorithm, applied in order: | Heading | Identifier | |---|---| -| `# Getting Started` | `getting-started` | -| `# Café & Crème` | `café-crème` | -| `# Über uns` | `über-uns` | -| `# Привет мир` | `привет-мир` | -| `# RFC 2119: Key Words` | `rfc-2119-key-words` | -| `# 2024 Recap` | `s-2024-recap` | -| `# What's New?` | `what’s-new` (the `'` smart-quotes to `’`, a non-ASCII char, then is preserved) | +| `# Getting Started` | `Getting-Started` | +| `# Café & Crème` | `Café-Crème` | +| `# Über uns` | `Über-uns` | +| `# Привет мир` | `Привет-мир` | +| `# RFC 2119: Key Words` | `RFC-2119-Key-Words` | +| `# 2024 Recap` | `s-2024-Recap` | | `# user_id field` | `user-id-field` | | `# 日本語の見出し` | `日本語の見出し` | -| `# Καλημέρα` | `καλημέρα` | | `# !!!` | `s` | -| `# Setup` then `# Setup` | `setup`, then `setup-2` | -| `# Introduction {#intro}` then `# Intro` | `intro`, then `intro-2` | - -Identifiers are **lowercase, with non-ASCII characters preserved** — the -GitHub/static-site-generator convention authors expect for anchors. carve lowercases -**by design**, deliberately diverging from djot.js / djot-php (which preserve case per -[jgm/djot#393](https://github.com/jgm/djot/pull/393)); lowercasing makes ids and the -common `` cross-reference case-insensitive. The rendered `id` is consumed by -anchor highlighting, `:target` rules, `document.querySelector('#' + id)`, and URL -fragments; a leading digit gets the `s-` prefix so it is always a valid bare CSS -selector. +| `# Setup` then `# Setup` | `Setup`, then `Setup-2` | + +Identifiers **preserve case and non-ASCII characters** — matching djot.js / +djot-php (per [jgm/djot#393](https://github.com/jgm/djot/pull/393)). Cross-references +resolve **case-insensitively**, so a lowercase `` still finds a capitalized +heading and links to the target's actual (case-preserved) id. The rendered `id` is +consumed by anchor highlighting, `:target` rules, `document.querySelector('#' + id)`, +and URL fragments; a leading digit gets the `s-` prefix so it is always a valid bare +CSS selector. Processors MAY apply the opt-in `lowercaseHeadingIds` / +`asciiHeadingIds` transforms for lowercase or ASCII-folded fragment portability. Non-ASCII ids are valid HTML5 and resolve in browsers (the fragment is percent-encoded when shared, e.g. `…/page#%C3%BCber-uns`). For **ASCII-only** diff --git a/docs/extensions.md b/docs/extensions.md index bfab8e62..bfcec799 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -131,10 +131,12 @@ differs by processor. The narrative below details each tier. the output element (the `spoiler` base class ahead of author classes) with the always-on attribute hardening. In carve-php / carve-js / carve-rs. -Inline and sidenote footnotes are **not** Tier 3. They are deferred core -reserved syntax (`[^…]` inline, `[>…]` sidenote; `resources/grammar.ebnf` -PART 9 §16), not an app extension. The djot-php `[…]{.fn}` form maps onto -carve's `[^…]`; see `native-features-analysis.md`. +Footnotes are **not** Tier 3. Reference footnotes `[^id]` and inline footnotes +`^[content]` are both implemented Tier-1 core (`resources/grammar.ebnf` PART 9 +§16); only the **sidenote** form `[>content]` is deferred core reserved syntax +(reserved but not yet implemented), not an app extension. The djot-php +`[…]{.fn}` form maps onto carve's inline `^[content]`; see +`native-features-analysis.md`. ## 2. Extension system diff --git a/docs/native-features-analysis.md b/docs/native-features-analysis.md index fbcf2688..7cb0df74 100644 --- a/docs/native-features-analysis.md +++ b/docs/native-features-analysis.md @@ -47,7 +47,7 @@ Grammar references point at `resources/grammar.ebnf`. | **Abbreviations** | `*[ABBR]: expansion` | `*[ABBR]: expansion` | ✅ In grammar (PART 5: Abbreviations). | | **Semantic spans** | `[text]{.kbd}` → `` | `:kbd[text]` | ✅ Via `:type[content]` extension syntax (4.20). | | **Autolinks** | `` / `` | Angle-bracket autolinks only | ✅ In spec (4.3). Bare URLs are *not* auto-linked (djot-aligned). | -| **Inline footnotes** | `[content]{.fn}` | `^[content]` | ✅ In grammar (§16). A carve extension beyond djot; pandoc-style `^[content]`, numbered into the shared endnotes. | +| **Inline footnotes** | `[content]{.fn}` | `^[content]` | ✅ Tier-1 core, in grammar (§16). A carve addition (not in djot); pandoc-style `^[content]`, numbered into the shared endnotes. | | **Table alignment** | `:--`, `--:`, `:--:` | `\|=<` / `\|=>` / `\|=~` markers | ✅ In spec (4.8). | | **Rowspan/colspan** | `^` and `<` markers | `^` and `<` markers | ✅ In grammar (span_cell / rowspan_marker / colspan_marker). | | **Multi-line cells** | `+` continuation | `+` continuation | ✅ In grammar (table multi-line cells). |