rapier-markdown-kit 1.2.45 → 1.2.46

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.
Files changed (41) hide show
  1. package/LICENSE +12 -5
  2. package/README.md +131 -10
  3. package/dist/agent/vendor/BROWSER-ENTITIES.json +36 -0
  4. package/dist/agent/vendor/MARKDOWN-RUNTIME.json +136 -0
  5. package/dist/agent/vendor/MARKDOWNIT.json +582 -0
  6. package/dist/agent/vendor/NOTICE.txt +60 -0
  7. package/dist/agent/vendor/markdown-licenses/entities-LICENSE.txt +11 -0
  8. package/dist/agent/vendor/markdown-licenses/linkify-it-LICENSE.txt +22 -0
  9. package/dist/agent/vendor/markdown-licenses/markdown-it-LICENSE.txt +22 -0
  10. package/dist/agent/vendor/markdown-licenses/mdurl-LICENSE.txt +45 -0
  11. package/dist/agent/vendor/markdown-licenses/punycode.js-LICENSE-MIT.txt +20 -0
  12. package/dist/agent/vendor/markdown-licenses/uc.micro-LICENSE.txt +20 -0
  13. package/dist/agent/vendor/markdown-plugins.json +46 -0
  14. package/dist/agent/vendor/markdown-plugins.mjs +27 -0
  15. package/dist/agent/vendor/markdown-runtime.mjs +13 -0
  16. package/dist/kit/assets.d.mts +106 -0
  17. package/dist/kit/index.d.mts +151 -0
  18. package/dist/kit/layout.d.mts +61 -0
  19. package/dist/kit/ledger/carried.d.mts +12 -6
  20. package/dist/kit/ledger/carried.mjs +1 -1
  21. package/dist/kit/ledger/format.mjs +5 -3
  22. package/dist/kit/ledger/text-merge.mjs +33 -0
  23. package/dist/kit/ledger/transport.mjs +61 -2
  24. package/dist/kit/ledger.d.mts +137 -31
  25. package/dist/kit/markdown-it.d.mts +2 -2
  26. package/dist/kit/marks.d.mts +241 -0
  27. package/dist/kit/marks.mjs +39 -1
  28. package/dist/kit/model.d.mts +191 -0
  29. package/dist/kit/render-entry.d.mts +654 -0
  30. package/dist/kit/render.mjs +8 -7
  31. package/dist/kit/typography.d.mts +42 -0
  32. package/dist/kit/will.d.mts +26 -3
  33. package/dist/math/delimiters.mjs +38 -0
  34. package/dist/math/markdown-rules.mjs +103 -0
  35. package/dist/spec/document-fonts.mjs +2 -10
  36. package/dist/spec/md-assets.mjs +1 -0
  37. package/dist/spec/md-comments.mjs +384 -0
  38. package/dist/spec/md-marks.mjs +9 -0
  39. package/dist/spec/md-parser-config.mjs +463 -0
  40. package/dist/spec/md-parser-runtime.mjs +23 -0
  41. package/package.json +30 -8
