Goals become one-line aims, their detail moves to the sections that meet them #152

Merged
lilleman merged 7 commits from goals-slogans into main 2026-10-02 21:58:15 +02:00
5 changed files with 83 additions and 92 deletions
Showing only changes of commit 94eaee6bae - Show all commits
+8 -10
View File
@@ -130,13 +130,11 @@ One-line commit messages and PR titles; short PR summaries. No AI-attribution ma
## 7. The working loop
`todo.md` lists what is left under the release that ships it, in shipping order. One item per
session — the first under the earliest release — in the smallest PR-able chunk; split a big item
into sub-items in `todo.md` before starting it. A chunk running a little over or under that is not
worth deliberating; what matters is that nothing is left undone in the end. The session stops there
whatever it was asked to finish: a release is a chain of sessions, so an instruction to work until a
release is done names the chain, not the session. An open PR is a chunk already in flight, and
finishing it is the session.
`todo.md` lists what is left under the release that ships it, in shipping order. A session works
one chunk, starting from the first item under the earliest release, and stops there whatever it
was asked to finish: a release is a chain of sessions, so an instruction to work until a release is
done names the chain, not the session. An open PR is a chunk already in flight, and finishing it is
the session.
Per chunk:
1. Fresh worktree off updated `origin/main`; implement tests-first (§3).
@@ -145,7 +143,7 @@ Per chunk:
result exists for the commit under review, or when the diff since that result cannot affect
it (docs-only) — re-run only what its own findings or fixes invalidate.
3. Merge the PR (standing authorization, this repo only, granted by the maintainer through the
`0.2.0` release), delete the item from `todo.md` — what a consumer sees of it is
`0.2.0` release), delete the chunk's items from `todo.md` — what a consumer sees of it is
reworded for them into `CHANGELOG.md`'s `## Unreleased` — report, stop.
Reserved for the maintainer whatever any rule here says: changing `version` in `package.json` (a
@@ -165,7 +163,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
keeps collecting instances is wrong: rewrite it.
Which output the audience expects — README goal 6 — is settled by a reader panel rather than
Which output the audience expects — README goal 5 — is settled by a reader panel rather than
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
allow, rendered, shuffled, with no rationale and nothing saying what is implemented. Three agreeing
@@ -174,7 +172,7 @@ The verdict lands in `docs/decisions.md`.
### Findings, numbers and empty releases
- A finding inside the chunk's item is fixed in the chunk. Outside it, a new `todo.md` item, always
- A finding inside the chunk's items is fixed in the chunk. Outside it, a new `todo.md` item, always
in a release, weighed against every item on that release by the personas and `docs/decisions.md`
§Plain markdown is a flavour of the grammar through §Names stay text — an item it outweighs moves
later. A weighing no entry decides is asked as a gap.
+26 -44
View File
@@ -23,47 +23,21 @@ represent.
## Goals
In priority order.
The most useful ADF conversion library available, by these goals in priority order:
1. **Lossless, and every call returns.** The round-trip holds for every document the lossless
conversions take, node types this version does not know included; one that has no spelling is
refused and says where, never silently reduced. No input makes a call loop forever or overflow
the stack. Every goal below gives way to this one.
2. **ADF is the hub.** Every format and flavour converts to and from ADF, and no two others
convert directly: markdown↔HTML composes through ADF. Adding a format or flavour costs one
reader and one writer. A flavour of a grammar shares that grammar's reader and writer and adds
only its own spellings.
3. **Plain CommonMark is input.** Markdown written for something else converts — the exceptions
below are the whole of them — and every spelling a flavour claims on top of CommonMark is
escapable, so each flavour is opt-in.
4. **Output a person can edit.** A node CommonMark can spell gets that spelling; the lossless
flavour's directive form carries only what CommonMark cannot hold.
5. **Lossy conversion keeps the content.** `adfToPlainMarkdown` and `plainMarkdownToAdf` drop what
plain markdown cannot hold — format, design, structure — never content the document holds: what
a reader of the rendered document sees or follows, its text, images and link targets. The lossy
pair creates and exports; it never saves back over the document it read — a document's identity
(task, mention, media ids) survives a round trip only through the lossless pair.
6. **What happens is what the audience expects.** Where the goals leave a choice, a 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.
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
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.
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
library reads and in what a conforming parser reads from what it writes. A call takes a whole
document and returns a whole result.
10. **Fast once correct.** Conversion time grows linearly with the document wherever the goals
above allow it; a faster path that risks one of them is not taken.
11. **Source a contributor can hold.** Any one function reads in one sitting, and no change makes
the longest one longer.
1. **Lossless, and every call returns a result, never a throw.**
2. **ADF is the hub.**
3. **Each format reads and writes as its standard says.**
4. **Our markdown is CommonMark, extended only where CommonMark has no spelling.**
5. **No surprises: output reads and edits the way its audience expects.**
6. **Lossy conversion drops form, never content.**
7. **Runs in any JavaScript engine, with nothing to install, configure or connect.**
8. **Fast, and linear in the document's size.**
9. **Easy to find, and clear at a glance what it does.**
## Audience
Application developers embedding the library, addressed as personas rather than named consumers
(AGENTS.md §1). All four rely on the guarantees below and on `code` being a closed list; none may
Application developers embedding the library, in four personas. All four rely on the guarantees below and on `code` being a closed list; none may
rely on an error message's wording, which is free text.
- **Viewer/editor app** — shows a document, lets a human edit, posts it back. Relies on the
@@ -94,7 +68,8 @@ if (result.ok) {
}
```
Pure functions, no I/O, no configuration.
Serves Goals 1 and 7. Pure functions, each taking a whole document and returning a whole result;
no I/O, no configuration.
```ts
adfToMarkdown(doc: AdfDocument): Result<string>
@@ -114,9 +89,10 @@ htmlToMarkdown(html: string): Result<string> // 0.2.0, via ADF
## Plain markdown
Plain markdown is a second flavour of the same grammar. `adfToPlainMarkdown` writes markdown other
tools render — GitHub, GitLab, Obsidian and the like — keeping the content and dropping the rest:
attributes, colours, layout, identity. It refuses only
Serves Goal 6. Plain markdown is a second flavour of the same grammar. `adfToPlainMarkdown` writes
markdown other tools render — GitHub, GitLab, Obsidian and the like — keeping the content and
dropping the rest: attributes, colours, layout, identity. Content is what a reader of the rendered
document sees or follows: its text, images and link targets. It refuses only
`not-an-adf-document`, `unsupported-document-version` and `unsupported-nesting-depth`, and writes
no directive.
@@ -167,7 +143,7 @@ read replaces mentions, attachments and macros with text.
## The errors
An ADF node type this version does not know is not an error: the lossless pair carries it opaquely
Serves Goal 1. An ADF node type this version does not know is not an error: the lossless pair carries it opaquely
and restores it unchanged ([`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#unknown-nodes-ride-the-carry)).
`ConvertError` is `{ code, message, path, position? }`. `code` is the exported `ConvertErrorCode`,
@@ -219,6 +195,11 @@ emit refuses:
## The guarantees
Serves Goals 1, 3 and 4.
- Markdown means what the CommonMark spec says, and well-formed HTML what the HTML standard
parses, both in what this library reads and in what a conforming parser reads from what it
writes; the bullets below name every exception.
- `markdownToAdf(adfToMarkdown(doc))` equals `doc` — unknown node types included, carried opaquely
([`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#unknown-nodes-ride-the-carry)).
- Plain CommonMark is valid input to `markdownToAdf` apart from the raw HTML `unmappable-html`
@@ -247,7 +228,8 @@ emit refuses:
input.
- Past that and `~~`, no GFM: an autolink literal and a `- [ ]` marker stay text, and a checklist
is the `taskList` directive — `plainMarkdownToAdf` turns the marker into a `taskList`.
- A document nested deeper than 500 levels is an error result, not a stack overflow.
- A document nested deeper than 500 levels is an error result, not a stack overflow, and no input
makes a call loop forever.
- The emitted formats are semver surface
([`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#the-formats-are-api)).
- **`0.2.0`** — `htmlToAdf(adfToHtml(doc))` equals `doc`; fidelity HTML cannot express rides
@@ -257,7 +239,7 @@ emit refuses:
## The package
ESM only, no runtime dependencies, public npm. Built JavaScript with `.d.ts` beside it.
Serves Goal 7. ESM only, no runtime dependencies, public npm. Built JavaScript with `.d.ts` beside it.
Pure ECMAScript at an ES2022 baseline, reaching for no host API; the test suite runs under Node,
Deno and Bun, and a headless Firefox converts the corpus through the built entrypoint.
Contract: [`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#any-es2022-engine), §Any
+36 -35
View File
@@ -23,7 +23,7 @@ sanitized and a mention keeps the test user's real account id.
## Markdown in is a canonical fixpoint
2026-08-23, the maintainer. Goals 1 and 3. Valid while markdown input may be written by hand.
2026-08-23, the maintainer. Goals 1 and 4. Valid while markdown input may be written by hand.
The other direction is a canonical fixpoint, not byte-identity: human markdown normalizes, the way
back yields the library's canonical spelling, and that spelling round-trips byte-identically —
@@ -55,7 +55,7 @@ Where a container's own spelling cannot hold the child it has — a `bulletList`
## Foreign HTML sorts three ways
2026-08-23, the sort 2026-09-20, the maintainer. Goals 1, 3 and 7. Valid while ADF holds no node
2026-08-23, the sort 2026-09-20, the maintainer. Goals 1 and 4. Valid while ADF holds no node
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
@@ -76,39 +76,40 @@ than they buy.
## Names stay text
2026-08-23, the maintainer. Goal 8. Valid while resolving a name to an id needs I/O.
2026-08-23, the maintainer. Goal 7. 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
mention/emoji/media nodes; resolving names to ids is the consumer's job.
## Directives under `!adf:`
2026-08-23, prefixed `!adf:` 2026-09-16, the maintainer. Goals 3 and 4. Valid while prose does not
2026-08-23, prefixed `!adf:` 2026-09-16, the maintainer. Goals 4 and 5. Valid while prose does not
write `!adf:`.
Directives are one grammar for everything markdown lacks, namespaced under `!adf:`:
`!adf:panel info` … `!adf:/panel` blocks, `!adf:mention[@Mikael]{id=5b10a2}` inline, `\!adf:` the
one escape. Not CommonMark's generic-directives proposal: its `:::` claims a form prose writes, and
its fence-length discipline ties a container's opener to its own body, where closing from the
opener nests by itself and leaf versus container falls out of the node's content model.
one escape. A node CommonMark can spell takes that spelling, never a directive. Not CommonMark's
generic-directives proposal: its `:::` claims a form prose writes, and its fence-length discipline
ties a container's opener to its own body, where closing from the opener nests by itself and leaf
versus container falls out of the node's content model.
## CommonMark is a subset
2026-08-23, the maintainer. Goal 3. Valid while prose rarely writes the shapes the carve-outs claim.
2026-08-23, the maintainer. Goal 4. Valid while prose rarely writes the shapes the carve-outs claim.
Plain CommonMark is a subset, with carve-outs (`spec/flavour.md`): literal text shaped like a
directive, a pipe table or a `~~` pair is claimed — plus one image gap.
## Tables
2026-08-23, the maintainer. Goal 4. Valid while a pipe table holds only one header row and inline
2026-08-23, the maintainer. Goal 5. Valid while a pipe table holds only one header row and inline
cells.
One header row plus plain inline cells → pipe table; anything richer → directive form.
## Links
2026-09-13, nesting 2026-09-17, the maintainer. Goals 1 and 4. Valid while CommonMark's link
2026-09-13, nesting 2026-09-17, the maintainer. Goals 1 and 5. Valid while CommonMark's link
syntax is what readers edit.
`[text](url "title")`, or `<url>` for a bare autolink-shaped text, wherever CommonMark spells the
@@ -127,7 +128,7 @@ accepted.
## Plain task ids come from position
2026-09-26, spelling 2026-09-29, the maintainer. Goals 5 and 8. Valid while a site rejects a task
2026-09-26, spelling 2026-09-29, the maintainer. Goals 6 and 7. Valid while a site rejects a task
node with no `localId`.
`plainMarkdownToAdf` gives each `taskList`, `taskItem` and `blockTaskItem` lacking one a `localId`
@@ -167,7 +168,7 @@ past `[x]`/`[ ]`, and lifting bare URLs, `@name`, `:shortcode:` or ISO dates int
## The HTML dialect
2026-08-23, the maintainer. Goals 4 and 8. Valid while HTML output is read by consumers styling it
2026-08-23, the maintainer. Goals 5 and 7. Valid while HTML output is read by consumers styling it
themselves.
The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*`
@@ -175,7 +176,7 @@ for what HTML cannot express, text always escaped. No stylesheet ships.
## No runtime dependencies
2026-08-23, the maintainer. Goal 8. Valid while ~20 lines of own code, or a vendored table, do each
2026-08-23, the maintainer. Goal 7. Valid while ~20 lines of own code, or a vendored table, do each
job a dependency would.
`dependencies` is empty. A runtime dependency enters only through an entry here stating why ~20
@@ -185,7 +186,7 @@ and HTML parsers are written in this repo.
## Standards ship as data
2026-08-30, the CommonMark suite 2026-09-05 and ADF's schemas 2026-09-13, the maintainer. Goals 1,
3 and 8. Valid while each table is fixed data a dependency would only wrap.
4 and 7. Valid while each table is fixed data a dependency would only wrap.
A table a standard fixes is data rather than a dependency: HTML5's 2125 semicolon-terminated
character references ship packed in their own module, so entity decoding is complete without one.
@@ -208,7 +209,7 @@ nodes that break it.
## Any ES2022 engine
2026-09-01, the maintainer. Goal 8. Valid while ES2022 is the floor browsers and servers share.
2026-09-01, the maintainer. Goal 7. 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
shipped source is ECMAScript and nothing else: no host import, no host global, no DOM.
@@ -222,13 +223,13 @@ never the higher one those repo-only tools want.
## ESM only
2026-08-23, the maintainer. Goal 8. Valid while the audience's toolchains all import ES modules.
2026-08-23, the maintainer. Goal 7. Valid while the audience's toolchains all import ES modules.
No CommonJS build, no dual-package hazard.
## One built entrypoint
2026-08-23, the maintainer. Goal 8. Valid while Node refuses to type-strip under `node_modules`.
2026-08-23, the maintainer. Goal 7. 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
type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve
@@ -236,7 +237,7 @@ an npm consumer.
## Public on npm
2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 8. Valid while the package's source
2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 7. Valid while the package's source
stays public beside it.
Published to public npm as `@larvit/adf-codec`. Public source: the Gitea repo goes public,
@@ -245,7 +246,7 @@ for the hub rather than the formats around it.
## The formats are API
2026-08-23, strict input 2026-09-01, content models 2026-09-16, the maintainer. Goals 1 and 7.
2026-08-23, strict input 2026-09-01, content models 2026-09-16, the maintainer. Goal 1.
Valid while consumers store what the library emits.
The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing
@@ -260,7 +261,7 @@ types in `src/result.ts` hold its shape.
## The code list
2026-08-25, the maintainer; dated below where a rule came later. Goal 7. Valid while a consumer
2026-08-25, the maintainer; dated below where a rule came later. Goal 1. Valid while a consumer
switches on `code` with no `default`.
- Adding, removing or renaming a code is breaking, so a new cause takes an existing code whose
@@ -276,7 +277,7 @@ switches on `code` with no `default`.
## Which code a cause takes
2026-08-28, the maintainer; dated below where a rule came later. Goal 7. Valid while a consumer
2026-08-28, the maintainer; dated below where a rule came later. Goal 1. Valid while a consumer
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
@@ -309,7 +310,7 @@ handles one cause alike whichever node, attribute or direction raised it.
## `message` and `path`
2026-09-03, the path 2026-09-23, the maintainer. Goals 3 and 7. Valid while a person fixing the
2026-09-03, the path 2026-09-23, the maintainer. Goals 1 and 4. Valid while a person fixing the
input reads `message`.
- A message names the violation, not the rule alone — a rule by itself states a truth the reader
@@ -323,7 +324,7 @@ input reads `message`.
## Publish on a version bump
2026-08-23, converging 2026-09-03, the maintainer. Goal 8. Valid while CI on `main` holds the npm
2026-08-23, converging 2026-09-03, the maintainer. Goal 7. Valid while CI on `main` holds the npm
token.
`package.json` version on `main` is the source of truth. CI on `main`: tests green and the version
@@ -339,7 +340,7 @@ depend on a store that the gate would then have to keep.
## Docs describe the release being built
2026-09-16, the maintainer. Goal 8. Valid while a bump on `main` publishes.
2026-09-16, the maintainer. Goal 7. 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
the moment the bump publishes; add no interim note marking the gap.
@@ -355,7 +356,7 @@ nesting a spelling inside its own kind (`*(*a*)*`) names that mark once.
## The gate runs on Deno and Bun
2026-09-01, Deno's reason 2026-09-28, the maintainer. Goals 8 and 9. Valid while the library claims
2026-09-01, Deno's reason 2026-09-28, the maintainer. Goals 3 and 7. Valid while the library claims
any ES2022 engine.
The gate runs the suite under Deno and Bun as well as Node. Bun runs JavaScriptCore, the one engine
@@ -367,7 +368,7 @@ over the corpus rather than over a smoke import.
## The gate installs the tarball
2026-09-03, the maintainer. Goal 8. Valid while consumers install the packed package.
2026-09-03, the maintainer. Goal 7. Valid while consumers install the packed package.
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
@@ -379,7 +380,7 @@ resolver maps them, under `NodeNext` alone; a `.d.ts` reader that is not `tsc` s
## Firefox reads the build
2026-09-04, the maintainer. Goal 8. Valid while the library claims a browser and no other leg runs
2026-09-04, the maintainer. Goal 7. Valid while the library claims a browser and no other leg runs
SpiderMonkey.
A headless Firefox loads `dist/index.js` over HTTP and converts the round-trip, normalization and
@@ -407,8 +408,8 @@ compared against `undefined` — have a half no valid document reaches.
## The size ratchet
2026-09-20, the maintainer. Goal 11. Valid while no measure picks out what readers find hard
better than a function's length.
2026-09-20, the maintainer. KISS, a technical principle. Valid while no measure picks out what
readers find hard better than a function's length.
`.oxlintrc.json`'s single rule, over the files `tsconfig.build.json` builds, is a per-function line
ceiling, set at that set's worst and moving only downward. It covers the built files alone, since
@@ -431,7 +432,7 @@ markdown, on a fixed seed in the gate; a counterexample found becomes a round-tr
## The CommonMark suite checks three ways
2026-08-27, the maintainer. Goals 1 and 9. Valid while the suite's answers are HTML ADF cannot be
2026-08-27, the maintainer. Goals 1 and 3. Valid while the suite's answers are HTML ADF cannot be
compared against.
Each example is a named error or markdown that parses and emits to itself byte for byte; its
@@ -494,7 +495,7 @@ termination is the loop's own check.
## Readers scan by index
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 10. Valid while the pipeline persona
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 8. Valid while the pipeline persona
feeds documents nobody typed.
A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the line
@@ -506,7 +507,7 @@ two cannot disagree — which is what makes the kept value a memo rather than a
## The spelling memo
2026-09-19, the maintainer. Goal 10. Valid while the `commonMarkSpelling` ask spells a node once
2026-09-19, the maintainer. Goal 8. Valid while the `commonMarkSpelling` ask spells a node once
per level above it otherwise.
The parse and the plain reduction keep each node's readable spelling in a memo, so the
@@ -518,7 +519,7 @@ one. Only what succeeded is kept, so no path minted at another position is ever
## Cost fixes are measured, never timed
2026-09-18, the maintainer and the stability-reviewer. Goal 10. Valid while Goal 10 promises growth
2026-09-18, the maintainer and the stability-reviewer. Goal 8. Valid while Goal 8 promises growth
rather than a figure.
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
@@ -537,7 +538,7 @@ refuses it — which is how the whitespace carry finds a line edge.
## Emphasis follows CommonMark's matching
2026-08-27, the maintainer. Goals 1 and 9. Valid while CommonMark's emphasis rules are the
2026-08-27, the maintainer. Goals 1 and 3. Valid while CommonMark's emphasis rules are the
reader's.
Emphasis is spelled against CommonMark's matching, never flanking alone: a delimiter run in text
@@ -546,7 +547,7 @@ only ones in play, and a pair that matching hands to another delimiter rides the
## Readable spellings take the `try` prefix
2026-09-21, the maintainer. Goals 1 and 4. Valid while a readable spelling's refusal would cost a
2026-09-21, the maintainer. Goals 1 and 5. Valid while a readable spelling's refusal would cost a
document the general form spells.
A readable spelling tried ahead of a general one takes the `try` prefix and fails only where the
+1
View File
@@ -1,3 +1,4 @@
// Export only the conversions, their types, isAdfDocument and what checking a README guarantee needs.
export type { AdfAttributes, AdfDocument, AdfMark, AdfNode } from './adf/document.ts'
export type { ConvertError, ConvertErrorCode, ConvertErrorPath, ParseError, Result, SourcePosition } from './result.ts'
export type { JsonValue } from './json-value.ts'
+12 -3
View File
@@ -2,6 +2,11 @@
## 0.2.0
- **43 — Give each markdown input its own reader, strict to its own standard.** Today
`markdownToAdf` reads CommonMark and the lossless flavour as one input, so text CommonMark reads
one way — shaped like a directive, a pipe table or a `~~` pair — the flavour claims (Goals 3 and
4). A caller names the markdown it hands in: CommonMark, read as its spec says, or the lossless
flavour, read as `spec/flavour.md` says. Breaking: `MIGRATION.md` says which call a caller 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
merge, an empty `attrs`, `marks` or `content` drops, and `-0` reads back `0` — shapes pipelines
@@ -26,11 +31,11 @@
measured 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
before riding the carry, quadratic in the runs (Goal 10), and the plain reduction's
before riding the carry, quadratic in the runs (Goal 8), and the plain reduction's
`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
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 10). Scan backward,
paragraph of `a`, 80 000 spaces, `b` takes 6.5 s in `adfToPlainMarkdown` (Goal 8). Scan backward,
as the expand title's trim does.
- **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
@@ -72,6 +77,10 @@
Revisit: whether npm has added Gitea or self-hosted OIDC, and otherwise whether the release moves
to the staged publish — which fits badly with publish-on-merge, and is the trade to weigh rather
than discover on a red release run. It stays out of `0.2.0` knowing the cutoff may land first.
- **8 — Ship a CLI, shaped around the personas.**
- **9 — Ship an online sandbox: a web page with two textboxes converting between ADF and markdown on
the library's browser build.**
## 0.4.0
- **8 — Ship a CLI.** Its goal and its persona land in the README's `## Goals` and `## Audience`
with it.