41 - a callout title keeps its link targets; Goal 6, what happens is what the audience expects #149
@@ -165,7 +165,7 @@ reading is the ask. Never ask "A or B?": state the gap, the earlier entries of i
|
|||||||
nearest text, a candidate entry in that file's voice, and the instance it yields. An entry that
|
nearest text, a candidate entry in that file's voice, and the instance it yields. An entry that
|
||||||
keeps collecting instances is wrong: rewrite it.
|
keeps collecting instances is wrong: rewrite it.
|
||||||
|
|
||||||
Which output the audience expects — README goal 5 — is settled by a reader panel rather than
|
Which output the audience expects — README goal 6 — is settled by a reader panel rather than
|
||||||
asked: three fresh-context readers, one per README persona the conversion serves, each given only
|
asked: three fresh-context readers, one per README persona the conversion serves, each given only
|
||||||
`## Audience` and the input, writing what they expect before picking among outputs the goals
|
`## Audience` and the input, writing what they expect before picking among outputs the goals
|
||||||
allow, rendered, shuffled, with no rationale and nothing saying what is implemented. Three agreeing
|
allow, rendered, shuffled, with no rationale and nothing saying what is implemented. Three agreeing
|
||||||
|
|||||||
@@ -42,22 +42,23 @@ In priority order.
|
|||||||
plain markdown cannot hold — format, design, structure — never content: what a reader of the
|
plain markdown cannot hold — format, design, structure — never content: what a reader of the
|
||||||
rendered document sees or follows, its text, images and link targets. Content the document only
|
rendered document sees or follows, its text, images and link targets. Content the document only
|
||||||
references is marked where it stood, by a note that reads as the converter's and names what was
|
references is marked where it stood, by a note that reads as the converter's and names what was
|
||||||
left out. What is dropped goes the way the audience expects. The lossy pair creates and
|
left out. The lossy pair creates and exports; it never saves back over the document it read —
|
||||||
exports; it never saves back over the document it read — identity (task, mention, media ids)
|
identity (task, mention, media ids) lives only in the lossless pair.
|
||||||
lives only in the lossless pair.
|
6. **What happens is what the audience expects.** Where the goals above leave a choice, a
|
||||||
6. **Failures are values.** Nothing throws, and `code` is a closed list — as much a contract as
|
conversion takes the one its audience would predict, reading the input as written.
|
||||||
|
7. **Failures are values.** Nothing throws, and `code` is a closed list — as much a contract as
|
||||||
the emitted formats are.
|
the emitted formats are.
|
||||||
7. **Nothing in the way.** No runtime dependencies, no I/O, no configuration, no host API: ESM on
|
8. **Nothing in the way.** No runtime dependencies, no I/O, no configuration, no host API: ESM on
|
||||||
any ES2022 engine, in a browser as readily as on a server, installed from public npm. The public
|
any ES2022 engine, in a browser as readily as on a server, installed from public npm. The public
|
||||||
surface is the conversions, their types, `isAdfDocument`, and what a consumer needs to check a
|
surface is the conversions, their types, `isAdfDocument`, and what a consumer needs to check a
|
||||||
guarantee this README makes; a helper is exported only when a persona cannot do without it.
|
guarantee this README makes; a helper is exported only when a persona cannot do without it.
|
||||||
8. **Correct before fast.** Each format means what its own specification says — markdown as the
|
9. **Correct before fast.** Each format means what its own specification says — markdown as the
|
||||||
CommonMark spec reads it, well-formed HTML as the HTML standard parses it — both in what this
|
CommonMark spec reads it, well-formed HTML as the HTML standard parses it — both in what this
|
||||||
library reads and in what a conforming parser reads from what it writes. A call takes a whole
|
library reads and in what a conforming parser reads from what it writes. A call takes a whole
|
||||||
document and returns a whole result.
|
document and returns a whole result.
|
||||||
9. **Fast once correct.** Conversion time grows linearly with the document wherever the goals above
|
10. **Fast once correct.** Conversion time grows linearly with the document wherever the goals
|
||||||
allow it; a faster path that risks one of them is not taken.
|
above allow it; a faster path that risks one of them is not taken.
|
||||||
10. **Source a contributor can hold.** Any one function reads in one sitting, and no change makes
|
11. **Source a contributor can hold.** Any one function reads in one sitting, and no change makes
|
||||||
the longest one longer.
|
the longest one longer.
|
||||||
|
|
||||||
## Audience
|
## Audience
|
||||||
@@ -131,7 +132,7 @@ read replaces mentions, attachments and macros with text.
|
|||||||
| ADF | Written | Read back |
|
| ADF | Written | Read back |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `panel` | a GitHub alert, `> [!WARNING]`: info `NOTE`, note `IMPORTANT`, tip and success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE` | `NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error, and Obsidian's: hint tip; success, check, done success; attention warning; danger, failure, fail, missing, bug, error error; any other word info — in any case; the rest of the marker's line is the first paragraph |
|
| `panel` | a GitHub alert, `> [!WARNING]`: info `NOTE`, note `IMPORTANT`, tip and success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE` | `NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error, and Obsidian's: hint tip; success, check, done success; attention warning; danger, failure, fail, missing, bug, error error; any other word info — in any case; the rest of the marker's line is the first paragraph |
|
||||||
| `expand`, `nestedExpand` | Obsidian's folded callout, `> [!NOTE]- Title` | `-` or `+` after any word, the rest of the marker's line the title; an expand inside an expand is a `nestedExpand` |
|
| `expand`, `nestedExpand` | Obsidian's folded callout, `> [!NOTE]- Title` | `-` or `+` after any word, the rest of the marker's line the title, a link as `text (target)`, an autolink as its text; an expand inside an expand is a `nestedExpand` |
|
||||||
| `taskList` | `- [x] Done`, `- [ ] Todo` | a bullet list whose every item is so marked, `[X]` too |
|
| `taskList` | `- [x] Done`, `- [ ] Todo` | a bullet list whose every item is so marked, `[X]` too |
|
||||||
| `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` |
|
| `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` |
|
||||||
| `table` | a pipe table: the first row its header, a cell's blocks on one line, a span kept under its header by empty cells | — |
|
| `table` | a pipe table: the first row its header, a cell's blocks on one line, a span kept under its header by empty cells | — |
|
||||||
|
|||||||
+29
-28
@@ -55,7 +55,7 @@ Where a container's own spelling cannot hold the child it has — a `bulletList`
|
|||||||
|
|
||||||
## Foreign HTML sorts three ways
|
## Foreign HTML sorts three ways
|
||||||
|
|
||||||
2026-08-23, the sort 2026-09-20, the maintainer. Goals 1, 3 and 6. Valid while ADF holds no node
|
2026-08-23, the sort 2026-09-20, the maintainer. Goals 1, 3 and 7. Valid while ADF holds no node
|
||||||
for a bare container, a comment or a script. Lands with `todo.md` 6.
|
for a bare container, a comment or a script. Lands with `todo.md` 6.
|
||||||
|
|
||||||
Every foreign element `htmlToAdf` and `markdownToAdf` read sorts one of three ways, never a silent
|
Every foreign element `htmlToAdf` and `markdownToAdf` read sorts one of three ways, never a silent
|
||||||
@@ -76,7 +76,7 @@ than they buy.
|
|||||||
|
|
||||||
## Names stay text
|
## Names stay text
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while resolving a name to an id needs I/O.
|
2026-08-23, the maintainer. Goal 8. Valid while resolving a name to an id needs I/O.
|
||||||
|
|
||||||
A bare `@name` or `:smile:` in typed text stays a text node. Only directives produce
|
A bare `@name` or `:smile:` in typed text stays a text node. Only directives produce
|
||||||
mention/emoji/media nodes; resolving names to ids is the consumer's job.
|
mention/emoji/media nodes; resolving names to ids is the consumer's job.
|
||||||
@@ -127,7 +127,7 @@ accepted.
|
|||||||
|
|
||||||
## Plain task ids come from position
|
## Plain task ids come from position
|
||||||
|
|
||||||
2026-09-26, spelling 2026-09-29, the maintainer. Goals 5 and 7. Valid while a site rejects a task
|
2026-09-26, spelling 2026-09-29, the maintainer. Goals 5 and 8. Valid while a site rejects a task
|
||||||
node with no `localId`.
|
node with no `localId`.
|
||||||
|
|
||||||
`plainMarkdownToAdf` gives each `taskList`, `taskItem` and `blockTaskItem` lacking one a `localId`
|
`plainMarkdownToAdf` gives each `taskList`, `taskItem` and `blockTaskItem` lacking one a `localId`
|
||||||
@@ -139,15 +139,16 @@ ids are only skipped.
|
|||||||
|
|
||||||
## A callout title keeps its link targets
|
## A callout title keeps its link targets
|
||||||
|
|
||||||
2026-09-29, the maintainer. Goal 5. Valid while an expand's `title` is a string. Lands with
|
2026-09-29, the maintainer. Goals 5 and 6. Valid while an expand's `title` is a string.
|
||||||
`todo.md` 41.
|
|
||||||
|
|
||||||
`plainMarkdownToAdf` writes a link in a folded callout's title as its text and its target in
|
`plainMarkdownToAdf` writes a link in a folded callout's title as its text and its target in
|
||||||
parentheses: `> [!faq]- See [x](http://y)` reads to the title `See x (http://y)`.
|
parentheses: `> [!faq]- See [x](http://y)` reads to the title `See x (http://y)`. A link whose text
|
||||||
|
is its target, `mailto:` aside, keeps its text alone: `<http://y>` titles `http://y`, `<a@b.c>`
|
||||||
|
`a@b.c` — three persona readers agreeing, 2026-09-30.
|
||||||
|
|
||||||
## The plain flavour's spellings
|
## The plain flavour's spellings
|
||||||
|
|
||||||
2026-09-14, panels 2026-09-25 and 2026-09-29, the maintainer. Goal 5. Valid while GitHub's
|
2026-09-14, panels 2026-09-25 and 2026-09-29, the maintainer. Goals 5 and 6. Valid while GitHub's
|
||||||
renderer is the one the audience's markdown is read in.
|
renderer is the one the audience's markdown is read in.
|
||||||
|
|
||||||
README §Plain markdown's rows come from a survey of GitHub, GitLab, Gitea, Obsidian, Pandoc,
|
README §Plain markdown's rows come from a survey of GitHub, GitLab, Gitea, Obsidian, Pandoc,
|
||||||
@@ -165,7 +166,7 @@ past `[x]`/`[ ]`, and lifting bare URLs, `@name`, `:shortcode:` or ISO dates int
|
|||||||
|
|
||||||
## The HTML dialect
|
## The HTML dialect
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goals 4 and 7. Valid while HTML output is read by consumers styling it
|
2026-08-23, the maintainer. Goals 4 and 8. Valid while HTML output is read by consumers styling it
|
||||||
themselves.
|
themselves.
|
||||||
|
|
||||||
The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*`
|
The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*`
|
||||||
@@ -173,7 +174,7 @@ for what HTML cannot express, text always escaped. No stylesheet ships.
|
|||||||
|
|
||||||
## No runtime dependencies
|
## No runtime dependencies
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while ~20 lines of own code, or a vendored table, do each
|
2026-08-23, the maintainer. Goal 8. Valid while ~20 lines of own code, or a vendored table, do each
|
||||||
job a dependency would.
|
job a dependency would.
|
||||||
|
|
||||||
`dependencies` is empty. A runtime dependency enters only through an entry here stating why ~20
|
`dependencies` is empty. A runtime dependency enters only through an entry here stating why ~20
|
||||||
@@ -206,7 +207,7 @@ nodes that break it.
|
|||||||
|
|
||||||
## Any ES2022 engine
|
## Any ES2022 engine
|
||||||
|
|
||||||
2026-09-01, the maintainer. Goal 7. Valid while ES2022 is the floor browsers and servers share.
|
2026-09-01, the maintainer. Goal 8. Valid while ES2022 is the floor browsers and servers share.
|
||||||
|
|
||||||
The library runs on any ES2022 engine, not only Node — a browser as readily as a server. The
|
The library runs on any ES2022 engine, not only Node — a browser as readily as a server. The
|
||||||
shipped source is ECMAScript and nothing else: no host import, no host global, no DOM.
|
shipped source is ECMAScript and nothing else: no host import, no host global, no DOM.
|
||||||
@@ -220,13 +221,13 @@ never the higher one those repo-only tools want.
|
|||||||
|
|
||||||
## ESM only
|
## ESM only
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while the audience's toolchains all import ES modules.
|
2026-08-23, the maintainer. Goal 8. Valid while the audience's toolchains all import ES modules.
|
||||||
|
|
||||||
No CommonJS build, no dual-package hazard.
|
No CommonJS build, no dual-package hazard.
|
||||||
|
|
||||||
## One built entrypoint
|
## One built entrypoint
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while Node refuses to type-strip under `node_modules`.
|
2026-08-23, the maintainer. Goal 8. Valid while Node refuses to type-strip under `node_modules`.
|
||||||
|
|
||||||
Built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — Node refuses to
|
Built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — Node refuses to
|
||||||
type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve
|
type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve
|
||||||
@@ -234,7 +235,7 @@ an npm consumer.
|
|||||||
|
|
||||||
## Public on npm
|
## Public on npm
|
||||||
|
|
||||||
2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 7. Valid while the package's source
|
2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 8. Valid while the package's source
|
||||||
stays public beside it.
|
stays public beside it.
|
||||||
|
|
||||||
Published to public npm as `@larvit/adf-codec`. Public source: the Gitea repo goes public,
|
Published to public npm as `@larvit/adf-codec`. Public source: the Gitea repo goes public,
|
||||||
@@ -243,7 +244,7 @@ for the hub rather than the formats around it.
|
|||||||
|
|
||||||
## The formats are API
|
## The formats are API
|
||||||
|
|
||||||
2026-08-23, strict input 2026-09-01, content models 2026-09-16, the maintainer. Goals 1 and 6.
|
2026-08-23, strict input 2026-09-01, content models 2026-09-16, the maintainer. Goals 1 and 7.
|
||||||
Valid while consumers store what the library emits.
|
Valid while consumers store what the library emits.
|
||||||
|
|
||||||
The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing
|
The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing
|
||||||
@@ -258,7 +259,7 @@ types in `src/result.ts` hold its shape.
|
|||||||
|
|
||||||
## The code list
|
## The code list
|
||||||
|
|
||||||
2026-08-25, the maintainer; dated below where a rule came later. Goal 6. Valid while a consumer
|
2026-08-25, the maintainer; dated below where a rule came later. Goal 7. Valid while a consumer
|
||||||
switches on `code` with no `default`.
|
switches on `code` with no `default`.
|
||||||
|
|
||||||
- Adding, removing or renaming a code is breaking, so a new cause takes an existing code whose
|
- Adding, removing or renaming a code is breaking, so a new cause takes an existing code whose
|
||||||
@@ -274,7 +275,7 @@ switches on `code` with no `default`.
|
|||||||
|
|
||||||
## Which code a cause takes
|
## Which code a cause takes
|
||||||
|
|
||||||
2026-08-28, the maintainer; dated below where a rule came later. Goal 6. Valid while a consumer
|
2026-08-28, the maintainer; dated below where a rule came later. Goal 7. Valid while a consumer
|
||||||
handles one cause alike whichever node, attribute or direction raised it.
|
handles one cause alike whichever node, attribute or direction raised it.
|
||||||
|
|
||||||
- A code names the cause; where one cause recurs across node types, across one mark's attributes
|
- A code names the cause; where one cause recurs across node types, across one mark's attributes
|
||||||
@@ -307,7 +308,7 @@ handles one cause alike whichever node, attribute or direction raised it.
|
|||||||
|
|
||||||
## `message` and `path`
|
## `message` and `path`
|
||||||
|
|
||||||
2026-09-03, the path 2026-09-23, the maintainer. Goals 3 and 6. Valid while a person fixing the
|
2026-09-03, the path 2026-09-23, the maintainer. Goals 3 and 7. Valid while a person fixing the
|
||||||
input reads `message`.
|
input reads `message`.
|
||||||
|
|
||||||
- A message names the violation, not the rule alone — a rule by itself states a truth the reader
|
- A message names the violation, not the rule alone — a rule by itself states a truth the reader
|
||||||
@@ -321,7 +322,7 @@ input reads `message`.
|
|||||||
|
|
||||||
## Publish on a version bump
|
## Publish on a version bump
|
||||||
|
|
||||||
2026-08-23, converging 2026-09-03, the maintainer. Goal 7. Valid while CI on `main` holds the npm
|
2026-08-23, converging 2026-09-03, the maintainer. Goal 8. Valid while CI on `main` holds the npm
|
||||||
token.
|
token.
|
||||||
|
|
||||||
`package.json` version on `main` is the source of truth. CI on `main`: tests green and the version
|
`package.json` version on `main` is the source of truth. CI on `main`: tests green and the version
|
||||||
@@ -337,7 +338,7 @@ depend on a store that the gate would then have to keep.
|
|||||||
|
|
||||||
## Docs describe the release being built
|
## Docs describe the release being built
|
||||||
|
|
||||||
2026-09-16, the maintainer. Goal 7. Valid while a bump on `main` publishes.
|
2026-09-16, the maintainer. Goal 8. Valid while a bump on `main` publishes.
|
||||||
|
|
||||||
Docs on `main` describe the release being built rather than the version npm holds, so they match it
|
Docs on `main` describe the release being built rather than the version npm holds, so they match it
|
||||||
the moment the bump publishes; add no interim note marking the gap.
|
the moment the bump publishes; add no interim note marking the gap.
|
||||||
@@ -353,7 +354,7 @@ nesting a spelling inside its own kind (`*(*a*)*`) names that mark once.
|
|||||||
|
|
||||||
## The gate runs on Deno and Bun
|
## The gate runs on Deno and Bun
|
||||||
|
|
||||||
2026-09-01, Deno's reason 2026-09-28, the maintainer. Goals 7 and 8. Valid while the library claims
|
2026-09-01, Deno's reason 2026-09-28, the maintainer. Goals 8 and 9. Valid while the library claims
|
||||||
any ES2022 engine.
|
any ES2022 engine.
|
||||||
|
|
||||||
The gate runs the suite under Deno and Bun as well as Node. Bun runs JavaScriptCore, the one engine
|
The gate runs the suite under Deno and Bun as well as Node. Bun runs JavaScriptCore, the one engine
|
||||||
@@ -365,7 +366,7 @@ over the corpus rather than over a smoke import.
|
|||||||
|
|
||||||
## The gate installs the tarball
|
## The gate installs the tarball
|
||||||
|
|
||||||
2026-09-03, the maintainer. Goal 7. Valid while consumers install the packed package.
|
2026-09-03, the maintainer. Goal 8. Valid while consumers install the packed package.
|
||||||
|
|
||||||
The gate packs the build and installs the tarball under `package-tests/`, so `files`, `exports`
|
The gate packs the build and installs the tarball under `package-tests/`, so `files`, `exports`
|
||||||
and `types` are proved on the artifact that ships rather than on the source tree a self-reference
|
and `types` are proved on the artifact that ships rather than on the source tree a self-reference
|
||||||
@@ -377,7 +378,7 @@ resolver maps them, under `NodeNext` alone; a `.d.ts` reader that is not `tsc` s
|
|||||||
|
|
||||||
## Firefox reads the build
|
## Firefox reads the build
|
||||||
|
|
||||||
2026-09-04, the maintainer. Goal 7. Valid while the library claims a browser and no other leg runs
|
2026-09-04, the maintainer. Goal 8. Valid while the library claims a browser and no other leg runs
|
||||||
SpiderMonkey.
|
SpiderMonkey.
|
||||||
|
|
||||||
A headless Firefox loads `dist/index.js` over HTTP and converts the round-trip, normalization and
|
A headless Firefox loads `dist/index.js` over HTTP and converts the round-trip, normalization and
|
||||||
@@ -405,7 +406,7 @@ compared against `undefined` — have a half no valid document reaches.
|
|||||||
|
|
||||||
## The size ratchet
|
## The size ratchet
|
||||||
|
|
||||||
2026-09-20, the maintainer. Goal 10. Valid while no measure picks out what readers find hard
|
2026-09-20, the maintainer. Goal 11. Valid while no measure picks out what readers find hard
|
||||||
better than a function's length.
|
better than a function's length.
|
||||||
|
|
||||||
`.oxlintrc.json`'s single rule, over the files `tsconfig.build.json` builds, is a per-function line
|
`.oxlintrc.json`'s single rule, over the files `tsconfig.build.json` builds, is a per-function line
|
||||||
@@ -429,7 +430,7 @@ markdown, on a fixed seed in the gate; a counterexample found becomes a round-tr
|
|||||||
|
|
||||||
## The CommonMark suite checks three ways
|
## The CommonMark suite checks three ways
|
||||||
|
|
||||||
2026-08-27, the maintainer. Goals 1 and 8. Valid while the suite's answers are HTML ADF cannot be
|
2026-08-27, the maintainer. Goals 1 and 9. Valid while the suite's answers are HTML ADF cannot be
|
||||||
compared against.
|
compared against.
|
||||||
|
|
||||||
Each example is a named error or markdown that parses and emits to itself byte for byte; its
|
Each example is a named error or markdown that parses and emits to itself byte for byte; its
|
||||||
@@ -492,7 +493,7 @@ termination is the loop's own check.
|
|||||||
|
|
||||||
## Readers scan by index
|
## Readers scan by index
|
||||||
|
|
||||||
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 9. Valid while the pipeline persona
|
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 10. Valid while the pipeline persona
|
||||||
feeds documents nobody typed.
|
feeds documents nobody typed.
|
||||||
|
|
||||||
A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the line
|
A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the line
|
||||||
@@ -504,7 +505,7 @@ two cannot disagree — which is what makes the kept value a memo rather than a
|
|||||||
|
|
||||||
## The spelling memo
|
## The spelling memo
|
||||||
|
|
||||||
2026-09-19, the maintainer. Goal 9. Valid while the `commonMarkSpelling` ask spells a node once
|
2026-09-19, the maintainer. Goal 10. Valid while the `commonMarkSpelling` ask spells a node once
|
||||||
per level above it otherwise.
|
per level above it otherwise.
|
||||||
|
|
||||||
The parse and the plain reduction keep each node's readable spelling in a memo, so the
|
The parse and the plain reduction keep each node's readable spelling in a memo, so the
|
||||||
@@ -516,7 +517,7 @@ one. Only what succeeded is kept, so no path minted at another position is ever
|
|||||||
|
|
||||||
## Cost fixes are measured, never timed
|
## Cost fixes are measured, never timed
|
||||||
|
|
||||||
2026-09-18, the maintainer and the stability-reviewer. Goal 9. Valid while Goal 9 promises growth
|
2026-09-18, the maintainer and the stability-reviewer. Goal 10. Valid while Goal 10 promises growth
|
||||||
rather than a figure.
|
rather than a figure.
|
||||||
|
|
||||||
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
|
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
|
||||||
@@ -535,7 +536,7 @@ refuses it — which is how the whitespace carry finds a line edge.
|
|||||||
|
|
||||||
## Emphasis follows CommonMark's matching
|
## Emphasis follows CommonMark's matching
|
||||||
|
|
||||||
2026-08-27, the maintainer. Goals 1 and 8. Valid while CommonMark's emphasis rules are the
|
2026-08-27, the maintainer. Goals 1 and 9. Valid while CommonMark's emphasis rules are the
|
||||||
reader's.
|
reader's.
|
||||||
|
|
||||||
Emphasis is spelled against CommonMark's matching, never flanking alone: a delimiter run in text
|
Emphasis is spelled against CommonMark's matching, never flanking alone: a delimiter run in text
|
||||||
|
|||||||
@@ -130,11 +130,31 @@ function quoteNode(blocks: readonly Block[], reading: Reading, path: ConvertErro
|
|||||||
const title = parseInlineContent(line, reading.definitions, path, 'paragraph', 'lossless')
|
const title = parseInlineContent(line, reading.definitions, path, 'paragraph', 'lossless')
|
||||||
if (!title.ok) return title
|
if (!title.ok) return title
|
||||||
if (title.value.image !== undefined) return failure('unmappable-image', imageOnMarkerLine, path)
|
if (title.value.image !== undefined) return failure('unmappable-image', imageOnMarkerLine, path)
|
||||||
const text = title.value.nodes.map((node) => node.text ?? '').join('')
|
const text = titleText(title.value.nodes)
|
||||||
const type = reading.inExpand ? 'nestedExpand' : 'expand'
|
const type = reading.inExpand ? 'nestedExpand' : 'expand'
|
||||||
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readMarked(paragraphsOf(led.position, rest), false, body, { ...reading, inExpand: true }, path, depth))
|
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readMarked(paragraphsOf(led.position, rest), false, body, { ...reading, inExpand: true }, path, depth))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// docs/decisions.md, A callout title keeps its link targets.
|
||||||
|
function titleText(nodes: readonly AdfNode[]): string {
|
||||||
|
let text = ''
|
||||||
|
let linked = ''
|
||||||
|
for (const [index, node] of nodes.entries()) {
|
||||||
|
const href = linkTarget(node)
|
||||||
|
text += node.text ?? ''
|
||||||
|
linked += href === undefined ? '' : node.text ?? ''
|
||||||
|
if (href === undefined || linkTarget(nodes[index + 1]) === href) continue
|
||||||
|
if (href !== linked && href !== `mailto:${linked}`) text += ` (${href})`
|
||||||
|
linked = ''
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkTarget(node: AdfNode | undefined): string | undefined {
|
||||||
|
const href = node?.marks?.find((mark) => mark.type === 'link')?.attrs?.href
|
||||||
|
return typeof href === 'string' ? href : undefined
|
||||||
|
}
|
||||||
|
|
||||||
// Atlassian's schema requires a panel and an expand to hold a block.
|
// Atlassian's schema requires a panel and an expand to hold a block.
|
||||||
function filledNode(node: AdfNode, content: Result<AdfNode[]>): Result<AdfNode> {
|
function filledNode(node: AdfNode, content: Result<AdfNode[]>): Result<AdfNode> {
|
||||||
if (!content.ok) return content
|
if (!content.ok) return content
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ test('reads a folded callout to an expand titled by the rest of its marker line,
|
|||||||
node('expand', { title: 'Why?' }, paragraph(text('See '), text('the docs', link), text(', '), text('now', strong), text('.'))),
|
node('expand', { title: 'Why?' }, paragraph(text('See '), text('the docs', link), text(', '), text('now', strong), text('.'))),
|
||||||
])
|
])
|
||||||
assert.deepEqual(read('> [!NOTE]- Two\\\n> lines\n'), [node('expand', { title: 'Two' }, said('lines'))])
|
assert.deepEqual(read('> [!NOTE]- Two\\\n> lines\n'), [node('expand', { title: 'Two' }, said('lines'))])
|
||||||
|
assert.deepEqual(read('> [!faq]- See [x](http://y)\n'), normal(node('expand', { title: 'See x (http://y)' }, paragraph())))
|
||||||
|
assert.deepEqual(read('> [!faq]- [a **b**](u "t")[c](u) and [d](v)\n'), normal(node('expand', { title: 'a bc (u) and d (v)' }, paragraph())))
|
||||||
|
assert.deepEqual(read('> [!faq]- <http://y> or <a@b.c>\n'), normal(node('expand', { title: 'http://y or a@b.c' }, paragraph())))
|
||||||
assert.deepEqual(read('> [!NOTE]- Set ==x== here\n'), normal(node('expand', { title: 'Set ==x== here' }, paragraph())))
|
assert.deepEqual(read('> [!NOTE]- Set ==x== here\n'), normal(node('expand', { title: 'Set ==x== here' }, paragraph())))
|
||||||
assert.deepEqual(read('> [!NOTE]-\n>\n> Line.\n'), [bare('expand', said('Line.'))])
|
assert.deepEqual(read('> [!NOTE]-\n>\n> Line.\n'), [bare('expand', said('Line.'))])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
- **41 — Keep a link's target when `plainMarkdownToAdf` reads a callout title.** Per
|
|
||||||
`docs/decisions.md` §A callout title keeps its link targets; today `> [!faq]- See [x](http://y)`
|
|
||||||
reads to an expand titled `See x`, the target gone.
|
|
||||||
- **40 — Make `markdownToAdf(adfToMarkdown(doc))` deep-equal `doc` for every document it takes.**
|
- **40 — Make `markdownToAdf(adfToMarkdown(doc))` deep-equal `doc` for every document it takes.**
|
||||||
Today it holds for editor-normal documents only: two adjacent text nodes with the same marks
|
Today it holds for editor-normal documents only: two adjacent text nodes with the same marks
|
||||||
merge, an empty `attrs`, `marks` or `content` drops, and `-0` reads back `0` — shapes pipelines
|
merge, an empty `attrs`, `marks` or `content` drops, and `-0` reads back `0` — shapes pipelines
|
||||||
@@ -29,11 +26,11 @@
|
|||||||
measured one.
|
measured one.
|
||||||
- **33 — Make a carried mark run cost the line one re-emit.** `adfToMarkdown` spends 23 s on one
|
- **33 — Make a carried mark run cost the line one re-emit.** `adfToMarkdown` spends 23 s on one
|
||||||
paragraph of 2000 × `un` plus `**-r**`: each run its flanking cannot spell re-emits the whole line
|
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 (Goal 9), and the plain reduction's
|
before riding the carry, quadratic in the runs (Goal 10), and the plain reduction's
|
||||||
`spellableLine` drops one mark per re-emit the same way. Make both linear.
|
`spellableLine` drops one mark per re-emit the same way. Make both linear.
|
||||||
- **42 — Trim a text leaf's trailing blanks in linear time.** `plain-inline.ts`'s `leafEdges` finds
|
- **42 — Trim a text leaf's trailing blanks in linear time.** `plain-inline.ts`'s `leafEdges` finds
|
||||||
the trail with an unanchored `/[ \t]*$/`, quadratic in a run of blanks inside one leaf: a
|
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` (Goal 9). Scan backward,
|
paragraph of `a`, 80 000 spaces, `b` takes 6.5 s in `adfToPlainMarkdown` (Goal 10). Scan backward,
|
||||||
as the expand title's trim does.
|
as the expand title's trim does.
|
||||||
- **34 — Read emphasis flanking by the whole character beside an astral symbol.** Check whether
|
- **34 — Read emphasis flanking by the whole character beside an astral symbol.** Check whether
|
||||||
`line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an astral
|
`line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an astral
|
||||||
|
|||||||
Reference in New Issue
Block a user