@@ -0,0 +1,151 @@
1
+ // SPDX-License-Identifier: MIT
2
+ export {
3
+ INDENT_LIMIT,
4
+ SPACING_FIELDS,
5
+ alignments,
6
+ decodeLayoutAttribute,
7
+ fields,
8
+ formatLayout,
9
+ imageStyle,
10
+ indentLevel,
11
+ linesHeightCss,
12
+ objectHeight,
13
+ parseLayout,
14
+ parseLayoutAttribute,
15
+ stepIndent,
16
+ textLayout,
17
+ textStyle,
18
+ validLayout,
19
+ wrapColumnFloor,
20
+ wrapNeighbour,
21
+ wrapTextBlock,
22
+ wraps
23
+ } from './layout.mjs';
24
+ export {
25
+ FONT_STACKS,
26
+ INK_CLOSE,
27
+ INK_INT_MAX,
28
+ INK_KINDS,
29
+ INK_OPEN_PATTERN,
30
+ INK_PATH_MAX,
31
+ PAGE_BREAK_MARKER,
32
+ TEXT_COLOR_NAMES,
33
+ fontFamilyCss,
34
+ formatColorRun,
35
+ formatFontRun,
36
+ formatInkOpen,
37
+ formatInkOpenFromBody,
38
+ formatInkRun,
39
+ formatPageBreak,
40
+ hasInkMarker,
41
+ inkOpenBody,
42
+ isInkClose,
43
+ isPageBreakBlock,
44
+ isPageBreakLine,
45
+ matchInkOpen,
46
+ mayRetireInk,
47
+ normalizeFontFamily,
48
+ pairInkMarkers,
49
+ pairInkSpans,
50
+ pairMarkers,
51
+ parseInkBody,
52
+ parseInkOpen,
53
+ prepareInkPaste,
54
+ quotedWillSpan,
55
+ readInkMatch,
56
+ scanInkMarkers,
57
+ sliceInkArrows,
58
+ stripInkMarkers,
59
+ typography,
60
+ typographySourceRanges
61
+ } from './marks.mjs';
62
+ export {
63
+ alphaProfile,
64
+ bandsProfile,
65
+ besideSlices,
66
+ boxClearance,
67
+ captureFontAuthority,
68
+ columnsFilled,
69
+ createLineMeasurer,
70
+ createPreparationScope,
71
+ edgeLines,
72
+ edgeMarks,
73
+ flowLines,
74
+ frameScale,
75
+ imageBox,
76
+ imageOnly,
77
+ invalidateRichMetrics,
78
+ layoutNextRichInlineLineRange,
79
+ lineMetrics,
80
+ linesHeight,
81
+ linesSlices,
82
+ linesTop,
83
+ mapFragment,
84
+ materializeRichInlineLineRange,
85
+ parseProfile,
86
+ pictureSlices,
87
+ polygonProfile,
88
+ prepareRichParagraph,
89
+ rasterReserved,
90
+ rasterTiltProfile,
91
+ rotatedBoundsRad,
92
+ rotatedRasterAlpha,
93
+ serializeProfile,
94
+ slotSequence,
95
+ slotsForBand,
96
+ supportsRichStyle,
97
+ wrapShape
98
+ } from './model.mjs';
99
+ export {
100
+ evaluateWill,
101
+ hasWillMarkers,
102
+ parseWill,
103
+ stripOneTerminator,
104
+ terminatorWidth,
105
+ willGovern,
106
+ willIntentOf,
107
+ willMarkerOf,
108
+ willOffsetBefore,
109
+ willRegionsIn,
110
+ willTouchesMarker,
111
+ willTransition
112
+ } from './will.mjs';
113
+ export {
114
+ IMAGE_LIMITS,
115
+ appendAsset,
116
+ appendAssetText,
117
+ assetOmissions,
118
+ assetTitle,
119
+ blockwiseAssets,
120
+ configureParser,
121
+ dataImage,
122
+ documentAssets,
123
+ escapeImageAlt,
124
+ imageEnvironment,
125
+ installMarkdownImages,
126
+ isAssetBlock,
127
+ markdownBodyOffset,
128
+ markdownParser,
129
+ mayRetireImageDefinitions,
130
+ normalizeLabel,
131
+ packAssetIndex,
132
+ parseAssets,
133
+ projectImageDefinitions,
134
+ referenceOccurs,
135
+ restoreAssetIndex,
136
+ retireDeletedImageDefinitions,
137
+ serializeAsset
138
+ } from './assets.mjs';
139
+ export {
140
+ createMarkdownRenderer,
141
+ createPrintRenderer,
142
+ createRenderSanitizer,
143
+ createRenderStyles,
144
+ createRenderer,
145
+ exportFontCss,
146
+ fontRequests,
147
+ requireDocumentFonts,
148
+ waitForFontRequests,
149
+ waitForFonts
150
+ } from './render-entry.mjs';
151
+ export * as ledger from './ledger.mjs';
@@ -0,0 +1,61 @@
1
+ // SPDX-License-Identifier: MIT
2
+ export {};
3
+ export type Alignment = 'left' | 'center' | 'right' | 'justify';
4
+ export type Wrap = 'around' | 'beside' | 'box' | 'behind' | 'front';
5
+ export type LineSpacing = number | `${number}pt` | `${number}pt+`;
6
+ export interface TextLayout {
7
+ first?: number;
8
+ indent?: number;
9
+ marker?: number;
10
+ hyphens?: 'auto';
11
+ caps?: 'small';
12
+ hanging?: 'on';
13
+ before?: number;
14
+ after?: number;
15
+ line?: LineSpacing;
16
+ }
17
+ interface LayoutFacts extends TextLayout {
18
+ rotate?: number;
19
+ opacity?: number;
20
+ lock?: 'on';
21
+ }
22
+ type LayoutSize = {width?: number; lines?: never} | {lines?: number; width?: never};
23
+ type LayoutPosition =
24
+ | {align?: Alignment; wrap?: never; x?: never; y?: never}
25
+ | {align?: never; wrap: Wrap; x?: number; y?: number}
26
+ | {align?: never; wrap?: never; width: number; x?: number; y?: never};
27
+ /** Numeric bounds and precision are checked by validLayout and formatLayout. */
28
+ export type Layout = LayoutFacts & LayoutSize & LayoutPosition;
29
+ export type LayoutField = 'align' | 'width' | 'lines' | 'wrap' | 'x' | 'y' | 'rotate' | 'opacity' |
30
+ 'first' | 'indent' | 'marker' | 'lock' | 'hyphens' | 'caps' | 'hanging' | 'before' | 'after' | 'line';
31
+ export const fields: Set<LayoutField>;
32
+ export const SPACING_FIELDS: readonly ['before', 'after', 'line'];
33
+ export const alignments: Set<Alignment>;
34
+ export const wraps: Set<Wrap>;
35
+ export const INDENT_LIMIT: 64;
36
+ export function indentLevel(value: unknown): value is number;
37
+ export function stepIndent(value: number | null | undefined, step: number): number;
38
+ export function validLayout(value: unknown): value is Layout;
39
+ export function parseLayout(comment: string): Layout | null;
40
+ export function decodeLayoutAttribute(value: string | null | undefined): string;
41
+ export function parseLayoutAttribute(value: string | null | undefined): Layout | null;
42
+ export function textStyle(value: Layout, options?: {portable?: boolean; pointSize?: number; indentOffset?: number}): string;
43
+ export function textLayout(style: CSSStyleDeclaration | Readonly<Record<string, string>> | null | undefined,
44
+ options?: {listMarker?: boolean; pointSize?: number; offset?: number}): TextLayout;
45
+ export function imageStyle(value: Layout): string;
46
+ export function linesHeightCss(lines: number): string;
47
+ export interface ObjectSizeAttributes {
48
+ height?: number | `${number}` | 'auto';
49
+ viewport?: `${number}x${number}`;
50
+ resize?: 'scale' | 'reflow';
51
+ }
52
+ export function objectHeight(width: number, naturalHeight: number, attrs: ObjectSizeAttributes, line: number): number;
53
+ export function wrapTextBlock<T extends Element>(element: T | null | undefined): T | null;
54
+ export type WrapClassification<Stop> = string | false | null | undefined | {stop: Stop};
55
+ export function wrapNeighbour<T extends Element, Stop = T | null>(node: T,
56
+ classify: (node: Element) => WrapClassification<Stop>): Element | Stop | null;
57
+ export function wrapNeighbour<T, Stop = T | null>(node: T,
58
+ classify: (node: T) => WrapClassification<Stop>, step: (node: T, direction: 1 | -1) => T | null | undefined): T | Stop | null;
59
+ export function wrapColumnFloor(fontSize: number | null | undefined): number;
60
+ /** Throws TypeError when the supplied values do not belong to the closed v1 grammar. */
61
+ export function formatLayout(value: Layout): string;
@@ -10,13 +10,19 @@ export function documentTrailer(text: string, parts?: DocumentParts): string;
10
10
  /** Splits a saved file into its exact text and verified parts; throws on a damaged or mismatched comment. */
