Compare commits

..

3 Commits

Author SHA1 Message Date
lilleman 2f03e20549 10b - 34 files the flanking read beside an astral symbol
CI / gate (push) Successful in 34s
CI / publish (push) Successful in 7s
2026-09-25 21:10:23 +02:00
lilleman ae2dc6054b 10b - a == pair is bounded by the code point outside it, CommonMark's word character test 2026-09-25 21:10:05 +02:00
lilleman 7a53b6f7a0 10b - tests: a symbol outside the BMP bounds a == pair 2026-09-25 21:09:12 +02:00
3 changed files with 13 additions and 10 deletions
+1
View File
@@ -143,6 +143,7 @@ test('lifts a == pair to the editor default highlight, Yellow200 #f8e6a0 in @atl
assert.deepEqual(lifted('==**a**_b_ `c`==\n'), [paragraph(text('a', highlight, strong), text('b', highlight, em), text(' ', highlight), text('c', code))])
assert.deepEqual(lifted('==`a`==\n'), [paragraph(text('a', code))])
assert.deepEqual(lifted('x==y==z ==a == b==, (==c==) _d_==e==\n'), [paragraph(text('x==y==z '), text('a == b', highlight), text(', ('), text('c', highlight), text(') '), text('d', em), text('e', highlight))])
assert.deepEqual(lifted('😀==b== ==c==😀 é==d==\n'), [paragraph(text('😀'), text('b', highlight), text(' '), text('c', highlight), text('😀 é==d=='))])
assert.deepEqual(lifted('# ==h==\n\n| ==c== |\n| --- |\n'), [
node('heading', { level: 1 }, text('h', highlight)),
bare('table', bare('tableRow', bare('tableHeader', paragraph(text('c', highlight))))),
+6 -7
View File
@@ -1,5 +1,6 @@
import type { AdfDocument, AdfMark, AdfNode } from '../../adf/document.ts'
import { blockNodeModel } from '../../adf/block-nodes.ts'
import { isWordCharacter } from '../commonmark/emphasis-matching.ts'
import { highlightDelimiter, readAlertMarker, readTaskMarker } from '../plain-conventions.ts'
import { mergeAdjacentText, sameMarks } from '../../adf/editor-normal.ts'
import { nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
@@ -114,8 +115,11 @@ function delimiters(inline: readonly AdfNode[]): Delimiter[] {
const end = offset + highlightDelimiter.length
const before = offset > 0 ? text[offset - 1] : textOf(inline[index - 1])?.at(-1)
const after = text[end] ?? textOf(inline[index + 1])?.[0]
const closes = flanks(before) && bounds(text[end])
found.push({ closes, holder: node, line, node: index, offset, opens: flanks(after) && bounds(text[offset - 1]), position: position + offset })
// Outside a delimiter a node's edge bounds it, standing where the source spelled a mark's punctuation.
const outsideBefore = Array.from(text.slice(Math.max(0, offset - 2), offset)).at(-1) ?? ''
const outsideAfter = Array.from(text.slice(end, end + 2))[0] ?? ''
const closes = flanks(before) && !isWordCharacter(outsideAfter)
found.push({ closes, holder: node, line, node: index, offset, opens: flanks(after) && !isWordCharacter(outsideBefore), position: position + offset })
}
position += text.length
}
@@ -126,11 +130,6 @@ function flanks(character: string | undefined): boolean {
return character !== undefined && !/\s/.test(character)
}
// Outside a delimiter, a node's edge stands where the source spelled a mark's punctuation.
function bounds(character: string | undefined): boolean {
return character === undefined || /[\s\p{P}\p{S}]/u.test(character)
}
// Each opener takes the next closer holding at least one character after it, both in one line and under the same marks.
function pairedDelimiters(inline: readonly AdfNode[]): Delimiter[] {
const found = delimiters(inline)
+6 -3
View File
@@ -22,7 +22,7 @@ Start a session with: `Read AGENTS.md and todo.md, then do what todo.md's "Next
## Milestones
Shipping order: 3h, 3i, 3j, 5a, 5b, 5c, 5d, 5 → `0.1.0` (shipped 2026-09-05); 3k, 11, 4, 12, 13, 4b,
4c, 14, 15, 16, 18, 4d, 28, 17, 29, 19, 20, 21, 22, 32, 23, 24, 25, 30, 26, 27, 10, 6, 7, 31, 33, 5f, 5g →
4c, 14, 15, 16, 18, 4d, 28, 17, 29, 19, 20, 21, 22, 32, 23, 24, 25, 30, 26, 27, 10, 6, 7, 31, 33, 34, 5f, 5g →
`0.2.0`;
8, 9 → TBD; 5e last.
The numbering is the order the work was planned in, not the order it ships. Everything known and
@@ -43,8 +43,7 @@ HTML doubles the importers and the file count they touch, and 25 to 27 because t
panel says the next reader pays for.
29 and 30 come from 17's prose pass (2026-09-20). 29 reads first because every goal is what a later
ask is settled against, 19's included; 30 sits beside 25, the other chunk rereading AGENTS.md.
33 comes from 10a (2026-09-25) and reads beside 31, the other chunk about what the pipeline
measures.
33 comes from 10a and 34 from 10b (2026-09-25); both read beside 31.
31 comes from 20's gate runs (2026-09-21) and reads beside 5f, the other chunk putting a measured
number under the pipeline. 32 comes from 21's review (2026-09-21) and reads beside 22, the other
chunk clearing a §11 seam.
@@ -62,6 +61,10 @@ chunk clearing a §11 seam.
on one paragraph of 2000 × `un` plus `**-r**`: each run its flanking cannot spell re-emits the
whole line before riding the carry, quadratic in the runs (§11 Bounds), and the plain
reduction's `spellableLine` drops one mark per re-emit the same way. Make both linear.
- [ ] **34 — Emphasis flanking reads a whole character (`0.2.0`).** Check whether
`line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an astral
symbol — a lone surrogate is neither punctuation nor symbol, where CommonMark reads `😀` as
punctuation — and, where they do, read the code point, with a fixture per direction.
- [x] **24 — The conformance gates have a directory (`0.2.0`).**
- [x] **25 — AGENTS.md §8 and §11 are findable (`0.2.0`).**
- [x] **26 — The two mutable structures say what they guarantee (`0.2.0`).**