Revise 10: an alert-and-callout lossy pair, to and from markdown #71

Merged
lilleman merged 1 commits from design-10-revised into main 2026-09-14 19:34:55 +02:00
Showing only changes of commit da58a59f70 - Show all commits
+63 -39
View File
@@ -167,48 +167,70 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
README's tagline and `package.json`'s `description` regain HTML (5g).
- [ ] **8 — CLI.** A later goal, shaped around the personas once the library exists.
- [ ] **9 — The online sandbox.** A web page with two textboxes converting back and forth between ADF and markdown, powered by the library's browser build.
- [ ] **10 — Lossy conversion (`0.2.0`).** A direction that only converts what Markdown actually
supports, keeping the ADF's data while dropping what markdown cannot hold — format, design
and the richer nodes.
**Settled** (the maintainer, 2026-09-13): `adfToPlainMarkdown(doc)` is one export whose body
reduces the document ADF→ADF in `src/adf/` and hands the result to `adfToMarkdown`, so §1's
four conversions stay four. Its markdown is the flavour without directives — CommonMark, the
pipe table and `~~` — and it refuses only what the document guard refuses
(`not-an-adf-document`, `unsupported-document-version`, `unsupported-nesting-depth`); every
other shape degrades. The reduction:
- `panel`, `layoutSection`/`layoutColumn`, `bodiedExtension`, `bodiedSyncBlock`,
`multiBodiedExtension` and `extensionFrame` unwrap to their body blocks in order; `expand`
and `nestedExpand` put their title first as a strong paragraph.
- The CommonMark blocks keep their spelling, attributes dropped.
- `taskList` and `decisionList` become bullet lists, a task item's state leading its text as
`[x]` or `[ ]`, the way Obsidian and GFM write a checkbox: `- [x] Write the spec`.
- `mention` and `status` become their text, `emoji` its text or else its `shortName`, and
`date` its ISO date in UTC (`2026-09-13`).
- `inlineCard`, `blockCard` and `embedCard` become a link to their `url`, dropped when they
carry only `data`; a `mediaSingle` holding an external image stays `![alt](url)`; `media`,
`mediaGroup` and `mediaInline` become their `alt` text or nothing; `caption` its text as a
paragraph; `extension`, `inlineExtension` and `syncBlock` their `text` attribute or nothing;
`placeholder` nothing; a node no row names, or one standing where no spelling holds it, its
blocks or its text.
- A table stays a pipe table: the first row becomes the header, a cell's blocks join on one
line with spaces, and spans and the cells they cover drop.
- `code`, `em`, `link`, `strike` and `strong` stay and every other mark drops, keeping its text;
a link no CommonMark escape writes becomes its text, and a mark run CommonMark's flanking or
matching cannot spell drops its mark.
- [ ] **10 — Lossy conversion (`0.2.0`).** Markdown other tools render readably, to and from ADF,
keeping the content while dropping what markdown cannot hold — format, design and the richer
nodes.
**Settled** (the maintainer, 2026-09-14): two exports composed around the lossless pair, so §1's
four conversions stay four. `adfToPlainMarkdown(doc)` reduces the document ADF→ADF and hands it
to `adfToMarkdown`; `plainMarkdownToAdf(markdown)` hands the markdown to `markdownToAdf` and
lifts the result ADF→ADF. Both carry markdown conventions, so the reduction sits in
`src/markdown/emit/`, the lift in `src/markdown/parse/` and what both read in `src/markdown/`
(§11). The markdown is the flavour without directives — CommonMark, the pipe table and `~~` —
plus the conventions below, chosen for readability from a survey of GitHub, GitLab, Gitea,
Obsidian, Pandoc, MkDocs, Docusaurus, Typora, Joplin, Logseq, Bear, Notion, Azure DevOps and
Discord, GitHub's renderer confirming each shape. Writing refuses only what the document guard
refuses (`not-an-adf-document`, `unsupported-document-version`, `unsupported-nesting-depth`)
and degrades every other shape; reading refuses what `markdownToAdf` refuses. A lifted node
carries no `localId`. The lift also reads other tools' spellings — type words in any case,
Obsidian's aliases, `[X]` — since it reads their output and never writes those spellings.
- A `panel` is an alert: the marker alone on the quote's first line, a blank `>`, then the body
(`> [!WARNING]`), in GitHub's five words by colour — info `NOTE`, note `IMPORTANT`, tip and
success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE`. The lift reads those words
back (`NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error) and
Obsidian's by meaning (hint tip; success, check and done success; attention warning; danger,
failure, fail, missing and bug error; any other word info). Text after a marker in its
paragraph is the panel's first body paragraph.
- An `expand` or `nestedExpand` is Obsidian's folded callout, `> [!NOTE]- Title`, a blank `>`,
then the body. The lift reads a fold sign (`-` or `+`) as an expand whatever the word, the
rest of the marker's paragraph as its title, and an expand inside an expand as a
`nestedExpand`.
- A `taskList` is a bullet list whose items lead with `[x]` or `[ ]` (`- [x] Write the spec`).
The lift reads a list whose every item is so marked back as a `taskList` — a `blockTaskItem`
where an item holds more than one block, a nested task list moved beside its item — and
leaves mixed and ordered lists plain. A `decisionList` is a plain bullet list.
- `backgroundColor` is `==text==`, and the lift gives `==text==` the Atlassian editor's default
highlight colour.
- `layoutSection`/`layoutColumn`, `bodiedExtension`, `bodiedSyncBlock`, `multiBodiedExtension`
and `extensionFrame` unwrap to their body blocks in order; the CommonMark blocks keep their
spelling, attributes dropped.
- `mention` and `status` become their text, the mention's `@` kept; `emoji` its text or else its
`shortName`; `date` its ISO date in UTC (`2026-09-13`); `inlineCard`, `blockCard` and
`embedCard` a link to their `url`, dropped when they carry only `data`; a `mediaSingle`
holding an external image stays `![alt](url)`; `media`, `mediaGroup` and `mediaInline` their
`alt` text or nothing; `caption` its text as a paragraph; `extension`, `inlineExtension` and
`syncBlock` their `text` attribute or nothing; `placeholder` nothing; a node no row names, or
one standing where no spelling holds it, its blocks or its text.
- A table stays a pipe table: the first row becomes the header, a cell's blocks join on one line
with spaces, and spans and the cells they cover drop.
- `code`, `em`, `link`, `strike` and `strong` stay and every other mark drops, keeping its text —
`subsup` too, since `~2~` is a strike on GitHub; a link no CommonMark escape writes becomes its
text, and a mark run CommonMark's flanking or matching cannot spell drops its mark.
- A newline in text becomes a hard break and edge whitespace is trimmed; carriage returns and
null characters are removed; a paragraph line opening with a code span whose backticks would
read as a fence loses the code mark; an empty paragraph drops, and adjacent lists of one type
merge.
- [ ] **10a — Obsidian's formats.** Look up the formats Obsidian-flavoured markdown adds —
callouts, highlights, embeds, task states and whatever else it writes — and propose which
of the reduction's rows should adopt one; the maintainer settles the proposal, revising the
rows above, before 10b starts (the maintainer's request, 2026-09-13).
- [ ] **10b — The reduction.** The reduction in `src/adf/`, tests first, a test per row as 10a
leaves them.
- [ ] **10c — `adfToPlainMarkdown`.** The export and its README section, and a property over
4.2's generators: it refuses only the guard's codes, and its output reads back through
`markdownToAdf` holding no node or mark the flavour spells as a directive. AGENTS.md §1
records the reduction as what keeps the conversions at four.
- Rejected in the survey: `~sub~` and `^sup^`, underline and colour spellings, raw HTML
(`<details>`, `<mark>`), MkDocs `!!!` and the `:::` admonition family, footnotes, definition
lists, wikilinks, embeds, tags, comments, TOC tokens, spoilers, task states past `[x]`/`[ ]`,
and lifting bare URLs, `@name`, `:shortcode:` or ISO dates into nodes.
- [ ] **10a — The reduction.** `adfToPlainMarkdown`'s ADF→ADF reduction, tests first, a test per
row above.
- [ ] **10b — The lift.** `plainMarkdownToAdf`'s ADF→ADF lift, tests first, a test per row it reads,
other tools' spellings included; the editor's default highlight colour looked up and cited.
- [ ] **10c — The exports.** `adfToPlainMarkdown` and `plainMarkdownToAdf` exported with their README
sections, and two properties over 4.2's generators: writing refuses only the guard's codes,
and markdown `adfToPlainMarkdown` wrote reads back through `plainMarkdownToAdf` and writes
again byte for byte. AGENTS.md §1 records the pair as composed around the lossless one.
- [x] **11 — Atlassian's ADF schema as the tables' truth.**
- [x] **11a — The vendored schema.**
- [x] **11b — The gate.**
@@ -259,7 +281,9 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
- [ ] **12b — The inline form.** Inline nodes, directive marks, `text` and the inline carry
`!adf:carry{json=…}` spelled and read as `!adf:name[content]{attrs}`, with the prefix claim
and its escape; the round-trip, normalization and `errors/` fixtures holding inline forms
re-spelled, and the gate green.
re-spelled, and the gate green. The content slot of `emoji`, `mention` and `status` refuses a
text node carrying attributes as `unsupported-node-shape`, which it drops silently today (the
maintainer, 2026-09-14).
- [ ] **12c — The block form.** Openers and `!adf:/name` closers, leaf vs container by content
model, empty pairs, `listBreak` and the `carry` fence, spelled and read; the fence-length
rule and the corpus test's fence nesting check deleted; the remaining fixtures re-spelled