sfora-cli 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -6
- package/dist/SforaFs.js +278 -10
- package/dist/api-client.d.ts +290 -5
- package/dist/api-client.js +307 -22
- package/dist/block-commands.d.ts +84 -0
- package/dist/block-commands.js +155 -0
- package/dist/cli.js +323 -29
- package/dist/format/__tests__/byteStable.d.ts +5 -0
- package/dist/format/__tests__/byteStable.js +64 -0
- package/dist/format/blockSplice.d.ts +135 -0
- package/dist/format/blockSplice.js +330 -0
- package/dist/format/blocks/dropClosure.d.ts +81 -0
- package/dist/format/blocks/dropClosure.js +196 -0
- package/dist/format/blocks/markdown-block-catalog.d.ts +18 -0
- package/dist/format/blocks/markdown-block-catalog.js +162 -0
- package/dist/format/blocks/markdown-block-ids.d.mts +1 -0
- package/dist/format/blocks/markdown-block-ids.mjs +25 -0
- package/dist/format/blocks/parsers.d.ts +105 -0
- package/dist/format/blocks/parsers.js +442 -0
- package/dist/format/blocks/structured-block-schema.d.ts +8 -0
- package/dist/format/blocks/structured-block-schema.js +30 -0
- package/dist/format/callout.d.ts +128 -0
- package/dist/format/callout.js +227 -0
- package/dist/format/cardMarkdown.d.ts +2 -0
- package/dist/format/cardMarkdown.js +10 -0
- package/dist/format/checklist.d.ts +34 -0
- package/dist/format/checklist.js +158 -0
- package/dist/format/formatAxes.d.ts +228 -0
- package/dist/format/formatAxes.js +454 -0
- package/dist/format/index.d.ts +19 -4
- package/dist/format/index.js +28 -4
- package/dist/format/lineGeometry.d.ts +100 -0
- package/dist/format/lineGeometry.js +424 -0
- package/dist/format/lint/appliesTo.d.ts +92 -0
- package/dist/format/lint/appliesTo.js +369 -0
- package/dist/format/lint/config.d.ts +106 -0
- package/dist/format/lint/config.js +205 -0
- package/dist/format/lint/fixAll.d.ts +62 -0
- package/dist/format/lint/fixAll.js +107 -0
- package/dist/format/lint/frontmatterSchema.d.ts +181 -0
- package/dist/format/lint/frontmatterSchema.js +660 -0
- package/dist/format/lint/index.d.ts +49 -0
- package/dist/format/lint/index.js +51 -0
- package/dist/format/lint/lintSource.d.ts +56 -0
- package/dist/format/lint/lintSource.js +188 -0
- package/dist/format/lint/rules/broken-wiki-link.d.ts +2 -0
- package/dist/format/lint/rules/broken-wiki-link.js +45 -0
- package/dist/format/lint/rules/frontmatter-schema.d.ts +2 -0
- package/dist/format/lint/rules/frontmatter-schema.js +92 -0
- package/dist/format/lint/rules/index.d.ts +11 -0
- package/dist/format/lint/rules/index.js +32 -0
- package/dist/format/lint/rules/malformed-callout.d.ts +2 -0
- package/dist/format/lint/rules/malformed-callout.js +88 -0
- package/dist/format/lint/rules/malformed-checklist.d.ts +2 -0
- package/dist/format/lint/rules/malformed-checklist.js +65 -0
- package/dist/format/lint/rules/malformed-frontmatter.d.ts +2 -0
- package/dist/format/lint/rules/malformed-frontmatter.js +98 -0
- package/dist/format/lint/rules/malformed-structured-block.d.ts +2 -0
- package/dist/format/lint/rules/malformed-structured-block.js +134 -0
- package/dist/format/lint/rules/malformed-wiki-link.d.ts +2 -0
- package/dist/format/lint/rules/malformed-wiki-link.js +43 -0
- package/dist/format/lint/rules/orphan-reference.d.ts +2 -0
- package/dist/format/lint/rules/orphan-reference.js +87 -0
- package/dist/format/lint/severity.d.ts +15 -0
- package/dist/format/lint/severity.js +50 -0
- package/dist/format/lint/textEdits.d.ts +86 -0
- package/dist/format/lint/textEdits.js +162 -0
- package/dist/format/lint/types.d.ts +116 -0
- package/dist/format/lint/types.js +16 -0
- package/dist/format/markdown/dates.js +2 -0
- package/dist/format/markdown/document.js +2 -0
- package/dist/format/markdown/index.js +2 -0
- package/dist/format/markdown/mentions.js +2 -0
- package/dist/format/markdown/slug.d.ts +28 -0
- package/dist/format/markdown/slug.js +65 -0
- package/dist/format/markdown/yaml.js +2 -0
- package/dist/format/noteMarkdown.js +2 -0
- package/dist/format/parseWithFallback.d.ts +13 -0
- package/dist/format/parseWithFallback.js +98 -0
- package/dist/format/plaintext.d.ts +5 -0
- package/dist/format/plaintext.js +51 -0
- package/dist/format/postMarkdown.js +3 -1
- package/dist/format/sheetCellSpans.d.ts +95 -0
- package/dist/format/sheetCellSpans.js +223 -0
- package/dist/format/sheetSelection.d.ts +136 -0
- package/dist/format/sheetSelection.js +282 -0
- package/dist/format/taskUploadFilename.d.ts +6 -0
- package/dist/format/taskUploadFilename.js +13 -0
- package/dist/format/textStats.d.ts +23 -0
- package/dist/format/textStats.js +80 -0
- package/dist/format/wayfinder.d.ts +50 -0
- package/dist/format/wayfinder.js +203 -0
- package/dist/format/wikiLinks.d.ts +78 -0
- package/dist/format/wikiLinks.js +266 -0
- package/dist/index.d.ts +26 -1
- package/dist/index.js +20 -3
- package/dist/mcp-server.js +5 -2
- package/dist/opener.d.ts +23 -0
- package/dist/opener.js +26 -0
- package/dist/render.d.ts +132 -0
- package/dist/render.js +208 -0
- package/dist/shell-commands.d.ts +34 -0
- package/dist/shell-commands.js +108 -0
- package/dist/watch.d.ts +79 -0
- package/dist/watch.js +113 -0
- package/dist/web-url.d.ts +39 -0
- package/dist/web-url.js +63 -0
- package/package.json +7 -6
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-format.mjs from packages/markdown/src — DO NOT EDIT.
|
|
2
|
+
// Edit packages/markdown/src and re-run the sync (any sfora-cli build does it).
|
|
3
|
+
// The drop ledger: every byte a structured-block parser throws away, signed
|
|
4
|
+
// for.
|
|
5
|
+
//
|
|
6
|
+
// A block parser is a filter — it keeps the lines it understands and returns
|
|
7
|
+
// the rest as nothing at all — so "the block rendered" has never been evidence
|
|
8
|
+
// that everything in it did. The parsers now report what they consumed and
|
|
9
|
+
// what they dropped (see `BlockAccounting` in ./parsers), and this file is the
|
|
10
|
+
// other half of that contract: a closed inventory in which every drop reason
|
|
11
|
+
// carries a verdict, a rationale, and a witness that RUNS.
|
|
12
|
+
//
|
|
13
|
+
// The four verdicts are open-knowledge's, and the discipline they encode is
|
|
14
|
+
// that "lossy" is not one thing:
|
|
15
|
+
//
|
|
16
|
+
// structural-only the bytes carry no content — a blank line, a container
|
|
17
|
+
// format-dof-axis the bytes are one spelling of a thing we keep; the
|
|
18
|
+
// axis id names WHICH degree of freedom, and it is an id
|
|
19
|
+
// from `../formatAxes` rather than a phrase — that file
|
|
20
|
+
// is the finite list, and it carries the DISCRIMINATION
|
|
21
|
+
// witness that proves the two spellings mean one thing.
|
|
22
|
+
// Not a round-trip witness, which is what this line used
|
|
23
|
+
// to call it: a discrimination witness shows the two
|
|
24
|
+
// spellings parse alike and that the capture layer can
|
|
25
|
+
// tell them apart, and says nothing about whether either
|
|
26
|
+
// survives a save. `formatAxes.ts`'s replay scoreboard is
|
|
27
|
+
// where that question is answered, and for most axes the
|
|
28
|
+
// answer today is no
|
|
29
|
+
// retained-by-capture the bytes are gone from the line but present in the
|
|
30
|
+
// parsed data, and `retained` says exactly where
|
|
31
|
+
// documented-residual real loss, admitted, witnessed. These — and only
|
|
32
|
+
// these — are what lint tells an author about.
|
|
33
|
+
//
|
|
34
|
+
// Closure is checked in both directions. TypeScript proves no reason is
|
|
35
|
+
// unadjudicated (the ledger is a total Record over the reason union); the
|
|
36
|
+
// suite in __tests__/blockDropClosure.test.ts proves no entry is stale, that
|
|
37
|
+
// every witness still provokes its drop, and that consumed plus dropped
|
|
38
|
+
// accounts for every line of every fixture.
|
|
39
|
+
export const BLOCK_DROP_ADJUDICATIONS = {
|
|
40
|
+
"blank-line": {
|
|
41
|
+
kind: "structural-only",
|
|
42
|
+
witness: {
|
|
43
|
+
lang: "chat",
|
|
44
|
+
source: "- @mara: on it\n\n- @ada: shipping\n",
|
|
45
|
+
dropped: "",
|
|
46
|
+
},
|
|
47
|
+
rationale: "the block grammars are line-based and read one entry per line, so a blank line separates nothing and carries nothing",
|
|
48
|
+
},
|
|
49
|
+
"block-unread": {
|
|
50
|
+
kind: "retained-by-capture",
|
|
51
|
+
witness: {
|
|
52
|
+
lang: "sheet",
|
|
53
|
+
source: "this is not a table\n",
|
|
54
|
+
dropped: "this is not a table",
|
|
55
|
+
},
|
|
56
|
+
retained: "the rendered code fence",
|
|
57
|
+
rationale: "a parser that returns null is the renderer's signal to draw a plain code fence, so every byte of an unreadable block stays on screen and copyable — the loss is the STRUCTURE, never the text",
|
|
58
|
+
},
|
|
59
|
+
"entry-bullet-marker": {
|
|
60
|
+
kind: "format-dof-axis",
|
|
61
|
+
axisIds: ["structured-block:entry-bullet"],
|
|
62
|
+
partial: true,
|
|
63
|
+
witness: {
|
|
64
|
+
lang: "chat",
|
|
65
|
+
source: "* @mara: on it\n",
|
|
66
|
+
dropped: "*",
|
|
67
|
+
},
|
|
68
|
+
rationale: "a status or chat entry may be authored bare, with `-`, or with `*`; all three render identically, so which one was typed is a spelling the entry-bullet axis owns rather than content",
|
|
69
|
+
},
|
|
70
|
+
"status:unsigned-line": {
|
|
71
|
+
kind: "documented-residual",
|
|
72
|
+
witnesses: [
|
|
73
|
+
{
|
|
74
|
+
lang: "status",
|
|
75
|
+
source: "state: building\n- @ada: on it\njust a loose note\n",
|
|
76
|
+
dropped: "just a loose note",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
rationale: "a status entry is `[time] @actor: message`; a line that is neither that nor the `state:` header has no field to land in, and the timeline would have to invent an author to show it",
|
|
80
|
+
},
|
|
81
|
+
"board:unparsed-line": {
|
|
82
|
+
kind: "documented-residual",
|
|
83
|
+
witnesses: [
|
|
84
|
+
{
|
|
85
|
+
lang: "board",
|
|
86
|
+
source: "## Todo\n- [ ] item one\nThis note vanishes\n",
|
|
87
|
+
dropped: "This note vanishes",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
rationale: "a board block holds column headings and cards; prose between them belongs to neither and there is no column to hang it under",
|
|
91
|
+
},
|
|
92
|
+
"chat:unparsed-line": {
|
|
93
|
+
kind: "documented-residual",
|
|
94
|
+
witnesses: [
|
|
95
|
+
{
|
|
96
|
+
lang: "chat",
|
|
97
|
+
source: "- @mara: on it\nplain note with no speaker\n",
|
|
98
|
+
dropped: "plain note with no speaker",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
rationale: "a chat entry is signed — `@actor: message` — and an unsigned line cannot be attributed without putting words in someone's mouth",
|
|
102
|
+
},
|
|
103
|
+
"sheet:non-row-line": {
|
|
104
|
+
kind: "documented-residual",
|
|
105
|
+
witnesses: [
|
|
106
|
+
{
|
|
107
|
+
lang: "sheet",
|
|
108
|
+
source: "| A | B |\n| --- | --- |\n| 1 | 2 |\njust a note\n",
|
|
109
|
+
dropped: "just a note",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
rationale: "GFM makes the outer pipes optional, so a row is a line carrying a pipe that would split it; a line with no unescaped, un-code-spanned pipe is not a row and has no cells",
|
|
113
|
+
},
|
|
114
|
+
"sheet:delimiter-row": {
|
|
115
|
+
kind: "retained-by-capture",
|
|
116
|
+
witness: {
|
|
117
|
+
lang: "sheet",
|
|
118
|
+
source: "| A | B |\n| --- | ---: |\n| 1 | 2 |\n",
|
|
119
|
+
dropped: "| --- | ---: |",
|
|
120
|
+
},
|
|
121
|
+
retained: "SheetData.columns[].alignment",
|
|
122
|
+
rationale: "the delimiter row is alignment, not data; its colons are read into every column and its dashes have nothing else to say",
|
|
123
|
+
},
|
|
124
|
+
"sheet:excess-cells": {
|
|
125
|
+
kind: "documented-residual",
|
|
126
|
+
partial: true,
|
|
127
|
+
witnesses: [
|
|
128
|
+
{
|
|
129
|
+
lang: "sheet",
|
|
130
|
+
source: "| A | B |\n| --- | --- |\n| 1 | 2 | 3 |\n",
|
|
131
|
+
dropped: "3",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
rationale: "a table is as wide as its header row, exactly as GFM has it — a cell past the last column is not a column a reader sees, and widening the table on its behalf would move every other cell",
|
|
135
|
+
},
|
|
136
|
+
"map:unparsed-line": {
|
|
137
|
+
kind: "documented-residual",
|
|
138
|
+
witnesses: [
|
|
139
|
+
{
|
|
140
|
+
lang: "map",
|
|
141
|
+
source: "destination: Ship it\n- [ ] Chart the first question\na stray note\n",
|
|
142
|
+
dropped: "a stray note",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
// Only the FIRST destination line is the header; a second one is not a
|
|
146
|
+
// ticket either, so it falls here.
|
|
147
|
+
lang: "map",
|
|
148
|
+
source: "destination: Ship it\ndestination: Ship it twice\n- [ ] Chart it\n",
|
|
149
|
+
dropped: "destination: Ship it twice",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
rationale: "a map block holds one destination header and tickets in the wayfinder line grammar; anything else has no node to draw",
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* True when the drop takes the whole line with it. A partial drop happened on
|
|
157
|
+
* a line that rendered anyway — the bullet marker in front of a chat entry,
|
|
158
|
+
* the cells past the end of a sheet's header — and must NOT be subtracted from
|
|
159
|
+
* the consumed set.
|
|
160
|
+
*/
|
|
161
|
+
export function isWholeLineDrop(reason) {
|
|
162
|
+
return BLOCK_DROP_ADJUDICATIONS[reason].partial !== true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* True when an author should hear about the drop. Only a documented residual
|
|
166
|
+
* is real loss; the other three verdicts say the bytes were structure, a
|
|
167
|
+
* spelling, or captured elsewhere, and a diagnostic for those would be noise
|
|
168
|
+
* on a block that rendered exactly as written.
|
|
169
|
+
*/
|
|
170
|
+
export function isReportableDrop(reason) {
|
|
171
|
+
return BLOCK_DROP_ADJUDICATIONS[reason].kind === "documented-residual";
|
|
172
|
+
}
|
|
173
|
+
/** Every witness in the ledger, flattened, with the reason it belongs to. */
|
|
174
|
+
export function dropWitnesses() {
|
|
175
|
+
const out = [];
|
|
176
|
+
for (const key of Object.keys(BLOCK_DROP_ADJUDICATIONS)) {
|
|
177
|
+
const entry = BLOCK_DROP_ADJUDICATIONS[key];
|
|
178
|
+
const witnesses = entry.kind === "documented-residual" ? entry.witnesses : [entry.witness];
|
|
179
|
+
for (const witness of witnesses)
|
|
180
|
+
out.push({ reason: key, witness });
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Close the ledger against what the parsers actually did. `observed` is every
|
|
186
|
+
* reason seen across the whole fixture corpus — one document's drops are never
|
|
187
|
+
* enough to close anything.
|
|
188
|
+
*/
|
|
189
|
+
export function checkDropClosure(observed) {
|
|
190
|
+
const seen = new Set(observed);
|
|
191
|
+
const adjudicated = Object.keys(BLOCK_DROP_ADJUDICATIONS);
|
|
192
|
+
return {
|
|
193
|
+
unadjudicated: [...seen].filter((r) => !adjudicated.includes(r)).sort(),
|
|
194
|
+
stale: adjudicated.filter((r) => !seen.has(r)).sort(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MARKDOWN_BLOCK_IDS } from "./markdown-block-ids.mjs";
|
|
2
|
+
export { MARKDOWN_BLOCK_IDS };
|
|
3
|
+
export type MarkdownBlockId = (typeof MARKDOWN_BLOCK_IDS)[number];
|
|
4
|
+
export interface MarkdownBlockSpec {
|
|
5
|
+
id: MarkdownBlockId;
|
|
6
|
+
label: string;
|
|
7
|
+
group: "Text" | "Structure" | "Rich blocks" | "References" | "Recovery";
|
|
8
|
+
description: string;
|
|
9
|
+
source: string;
|
|
10
|
+
renderMermaid?: boolean;
|
|
11
|
+
interactiveTasks?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const POST_ID = "post000000000000000000000001";
|
|
14
|
+
declare const CARD_ID = "card000000000000000000000001";
|
|
15
|
+
export declare const MARKDOWN_BLOCK_CATALOG: readonly MarkdownBlockSpec[];
|
|
16
|
+
export declare function getMarkdownBlockSpec(id?: string): MarkdownBlockSpec | undefined;
|
|
17
|
+
export declare const MARKDOWN_BLOCK_FIXTURE_IDS: `markdown-block-${string}`[];
|
|
18
|
+
export { CARD_ID as MARKDOWN_FIXTURE_CARD_ID, POST_ID as MARKDOWN_FIXTURE_POST_ID };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-format.mjs from packages/markdown/src — DO NOT EDIT.
|
|
2
|
+
// Edit packages/markdown/src and re-run the sync (any sfora-cli build does it).
|
|
3
|
+
import { MARKDOWN_BLOCK_IDS } from "./markdown-block-ids.mjs";
|
|
4
|
+
export { MARKDOWN_BLOCK_IDS };
|
|
5
|
+
const POST_ID = "post000000000000000000000001";
|
|
6
|
+
const CARD_ID = "card000000000000000000000001";
|
|
7
|
+
export const MARKDOWN_BLOCK_CATALOG = [
|
|
8
|
+
{
|
|
9
|
+
id: "typography",
|
|
10
|
+
label: "Typography",
|
|
11
|
+
group: "Text",
|
|
12
|
+
description: "Heading hierarchy and document rhythm.",
|
|
13
|
+
source: "# Document title\n\n## Section heading\n\n### Supporting heading\n\nA paragraph owns the primary reading measure. A second sentence proves wrapping and vertical rhythm without creating another surface.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "inline-formatting",
|
|
17
|
+
label: "Inline formatting",
|
|
18
|
+
group: "Text",
|
|
19
|
+
description: "Emphasis, code, mentions, and links in one line flow.",
|
|
20
|
+
source: "Use **strong emphasis**, *considered emphasis*, ~~retired work~~, and `pnpm design:check` without disrupting the baseline. @[Mara Voss](member-mara) can review the [design evidence](https://developer.apple.com/design/human-interface-guidelines/).",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "lists",
|
|
24
|
+
label: "Lists",
|
|
25
|
+
group: "Structure",
|
|
26
|
+
description: "Sibling items remain one semantic run.",
|
|
27
|
+
source: "- Preserve the reading datum\n- Keep sibling items together\n- Use a boundary only when the object earns one\n\n1. Inspect the real route\n2. Capture compact and desktop\n3. Judge the rendered result",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "tasks",
|
|
31
|
+
label: "Task list",
|
|
32
|
+
group: "Structure",
|
|
33
|
+
description: "Interactive and completed task states.",
|
|
34
|
+
source: "- [x] Inventory the canonical blocks\n- [ ] Verify compact overflow\n- [ ] Review dark-mode boundaries",
|
|
35
|
+
interactiveTasks: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "quote",
|
|
39
|
+
label: "Quote",
|
|
40
|
+
group: "Structure",
|
|
41
|
+
description: "Quoted context is subordinate but still readable.",
|
|
42
|
+
source: "> The document is the primary field.\n> Controls explain state without competing with the argument.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "callout",
|
|
46
|
+
label: "Callout",
|
|
47
|
+
group: "Structure",
|
|
48
|
+
description: "An aside earns a tinted surface, never a colored bar.",
|
|
49
|
+
source: "> [!NOTE]\n> A callout is a blockquote with a type marker, so it stays readable everywhere markdown is read.\n\n> [!WARNING] Run the migration first\n> The deploy assumes the new stage columns already exist.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "divider",
|
|
53
|
+
label: "Divider",
|
|
54
|
+
group: "Structure",
|
|
55
|
+
description: "A quiet transition between related passages.",
|
|
56
|
+
source: "The evidence ends here.\n\n---\n\nThe decision begins on the same reading field.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "table",
|
|
60
|
+
label: "Table",
|
|
61
|
+
group: "Structure",
|
|
62
|
+
description: "Aligned data with compact horizontal recovery.",
|
|
63
|
+
source: "| Route | State | Owner | Last review |\n| --- | :---: | --- | ---: |\n| Home | Ready | Product | Today |\n| Library workbench | Review | Design systems | Yesterday |\n| Post reader | Ready | Documents | 2 days |",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "code",
|
|
67
|
+
label: "Code block",
|
|
68
|
+
group: "Structure",
|
|
69
|
+
description: "Monospace source, syntax color, copy action, and overflow.",
|
|
70
|
+
source: "```typescript\ntype SurfaceRole = \"primary\" | \"grouped\" | \"module\"\n\nexport function ownsBoundary(role: SurfaceRole) {\n return role === \"module\"\n}\n```",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "image",
|
|
74
|
+
label: "Image",
|
|
75
|
+
group: "Structure",
|
|
76
|
+
description: "Authored media keeps its alternative text and document measure.",
|
|
77
|
+
source: "",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "mermaid",
|
|
81
|
+
label: "Mermaid",
|
|
82
|
+
group: "Rich blocks",
|
|
83
|
+
description: "Expandable diagram evidence inside the document flow.",
|
|
84
|
+
source: "```mermaid\nflowchart LR\n Source[Markdown source] --> Parse[Canonical parser]\n Parse --> Reader[Reader]\n Parse --> Editor[Editor]\n Reader --> Capture[Visual acceptance]\n```",
|
|
85
|
+
renderMermaid: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "summary-cards",
|
|
89
|
+
label: "Summary cards",
|
|
90
|
+
group: "Rich blocks",
|
|
91
|
+
description: "Legacy digest summaries resolve into one grouped surface.",
|
|
92
|
+
source: "- **Design graph is live** — Thijs Verreck\n> Every rendered state is now addressable from the system graph.\n- **Compact review passed** — Dogfood Bot\n> The reading hierarchy survives the narrow viewport.",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "status",
|
|
96
|
+
label: "Status timeline",
|
|
97
|
+
group: "Rich blocks",
|
|
98
|
+
description: "Current state followed by signed chronological updates.",
|
|
99
|
+
source: "```status #run\nstate: building\n- 2026-08-13T09:02Z integrator: renderer inventory complete\n- 2026-08-13T09:18Z reviewer: compact capture is ready\n```",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "board",
|
|
103
|
+
label: "Board",
|
|
104
|
+
group: "Rich blocks",
|
|
105
|
+
description: "Compact work columns with stable narrow-screen overflow.",
|
|
106
|
+
source: "```board #tickets\n## In progress\n- [ ] Fix status anatomy\n- [ ] Verify card references\n## Review\n- [ ] Inspect compact overflow\n## Done\n- [x] Build the state graph\n```",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "chat",
|
|
110
|
+
label: "Conversation",
|
|
111
|
+
group: "Rich blocks",
|
|
112
|
+
description: "Attributed messages remain sibling rows.",
|
|
113
|
+
source: "```chat #general\n- 2026-08-13T09:04Z @integrator (agent): the renderer matrix is live\n- @Alexandra Very Long Operator Name (reviewer): the reference evidence is attached and the compact reading order remains intact\n- 2026-08-13T09:11Z @Mara: review the dark-mode boundary next\n```",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "sheet",
|
|
117
|
+
label: "Data sheet",
|
|
118
|
+
group: "Rich blocks",
|
|
119
|
+
description: "Structured rows and columns with one owning frame.",
|
|
120
|
+
source: "```sheet #coverage\n| Block | Reader | Editor | Compact |\n| --- | --- | --- | --- |\n| Status | Ready | Ready | Review |\n| Board | Ready | Ready | Ready |\n| References | Review | Ready | Ready |\n```",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "map",
|
|
124
|
+
label: "Map",
|
|
125
|
+
group: "Rich blocks",
|
|
126
|
+
description: "Decision tickets and their blocking edges, drawn as the route to a destination.",
|
|
127
|
+
source: "```map #launch\ndestination: A locked launch checklist\n- [x] Name the destination\n- [~] Pick the payment provider (research) <- Name the destination\n- [ ] Do we need a waitlist page? (prototype) <- Name the destination\n- [ ] Settle the seat model <- Pick the payment provider, Do we need a waitlist page?\n- [-] Native mobile app\n```",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "post-reference",
|
|
131
|
+
label: "Post reference",
|
|
132
|
+
group: "References",
|
|
133
|
+
description: "A linked post previews before canonical navigation.",
|
|
134
|
+
source: `[[${POST_ID}|The reader becomes one intentional work surface]]`,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "card-reference",
|
|
138
|
+
label: "Card reference",
|
|
139
|
+
group: "References",
|
|
140
|
+
description: "A linked task exposes state and project context.",
|
|
141
|
+
source: `[[c:${CARD_ID}|Audit every rendered Markdown block]]`,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "unknown-reference",
|
|
145
|
+
label: "Unknown reference",
|
|
146
|
+
group: "Recovery",
|
|
147
|
+
description: "Unresolved work remains visibly unavailable and does not imply navigation.",
|
|
148
|
+
source: "The evidence points to [[missing000000000000000000001|a post that is no longer available]].",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "malformed-structured",
|
|
152
|
+
label: "Malformed structured source",
|
|
153
|
+
group: "Recovery",
|
|
154
|
+
description: "Invalid enhanced Markdown remains visible and copyable.",
|
|
155
|
+
source: "```sheet #broken\nthis is not a valid table\nand the source must not disappear\n```",
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
export function getMarkdownBlockSpec(id) {
|
|
159
|
+
return MARKDOWN_BLOCK_CATALOG.find((block) => block.id === id);
|
|
160
|
+
}
|
|
161
|
+
export const MARKDOWN_BLOCK_FIXTURE_IDS = MARKDOWN_BLOCK_IDS.map((id) => `markdown-block-${id}`);
|
|
162
|
+
export { CARD_ID as MARKDOWN_FIXTURE_CARD_ID, POST_ID as MARKDOWN_FIXTURE_POST_ID };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MARKDOWN_BLOCK_IDS: string[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-format.mjs from packages/markdown/src — DO NOT EDIT.
|
|
2
|
+
// Edit packages/markdown/src and re-run the sync (any sfora-cli build does it).
|
|
3
|
+
export const MARKDOWN_BLOCK_IDS = [
|
|
4
|
+
"typography",
|
|
5
|
+
"inline-formatting",
|
|
6
|
+
"lists",
|
|
7
|
+
"tasks",
|
|
8
|
+
"quote",
|
|
9
|
+
"callout",
|
|
10
|
+
"divider",
|
|
11
|
+
"table",
|
|
12
|
+
"code",
|
|
13
|
+
"image",
|
|
14
|
+
"mermaid",
|
|
15
|
+
"summary-cards",
|
|
16
|
+
"status",
|
|
17
|
+
"board",
|
|
18
|
+
"chat",
|
|
19
|
+
"sheet",
|
|
20
|
+
"map",
|
|
21
|
+
"post-reference",
|
|
22
|
+
"card-reference",
|
|
23
|
+
"unknown-reference",
|
|
24
|
+
"malformed-structured",
|
|
25
|
+
];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type WayfinderMap } from "../wayfinder.js";
|
|
2
|
+
import { type BlockDropReason } from "./dropClosure.js";
|
|
3
|
+
import type { StructuredBlockLanguage } from "./structured-block-schema.js";
|
|
4
|
+
export interface TimelineEntry {
|
|
5
|
+
time?: string;
|
|
6
|
+
actor: string;
|
|
7
|
+
message: string;
|
|
8
|
+
/**
|
|
9
|
+
* A `state:` line AFTER the header one: a state change, rendered as a
|
|
10
|
+
* marker row rather than a signed update. `actor` is empty for these.
|
|
11
|
+
*/
|
|
12
|
+
kind?: "state";
|
|
13
|
+
}
|
|
14
|
+
export interface ChatEntry extends TimelineEntry {
|
|
15
|
+
role?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface BoardColumn {
|
|
18
|
+
title: string;
|
|
19
|
+
items: Array<{
|
|
20
|
+
state: string;
|
|
21
|
+
text: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export type SheetAlignment = "left" | "center" | "right";
|
|
25
|
+
export interface SheetData {
|
|
26
|
+
columns: Array<{
|
|
27
|
+
label: string;
|
|
28
|
+
alignment: SheetAlignment;
|
|
29
|
+
}>;
|
|
30
|
+
rows: string[][];
|
|
31
|
+
}
|
|
32
|
+
export interface TimelineData {
|
|
33
|
+
state?: string;
|
|
34
|
+
entries: TimelineEntry[];
|
|
35
|
+
}
|
|
36
|
+
export declare function cleanLines(source: string): string[];
|
|
37
|
+
/** Bytes a parser did not keep. `line` indexes the block body, 0-based. */
|
|
38
|
+
export interface BlockDrop {
|
|
39
|
+
line: number;
|
|
40
|
+
/** The dropped text, trimmed. Empty for a blank line. */
|
|
41
|
+
text: string;
|
|
42
|
+
reason: BlockDropReason;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* What a parse produced and what it cost. `consumed` and the whole-line drops
|
|
46
|
+
* partition the body's lines exactly — that is the closure the fixture suite
|
|
47
|
+
* asserts, and the reason a parser can no longer quietly widen what it ignores.
|
|
48
|
+
*/
|
|
49
|
+
export interface BlockAccounting<T> {
|
|
50
|
+
data: T | null;
|
|
51
|
+
/** Body line indices that reached the rendered block, ascending. */
|
|
52
|
+
consumed: number[];
|
|
53
|
+
drops: BlockDrop[];
|
|
54
|
+
}
|
|
55
|
+
export declare function parseTimelineWithAccounting(source: string): BlockAccounting<TimelineData>;
|
|
56
|
+
export declare function parseTimeline(source: string): TimelineData | null;
|
|
57
|
+
/**
|
|
58
|
+
* True for a line parseBoard keeps: a column heading or a card. A convenience
|
|
59
|
+
* for callers holding one line — the parser's own accounting is what lint and
|
|
60
|
+
* the closure suite read, so this is never the second opinion.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isBoardLine(line: string): boolean;
|
|
63
|
+
export declare function parseBoardWithAccounting(source: string): BlockAccounting<BoardColumn[]>;
|
|
64
|
+
export declare function parseBoard(source: string): BoardColumn[] | null;
|
|
65
|
+
export declare function parseChatWithAccounting(source: string): BlockAccounting<ChatEntry[]>;
|
|
66
|
+
export declare function parseChat(source: string): ChatEntry[] | null;
|
|
67
|
+
/**
|
|
68
|
+
* True for a line parseSheet reads as a table row. GFM makes the outer pipes
|
|
69
|
+
* optional, so the test is "carries a pipe that would split it" — an escaped
|
|
70
|
+
* or code-spanned pipe is content, not structure.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isSheetRow(line: string): boolean;
|
|
73
|
+
export declare function parseSheetWithAccounting(source: string): BlockAccounting<SheetData>;
|
|
74
|
+
export declare function parseSheet(source: string): SheetData | null;
|
|
75
|
+
/**
|
|
76
|
+
* True for a line parseMapBlock keeps: the destination header or a ticket in
|
|
77
|
+
* the wayfinder line grammar. Same standing as {@link isBoardLine} — a
|
|
78
|
+
* convenience, not the authority.
|
|
79
|
+
*/
|
|
80
|
+
export declare function isMapLine(line: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Parse a ```map block: an optional `destination:` header, then tickets in
|
|
83
|
+
* the wayfinder line grammar (`- [~] Name (type) <- Blocker`). Null when no
|
|
84
|
+
* ticket parses — the renderer's signal to fall back to a visible code fence,
|
|
85
|
+
* same contract as every other structured block.
|
|
86
|
+
*/
|
|
87
|
+
export declare function parseMapBlockWithAccounting(source: string): BlockAccounting<WayfinderMap>;
|
|
88
|
+
/**
|
|
89
|
+
* Parse a ```map block: an optional `destination:` header, then tickets in
|
|
90
|
+
* the wayfinder line grammar (`- [~] Name (type) <- Blocker`). Null when no
|
|
91
|
+
* ticket parses — the renderer's signal to fall back to a visible code fence,
|
|
92
|
+
* same contract as every other structured block.
|
|
93
|
+
*/
|
|
94
|
+
export declare function parseMapBlock(source: string): WayfinderMap | null;
|
|
95
|
+
/**
|
|
96
|
+
* Parse a fence body as the block its language names, with the ledger. The
|
|
97
|
+
* one door lint and the closure suite use, so neither carries its own table of
|
|
98
|
+
* which parser reads which language.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parseStructuredBlock(language: StructuredBlockLanguage, source: string): BlockAccounting<unknown>;
|
|
101
|
+
/**
|
|
102
|
+
* Body line indices whose bytes did not survive the parse at all. Partial
|
|
103
|
+
* drops are excluded by construction — their line rendered.
|
|
104
|
+
*/
|
|
105
|
+
export declare function droppedLineNumbers(accounting: BlockAccounting<unknown>): number[];
|