3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-13 04:08:29 +01:00

Answer architecture round 1: 1 🔴, 3 🟡; name the other-target link and no-alt case, pin both, file items 117 and 118

This commit is contained in:
2026-10-14 22:24:48 +02:00
parent 8e7f21207d
commit c9f24f634d
5 changed files with 31 additions and 9 deletions
+1 -1
View File
@@ -374,7 +374,7 @@ saving what this pair read replaces mentions, attachments and macros with text.
| `backgroundColor` | `==text==` | `==text==` on one line, the text touching both delimiters, bounded outside by whitespace, punctuation or a line edge, or touching a Han, Hangul, Hiragana, Katakana, Thai, Lao, Khmer or Myanmar character on either side, in the editor's default highlight `#f8e6a0` |
| `decisionList` | a bullet list | — |
| `expand`, `nestedExpand` | Obsidian's folded callout, `> [!NOTE]- Title` | `-` or `+` after any word, the rest of the marker's line the title; a link reads `text (target)`, or its text alone where the text is the target with or without `mailto:`; an expand inside an expand is a `nestedExpand` |
| external `media` | in a block `![alt](url)`, or `[![alt](url)](href)` where a link marks it; inline `[alt](url)`, or `[alt](href) ([image](url))` where a link marks it | — |
| external `media` | in a block `![alt](url)`, or `[![alt](url)](href)` where a link marks it; inline `[alt](url)`, or `[alt](href) ([image](url))` where a link to another target marks it, the link text `href` where `alt` is empty | — |
| `inlineCard`, `blockCard`, `embedCard` | a link to the card's URL, else its name | — |
| `layoutSection`, `bodiedExtension`, `bodiedSyncBlock`, `multiBodiedExtension`, `extensionFrame`, `caption`, a node this version does not know | its blocks or its text | — |
| `mention`, `status`, `emoji`, `date` | their text: `@` kept, a mention with none `@` and its id, an emoji its `shortName` without, a date `2026-09-13` in UTC | — |
+3 -2
View File
@@ -288,8 +288,9 @@ reads that title as the caption; `todo.md` item 100 puts that reading back to a
2026-10-14, a reader panel of the export tool, the LLM pipeline and a support agent, 3 of 3. Goals 6
and 7. Valid while the portable reader reads an image inside a link as the link's text.
Where `adfToPortableMarkdown` writes an external image inline, as in a table cell, a link marking it
keeps its target and the image follows as a link reading `image`. Every reader first wrote
Where `adfToPortableMarkdown` writes an external image inline, as in a table cell, and a link to
another target marks it, the link keeps its target and the image follows as a link reading `image`.
With no alt text, the link text is the link's target, as `[](href)` reads. Every reader first wrote
`[![alt](url)](href)`, which reads back as `[alt](href)` and so breaks the byte-for-byte rewrite.
Each then picked this over `[alt](url)` and `[alt](href)`, which drop a target.
@@ -247,8 +247,10 @@ test('keeps both targets of a linked external image standing inline, the image l
assert.equal(inCell, '| h |\n| --- |\n| [Moon](https://example.com/docs "Docs") ([image](https://example.com/moon.png)) |\n')
const read = portableMarkdownToAdf(inCell)
assert.equal(read.ok && portableDocument(read.value), inCell)
assert.equal(portable(table(said('See'), node('mediaSingle', {}, linked('https://example.com/docs')))), '| h |\n| --- |\n| See [https://example.com/moon.png](https://example.com/docs "Docs") ([image](https://example.com/moon.png)) |\n')
assert.equal(portable(table(said('See'), node('mediaSingle', {}, linked('https://example.com/docs')))), '| h |\n| --- |\n| See [https://example.com/docs](https://example.com/docs "Docs") ([image](https://example.com/moon.png)) |\n')
assert.equal(portable(paragraph(text('See '), linked('https://example.com/moon.png', 'Moon'))), 'See [Moon](https://example.com/moon.png "Docs")\n')
assert.equal(portable(paragraph(text('See '), linked('https://example.com/moon.png'))), 'See [https://example.com/moon.png](https://example.com/moon.png "Docs")\n')
assert.equal(portable(paragraph(text('See '), linked(''))), 'See [https://example.com/moon.png](<> "Docs") ([image](https://example.com/moon.png))\n')
})
test('keeps an external image wherever it stands and spells a stored file as its alt text, else a note', () => {
+7 -4
View File
@@ -80,15 +80,18 @@ function idMention(id: unknown): string | undefined {
return typeof id === 'string' && id !== '' ? `@${id}` : undefined
}
// An image standing inline is a link to it: CommonMark's inline image reads back as no node.
// An image standing inline is a link to it: CommonMark's inline image reads back as no node, and one inside a link as the link's text.
function mediaLeaves(attrs: Readonly<AdfAttributes>, marks: readonly AdfMark[], container: LineContainer): AdfNode[] {
const alt = nonEmpty(attrs['alt'])
const url = nonEmpty(attrs['url'])
if (attrs['type'] !== 'external' || url === undefined) return textLeaves(alt, marks, container, 'image')
const href = nodeAttrs(marks.find((mark) => mark.type === 'link') ?? {})['href']
if (href === url) return textLeaves(alt ?? url, marks, container)
const link = marks.find((mark) => mark.type === 'link')
const href = link === undefined ? undefined : nodeAttrs(link)['href']
if (typeof href !== 'string') return linkedLeaves(alt ?? url, url, marks, container)
return [...textLeaves(alt ?? url, marks, container), textLeaf(' (', []), ...linkedLeaves('image', url, marks, container), textLeaf(')', [])]
if (href === url) return textLeaves(alt ?? url, marks, container)
const linkedAlt = textLeaves(alt ?? nonEmpty(href) ?? url, marks, container)
const image = linkedLeaves('image', url, marks, container)
return [...linkedAlt, textLeaf(' (', []), ...image, textLeaf(')', [])]
}
function noteLeaves(name: string): AdfNode[] {
+17 -1
View File
@@ -6,7 +6,7 @@
`Bar = 9`
`Next ID = 117`
`Next ID = 119`
| Goal | W |
|---|---|
@@ -49,6 +49,7 @@
| 81 | 0.4.0 | | **Fail the gate unless the README's error tables list exactly the codes `ConvertErrorCode` holds.** | 1 | 2 | 2 | 5 | 5 | 3 | 16.3 |
| 112 | 0.4.0 | | **Pad a GFM pipe table's short rows with empty cells, read its body rows holding no pipe, and read a one-column table without leading pipes in `portableMarkdownToAdf`.** | 3 | 3 | 5 | 6 | 6 | 4 | 16.3 |
| 46 | 0.4.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 3 | 5 | 5 | 3, 8 | 14.8 |
| 117 | 0.4.0 | defect | **Read a GFM pipe-table row holding more cells than its header as GFM does, or name the goal that outranks Goal 4 in refusing it.** | 2 | 2 | 3 | 4 | 6 | 4 | 13.5 |
| 72 | 0.4.0 | decision | **Re-create `gitea.larvit.se/larvit/adf-codec` as a read-only pull mirror of GitHub, its issues and Actions off and its description pointing at GitHub.** | 2 | 1 | 1 | 3 | 6 | 3 | 13.1 |
| 107 | 0.4.0 | decision | **Spell the block carry's JSON so its length stays linear in the node's JSON.** | 4 | 5 | 7 | 3 | 5 | 1 | 10.8 |
| 51 | 0.4.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 4 | 5 | 2 | 7 | 4, 5 | 10.4 |
@@ -61,6 +62,7 @@
| 56 | 0.5.0 | principle | **Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.** | 4 | 5 | 8 | 2 | 4 | 1 | 6.8 |
| 76 | 0.5.0 | principle | **State the files the package does not ship once, so the build, the lint ceiling and the coverage exclusions cannot drift apart.** | 2 | 2 | 3 | 1 | 3 | 1 | 5.5 |
| 82 | 0.5.0 | principle | **Give the directive tests and fixtures random UUID v7 ids in place of `a-1`, `a`, `x` and `h`.** | 1 | 3 | 1 | 1 | 2 | 1 | 4.2 |
| 118 | 0.5.0 | principle | **Spell a portable link's title through `oneLine`, as the text beside it is.** | 1 | 1 | 1 | 1 | 2 | 6 | 2.5 |
| 57 | 0.5.0 | principle | **Make each `ci.sh` leg build what it reads, so one leg run alone tests the current tree.** | 2 | 3 | 3 | 1 | 3 | 8 | 0.6 |
| 58 | 0.5.0 | principle | **Port `ci.sh`, `publish.sh` and `docker-runner.sh` to standalone Python scripts.** | 4 | 6 | 4 | 1 | 2 | 8 | -2.6 |
| 8 | 0.6.0 | | **Ship a CLI.** | 3 | 7 | 5 | 7 | 6 | 3 | 18.6 |
@@ -271,6 +273,14 @@ or report the unminified gzip). The figure lands in README §The package beside
dependencies" claim, and in a size column of the comparison table. Measured today,
unminified: tarball 60.4 kB, unpacked 221.5 kB, JS gzipped 45.6 kB.
### 117. Read a GFM pipe-table row holding more cells than its header as GFM does, or name the goal that outranks Goal 4 in refusing it.
`docs/decisions.md` §The portable reader refuses what GFM's reading would drop refuses such a row as
`malformed-pipe-table` by Goal 7, which ranks below Goal 4: a bot whose model writes an over-wide row
gets an error where GitHub renders a table. The maintainer either reads the row as GFM does, cutting
the cells, or states the goal that outranks Goal 4 here and cites it in the entry. Found by the
principles audit of item 99, 2026-10-14.
### 72. Re-create `gitea.larvit.se/larvit/adf-codec` as a read-only pull mirror of GitHub, its issues and Actions off and its description pointing at GitHub.
Lands after every other `0.4.0` item (the maintainer, 2026-10-05). The maintainer does this. Gitea
@@ -369,6 +379,12 @@ agree. Found by the technical-principles audit, 2026-10-04.
Technical principle: tests use random UUID v7 ids; `portable-markdown-to-adf.test.ts` already does.
Found by the technical-principles audit of item 78, 2026-10-05.
### 118. Spell a portable link's title through `oneLine`, as the text beside it is.
`portableLink` (`src/markdown/portable/inline-reduction.ts`) repeats `oneLine`'s replacements by
hand, so a change to what a portable line drops would miss link titles. Technical principle: DRY.
Found by the principles audit of item 99, 2026-10-14.
### 57. Make each `ci.sh` leg build what it reads, so one leg run alone tests the current tree.
Technical principle "Compose, do not entangle": Deno, Bun and the tests read the install leg's