11
11
  export function readDocument(source: string): ReadParts;
12
12
  export function writeParts(text: string, parts?: DocumentParts): string;
13
- export function readParts(text: string, elements: unknown): {ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
13
+ /** Inert browser script elements or equivalent parsed records; no DOM dependency. */
14
+ export interface CarriedElement { id: string; type: string; textContent: string | null; tagName?: string; }
15
+ export function readParts(text: string, elements: Iterable<CarriedElement>): {ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
14
16
  export function validateParts(text: string, parts?: DocumentParts): {ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
15
- export function takeParts(html: string, text?: string): {html: string; ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
17
+ export function takeParts(html: string, text?: string | null): {html: string; ledger: LedgerEnvelope | null; authorship: AuthorshipEnvelope | null};
16
18
  export function encodeCarried(text: string): string;
17
19
  export function decodeCarried(text: string): string;
18
- export interface ComparisonBase { text: string; sha256: string; revision: string; name: string; by: string; at: string; }
19
- export function readBase(value: Partial<ComparisonBase>): ComparisonBase;
20
- export function writeBase(text: string, value: Partial<ComparisonBase>): string;
21
- export function readBaseElements(elements: unknown): ComparisonBase | null;
20
+ export interface ComparisonBase { text: string; sha256: string; revision: string; name: string; by: string; at: string; fonts?: string; }
21
+ export function readBase(value: ComparisonBase): ComparisonBase;
22
+ export function writeBase(text: string, value?: ComparisonBase | null): string;
23
+ export interface ComparisonBaseElement extends CarriedElement {
24
+ getAttribute?: (name: string) => string | null;
25
+ name?: string; sha256?: string; revision?: string; by?: string; at?: string; fonts?: string;
26
+ }
27
+ export function readBaseElements(elements: Iterable<ComparisonBaseElement>): ComparisonBase | null;
22
28
  export function takeBase(html: string): {html: string; base: ComparisonBase | null};
@@ -57,8 +57,8 @@ export function writeDocument(text, options) {
57
57
  return text + documentTrailer(text, options);
58
58
  }
59
59
  function unfinishedTrailer(source) {
60
+ if (!source.includes(OPEN)) return false;
60
61
  const at = source.lastIndexOf(OPEN);
61
- if (at < 0) return false;
62
62
  let fence = null;
63
63
  for (const line of source.slice(0, at).split('\n')) {
64
64
  const found = /^ {0,3}(`{3,}|~{3,})/.exec(line);
@@ -170,6 +170,7 @@ const expandedRows = new WeakMap();
170
170
  const sameSplices = (a, b) => a === b || a.length === b.length &&
171
171
  a.every((row, i) => row.pos === b[i].pos && row.removed === b[i].removed && row.inserted === b[i].inserted);
172
172
  const sameEffect = (a, b) => a === b || !!a && !!b && canonicalJSON(a) === canonicalJSON(b);
173
+ const RECENT_TEXT_LIMIT = 1 << 19;
173
174
  let recentLedgers = [];
174
175
  function* produceLedger({text, metadata, records, documentAuthority, revision, root, complete = false, earlier = records} = {}, native = false) {
175
176
  if (!_rapierValidMetadata(metadata)) throw fault('document metadata required');
@@ -239,9 +240,10 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
239
240
  const replayedMetadata = prepared.replayMetadata(startMetadata);
240
241
  if (!replayedMetadata || canonicalJSON(replayedMetadata) !== canonicalJSON(metadata)) throw fault('metadata does not describe this document');
241
242
  const ledger = {...payload, sha256: yield* checksum(envelopeParts(payload), native)};
242
- recentLedgers = [{rows: expanded, headText: text, headMetadata: canonicalJSON(metadata), headBytes, startText, startMetadata: {...startMetadata},
243
- startFacts, startBytes: bytes, documentAuthority, complete: payload.complete, startRoot, startRevision, chainRoot: payload.head.root,
244
- chainRevision: payload.head.revision}, ...recentLedgers.filter(row => row.rows[0] !== expanded[0] && row.documentAuthority === documentAuthority)].slice(0, 4);
243
+ const others = recentLedgers.filter(row => row.rows[0] !== expanded[0] && row.documentAuthority === documentAuthority);
244
+ recentLedgers = text.length + startText.length > RECENT_TEXT_LIMIT ? [] : [{rows: expanded, headText: text, headMetadata: canonicalJSON(metadata),
245
+ headBytes, startText, startMetadata: {...startMetadata}, startFacts, startBytes: bytes, documentAuthority, complete: payload.complete,
246
+ startRoot, startRevision, chainRoot: payload.head.root, chainRevision: payload.head.revision}, ...others].slice(0, 4);
245
247
  ownLedger(ledger);
246
248
  proofs.set(ledger, {shell: Array.from(envelopeParts({...ledger, start: {...ledger.start, text: ''}})).join(''), startText,
247
249
  text, metadata: {...metadata}, root, revision, prepared: _rapierPrepareHistoryRecords(ledger.records),
@@ -1,5 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {parseWill, stripOneTerminator} from '../../agent/will.mjs';
3
+ import {diff} from '../../packages/rapier-diff/index.mjs';
4
+ import {transposeRecord} from './transport.mjs';
5
+ import {parseComments, commentSplices, derivedCommentEdits} from '../../spec/md-comments.mjs';
3
6
 
4
7
  export function willMergeConflict(base, ours, theirs) {
5
8
  const snapshot = text => {
@@ -450,6 +453,32 @@ function textRefineGroup({before, av, bv}, options) {
450
453
  }
451
454
  return textRefineLevel(before, av, bv, options, 'word');
452
455
  }
456
+ function mergeDerivedComments(base, ours, theirs, options) {
457
+ if (!base.includes('<!-- md-comments:')) return null;
458
+ try {
459
+ const parsed = [base, ours, theirs].map(parseComments);
460
+ if (parsed.some(value => !value.record || value.reason || !value.current)) return null;
461
+ const edits = [ours, theirs].map(text => {
462
+ const changed = diff(base, text, {_unit: 'char'}).edits;
463
+ if (changed.length > 64 || changed.some(row => row.coarse)) return null;
464
+ const rows = changed.map(row => ({pos: row.before[0], removed: base.slice(...row.before),
465
+ inserted: text.slice(...row.after)})).reverse();
466
+ const proof = derivedCommentEdits(base, rows);
467
+ if (!proof) return null;
468
+ const footer = commentSplices(base, proof.edits);
469
+ return footer.length === 1 ? {splices: [...proof.edits, ...footer], derivedCommentIndex: proof.edits.length} : null;
470
+ });
471
+ if (edits.some(value => !value)) return null;
472
+ const body = mergeText(...parsed.map(value => value.body), options);
473
+ if (!body.clean) return null;
474
+ const [oursId, theirsId] = textDeviceOptions(options);
475
+ const moved = transposeRecord(base, edits[1].splices, theirsId,
476
+ [{...edits[0], transaction: {id: oursId}}], edits[1].derivedCommentIndex);
477
+ if (parseComments(moved.text).body !== body.text) return null;
478
+ return {text: moved.text, clean: true, conflicts: []};
479
+ } catch (_) { return null; }
480
+ }
481
+
453
482
  export function mergeText(base, ours, theirs, options = {}) {
454
483
  canonicalNote(ours); canonicalNote(theirs); if (base !== null) canonicalNote(base);
455
484
  const unchanged = value => ({text: value, clean: !value.includes(TEXT_CONFLICT), conflicts: value.includes(TEXT_CONFLICT) ? [{kind: 'existing-conflict', advisory: true}] : []});
@@ -457,6 +486,10 @@ export function mergeText(base, ours, theirs, options = {}) {
457
486
  const willConflict = willMergeConflict(base, ours, theirs);
458
487
  if (!willConflict && base === ours) return unchanged(theirs);
459
488
  if (!willConflict && base === theirs) return unchanged(ours);
489
+ if (!willConflict && base !== null) {
490
+ const commented = mergeDerivedComments(base, ours, theirs, options);
491
+ if (commented) return commented;
492
+ }
460
493
  const conflicts = []; let text = '', a, b;
461
494
  const conflict = (before, a, b, kind) => {
462
495
  const ab = inspectTextConflicts(a), bb = inspectTextConflicts(b);
@@ -4,6 +4,7 @@ import {projectInverseSource} from './inverse-source.mjs';
4
4
  import {authoredBasis, authoredPlacement, authoredPlacements} from './authored-origin.mjs';
5
5
  import {sha256} from './hash.mjs';
6
6
  import {canonicalJSON} from './data.mjs';
7
+ import {parseComments, commentSplices, derivedCommentEdits} from '../../spec/md-comments.mjs';
7
8
 
8
9
  const whole = text => typeof text === 'string' && !/[\uD800-\uDFFF]/u.test(text);
9
10
  function admitted(rows) {
@@ -59,7 +60,65 @@ function applyBatch(text, rows) {
59
60
  return {text: after, splices: exact};
60
61
  }
61
62
 
62
- export function transposeRecord(base, splices, id, records) {
63
+ export function transposeRecord(base, splices, id, records, derivedCommentIndex) {
64
+ const comments = transposeCommentRecord(base, splices, id, records, derivedCommentIndex);
65
+ return comments || transposeTextRecord(base, splices, id, records);
66
+ }
67
+
68
+ function commentRecordProof(source, splices, index) {
69
+ if (typeof source !== 'string' || !source.includes('<!-- md-comments:') ||
70
+ !Array.isArray(splices) || !Number.isSafeInteger(index) || index !== splices.length - 1) return null;
71
+ try {
72
+ const parsed = parseComments(source), proof = derivedCommentEdits(source, splices);
73
+ if (!parsed.record || parsed.reason || !parsed.current || !proof || proof.edits.length !== index ||
74
+ canonicalJSON(proof.edits) !== canonicalJSON(splices.slice(0, index))) return null;
75
+ let start = parsed.record.start;
76
+ for (const row of proof.edits) {
77
+ if (row.pos >= start || row.pos + row.removed.length > start) return null;
78
+ start += row.inserted.length - row.removed.length;
79
+ }
80
+ const derived = commentSplices(source, proof.edits);
81
+ if (derived.length !== 1 || canonicalJSON(derived[0]) !== canonicalJSON(splices[index])) return null;
82
+ return {parsed, edits: proof.edits, text: _rapierTransformSplices(source, splices)};
83
+ } catch (_) { return null; }
84
+ }
85
+
86
+ function transposeCommentRecord(base, splices, id, records, derivedCommentIndex) {
87
+ if (!Array.isArray(records) || !records.length) return null;
88
+ const local = commentRecordProof(base, splices, derivedCommentIndex);
89
+ if (!local) return null;
90
+ const bodyRecords = []; let current = base;
91
+ for (const record of records) {
92
+ const proof = commentRecordProof(current, record.splices, record.derivedCommentIndex);
93
+ if (!proof) return null;
94
+ bodyRecords.push({...record, splices: proof.edits}); current = proof.text;
95
+ }
96
+ const moved = transposeTextRecord(local.parsed.body, local.edits, id, bodyRecords);
97
+ const derive = (source, rows) => {
98
+ const parsed = parseComments(source); let start = parsed.record?.start;
99
+ if (!parsed.record || parsed.reason || !parsed.current) throw new RangeError('canonical_act_rebase_conflict');
100
+ for (const row of rows) {
101
+ if (row.pos >= start || row.pos + row.removed.length > start) throw new RangeError('canonical_act_rebase_conflict');
102
+ start += row.inserted.length - row.removed.length;
103
+ }
104
+ const footer = commentSplices(source, rows), splices = [...rows, ...footer];
105
+ const text = _rapierTransformSplices(source, splices), after = text === null ? null : parseComments(text);
106
+ if (!after?.record || after.reason || !after.current) throw new RangeError('canonical_act_rebase_conflict');
107
+ return {text, splices, footer};
108
+ };
109
+ const accepted = derive(current, moved.splices);
110
+ let reciprocal = local.text;
111
+ const against = moved.against.map(record => {
112
+ const value = derive(reciprocal, record.splices); reciprocal = value.text;
113
+ return {...record, splices: value.splices};
114
+ });
115
+ if (reciprocal !== accepted.text || parseComments(accepted.text).body !== moved.text)
116
+ throw new RangeError('canonical_transport_invalid');
117
+ return {text: accepted.text, splices: accepted.splices, against,
118
+ sourceIndices: [...moved.sourceIndices, ...accepted.footer.map(() => derivedCommentIndex)]};
119
+ }
120
+
121
+ function transposeTextRecord(base, splices, id, records) {
63
122
  if (typeof base !== 'string' || !Array.isArray(splices) || typeof id !== 'string' || !Array.isArray(records))
64
123
  throw new RangeError('canonical_transport_invalid');
65
124
  let source = base, current = base;
@@ -305,7 +364,7 @@ function authoredReplay(base, records, append = null) {
305
364
  throw new RangeError('canonical_origin_invalid');
306
365
  const accepted = projectInverse(text, aligned.records, id);
307
366
  moved = {text: accepted.after, splices: accepted.splices};
308
- } else moved = transposeRecord(basis.text, origin.splices, id, crossed);
367
+ } else moved = transposeRecord(basis.text, origin.splices, id, crossed, original.derivedCommentIndex);
309
368
  if (_rapierTransformSplices(text, moved.splices) !== moved.text) throw new RangeError('canonical_origin_invalid');
310
369
  placed.push({...original, splices: moved.splices}); text = moved.text; ids.add(id);
311
370
  if (original.derivedCommentIndex != null) sourceIndices.set(id, moved.sourceIndices);
@@ -2,17 +2,32 @@
2
2
  // rapier-markdown-kit/rapier-ledger: a document's edit history with authorship, Undo of any act and offline merge.
3
3
  // Offsets count UTF-16 code units; splices run in their recorded order.
4
4
  export type ActorKind = 'human' | 'agent' | 'system';
5
- export interface Actor { kind: ActorKind; id: string; }
5
+ export interface Actor { kind: ActorKind; id: string; name?: string; }
6
6
  export type DocKind = 'markdown' | 'text' | 'code';
7
7
  export interface Metadata { filename: string; docKind: DocKind; }
8
+ export interface MetadataEffect {
9
+ filename?: {before: string; after: string};
10
+ docKind?: {before: DocKind; after: DocKind};
11
+ }
12
+ export type LedgerData = null | boolean | number | string | LedgerData[] | {[key: string]: LedgerData};
8
13
  export interface Splice { pos: number; removed: string; inserted: string; }
9
14
  export interface Transaction {
10
15
  id: string; documentAuthority: string; baseRevision: number; revision: number; actor: Actor;
11
- transport: string; operation: string; requestId: string | null; sourceTransactionId: string | null;
12
- affectedBlockIds: string[]; parent: string | null; reverts: string | null; reapplies: string | null;
13
- createdAt: string | null; turnId?: string; sourceTransactionIds?: string[];
16
+ transport: 'platform' | 'webmcp'; operation: string; requestId: string | null; sourceTransactionId: string | null;
17
+ affectedBlockIds: number[]; parent: string | null; reverts: string | null; reapplies: string | null;
18
+ createdAt: number | null; turnId?: string | null; sourceTransactionIds?: string[];
19
+ label?: string | null; turnLabel?: string | null; turnBaseRevision?: number;
20
+ contribution?: string; contributionBaseRevision?: number; remoteTransactionId?: string;
21
+ }
22
+ export interface AuthoredPlacement { basis: string[]; source: string; splices: Splice[]; }
23
+ /** A retained journal may omit navigation splices: the target's exact rows supply them. */
24
+ export interface JournalRecord {
25
+ transaction: Transaction; splices?: Splice[]; beforeHash: string; afterHash: string;
26
+ metadata?: MetadataEffect; authored?: AuthoredPlacement; derivedCommentIndex?: number;
27
+ changeSet?: {[key: string]: LedgerData};
14
28
  }
15
- export interface LedgerRecord { transaction: Transaction; splices: Splice[]; beforeHash: string; afterHash: string; metadata?: Record<string, unknown>; authored?: unknown; }
29
+ /** An exported envelope expands every navigation record to exact splices. */
30
+ export interface LedgerRecord extends JournalRecord { splices: Splice[]; }
16
31
  /** rapier-ledger/2: plain JSON. */
17
32
  export interface LedgerEnvelope {
18
33
  format: 'rapier-ledger/2';
@@ -24,9 +39,9 @@ export interface LedgerEnvelope {
24
39
  sha256: string;
25
40
  }
26
41
  /** Runs of [start, end, actor kind, actor id, createdAt] over the current text. */
27
- export interface AuthorshipEnvelope { format: 'rapier-authorship/1'; sha256: string; runs: [number, number, ActorKind, string, string | number | null][]; }
42
+ export interface AuthorshipEnvelope { format: 'rapier-authorship/1'; sha256: string; runs: [number, number, ActorKind, string, number | null][]; }
28
43
  export interface ReadLedger { ledger: LedgerEnvelope; text: string; metadata: Metadata; root: string; revision: number; complete: boolean; }
29
- export interface LedgerInput { text: string; metadata: Metadata; records: LedgerRecord[]; documentAuthority?: string; revision?: number; root?: string; complete?: boolean; }
44
+ export interface LedgerInput { text: string; metadata: Metadata; records: JournalRecord[]; earlier?: JournalRecord[]; documentAuthority?: string; revision?: number; root?: string; complete?: boolean; }
30
45
 
31
46
  /** A new ledger with no history. Give every copy that may later merge the same documentAuthority. */
32
47
  export function openLedger(text: string, options?: {filename?: string; docKind?: DocKind; documentAuthority?: string}): LedgerEnvelope;
@@ -34,20 +49,31 @@ export function exportLedger(input: LedgerInput): LedgerEnvelope;
34
49
  /** Verifies the envelope (and, when given, that it describes this text and metadata); throws when it does not. */
35
50
  export function readLedger(input: LedgerEnvelope, expectedText?: string, expectedMetadata?: Metadata): ReadLedger;
36
51
  /** Records one replacement of the whole text as an act by `actor`. */
37
- export function replaceLedgerText(input: LedgerEnvelope, text: string, options?: {actor?: Actor; operation?: string; at?: string}): LedgerEnvelope;
52
+ export function replaceLedgerText(input: LedgerEnvelope, text: string, options?: {actor?: Actor; operation?: string; at?: number | null}): LedgerEnvelope;
38
53
  export function ledgerWithStart(input: LedgerEnvelope, text: string): LedgerEnvelope;
39
- export function historyEnvelope(input: LedgerEnvelope, segmentIdentity?: unknown[], expectedText?: string, expectedMetadata?: Metadata): Record<string, unknown>;
54
+ export interface HistoryEnvelope<Segment = unknown> {
55
+ schemaVersion: 5; documentAuthority: string; documentRevision: number;
56
+ ledger: LedgerRecord[]; branch: string[]; cursor: number; earliestRevision: number;
57
+ earliestHash: string; sourceRootId: string; earliestMetadata: Metadata; documentMetadata: Metadata;
58
+ segmentIdentity: Segment[]; historyComplete: boolean; trimReason: '' | 'carried_start'; trimmedBytes: 0;
59
+ }
60
+ export function historyEnvelope<Segment = unknown>(input: LedgerEnvelope, segmentIdentity?: Segment[], expectedText?: string, expectedMetadata?: Metadata): HistoryEnvelope<Segment>;
40
61
  /** The actor id an agent writes under, for a door and an agent name. */
41
- export function agentActorId(door: string, agent: string): string;
62
+ export function agentActorId(door: string, agent?: {name: string} | null): string;
42
63
  export function textRoot(text: string): string;
43
64
  export function rootAfter(root: string, splice: Splice): string;
44
- export function sha256State(): {update(text: string): unknown; digest(): string};
65
+ /** Feed bytes, then call finish once. `bytes` counts the input supplied so far. */
66
+ export function sha256State(): {update(input: Uint8Array): void; finish(): string; readonly bytes: number};
45
67
 
46
68
  export function authorship(input: LedgerEnvelope): AuthorshipEnvelope;
47
69
  export function readAuthorship(input: AuthorshipEnvelope, text: string): AuthorshipEnvelope;
48
70
  export function ledgerFromAuthorship(input: AuthorshipEnvelope, text: string, documentAuthority: string, metadata: Metadata): LedgerEnvelope;
49
71
 
50
- export interface MergeConflict { kind: string; code?: string; actId?: string; [key: string]: unknown; }
72
+ export type MergeConflict =
73
+ | {kind: 'identity'; code: 'act_identity_conflict' | 'act_order_conflict'; actId?: string}
74
+ | {kind: 'history'; code: 'record_transport_conflict'}
75
+ | {kind: 'metadata'; code: 'metadata_assignment_conflict' | 'inverse_target_unavailable' | 'record_transport_conflict'; fields?: (keyof Metadata)[]}
76
+ | (TextMergeConflict & {code: 'text_overlap_unpreserved'});
51
77
  export interface MergeResult {
52
78
  text: string; metadata: Metadata; ledger: LedgerEnvelope;
53
79
  /** False when a person must review: conflicts, or Will keep regions that differ. */
@@ -60,29 +86,109 @@ export function merge(first: LedgerEnvelope, second: LedgerEnvelope): MergeResul
60
86
  export function transportInterval(start: number, end: number, splices: Splice[]): {start: number; end: number} | null;
61
87
  export function transportTouchedInterval(start: number, end: number, splices: Splice[]): {start: number; end: number};
62
88
 
63
- export interface HistoryInput { source: string; metadata: Metadata; records: LedgerRecord[]; revision: number; earliestRevision: number; complete: boolean; }
64
- export interface HistoryFailure { ok?: false; code: string; [key: string]: unknown; }
89
+ export type HistoryTransaction = Pick<Transaction, 'id' | 'baseRevision' | 'revision' | 'actor' | 'createdAt'> & Partial<Transaction>;
90
+ export interface HistoryRecord {
91
+ transaction: HistoryTransaction; splices?: Splice[]; beforeHash?: string; afterHash?: string;
92
+ metadata?: MetadataEffect; authored?: AuthoredPlacement; derivedCommentIndex?: number;
93
+ changeSet?: {[key: string]: LedgerData};
94
+ }
95
+ export interface HistoryInput {
96
+ source: string; metadata: Metadata; records: HistoryRecord[];
97
+ revision?: number; earliestRevision?: number; earliestRoot?: string; complete?: boolean;
98
+ }
99
+ export interface HistoryFailure {
100
+ ok: false;
101
+ reason: 'history_invalid' | 'history_unavailable' | 'range_invalid' | 'act_unavailable' | 'act_invalid' | 'act_forbidden' | 'will_refused';
102
+ }
103
+ export interface HistoryAct {
104
+ id: string; actor: Actor; createdAt: number | null; baseRevision: number; revision: number; operation: string;
105
+ turnId?: string; contribution?: string; sourceTransactionId?: string; sourceTransactionIds?: string[];
106
+ reverts?: string; reapplies?: string; label?: LedgerData; affectedBlockIds?: number[];
107
+ metadata?: MetadataEffect; active?: boolean;
108
+ }
109
+ export interface HistoryGroup { id: string; actor: Actor; actIds: string[]; createdAt: number | null; turnId?: string; }
110
+ export interface HistorySnapshot { source: string; revision: number; earliestRevision: number; complete: boolean; }
111
+ export interface HistoryReplay extends HistorySnapshot {
112
+ ok: true; initialSource: string; metadata: Metadata; initialMetadata: Metadata;
113
+ acts: HistoryAct[]; effectiveActIds: string[]; groups: HistoryGroup[];
114
+ }
115
+ export interface HistorySourceBefore extends HistorySnapshot { ok: true; metadata: Metadata; act: HistoryAct; }
116
+ export interface HistoryRange { from?: number; to?: number; actId?: string; }
117
+ export interface HistorySourceRange { from: number; to: number; source: string; }
118
+ export interface ProjectedHistoryAct extends HistoryAct { before: HistorySourceRange; after: HistorySourceRange; }
119
+ export interface HistoryProjection extends HistorySnapshot, HistorySourceRange {
120
+ ok: true; acts: ProjectedHistoryAct[]; groups: HistoryGroup[]; before?: HistorySourceRange;
121
+ }
122
+ export interface HistoryBlock<Id = unknown> { id: Id; from: number; to: number; }
123
+ export interface HistoryPlaces<Id = unknown> extends HistorySnapshot {
124
+ ok: true; initialSource: string; acts: HistoryAct[];
125
+ blocks: (HistoryBlock<Id> & {source: string; acts: ProjectedHistoryAct[]})[]; reversed: string[];
126
+ }
127
+ export interface UndoHunk {
128
+ actId: string; spliceIndex: number; hunkIndex: number; splice: Splice;
129
+ beforeSource: string; actSource: string; priorSplices: Splice[];
130
+ }
131
+ export interface SelectiveUndoOptions {
132
+ actor?: Actor;
133
+ /** Retain this exact hunk while reversing the other selected hunks. */
134
+ keepHunk?: (hunk: UndoHunk) => boolean;
135
+ /** Refinements must replay to the same exact text as the supplied splice. */
136
+ splitSplice?: (input: {actId: string; beforeSource: string; splice: Splice}) => Splice[];
137
+ }
138
+ export interface SupersededHunk { actId: string; hunk: string; laterActIds: string[]; reason: 'later_edit' | 'already_undone'; }
139
+ export interface SelectiveUndo {
140
+ ok: true; splices: Splice[]; after: string; actIds: string[]; sourceTransactionIds: string[];
141
+ metadata: MetadataEffect | null; afterMetadata: Metadata; effectChanged: boolean; unchanged: boolean;
142
+ superseded: SupersededHunk[]; overlaps: SupersededHunk[];
143
+ }
65
144
  /** Reverses chosen acts while later work stays. */
66
- export function selectiveUndo(input: HistoryInput, actIds: string[], options?: {actor?: Actor; splitSplice?: (...args: unknown[]) => unknown}): Record<string, unknown> | HistoryFailure;
67
- export function replayHistory(input: HistoryInput): unknown;
68
- export function sourceBefore(input: HistoryInput, actId: string): unknown;
69
- export function historyProjection(input: HistoryInput, range?: {from?: number; to?: number; actId?: string}): unknown;
70
- export function historyProjections(input: HistoryInput, ranges: unknown[]): unknown;
71
- export function historyPlaces(input: HistoryInput, blocks: unknown[]): unknown;
72
- export function groupHistoryActs(acts: unknown[]): unknown;
73
- export function pictureHistorySplices(input: unknown, target: unknown, redo: boolean): Splice[] | null;
74
- export function transposeSource(base: string, splices: Splice[], client: unknown, log: unknown): unknown;
75
- export function authoredBasis(...args: unknown[]): unknown;
76
- export function authoredPlacement(...args: unknown[]): unknown;
77
- export function authoredHistory(base: string, records: LedgerRecord[], selected?: unknown): unknown;
78
- export function transposeAuthored(base: string, records: LedgerRecord[], incoming: unknown, submitted: unknown): unknown;
145
+ export function selectiveUndo(input: HistoryInput, actIds: string[], options?: SelectiveUndoOptions): SelectiveUndo | HistoryFailure;
146
+ export function replayHistory(input: HistoryInput): HistoryReplay | HistoryFailure;
147
+ export function sourceBefore(input: HistoryInput, actId: string): HistorySourceBefore | HistoryFailure;
148
+ export function historyProjection(input: HistoryInput, range?: HistoryRange): HistoryProjection | HistoryFailure;
149
+ export function historyProjections(input: HistoryInput, ranges: (HistoryRange | undefined)[]): {ok: true; projections: (HistoryProjection | HistoryFailure)[]} | HistoryFailure;
150
+ export function historyPlaces<Id>(input: HistoryInput, blocks: HistoryBlock<Id>[]): HistoryPlaces<Id> | HistoryFailure;
151
+ export function groupHistoryActs(acts: Pick<HistoryAct, 'id' | 'actor' | 'createdAt' | 'turnId'>[]): HistoryGroup[];
152
+ export function pictureHistorySplices(input: Pick<HistoryInput, 'source' | 'records'>, target: HistoryRecord, redo: boolean): Splice[] | null;
153
+ /** Canonical input splices use `pos/removed/inserted`; the host's reciprocal log uses `at/remove/insert`. */
154
+ export interface SourceTransportEntry { client: string; splices: {at: number; remove: number; insert: string}[]; }
155
+ export interface AuthoredRecord {
156
+ transaction: Pick<Transaction, 'id'> & Partial<Transaction>; splices: Splice[];
157
+ authored?: AuthoredPlacement; metadata?: MetadataEffect; derivedCommentIndex?: number;
158
+ }
159
+ export interface SourceTransposition {
160
+ text: string; splices: Splice[]; sourceIndices: number[];
161
+ against: {transaction: {id: string}; splices: Splice[]}[];
162
+ }
163
+ export interface AuthoredHistory<Record extends AuthoredRecord = AuthoredRecord> {
164
+ text: string; records: Record[]; origins: Map<string, AuthoredPlacement>;
165
+ }
166
+ export interface ProjectedAuthoredHistory<Record extends AuthoredRecord = AuthoredRecord> extends AuthoredHistory<Record> {
167
+ sourceIndices: Map<string, number[]>;
168
+ }
169
+ export interface AuthoredTransposition<Record extends AuthoredRecord = AuthoredRecord> {
170
+ text: string; splices: Splice[]; submittedBefore: string; submitted: string; against: Record[];
171
+ }
172
+ export function transposeSource(base: string, splices: Splice[], client: string, log: SourceTransportEntry[]): SourceTransposition;
173
+ export function authoredBasis(records: Pick<AuthoredRecord, 'transaction' | 'authored'>[]): string[];
174
+ export function authoredPlacement(source: string, splices: Splice[], basis: string[]): AuthoredPlacement;
175
+ export function authoredHistory<Record extends AuthoredRecord>(base: string, records: Record[], selected: ReadonlySet<string>): ProjectedAuthoredHistory<Record>;
176
+ export function authoredHistory<Record extends AuthoredRecord>(base: string, records: Record[], selected?: null): AuthoredHistory<Record>;
177
+ export function authoredHistory<Record extends AuthoredRecord>(base: string, records: Record[], selected?: ReadonlySet<string> | null): AuthoredHistory<Record> | ProjectedAuthoredHistory<Record>;
178
+ export function transposeAuthored<Record extends AuthoredRecord>(base: string, records: Record[], incoming: AuthoredRecord & {authored: AuthoredPlacement}, submitted: string[]): AuthoredTransposition<Record>;
79
179
 
80
180
  export {writeDocument, readDocument, writeParts, readParts} from './ledger/carried.mjs';
81
181
 
82
- export interface TextConflict { kind: 'text'; id: string; start: number; end: number; block: string; variants: string[]; }
83
- export interface TextMerge { text: string; clean: boolean; conflicts: (TextConflict | {kind: string; advisory?: boolean})[]; }
182
+ export interface TextConflictVariant { device: string; text: string; }
183
+ export interface TextConflictDescriptor { start: number; end: number; block: string; variants: TextConflictVariant[]; }
184
+ export interface TextConflict extends TextConflictDescriptor { kind: 'text'; id: string; }
185
+ export type TextMergeConflict =
186
+ | (TextConflictDescriptor & {kind: 'text' | 'no-ancestor' | 'will' | 'document-bindings' | 'ambiguous-alignment-or-budget'; base: string | null})
187
+ | {kind: 'existing-conflict'; advisory: true};
188
+ export interface TextMerge { text: string; clean: boolean; conflicts: TextMergeConflict[]; }
189
+ export interface TextMergeOptions { oursId: string; theirsId: string; }
84
190
  /** Exact three-way text merge; a region both sides changed becomes a marked choice in the text. */
85
- export function mergeText(base: string | null, ours: string, theirs: string, options?: Record<string, unknown>): TextMerge;
191
+ export function mergeText(base: string | null, ours: string, theirs: string, options?: TextMergeOptions): TextMerge;
86
192
  export function inspectTextConflicts(text: string, options?: {nested?: boolean}): TextConflict[];
87
- export function resolveTextConflict(text: string, conflict: TextConflict | string, device: unknown): string;
193
+ export function resolveTextConflict(text: string, conflict: TextConflictDescriptor, device: string | number): string;
88
194
  export function mapTextConflictVariants(text: string, mapper: (variant: string) => string): string;
@@ -1,4 +1,4 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // md.use(rapierMarkdown): Self-contained Markdown in markdown-it, with the reference style's markup.
3
- interface MarkdownItLike { use(plugin: (md: any, ...params: any[]) => unknown, ...params: any[]): any; renderer: {rules: Record<string, unknown>}; core: {ruler: {after(after: string, name: string, rule: (state: any) => unknown): void}}; }
4
- export function rapierMarkdown<T extends MarkdownItLike>(md: T): T;
3
+ import type {MarkdownParser} from './marks.mjs';
4
+ export function rapierMarkdown<T extends MarkdownParser>(md: T): T;