mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-13 12:18:28 +01:00
Name the CommonMark spelling a refused directive takes, split the unsupported-node-shape row by cause, and pass readAttributes its pair fault
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- `losslessMarkdownToAdf` names the CommonMark spelling to write when it refuses a directive whose
|
||||
node CommonMark spells, such as `[](href "title")` for a linked `!adf:mediaSingle`.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- **Breaking:** each call names the markdown it reads or writes. `adfToMarkdown` and `markdownToAdf`
|
||||
|
||||
@@ -452,7 +452,31 @@ Every call can raise these:
|
||||
| --- | --- | --- |
|
||||
| `engine-limit` | in every call: a conversion that passes the longest array, `Map` or `Set` the JavaScript engine builds, or that the engine reports as out of memory; on JavaScriptCore (Bun, Safari), also one that passes its longest string, 2^31 − 1 characters | retry once; if it repeats, writing: set the document aside, keeping the ADF and passing it over or showing it read-only; reading: shorten the markdown, or nest its lists and quotes less deeply |
|
||||
| `unsupported-nesting-depth` | blocks, marks, an attribute's JSON or a carried node's JSON nest past 500 levels | keep the ADF and pass the document over, or show it read-only; flatten the input where you are the one who wrote it |
|
||||
| `unsupported-node-shape` | parsing, in `losslessMarkdownToAdf`: markdown spells a node with an attribute, value, argument or body its type does not take, or without one it needs, puts a line ending or a null character in an `emoji`, `mention` or `status` content slot, wraps a directive mark around nothing or only hard breaks, writes as a directive a node or mark the lossless flavour spells as CommonMark, or one that, written out as plain CommonMark, would be longer than 536870888 characters, or puts a reserved directive out of place: `!adf:textBreak{}` parting nothing, `!adf:doc` anywhere but as the whole document. Emitting, in `adfToLosslessMarkdown`: a node not riding the carry that carries one mark type twice, a block or a non-text inline node holding `text`, or a leaf block or a non-text inline node holding `content`. Emitting, in `adfToPortableMarkdown`: a line or task list the portable writer cannot spell, a bug in this library. Emitting, in both writers: a document whose markdown would be longer than 536870888 characters, V8's longest string, on every engine; in `adfToPortableMarkdown`, also a document whose text alone is that long, edge whitespace included. In every call: a conversion that passes V8's or SpiderMonkey's longest string | fix what the message names; [`spec/flavour.md`](https://github.com/larvit/adf-codec/blob/main/spec/flavour.md) lists every type's attributes and body. For a line ending in a content slot, replace it with a space or write the node as `!adf:carry`; for a null character there, write the node as `!adf:carry`. For a portable line or task list, report it as a bug. Past 536870888 characters or the engine's longest string, writing: set the document aside, keeping the ADF and passing it over or showing it read-only; reading: shorten the markdown, or nest its lists and quotes less deeply |
|
||||
| `unsupported-node-shape` | a node the call cannot read or write in its shape; the list below names each cause | the list below names each cause's remedy |
|
||||
|
||||
`unsupported-node-shape` fires on these causes:
|
||||
|
||||
- **Reading, in `losslessMarkdownToAdf`**: markdown spells a node with an attribute, value,
|
||||
argument or body its type does not take, or without one it needs, puts a line ending or a null
|
||||
character in an `emoji`, `mention` or `status` content slot, wraps a directive mark around nothing
|
||||
or only hard breaks, writes as a directive a node or mark the lossless flavour spells as
|
||||
CommonMark, or puts a reserved directive out of place: `!adf:textBreak{}` parting nothing,
|
||||
`!adf:doc` anywhere but as the whole document. Fix what the message names;
|
||||
[`spec/flavour.md`](https://github.com/larvit/adf-codec/blob/main/spec/flavour.md) lists every
|
||||
type's attributes and body. For a line ending in a content slot, replace it with a space or write
|
||||
the node as `!adf:carry`; for a null character there, write the node as `!adf:carry`.
|
||||
- **Writing, in `adfToLosslessMarkdown`**: a node not riding the carry that carries one mark type
|
||||
twice, a block or a non-text inline node holding `text`, or a leaf block or a non-text inline node
|
||||
holding `content`. Fix what the message names in the ADF.
|
||||
- **Writing, in `adfToPortableMarkdown`**: a line or task list the portable writer cannot spell.
|
||||
This is a bug in this library: report it.
|
||||
- **Past the longest string**: in both writers, a document whose markdown would be longer than
|
||||
536870888 characters, V8's longest string, on every engine, and in `adfToPortableMarkdown` also a
|
||||
document whose text alone is that long, edge whitespace included; in `losslessMarkdownToAdf`, a
|
||||
directive whose plain CommonMark would be that long; in every call, a conversion that passes V8's
|
||||
or SpiderMonkey's longest string. Writing, set the document aside, keeping the ADF and passing it
|
||||
over or showing it read-only; reading, shorten the markdown, or nest its lists and quotes less
|
||||
deeply.
|
||||
|
||||
## The guarantees
|
||||
|
||||
@@ -494,7 +518,7 @@ Serves [Goals](#goals) 1, 4 and 5.
|
||||
the outer brackets literal text, which the spec requires and the reference itself breaks, nesting
|
||||
one `<a>` in the other.
|
||||
- Not every document converts back: `adfToLosslessMarkdown` refuses a document nesting past 500
|
||||
levels, the node shapes the `unsupported-node-shape` row lists, and a conversion `engine-limit`
|
||||
levels, the causes `unsupported-node-shape` lists, and a conversion `engine-limit`
|
||||
stops. Show the refusal and keep the document read-only; saving markdown you could not produce is the loss the round-trip exists to
|
||||
stop.
|
||||
- Both readers narrow GFM's pipe table. A body row whose cell count differs from the header's is
|
||||
|
||||
@@ -54,13 +54,15 @@ const noAttributes: DirectiveAttributes = new Map()
|
||||
|
||||
export const noSpans: NestedSpans = new Map()
|
||||
|
||||
export const attributePairCause = 'an attribute reads key=value, the value bare or double-quoted: this one does not'
|
||||
|
||||
export const directiveEscape = `\\${directivePrefix} keeps the prefix literal`
|
||||
|
||||
const closerFault = `a closer carries nothing after its name: this one does; ${directiveEscape}`
|
||||
const emptyFault = 'an empty {attrs} is omitted unless the { itself claims the directive: this one spells {}'
|
||||
const openerFault = `a directive opener reads a name, one bare argument and {attrs}, one space apart: this one does not; ${directiveEscape}`
|
||||
const orderFault = 'the {attrs} keys read in alphabetical order'
|
||||
const pairFault = `an attribute reads key=value, the value bare or double-quoted: this one does not; ${directiveEscape}`
|
||||
const pairFault = `${attributePairCause}; ${directiveEscape}`
|
||||
const prefixFault = `an unescaped ${directivePrefix} completes no directive; ${directiveEscape}`
|
||||
|
||||
export function attributeValue(text: string, kind: AttributeKind): AttributeReading {
|
||||
@@ -251,7 +253,7 @@ function readContentSlot(text: string, index: number, depth: number): Read<Conte
|
||||
|
||||
function readAttributesAt(text: string, index: number, braceClaims: boolean): Read<Attributes> {
|
||||
if (text.charAt(index) !== '{') return { value: { attributes: noAttributes, end: index } }
|
||||
const read = readAttributes(text, index)
|
||||
const read = readAttributes(text, index, pairFault)
|
||||
if (read.fault !== undefined || braceClaims || read.value.attributes.size > 0) return read
|
||||
return { fault: malformedDirective(emptyFault) }
|
||||
}
|
||||
@@ -296,16 +298,16 @@ function readCodeSpanEnd(text: string, index: number): number | undefined {
|
||||
return text.slice(index, closer + opener).includes('\n') ? undefined : closer + opener
|
||||
}
|
||||
|
||||
export function readAttributes(text: string, index: number): Read<Attributes> {
|
||||
export function readAttributes(text: string, index: number, pairMessage: string): Read<Attributes> {
|
||||
const attributes = new Map<string, DirectiveValue>()
|
||||
let cursor = index + 1
|
||||
let previous = ''
|
||||
while (cursor < text.length && text.charAt(cursor) !== '}') {
|
||||
if (attributes.size > 0) {
|
||||
if (text.charAt(cursor) !== ' ') return { fault: malformedDirective(pairFault) }
|
||||
if (text.charAt(cursor) !== ' ') return { fault: malformedDirective(pairMessage) }
|
||||
cursor += 1
|
||||
}
|
||||
const pair = readAttributePair(text, cursor)
|
||||
const pair = readAttributePair(text, cursor, pairMessage)
|
||||
if (pair.fault !== undefined) return { fault: pair.fault }
|
||||
const key = pair.value.key
|
||||
if (attributes.has(key)) return { fault: malformedDirective(`the attribute key ${key} is spelled twice`) }
|
||||
@@ -318,10 +320,10 @@ export function readAttributes(text: string, index: number): Read<Attributes> {
|
||||
return { value: { attributes, end: cursor + 1 } }
|
||||
}
|
||||
|
||||
function readAttributePair(text: string, index: number): Read<AttributePair> {
|
||||
function readAttributePair(text: string, index: number, pairMessage: string): Read<AttributePair> {
|
||||
bareRun.lastIndex = index
|
||||
const key = bareRun.exec(text)?.[0]
|
||||
if (key === undefined || text.charAt(index + key.length) !== '=') return { fault: malformedDirective(pairFault) }
|
||||
if (key === undefined || text.charAt(index + key.length) !== '=') return { fault: malformedDirective(pairMessage) }
|
||||
const start = index + key.length + 1
|
||||
if (text.charAt(start) === '"') {
|
||||
const quoted = readQuotedValue(text, start)
|
||||
@@ -330,7 +332,7 @@ function readAttributePair(text: string, index: number): Read<AttributePair> {
|
||||
}
|
||||
bareRun.lastIndex = start
|
||||
const bare = bareRun.exec(text)?.[0]
|
||||
if (bare === undefined) return { fault: malformedDirective(pairFault) }
|
||||
if (bare === undefined) return { fault: malformedDirective(pairMessage) }
|
||||
return { value: { end: start + bare.length, key, value: { decoded: bare, spelling: bare } } }
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ConvertFault } from '../../result.ts'
|
||||
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
|
||||
import type { Elsewhere } from './directive-attributes.ts'
|
||||
import { attributeNestingMessage, isUnmarkedSpellableText } from '../../adf/document.ts'
|
||||
import { attributeValue, directiveEscape, directivePrefix, readAttributes, readDirectiveName, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
import { attributePairCause, attributeValue, directivePrefix, readAttributes, readDirectiveName, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
import { blockArgument, blockDirectiveForm, marksAttribute, readMarkValues, spellBlockMarks } from '../block-directive.ts'
|
||||
import { blockNodeModel } from '../../adf/block-nodes.ts'
|
||||
import { carryFencePrefix, carryName } from '../opaque-carry.ts'
|
||||
@@ -134,11 +134,10 @@ function readNamedMark(type: string, text: string, index: number, path: ConvertE
|
||||
const vocabulary = markVocabularies.get(name)
|
||||
if (vocabulary === undefined) return failure('unsupported-node-shape', `the ${marksAttribute} attribute of ${type} names ${name}, no mark the flavour lists: write the marks as their JSON array, marks="[{\\"type\\":\\"${name}\\"}]"`, path)
|
||||
const start = index + name.length
|
||||
const attributes = text.charAt(start) === '{' ? readAttributes(text, start) : { value: { attributes: new Map<string, DirectiveValue>(), end: start } }
|
||||
// A quoted value holds no directive, so a malformed pair takes no escape hint.
|
||||
const attributes = text.charAt(start) === '{' ? readAttributes(text, start, attributePairCause) : { value: { attributes: new Map<string, DirectiveValue>(), end: start } }
|
||||
if (attributes.fault !== undefined) {
|
||||
// A quoted value holds no directive, so the fault's escape hint does not apply.
|
||||
const cause = attributes.fault.message.replace(`; ${directiveEscape}`, '')
|
||||
return failure('unsupported-node-shape', `the ${name} mark in the ${marksAttribute} attribute of ${type}: ${cause}`, path)
|
||||
return failure('unsupported-node-shape', `the ${name} mark in the ${marksAttribute} attribute of ${type}: ${attributes.fault.message}`, path)
|
||||
}
|
||||
const mark = readMarkAttributes(name, attributes.value.attributes, vocabulary, path)
|
||||
return mark.ok ? success({ end: attributes.value.end, mark: mark.value }) : mark
|
||||
|
||||
@@ -327,19 +327,35 @@ test('reads the three directive forms into the nodes the tables name', () => {
|
||||
assert.deepEqual(content(losslessMarkdownToAdf('Part!adf:hardBreak{}.\n')), [{ content: [text('Part'), hardBreak(), text('.')], type: 'paragraph' }])
|
||||
})
|
||||
|
||||
test('names the directive form a node CommonMark spells refuses', () => {
|
||||
const named = (type: string): string => `unsupported-node-shape: ${type} takes the CommonMark spelling, not the directive form`
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:rule\n')), named('rule'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:blockquote\nPart.\n!adf:/blockquote\n')), named('blockquote'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:heading {level=2}\nPart.\n!adf:/heading\n')), named('heading'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:paragraph\nPart.\n!adf:/paragraph\n')), named('paragraph'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:bulletList\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/bulletList\n')), named('bulletList'))
|
||||
test('names the CommonMark spelling to write for the directive form a node CommonMark spells', () => {
|
||||
const named = (type: string, spelling: string): string => `unsupported-node-shape: ${type} takes the CommonMark spelling, not the directive form: write ${spelling}`
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:rule\n')), named('rule', '`---`'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:blockquote\nPart.\n!adf:/blockquote\n')), named('blockquote', 'each line behind `> `'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:heading {level=2}\nPart.\n!adf:/heading\n')), named('heading', 'one `#` per level before its text, `## Title` at level 2'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:paragraph\nPart.\n!adf:/paragraph\n')), named('paragraph', 'its text alone'))
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:bulletList\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/bulletList\n')), named('bulletList', 'each item behind `- `'))
|
||||
assert.equal(
|
||||
content(losslessMarkdownToAdf('!adf:orderedList {order=1}\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/orderedList\n')),
|
||||
named('orderedList', 'each item behind `1. `'),
|
||||
)
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:codeBlock\n```\nx\n```\n!adf:/codeBlock\n')), named('codeBlock', 'its text in a ``` fence'))
|
||||
assert.equal(
|
||||
content(losslessMarkdownToAdf('!adf:mediaSingle {layout=center}\n!adf:media {alt=a type=external url=u}\n!adf:/mediaSingle\n')),
|
||||
named('mediaSingle', '``, or `[](href "title")` when linked'),
|
||||
)
|
||||
assert.equal(
|
||||
content(losslessMarkdownToAdf('!adf:table\n!adf:tableRow\n!adf:tableHeader\nPart.\n!adf:/tableHeader\n!adf:/tableRow\n!adf:/table\n')),
|
||||
named('table', 'a pipe table'),
|
||||
)
|
||||
// The item whose first line reads back as a thematic break keeps the directive form the emitter falls back to.
|
||||
assert.deepEqual(content(losslessMarkdownToAdf('!adf:bulletList\n!adf:listItem\n---\n!adf:/listItem\n!adf:/bulletList\n')), [bulletList(item({ type: 'rule' }))])
|
||||
})
|
||||
|
||||
test('refuses the directive form of a blockquote opening with a code span', () => {
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:blockquote\n` `` `\n!adf:/blockquote\n')), 'unsupported-node-shape: blockquote takes the CommonMark spelling, not the directive form')
|
||||
assert.equal(
|
||||
content(losslessMarkdownToAdf('!adf:blockquote\n` `` `\n!adf:/blockquote\n')),
|
||||
'unsupported-node-shape: blockquote takes the CommonMark spelling, not the directive form: write each line behind `> `',
|
||||
)
|
||||
})
|
||||
|
||||
// The input is some 1000 characters short of V8's longest string; the `> ` the quote adds to each of its 600 lines passes it. Reading this much outlasts Bun's 5 s default timeout.
|
||||
|
||||
@@ -266,9 +266,23 @@ function directiveNode(block: DirectiveBlock, reading: Reading, path: ConvertErr
|
||||
const readable = withinEngineLimits(() => commonMarkSpelling(built.value, path, depth, { flavour: 'lossless', memo: reading.memo }))
|
||||
if (!readable.ok) return readable
|
||||
if (!readable.value) return built
|
||||
return failure('unsupported-node-shape', `${built.value.type} takes the CommonMark spelling, not the directive form`, path)
|
||||
const spelling = commonMarkSpellings.get(built.value.type)
|
||||
return failure('unsupported-node-shape', `${built.value.type} takes the CommonMark spelling, not the directive form${spelling === undefined ? '' : `: write ${spelling}`}`, path)
|
||||
}
|
||||
|
||||
// The nodes `commonMarkSpelling` spells in the lossless flavour.
|
||||
const commonMarkSpellings: ReadonlyMap<string, string> = new Map([
|
||||
['blockquote', 'each line behind `> `'],
|
||||
['bulletList', 'each item behind `- `'],
|
||||
['codeBlock', 'its text in a ``` fence'],
|
||||
['heading', 'one `#` per level before its text, `## Title` at level 2'],
|
||||
['mediaSingle', '``, or `[](href "title")` when linked'],
|
||||
['orderedList', 'each item behind `1. `'],
|
||||
['paragraph', 'its text alone'],
|
||||
['rule', '`---`'],
|
||||
['table', 'a pipe table'],
|
||||
])
|
||||
|
||||
function directiveBody(read: BlockDirectiveNode, blocks: Block[] | undefined, reading: Reading, path: ConvertErrorPath, depth: number): Result<AdfNode> {
|
||||
const { contentModel, node } = read
|
||||
if (blocks === undefined) return success(node)
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
| ID | Release | Exempt | Item | R | S | C | A | G | Goals | Score |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 111 | 0.2.1 | | **Split README's `unsupported-node-shape` error-table row into one bullet per cause group, each carrying its remedy.** | 2 | 2 | 1 | 5 | 5 | 3 | 15.3 |
|
||||
| 89 | 0.2.1 | | **Build a `date`, `emoji`, `inlineCard`, `mention` or `status` holding no mark Atlassian's schema withholds from it, in every reader.** | 4 | 4 | 4 | 5 | 6 | 4 | 13.0 |
|
||||
| 99 | 0.2.1 | defect | **Write a linked external image standing inline or in a table cell in the spelling a reader panel picks, in `adfToPortableMarkdown`.** | 3 | 3 | 5 | 6 | 7 | 7 | 12.9 |
|
||||
| 100 | 0.2.1 | decision | **Put the reading of an image's title as its caption back to a reader panel, now that a writer panel reads a title as hover text.** | 3 | 3 | 3 | 4 | 5 | 4, 6 | 10.9 |
|
||||
@@ -33,12 +32,10 @@
|
||||
| 59 | 0.2.1 | decision | **Group the directive grammar into `src/markdown/directive/`, move `Read<T>` to `result.ts`, and move `Flavour` and `flavourClaims` to `markdown/flavour.ts`.** | 3 | 5 | 3 | 2 | 6 | 2 | 10.4 |
|
||||
| 62 | 0.2.1 | decision | **Move the portable flavour's reading out of `parse/` and its writing out of `emit/` into `markdown/portable/`, so `emit/` no longer imports `portable/`.** | 4 | 5 | 5 | 2 | 6 | 2 | 9.4 |
|
||||
| 96 | 0.2.1 | defect | **Read and write a text holding many unclosed backtick runs, or many `<!--`, `<?`, `<![CDATA[` or `<!` openers without a terminator, in time linear in the text.** | 2 | 3 | 7 | 5 | 9 | 9 | 9.2 |
|
||||
| 101 | 0.2.1 | | **Name the CommonMark spelling to write in the refusal of a directive whose node CommonMark spells.** | 2 | 2 | 4 | 4 | 4 | 6 | 9.0 |
|
||||
| 92 | 0.2.1 | | **Lower the reading cost of the files a chunk reads most before its first commit.** | 2 | 4 | 5 | 2 | 4 | 1 | 9.0 |
|
||||
| 33 | 0.2.1 | defect | **Emit a line in time linear in its mark runs, in `adfToLosslessMarkdown` and `adfToPortableMarkdown`.** | 4 | 5 | 8 | 6 | 9 | 9 | 8.7 |
|
||||
| 98 | 0.2.1 | defect | **Read blank lines inside deeply nested list items in time linear in the markdown.** | 3 | 3 | 7 | 5 | 9 | 9 | 8.2 |
|
||||
| 102 | 0.2.1 | decision | **Place a block a portable task list refuses at its path in the returned document without replaying `splitTaskList` on stand-in nodes.** | 3 | 4 | 7 | 2 | 4 | 1 | 8.0 |
|
||||
| 113 | 0.2.1 | principle | **Name a malformed block mark's `{attrs}` cause in `readNamedMark` without editing another module's message text.** | 2 | 2 | 3 | 2 | 3 | 1 | 7.5 |
|
||||
| 109 | 0.2.1 | defect | **Spell nested lists and quotes in time linear in the markdown.** | 3 | 5 | 8 | 4 | 7 | 9 | 5.3 |
|
||||
| 7 | 0.3.0 | decision | **Ship HTML: `adfToHtml`, `htmlToAdf`, and markdown to and from HTML composed through ADF, each call named by its flavour.** | 6 | 9 | 9 | 9 | 9 | 2, 4 | 25.8 |
|
||||
| 45 | 0.3.0 | | **Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.** | 2 | 3 | 4 | 6 | 8 | 1 | 25.2 |
|
||||
@@ -72,13 +69,6 @@
|
||||
|
||||
## Details
|
||||
|
||||
### 111. Split README's `unsupported-node-shape` error-table row into one bullet per cause group, each carrying its remedy.
|
||||
|
||||
The row's "Fires when" cell holds about a dozen causes in four groups (parsing, each writer, both
|
||||
writers, every call) and its "What you can do" cell mixes three remedies, so a reader searches it.
|
||||
List the groups under the table, each with its remedy. Found by the prose review of item 108,
|
||||
2026-10-13.
|
||||
|
||||
### 89. Build a `date`, `emoji`, `inlineCard`, `mention` or `status` holding no mark Atlassian's schema withholds from it, in every reader.
|
||||
|
||||
`*!adf:mention[@a]{id=…}*` and `[!adf:status[Done]{…}](/u)` build a node holding `em` or `link`.
|
||||
@@ -131,13 +121,6 @@ in a text of length n, backtick runs of lengths 1 to k with no closers cost abou
|
||||
repeated with no `-->` is quadratic, in the reader and the emitter's escape walk alike.
|
||||
commonmark.js avoids the backtick cost by caching backtick runs by length.
|
||||
|
||||
### 101. Name the CommonMark spelling to write in the refusal of a directive whose node CommonMark spells.
|
||||
|
||||
`directiveNode` (`src/markdown/parse/markdown-to-adf.ts`) refuses `!adf:mediaSingle` holding a linked
|
||||
image as `unsupported-node-shape`: "mediaSingle takes the CommonMark spelling, not the directive
|
||||
form". README §The errors says to fix what the message names, and the message names no spelling,
|
||||
here `[](href "title")`.
|
||||
|
||||
### 92. Lower the reading cost of the files a chunk reads most before its first commit.
|
||||
|
||||
The last five chunks' median is 168135 new input tokens before their first commit, the five before
|
||||
@@ -172,13 +155,6 @@ the fix.
|
||||
`taskOfParagraphs` keeps the original child objects. Both paired-ruling seats on 2026-10-11 named it
|
||||
the reason the project read worse.
|
||||
|
||||
### 113. Name a malformed block mark's `{attrs}` cause in `readNamedMark` without editing another module's message text.
|
||||
|
||||
Today `readNamedMark` in `src/markdown/parse/directive-nodes.ts` deletes `; ${directiveEscape}` from
|
||||
the fault `readAttributes` returns, so rewording a fault in `directive-syntax.ts` silently changes the
|
||||
block marks error. Both paired seats of PR #43's comprehension ruling named it the place that read
|
||||
worse. Principle: compose, do not entangle (`~/.claude/principles/technical-principles.md`).
|
||||
|
||||
### 109. Spell nested lists and quotes in time linear in the markdown.
|
||||
|
||||
Each level of a nested list or quote re-indents its whole text, so the work is the depth times the
|
||||
|
||||
Reference in New Issue
Block a user