Prose round 3: retitle the floor decision, put the plain/ exception first, sharpen items 59-62
CI / gate (push) Successful in 57s
CI / publish (push) Has been skipped

This commit is contained in:
2026-10-03 16:11:56 +02:00
parent b39525cad7
commit 7f67998c3b
5 changed files with 26 additions and 27 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ In `docs/decisions.md`:
- Firefox reads the build
- The coverage floors
- The size ratchet
- The comprehension floor ships under 7 until items 59, 60, 61 and 62 land
- The project ships under the comprehension floor until items 59, 60, 61 and 62 land
- Properties on a fixed seed
- The CommonMark suite checks three ways
- The flavour spec is read as a source
+1 -1
View File
@@ -198,7 +198,7 @@ emit refuses:
| `unspellable-line-start` | a paragraph line begins with a code span whose backticks would read back as a code fence | put any text before the code span |
| `unspellable-whitespace` | an `emoji`, `mention` or `status` holds a newline in the text its inline directive spells in the content slot | replace it with a space — an inline directive never spans lines |
| `unsupported-nesting-depth` | blocks, marks, an attribute's JSON or a carried node's JSON nest past 500 levels | keep the ADF and pass the document over, or show it read-only; flatten the input where you are the one who wrote it |
| `unsupported-node-shape` | a node carries an attribute, value, argument or body its type does not take, or lacks one it needs — or markdown writes as a directive a node or mark the lossless flavour spells as CommonMark, or a reserved directive stands out of place: `!adf:textBreak{}` or `!adf:listBreak` parting nothing, `!adf:doc` beside another block | fix what the message names, or give the node the shape it names; `spec/flavour.md` lists every type's attributes and body |
| `unsupported-node-shape` | a node carries an attribute, value, argument or body its type does not take, or lacks one it needs — or markdown writes as a directive a node or mark the lossless flavour spells as CommonMark, or a reserved directive stands out of place: `!adf:textBreak{}` or `!adf:listBreak` parting nothing, `!adf:doc` anywhere but as the whole document | fix what the message names; `spec/flavour.md` lists every type's attributes and body |
## The guarantees
+12 -15
View File
@@ -496,14 +496,13 @@ config gone missing fails the leg instead of falling back to oxlint's own defaul
`--deny-warnings`, since a rule from a category this config never names arrives as a warning it
exits 0 on.
## The comprehension floor ships under 7 until items 59, 60, 61 and 62 land
## The project ships under the comprehension floor until items 59, 60, 61 and 62 land
2026-10-03, the maintainer. KISS, a technical principle, and its comprehension floor of 7. Valid
while `todo.md` items 59, 60, 61 and 62 are open.
A four-seat comprehension panel scored the project 6 against the floor of 7, and the project ships
under it while the restructures it named land: `todo.md` items 59, 60, 61 and 62 end this. The
scores are the baseline — a later panel may not score lower on any dimension:
A four-seat comprehension panel scored the project 6 against the floor of 7. The scores are the
baseline: a later panel may not score lower on any dimension.
| Seat | Navigation | Locality | Shape | Self-sufficiency | Overall |
|---|---|---|---|---|---|
@@ -672,16 +671,14 @@ them. A primitive knowing neither ADF nor a format stays at `src/` root. A const
`spec/flavour.md` draws, and a placement nothing here settles goes beside its only reader, or in
what both read where there are two.
Each format directory parts into `emit/` (ADF→format) and `parse/` (format→ADF), the rest of it
holding what both directions read. A construct's reader lives there beside the regex the emitter
escapes against, so the two cannot drift; a reader with no emit counterpart goes in `parse/`,
unless it is part of a construct that side already holds — a grammar stays in one file rather than
splitting across the seam. A rule both directions must answer alike — whether a list marker
interrupts a paragraph — is one function there too, never a copy per direction, however
conservative the copy would be. Where the rule is the emitter's own choice, input consults it
rather than restating it, and that is the only import `parse/` takes from `emit/` —
A flavour's own code, both directions included, sits in its own directory: `markdown/plain/`.
Otherwise each format directory parts into `emit/` (ADF→format) and `parse/` (format→ADF), the rest
of it holding what both directions read. A construct's reader lives there beside the regex the
emitter escapes against, so the two cannot drift; a reader with no emit counterpart goes in
`parse/`, unless it is part of a construct that side already holds — a grammar stays in one file
rather than splitting across the seam. A rule both directions must answer alike — whether a list
marker interrupts a paragraph — is one function there too, never a copy per direction, however
conservative the copy would be. Where the rule is the emitter's own choice, input consults it rather
than restating it, and that is the only value import `parse/` takes from `emit/` —
`commonMarkSpelling` and `openingLinkTakesDirective` — so no fixture the emitter writes can be
refused, and a spelling the emitter refuses gives its own error rather than a second name for it.
`markdown/plain/` holds the plain flavour's own code — the writer's reduction to editor-normal ADF
and the conventions and task ids the reader takes — so neither direction reaches into the other
for it.
+1 -1
View File
@@ -14,7 +14,7 @@ export function joinsWhenRead(previous: AdfNode, node: AdfNode): boolean {
return isPlainText(previous) && isPlainText(node) && identicalMarks(nodeMarks(previous), nodeMarks(node))
}
// The editor's rule, parting from the reader's only on shapes editor-normal ADF erases: an empty attrs, content or marks, and -0 in a mark.
// The editor's rule, differing from the reader's only on shapes editor-normal ADF erases: an empty attrs, content or marks, and -0 in a mark.
export function joinsWhenEditorNormal(previous: AdfNode, node: AdfNode): boolean {
if (!joinsAsEditorText(previous) || !joinsAsEditorText(node)) return false
return identicalMarks(nodeMarks(previous).map(normalMark), nodeMarks(node).map(normalMark))
+11 -9
View File
@@ -34,8 +34,8 @@
| 52 | 0.2.0 | | **Spell `colwidth` as a comma list, `colwidth="340,420"`.** | 3 | 3 | 5 | 6 | 5 | 13.0 |
| 51 | 0.2.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 2 | 2 | 7 | 3, 4 | 12.4 |
| 53 | 0.2.0 | | **Put a block's `marks` spelling to the writer panel and adopt its pick.** | 4 | 5 | 5 | 6 | 5 | 11.5 |
| 60 | 0.2.0 | decision | **Give the parser's questions to the emitter one named module, so the parse→emit dependency reads as intended.** | 3 | 4 | 2 | 6 | 2 | 10.7 |
| 59 | 0.2.0 | decision | **Group the directive grammar into `src/markdown/directive/`, and move `Read<T>` to `result.ts` and `Flavour` out of the plain flavour.** | 3 | 5 | 2 | 6 | 2 | 10.4 |
| 60 | 0.2.0 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 2 | 6 | 2 | 10.7 |
| 59 | 0.2.0 | decision | **Group the directive grammar into `src/markdown/directive/`, move `Read<T>` to `result.ts`, and move `Flavour` to `markdown/flavour.ts`.** | 3 | 5 | 2 | 6 | 2 | 10.4 |
| 50 | 0.2.0 | | **Read `[](/url)` and `[]()` as CommonMark's empty link.** | 4 | 4 | 3 | 6 | 3, 4, 6 | 10.4 |
| 62 | 0.2.0 | decision | **Move the plain reader out of `parse/markdown-to-adf.ts` into `markdown/plain/`.** | 3 | 4 | 2 | 5 | 2 | 8.9 |
| 38 | 0.3.0 | | **Spell a lone surrogate in a text node so it survives a UTF-8 encode.** | 2 | 2 | 4 | 7 | 1 | 19.5 |
@@ -106,7 +106,7 @@ spellings" bullet, which counts one fewer.
The comprehension panel's worst place: `escapeClaims` and `escapeClosedRuns` re-implement the
reader's view — flanking, code-span closers, link-definition openings, highlight flanking — and
only the property tests catch drift. It caps Locality.
only the property tests catch drift. It caps the panel's Locality score.
### 52. Spell `colwidth` as a comma list, `colwidth="340,420"`.
@@ -126,16 +126,17 @@ Today `marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"`, the mar
escaped JSON. Breaking where the panel picks another spelling: `MIGRATION.md`'s Spellings table
gains its row.
### 60. Give the parser's questions to the emitter one named module, so the parse→emit dependency reads as intended.
### 60. Collect the questions `parse/` asks `emit/` into one named module.
`parse/` asks `emit/` through `commonMarkSpelling` and `openingLinkTakesDirective`, each imported
from where it happens to live. One module naming the questions keeps `docs/decisions.md` §The source
parts by ADF and format true as they grow.
### 59. Group the directive grammar into `src/markdown/directive/`, and move `Read<T>` to `result.ts` and `Flavour` out of the plain flavour.
### 59. Group the directive grammar into `src/markdown/directive/`, move `Read<T>` to `result.ts`, and move `Flavour` to `markdown/flavour.ts`.
Eight directive files sit across three directories, and the `markdown/` root holds 14 entries. HTML
needs `Read<T>`, `Flavour`, the carry and the mark spellings out of `markdown/`.
needs `Read<T>` and `Flavour` out of the plain flavour and `markdown/`; it needs the carry and the
mark spellings too, which item 7 moves where it learns what HTML shares.
### 50. Read `[](/url)` and `[]()` as CommonMark's empty link.
@@ -148,7 +149,8 @@ which counts one fewer.
### 62. Move the plain reader out of `parse/markdown-to-adf.ts` into `markdown/plain/`.
What moves: the plain reader's code in `markdown-to-adf.ts` — the alert and task-marker reads, the
`mintTaskIds` call and the `inlineLeaves` use.
### 38. Spell a lone surrogate in a text node so it survives a UTF-8 encode.
@@ -172,8 +174,8 @@ punctuation — and, where they do, read the code point, with a fixture per dire
### 42. Trim a text leaf's trailing blanks in linear time.
`plain/inline-reduction.ts`'s `leafEdges` finds the trail with an unanchored `/[ \t]*$/`, quadratic in a run
of blanks inside one leaf: a paragraph of `a`, 80 000 spaces, `b` takes 6.5 s in
`plain/inline-reduction.ts`'s `leafEdges` finds the trail with an unanchored `/[ \t]*$/`, quadratic
in a run of blanks inside one leaf: a paragraph of `a`, 80 000 spaces, `b` takes 6.5 s in
`adfToPlainMarkdown`. Scan backward, as the expand title's trim does.
### 48. Keep the release path publishing past npm's bypass-2FA token retirement.