@dev.fast/whiteboard 0.3.4 → 0.3.5-preview.20261021.131

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 (120) hide show
  1. package/dist/{account-alias-CprZojXd.js → account-alias-CisiYUZT.js} +13 -8
  2. package/dist/agent-cli-BglNRDZN.js +2 -0
  3. package/dist/{agent-cli-CThN7Q59.js → agent-cli-TJ7_5_B3.js} +15 -10
  4. package/dist/{agent-client-BOOi7EUR.js → agent-client-H53ajvrm.js} +13 -3
  5. package/dist/{authoring-tools-BNwuMnz6.js → authoring-tools-BCquUsB3.js} +34 -27
  6. package/dist/{background-server-DLSDogkI.js → background-server-BSYXD9au.js} +1 -1
  7. package/dist/{background-server-CkvxA-6O.js → background-server-Xom8cA_u.js} +5 -5
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-R4yfa5DT.js → cli-BOnV4cCh.js} +1 -1
  10. package/dist/{cli-runner-Cuc9FSzu.js → cli-runner-cRY5NOno.js} +70 -34
  11. package/dist/cli.js +4 -3
  12. package/dist/{client-BN_FDxFr.js → client-UH1bEG7Z.js} +260 -49
  13. package/dist/{fuzzy-match-BO9bK37N.js → fuzzy-match-D_x3wumC.js} +6 -4
  14. package/dist/{headless-host-CVBOQyRS.js → headless-host-BEe-aFtB.js} +1 -1
  15. package/dist/{headless-host-Cog6SpS9.js → headless-host-CEb5d-A0.js} +563 -175
  16. package/dist/lens-selection-BxPk53c8.js +214 -0
  17. package/dist/{local-data-D_9tvdsp.js → local-data-mbCb1HDH.js} +2448 -347
  18. package/dist/{mcp-D1hziANa.js → mcp-cBtzXsmN.js} +9 -12
  19. package/dist/{package-paths-C--__jEv.js → package-paths-BkeQsdaw.js} +1 -1
  20. package/dist/profile-Dbyidrcr.js +2 -0
  21. package/dist/{profile-DokFw782.js → profile-duqp5uPa.js} +20 -8
  22. package/dist/{remote-attach-CEJ-kvDY.js → remote-attach-BeKuMm_x.js} +4 -4
  23. package/dist/{remote-extensions-GN8Gf_qG.js → remote-extensions-CX2fAec1.js} +1 -1
  24. package/dist/{remote-extensions-Blcjr9Ze.js → remote-extensions-CYWNTVSf.js} +3 -3
  25. package/dist/{remote-language-server-BTKBQ7x4.js → remote-language-server-DLWluAeR.js} +5 -5
  26. package/dist/{remote-uninstall-DW7Gdryh.js → remote-uninstall-Y3S0YmJs.js} +6 -6
  27. package/dist/{request-origin-Zcn_CjX4.js → request-origin-icXPv6jh.js} +55 -18
  28. package/dist/{review-app-launcher-CkwLclzR.js → review-app-launcher-DZvG5ZAh.js} +2 -2
  29. package/dist/{review-home-paths-j2BRAaNv.js → review-home-paths-BwH08ihj.js} +19 -17
  30. package/dist/{review-telemetry-BLNwYnLV.js → review-telemetry-AZ0imsmJ.js} +6 -6
  31. package/dist/runtime-CF4JXdb8.js +27 -0
  32. package/dist/runtime.d.ts +1 -1
  33. package/dist/runtime.js +1 -1
  34. package/dist/{s3-DtJFWC0G.js → s3-CsG2EWED.js} +1 -1
  35. package/dist/{s3-config-BTO12tCC.js → s3-config-CjcLyFV5.js} +4 -2
  36. package/dist/{s3-config-CSy3fx8H.js → s3-config-zz4GWh2T.js} +1 -1
  37. package/dist/server/desktop-host.js +28 -104
  38. package/dist/{server-discovery-Bey8IqXH.js → server-discovery-DeVTdcqK.js} +3 -3
  39. package/dist/sharing/index.d.ts +484 -74
  40. package/dist/sharing/index.js +1 -1
  41. package/dist/{src-DsM3jO8G.js → src-DYZLCUP_.js} +98 -18
  42. package/dist/{src-BMPuxWv4.js → src-DtfSndqS.js} +269 -227
  43. package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-B29lve9-.js} +318 -271
  44. package/dist/{thread-state-5pYr6_zM.js → thread-state-BPpvKFwQ.js} +12 -2
  45. package/dist/{tool-failure-C9MpbXJJ.js → tool-failure-CQhyLht3.js} +10 -3
  46. package/instructions/authoring.md +20 -5
  47. package/package.json +4 -4
  48. package/src/agent-selection.ts +6 -0
  49. package/src/ask/agents.ts +13 -1
  50. package/src/ask/checkout-files.ts +2 -5
  51. package/src/ask/thread-state.ts +4 -0
  52. package/src/ask/thread.ts +61 -31
  53. package/src/ask/threads.ts +24 -3
  54. package/src/cli-install.ts +13 -2
  55. package/src/cli-runner.ts +4 -1
  56. package/src/cli.ts +4 -1
  57. package/src/diff-selection-migration.ts +48 -34
  58. package/src/heading-anchors.ts +42 -0
  59. package/src/lens-selection.ts +38 -2
  60. package/src/markdown.ts +42 -0
  61. package/src/posthog-capture-client.ts +1 -1
  62. package/src/review-api/README.md +52 -13
  63. package/src/review-api/activity.ts +1 -4
  64. package/src/review-api/agent-cli.ts +9 -1
  65. package/src/review-api/anchor-quotes.ts +14 -7
  66. package/src/review-api/ask-history.ts +118 -51
  67. package/src/review-api/authoring-tools.ts +45 -30
  68. package/src/review-api/azure-devops-provider.ts +2 -20
  69. package/src/review-api/azure-remote.ts +21 -0
  70. package/src/review-api/blocks/definition.ts +1 -1
  71. package/src/review-api/blocks/markdown.ts +2 -2
  72. package/src/review-api/board-order.ts +144 -0
  73. package/src/review-api/checkout-groups.ts +586 -0
  74. package/src/review-api/document-headings.ts +58 -27
  75. package/src/review-api/document-text.ts +31 -2
  76. package/src/review-api/document.ts +362 -67
  77. package/src/review-api/foreign-references.ts +328 -0
  78. package/src/review-api/github-provider.ts +51 -13
  79. package/src/review-api/http.ts +734 -135
  80. package/src/review-api/instructions.ts +0 -19
  81. package/src/review-api/link-preview.ts +113 -0
  82. package/src/review-api/local-data.ts +310 -46
  83. package/src/review-api/mcp.ts +7 -13
  84. package/src/review-api/onboarding.ts +1 -1
  85. package/src/review-api/profile.ts +37 -6
  86. package/src/review-api/public-tools.ts +24 -2
  87. package/src/review-api/pull-request.ts +82 -49
  88. package/src/review-api/request-origin.ts +10 -0
  89. package/src/review-api/review-progress.ts +61 -27
  90. package/src/review-api/scratchpad-plan-upgrade.ts +250 -0
  91. package/src/review-api/store-migrations.ts +355 -0
  92. package/src/review-api/store-schema.ts +38 -29
  93. package/src/review-api/store.ts +2176 -495
  94. package/src/review-api/worktree-events.ts +3 -5
  95. package/src/review-api/worktree-source.ts +3 -7
  96. package/src/review-api/worktree-structural.integration.ts +72 -0
  97. package/src/review-stack.ts +15 -23
  98. package/src/runtime.ts +4 -30
  99. package/src/server/desktop-host.ts +6 -1
  100. package/src/server/desktop-server.ts +1 -36
  101. package/src/server/headless-host.ts +0 -2
  102. package/src/server/hono-http.ts +2 -1
  103. package/src/server/review-gateway-list.ts +3 -5
  104. package/src/server/review-gateway-memory.ts +1 -1
  105. package/src/server/review-lifecycle-telemetry.ts +2 -2
  106. package/src/server/review-server-core.ts +1 -5
  107. package/src/server/structural-comparisons.ts +47 -0
  108. package/src/sharing/export.ts +36 -7
  109. package/src/sharing/host.ts +34 -14
  110. package/src/sharing/import.ts +156 -24
  111. package/src/sharing/repository.ts +143 -24
  112. package/src/startup-trace.ts +3 -3
  113. package/src/stored-document-migration.ts +110 -19
  114. package/src/ui-telemetry-events.ts +44 -18
  115. package/dist/agent-cli-C6SPSII0.js +0 -2
  116. package/dist/profile-CYPmvBoA.js +0 -2
  117. package/dist/runtime-CKiWhGrf.js +0 -52
  118. package/instructions/scratchpad.md +0 -23
  119. package/src/review-preferences.ts +0 -88
  120. package/src/server/legacy-tutorial-cleanup.ts +0 -38
@@ -1,196 +1,12 @@
1
- import { f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
2
- import "./src-DsM3jO8G.js";
1
+ import { d as isBooleanValue, f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject, t as isJsonArray } from "./src-X9phtB2j.js";
2
+ import "./src-DYZLCUP_.js";
3
+ import { a as commitLinkTarget, c as parseAnchor, d as selectionProblem, f as sourceAnchors, i as anchorSelection, r as anchorSchema, s as formatAnchor } from "./lens-selection-BxPk53c8.js";
3
4
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
5
  import { z } from "zod";
5
6
  import { fromMarkdown } from "mdast-util-from-markdown";
6
7
  import { gfmFromMarkdown } from "mdast-util-gfm";
7
8
  import { mathFromMarkdown } from "mdast-util-math";
8
9
  import { gfm } from "micromark-extension-gfm";
9
- //#region src/viewed-coverage.ts
10
- const coverageSchema = z.object({
11
- base: z.array(z.tuple([z.number().int().nonnegative(), z.number().int().nonnegative()])),
12
- head: z.array(z.tuple([z.number().int().nonnegative(), z.number().int().nonnegative()]))
13
- });
14
- const emptyCoverage = () => ({
15
- base: [],
16
- head: []
17
- });
18
- function unionIntervals(ranges) {
19
- const result = [];
20
- for (const [start, end] of [...ranges].sort((a, b) => a[0] - b[0])) {
21
- if (end <= start) continue;
22
- const last = result.at(-1);
23
- if (last && start <= last[1]) last[1] = Math.max(last[1], end);
24
- else result.push([start, end]);
25
- }
26
- return result;
27
- }
28
- function intersectIntervals(left, right) {
29
- const result = [];
30
- const a = unionIntervals(left), b = unionIntervals(right);
31
- let i = 0, j = 0;
32
- while (i < a.length && j < b.length) {
33
- const start = Math.max(a[i][0], b[j][0]), end = Math.min(a[i][1], b[j][1]);
34
- if (start < end) result.push([start, end]);
35
- if (a[i][1] < b[j][1]) i++;
36
- else j++;
37
- }
38
- return result;
39
- }
40
- function subtractIntervals(left, right) {
41
- const result = [];
42
- const removed = unionIntervals(right);
43
- for (const [start, end] of unionIntervals(left)) {
44
- let cursor = start;
45
- for (const [from, to] of removed) {
46
- if (to <= cursor) continue;
47
- if (from >= end) break;
48
- if (from > cursor) result.push([cursor, from]);
49
- cursor = Math.min(end, Math.max(cursor, to));
50
- }
51
- if (cursor < end) result.push([cursor, end]);
52
- }
53
- return result;
54
- }
55
- function updateCoverage(current, scope, viewed) {
56
- const apply = (side) => viewed ? unionIntervals([...current[side], ...scope[side]]) : subtractIntervals(current[side], scope[side]);
57
- return {
58
- base: apply("base"),
59
- head: apply("head")
60
- };
61
- }
62
- function scopedCoverage(file, sources) {
63
- if (!sources) return file.changed;
64
- const side = (side) => intersectIntervals(file.changed[side], sources.flatMap((source) => source.side === side && source.file === (side === "base" ? file.previousPath ?? file.path : file.path) ? [[source.fromLine - 1, source.toLine]] : []));
65
- return {
66
- base: side("base"),
67
- head: side("head")
68
- };
69
- }
70
- function coverageSources(file, coverage = file.viewed) {
71
- return ["base", "head"].flatMap((side) => coverage[side].map(([start, end]) => ({
72
- side,
73
- file: side === "base" ? file.previousPath ?? file.path : file.path,
74
- fromLine: start + 1,
75
- toLine: end
76
- })));
77
- }
78
- //#endregion
79
- //#region src/lens-selection.ts
80
- const ANCHOR_FORMS = "\"head/path#L10-L24\" or \"base/path#L7\" (one side, lines 10–24 or line 7); across sides, GitHub diff style \"diff/path#L84-R90\" (L = base line, R = head line)";
81
- const sameSideAnchor = /^(head|base)\/(.+)#L(\d+)(?:-L(\d+))?$/;
82
- const crossSideAnchor = /^diff\/(.+)#([LR])(\d+)-([LR])(\d+)$/;
83
- /** An anchor's selection, as markdown source links write it too (without
84
- * their `review-source:` scheme). Undefined when it isn't one. */
85
- function parseAnchor(text) {
86
- const same = sameSideAnchor.exec(text);
87
- if (same) {
88
- const side = same[1] === "base" ? "base" : "head";
89
- return {
90
- file: same[2],
91
- start: {
92
- side,
93
- line: Number(same[3])
94
- },
95
- end: {
96
- side,
97
- line: Number(same[4] ?? same[3])
98
- }
99
- };
100
- }
101
- const cross = crossSideAnchor.exec(text);
102
- if (!cross || cross[2] === cross[4]) return void 0;
103
- const side = (letter) => letter === "L" ? "base" : "head";
104
- return {
105
- file: cross[1],
106
- start: {
107
- side: side(cross[2]),
108
- line: Number(cross[3])
109
- },
110
- end: {
111
- side: side(cross[4]),
112
- line: Number(cross[5])
113
- }
114
- };
115
- }
116
- /** Why a parsed selection can't be quoted, or undefined when it can. */
117
- function selectionProblem(selection) {
118
- if (selection.start.line < 1 || selection.end.line < 1) return "Source lines count from 1.";
119
- if (selection.start.side === selection.end.side && selection.start.line > selection.end.line) return "Source range ends before it starts.";
120
- if (selection.pins && selection.pins.base === void 0 && (selection.start.side === "base" || selection.end.side === "base")) return "A base-side anchor needs base pins.";
121
- }
122
- const anchorSchema = z.string({ error: `Expected a source anchor: ${ANCHOR_FORMS}.` }).superRefine((text, context) => {
123
- const selection = parseAnchor(text);
124
- const problem = selection ? selectionProblem(selection) : `Use ${ANCHOR_FORMS} for a source anchor.`;
125
- if (problem) context.addIssue({
126
- code: "custom",
127
- message: problem
128
- });
129
- }).describe("head/path#L10-L24, base/path#L7, or diff/path#L84-R90");
130
- /** A stored anchor's selection, read at the pins of whatever holds it. */
131
- function anchorSelection(anchor, pins) {
132
- const selection = parseAnchor(anchor);
133
- if (!selection) throw new Error(`Not a source anchor: ${anchor}`);
134
- return pins ? {
135
- ...selection,
136
- pins
137
- } : selection;
138
- }
139
- /** The anchor that writes a selection; its pins belong to its holder. */
140
- function formatAnchor(selection) {
141
- const { file, start, end } = selection;
142
- if (start.side === end.side) return start.line === end.line ? `${start.side}/${file}#L${start.line}` : `${start.side}/${file}#L${start.line}-L${end.line}`;
143
- const letter = (side) => side === "base" ? "L" : "R";
144
- return `diff/${file}#${letter(start.side)}${start.line}-${letter(end.side)}${end.line}`;
145
- }
146
- /** Pins-less selections keep the key they always had. */
147
- function selectionKey(source) {
148
- return JSON.stringify([
149
- source.file,
150
- source.start.side,
151
- source.start.line,
152
- source.end.side,
153
- source.end.line,
154
- ...source.pins ? [sourcePinsKey(source.pins)] : []
155
- ]);
156
- }
157
- function sourcePinsKey(pins) {
158
- return `${pins.repositoryId}:${pins.base ?? ""}:${pins.head}`;
159
- }
160
- /** The comparison a source's pins name: head alone means head against
161
- * itself. Reference coverage is grouped under this key on both sides. */
162
- function comparisonKey(pins) {
163
- return `${pins.repositoryId}:${pins.base ?? pins.head}:${pins.head}`;
164
- }
165
- /** Navigation/quote anchors only; never use these as lens coverage. */
166
- function sourceAnchors(source) {
167
- return ["head", "base"].flatMap((side) => {
168
- const lines = [source.start, source.end].flatMap((endpoint) => endpoint.side === side ? [endpoint.line] : []);
169
- if (!lines.length) return [];
170
- const anchor = {
171
- file: source.file,
172
- side,
173
- fromLine: Math.min(...lines),
174
- toLine: Math.max(...lines)
175
- };
176
- if (source.pins) anchor.pins = source.pins;
177
- return [anchor];
178
- });
179
- }
180
- /** Resolve once against the renderer's rows. No matching or context expansion. */
181
- function resolveDiffSelection(source, rows, file) {
182
- const locate = (endpoint) => rows.findIndex((row) => row[endpoint.side === "base" ? 0 : 1] === endpoint.line - 1);
183
- const start = locate(source.start);
184
- const end = locate(source.end);
185
- if (start < 0 || end < start) throw new Error("Lens endpoints do not identify an ordered interval in this diff.");
186
- const selected = rows.slice(start, end + 1);
187
- return ["base", "head"].flatMap((side, index) => unionIntervals(selected.flatMap((row) => row[index] === null ? [] : [[row[index], row[index] + 1]])).map(([from, to]) => ({
188
- file: side === "base" ? file.previousPath ?? file.path : file.path,
189
- side,
190
- fromLine: from + 1,
191
- toLine: to
192
- })));
193
- }
194
10
  /**
195
11
  * Check whether a character code is a markdown line ending.
196
12
  *
@@ -956,6 +772,29 @@ function* markdownNodes(node) {
956
772
  function markdownText(node) {
957
773
  return [...markdownNodes(node)].map((child) => child.value ?? "").join("");
958
774
  }
775
+ /** Link destination spans in source order; never inside code. */
776
+ function linkDestinations(nodes) {
777
+ const spans = [];
778
+ for (const node of nodes) {
779
+ const from = node.position?.start.offset;
780
+ const to = node.position?.end.offset;
781
+ if (from === void 0 || to === void 0) continue;
782
+ if (node.type === "link") spans.push({
783
+ from: node.children?.at(-1)?.position?.end.offset ?? from + 1,
784
+ to
785
+ });
786
+ else if (node.type === "image" || node.type === "definition") spans.push({
787
+ from,
788
+ to
789
+ });
790
+ }
791
+ return spans.toSorted((a, b) => a.from - b.from);
792
+ }
793
+ function rewriteLinkDestinations(markdown, rewrite) {
794
+ let result = markdown;
795
+ for (const { from, to } of linkDestinations(markdownNodes(parseMarkdown(markdown))).toReversed()) result = result.slice(0, from) + rewrite(result.slice(from, to)) + result.slice(to);
796
+ return result;
797
+ }
959
798
  /** Shared syntax for submit-time source checks and safe Markdown rendering. */
960
799
  function parseMarkdown(source) {
961
800
  const tree = fromMarkdown(source, {
@@ -1036,7 +875,7 @@ const identity = { id: text$1.optional() };
1036
875
  /** The repository and commits an element's anchors quote. A step, frame,
1037
876
  * attachment or operation without them reads its block's; a block without
1038
877
  * them reads its document's. */
1039
- const elementPins = sourcePinsSchema.optional().describe("Pins for the anchors here; needed on the scratchpad.");
878
+ const elementPins = sourcePinsSchema.optional().describe("Pins for the anchors here.");
1040
879
  /** The strict schema for a block's own fields; id and type are added here. */
1041
880
  function defineBlock(type, props) {
1042
881
  return z.strictObject({
@@ -1376,8 +1215,8 @@ const image = {
1376
1215
  const markdown = {
1377
1216
  type: "markdown",
1378
1217
  schema: defineBlock("markdown", {
1379
- markdown: text$1.describe("Safe Markdown. Repository file links must use [label](review-source:head/path#L10-L24) or review-source:base/path#L10-L24, with a repository-relative path and verified line numbers. Relative paths, absolute filesystem paths, and file/editor URLs are rejected. External links use https://, http://, or mailto:; document anchors use #heading."),
1380
- pins: sourcePinsSchema.optional().describe("Repository and commits this block's review-source links resolve against, instead of the document's pins.")
1218
+ markdown: text$1.describe("Safe Markdown. Repository file links must use [label](whiteboard:source/head/path#L10-L24) or whiteboard:source/base/path#L10-L24, or whiteboard:source/<commit>/path#L10-L24 at any commit of the board's repository, with a repository-relative path and verified line numbers. Link another board as [label](whiteboard:board/<sessionId>) or one of its blocks as whiteboard:board/<sessionId>#<blockId>; both must exist. Relative paths, absolute filesystem paths, and file/editor URLs are rejected. External links use https://, http://, or mailto:; document anchors use #heading."),
1219
+ pins: sourcePinsSchema.optional().describe("Repository and commits this block's whiteboard:source/ links resolve against, instead of the document's pins.")
1381
1220
  })
1382
1221
  };
1383
1222
  //#endregion
@@ -1581,28 +1420,112 @@ function pinnedRepositoryIds(document) {
1581
1420
  })) if (source.pins) ids.add(source.pins.repositoryId);
1582
1421
  return ids;
1583
1422
  }
1584
- const targets = (repository) => z.discriminatedUnion("kind", [z.strictObject({
1585
- kind: z.literal("worktree"),
1586
- ...repository,
1587
- base: label.optional()
1588
- }), z.strictObject({
1589
- kind: z.literal("commits"),
1590
- ...repository,
1591
- head: label,
1592
- base: label.optional()
1593
- })]);
1423
+ const targets = (repository) => z.discriminatedUnion("kind", [
1424
+ z.strictObject({
1425
+ kind: z.literal("project"),
1426
+ ...repository
1427
+ }),
1428
+ z.strictObject({
1429
+ kind: z.literal("worktree"),
1430
+ ...repository,
1431
+ base: label.optional()
1432
+ }),
1433
+ z.strictObject({
1434
+ kind: z.literal("commits"),
1435
+ ...repository,
1436
+ head: label,
1437
+ base: label.optional()
1438
+ })
1439
+ ]);
1594
1440
  const reviewTargetSchema = targets({ repositoryId: label });
1595
- /** How agents name a target: by the checkout's path, registered on acceptance. */
1596
- const pathTargetSchema = targets({ repositoryPath: label });
1441
+ /** Targets as agents write them: a checkout path instead of a repository id,
1442
+ * plus a PR by URL or number. */
1443
+ const pathTargetSchema = z.discriminatedUnion("kind", [...targets({ repositoryPath: label }).options, z.strictObject({
1444
+ kind: z.literal("pullRequest"),
1445
+ repositoryPath: label,
1446
+ pullRequest: z.union([label, z.number().int().positive()])
1447
+ })]);
1597
1448
  const unitTypes = /* @__PURE__ */ new Set([
1598
1449
  "step",
1599
1450
  "flow_node",
1600
1451
  "flow_edge"
1601
1452
  ]);
1602
1453
  const isUnit = (element) => unitTypes.has(element.type);
1454
+ /** Rewrites old review-source: and review-trace: links to their whiteboard: form. */
1455
+ function canonicalLink(href) {
1456
+ return href.replace(/^review-(source|trace):/i, (_, kind) => `whiteboard:${kind.toLowerCase()}/`);
1457
+ }
1458
+ /** Markdown link destinations and definitions that begin with `target`, a
1459
+ * regex source; the match's first group is what comes before it. */
1460
+ const linkDestination = (target) => new RegExp(String.raw`(\]\(\s*<?|^\s*\[[^\]\n]+\]:\s*<?)` + target, "gim");
1461
+ function rewriteMarkdown(value, rewrite) {
1462
+ const visit = (node) => {
1463
+ if (isJsonArray(node)) node.forEach(visit);
1464
+ else if (isJsonObject(node)) for (const [key, field] of Object.entries(node)) if (key === "markdown" && isStringValue(field)) {
1465
+ const rewritten = rewrite(field);
1466
+ if (rewritten !== field) node[key] = rewritten;
1467
+ } else visit(field);
1468
+ };
1469
+ visit(value);
1470
+ return value;
1471
+ }
1472
+ const retiredLinks = linkDestination("review-(source|trace):");
1473
+ /** Rewrites old link prefixes in link destinations only, not code. */
1474
+ function respellLinks(markdown) {
1475
+ if (!/review-(source|trace):/i.test(markdown)) return markdown;
1476
+ return rewriteLinkDestinations(markdown, (span) => span.replace(retiredLinks, (_, before, kind) => `${before}whiteboard:${kind.toLowerCase()}/`));
1477
+ }
1478
+ /** Every Markdown text in `document` with its links spelled the current
1479
+ * way. */
1480
+ function respellDocumentLinks(document) {
1481
+ return rewriteMarkdown(document, respellLinks);
1482
+ }
1483
+ function respellEditLinks(edit) {
1484
+ return rewriteMarkdown(edit, respellLinks);
1485
+ }
1486
+ /** Parses whiteboard:board/<id> or whiteboard:board/<id>#<blockId>. */
1487
+ function boardLink(href) {
1488
+ const match = /^whiteboard:board\/([^#/]+)(?:#(.+))?$/.exec(href);
1489
+ if (!match) return void 0;
1490
+ try {
1491
+ const board = decodeURIComponent(match[1]);
1492
+ return match[2] === void 0 ? { board } : {
1493
+ board,
1494
+ block: decodeURIComponent(match[2])
1495
+ };
1496
+ } catch {
1497
+ return;
1498
+ }
1499
+ }
1500
+ const parsedBlocks = /* @__PURE__ */ new WeakMap();
1501
+ /** Parsed once per text; a save reads links, sources and resources from
1502
+ * it. */
1503
+ function blockNodes(block) {
1504
+ const cached = parsedBlocks.get(block);
1505
+ if (cached?.markdown === block.markdown) return cached.nodes;
1506
+ const nodes = [...markdownNodes(parseMarkdown(block.markdown))];
1507
+ parsedBlocks.set(block, {
1508
+ markdown: block.markdown,
1509
+ nodes
1510
+ });
1511
+ return nodes;
1512
+ }
1513
+ /** Every board link in a document. Malformed links are skipped; writes reject them. */
1514
+ function boardLinks(board, document) {
1515
+ return elements(document).flatMap((element) => element.type === "markdown" ? blockNodes(element).flatMap((node) => {
1516
+ const to = node.type === "link" ? boardLink(node.url ?? "") : void 0;
1517
+ return to ? [{
1518
+ from: {
1519
+ board,
1520
+ block: element.id
1521
+ },
1522
+ to
1523
+ }] : [];
1524
+ }) : []);
1525
+ }
1603
1526
  /** Inline trace quotes keep paragraph/list layout while using retained resources. */
1604
1527
  function traceQuoteLink(href) {
1605
- const match = /^review-trace:([^#]+)#(.+)$/.exec(href);
1528
+ const match = /^whiteboard:trace\/([^#]+)#(.+)$/.exec(canonicalLink(href));
1606
1529
  return match ? {
1607
1530
  traceId: decodeURIComponent(match[1]),
1608
1531
  eventId: decodeURIComponent(match[2])
@@ -1628,9 +1551,9 @@ function resourceReference(block) {
1628
1551
  function resourceReferences(document) {
1629
1552
  return elements(document).flatMap((block) => {
1630
1553
  if (block.type !== "markdown") return block.type === "image" || block.type === "trace_quote" || block.type === "software_map" ? [block] : [];
1631
- return [...markdownNodes(parseMarkdown(block.markdown))].flatMap((node) => {
1632
- if (node.type !== "link" || !node.url?.startsWith("review-trace:")) return [];
1633
- const quote = traceQuoteLink(node.url);
1554
+ return blockNodes(block).flatMap((node) => {
1555
+ if (node.type !== "link" || !canonicalLink(node.url ?? "").startsWith("whiteboard:trace/")) return [];
1556
+ const quote = traceQuoteLink(node.url ?? "");
1634
1557
  if (!quote) throw new ReviewInputError("Invalid trace quote link.");
1635
1558
  return [{
1636
1559
  type: "trace_quote",
@@ -1645,7 +1568,7 @@ function resourceReferences(document) {
1645
1568
  * `tolerant` skips malformed Markdown source links instead of rejecting, for
1646
1569
  * content that is already stored.
1647
1570
  */
1648
- function documentReferences(document, { tolerant = false, markdown = true } = {}) {
1571
+ function documentReferences(document, { tolerant = false, markdown = true, repositoryId } = {}) {
1649
1572
  const reject = (message) => {
1650
1573
  if (tolerant) return [];
1651
1574
  throw new ReviewInputError(message);
@@ -1662,22 +1585,31 @@ function documentReferences(document, { tolerant = false, markdown = true } = {}
1662
1585
  };
1663
1586
  return elements(document).flatMap((element) => {
1664
1587
  if (element.type === "markdown" && !markdown) return [];
1665
- if (element.type === "markdown") return [...markdownNodes(parseMarkdown(element.markdown))].flatMap((node) => {
1588
+ if (element.type === "markdown") return blockNodes(element).flatMap((node) => {
1666
1589
  if (node.type !== "link") return [];
1667
- const href = node.url ?? "";
1668
- if (!/^review-source:/i.test(href)) {
1669
- if (href.startsWith("review-trace:")) return [];
1590
+ const href = canonicalLink(node.url ?? "");
1591
+ if (!href.startsWith("whiteboard:source/")) {
1592
+ if (href.startsWith("whiteboard:trace/")) return [];
1593
+ if (href.startsWith("whiteboard:board/")) return boardLink(href) ? [] : reject(`Use whiteboard:board/<sessionId> or whiteboard:board/<sessionId>#<blockId> to link a board, in block ${element.id}.`);
1670
1594
  if (/^(?:https?:\/\/|mailto:|#)/i.test(href)) return [];
1671
- return reject(`Unsupported Markdown link ${JSON.stringify(href)} in block ${element.id}. Use [label](review-source:head/path#L10-L24) or review-source:base/path#L10-L24 for repository files, or review-source:diff/path#L84-R90 across sides, with a repository-relative path and verified line numbers. External links must use https://, http://, or mailto:; document anchors use #heading.`);
1595
+ return reject(`Unsupported Markdown link ${JSON.stringify(href)} in block ${element.id}. Use [label](whiteboard:source/head/path#L10-L24) or whiteboard:source/base/path#L10-L24 for repository files, whiteboard:source/diff/path#L84-R90 across sides, or whiteboard:source/<commit>/path#L10-L24 at a commit of the board's repository, with a repository-relative path and verified line numbers. Link another board as whiteboard:board/<sessionId>, or one of its blocks as whiteboard:board/<sessionId>#<blockId>. External links must use https://, http://, or mailto:; document anchors use #heading.`);
1672
1596
  }
1673
1597
  let target;
1674
1598
  try {
1675
- target = decodeURIComponent(href.slice(14));
1599
+ target = decodeURIComponent(href.slice(18));
1676
1600
  } catch {
1677
1601
  return reject("Invalid URL encoding in source link.");
1678
1602
  }
1603
+ const commit = commitLinkTarget(target);
1604
+ if (commit) return repositoryId ? select(commit.anchor, {
1605
+ repositoryId,
1606
+ head: commit.commit
1607
+ }).map((source) => ({
1608
+ id: `${element.id}:${node.url}`,
1609
+ source
1610
+ })) : reject(`A commit link names a commit of the board's repository, and this board has none, in block ${element.id}.`);
1679
1611
  const anchor = target.replace(/^(head|base|diff)\//i, (side) => side.toLowerCase());
1680
- if (!parseAnchor(anchor)) return reject("Use review-source:head/path#L10-L24 (or base, or diff/path#L84-R90 across sides) for a source link.");
1612
+ if (!parseAnchor(anchor)) return reject("Use whiteboard:source/head/path#L10-L24 (or base, diff/path#L84-R90 across sides, or <commit>/path#L10-L24 at a commit of the board's repository) for a source link.");
1681
1613
  return select(anchor, element.pins).map((source) => ({
1682
1614
  id: `${element.id}:${node.url}`,
1683
1615
  source
@@ -1738,6 +1670,60 @@ function documentReferences(document, { tolerant = false, markdown = true } = {}
1738
1670
  }
1739
1671
  /** All authored attachments, including code peeks, select the aligned diff. */
1740
1672
  const selectionReferences = documentReferences;
1673
+ const commitLinkDestination = linkDestination(String.raw`whiteboard:source\/([0-9a-f]{7,40})\/`);
1674
+ const hasCommitLink = /whiteboard:source\//i;
1675
+ /** Commits named by commit links, not by code samples. */
1676
+ function commitLinkCommits(document) {
1677
+ const commits = /* @__PURE__ */ new Set();
1678
+ for (const element of elements(document)) if (element.type === "markdown" && hasCommitLink.test(element.markdown)) for (const { from, to } of linkDestinations(blockNodes(element))) for (const match of element.markdown.slice(from, to).matchAll(commitLinkDestination)) commits.add(match[2]);
1679
+ return [...commits];
1680
+ }
1681
+ /** Rewrites the commits Markdown commit links name, as a short commit
1682
+ * becomes the full one on save. */
1683
+ function respellCommitLinks(document, commits) {
1684
+ return rewriteMarkdown(document, (markdown) => hasCommitLink.test(markdown) ? rewriteLinkDestinations(markdown, (span) => span.replace(commitLinkDestination, (_, before, commit) => `${before}whiteboard:source/${commits.get(commit) ?? commit}/`)) : markdown);
1685
+ }
1686
+ /** Blocks that carry pins of their own for the references in them. */
1687
+ const pinnedBlockTypes = /* @__PURE__ */ new Set([
1688
+ "markdown",
1689
+ "code_peek",
1690
+ "sequence",
1691
+ "flow_diagram",
1692
+ "call_stack_diff",
1693
+ "database_lens"
1694
+ ]);
1695
+ const hasSideLink = /whiteboard:source\/(head|base)\//i;
1696
+ const sideLinkDestination = linkDestination(String.raw`whiteboard:source\/(head|base)\/`);
1697
+ /**
1698
+ * Makes every reference that reads at the document's pins carry them
1699
+ * itself, for a board about to lose its pins: Markdown head/ and base/
1700
+ * links become commit links, and a block whose references still lean on
1701
+ * the document's pins (any anchor, or a diff/ link) takes them as its own.
1702
+ * Nothing changes what it points at.
1703
+ */
1704
+ function pinDocumentReferences(document, pins) {
1705
+ const own = {
1706
+ repositoryId: pins.repositoryId,
1707
+ base: pins.base,
1708
+ head: pins.head
1709
+ };
1710
+ const pinned = structuredClone(document);
1711
+ for (const element of elements(pinned)) {
1712
+ if (!pinnedBlockTypes.has(element.type)) continue;
1713
+ const block = element;
1714
+ if (block.pins) continue;
1715
+ if (block.type === "markdown") {
1716
+ block.markdown = respellLinks(block.markdown);
1717
+ if (hasSideLink.test(block.markdown)) block.markdown = rewriteLinkDestinations(block.markdown, (span) => span.replace(sideLinkDestination, (_, before, side) => `${before}whiteboard:source/${side.toLowerCase() === "base" ? pins.base : pins.head}/`));
1718
+ }
1719
+ if (documentReferences([block], { tolerant: true }).some((reference) => !reference.source.pins)) block.pins = own;
1720
+ }
1721
+ return pinned;
1722
+ }
1723
+ /** The repository a board's commit links read from: its target's. */
1724
+ function boardRepositoryId(board) {
1725
+ return board.target?.repositoryId ?? board.pins?.repositoryId;
1726
+ }
1741
1727
  /** Per-side read coordinates for endpoint validation and retained source quotes. */
1742
1728
  function sourceReferences(document, options = {}) {
1743
1729
  return selectionReferences(document, options).flatMap((ref) => sourceAnchors(ref.source).map((source) => ({
@@ -1746,8 +1732,10 @@ function sourceReferences(document, options = {}) {
1746
1732
  })));
1747
1733
  }
1748
1734
  /** Includes lenses and maps without inline ranges. */
1749
- function hasCodeReferences(review) {
1750
- return Boolean(sourceReferences(review.document).length || review.lenses?.length || resourceReferences(review.document).some((block) => block.type === "software_map"));
1735
+ /** Whether a board reads code at its own pins: a commit link or a block
1736
+ * with pins of its own names its commits itself. */
1737
+ function readsAtBoardPins(review) {
1738
+ return Boolean(sourceReferences(review.document, { tolerant: true }).some((reference) => !reference.source.pins) || review.lenses?.length || resourceReferences(review.document).some((block) => block.type === "software_map"));
1751
1739
  }
1752
1740
  const documentSchema = z.array(blockSchema);
1753
1741
  /** Discriminated by type, like blockSchema, so a malformed insert reports
@@ -1910,20 +1898,27 @@ function adoptFlowUnits(document, allocate) {
1910
1898
  /** Mutate a private candidate. Only the store owns allocation and commits. */
1911
1899
  /** Assign server ids to an element tree that arrives without any. */
1912
1900
  function assignFreshIds(element, allocate) {
1913
- if (element.id !== void 0) throw new ReviewInputError("IDs are assigned by the server.");
1914
- element.id = allocate(idPrefix(element));
1915
- for (const child of children(element)) assignFreshIds(child, allocate);
1901
+ assignIds(element, allocate, true);
1902
+ }
1903
+ /** Give ids to the parts of an element tree that have none, keeping the
1904
+ * rest: a person's save names the blocks it kept and leaves new ones bare. */
1905
+ function assignMissingIds(element, allocate) {
1906
+ assignIds(element, allocate, false);
1907
+ }
1908
+ function assignIds(element, allocate, fresh) {
1916
1909
  const assign = (item, prefix) => {
1917
- if (item.id !== void 0) throw new ReviewInputError("IDs are assigned by the server.");
1918
- item.id = allocate(prefix);
1910
+ if (item.id === void 0) item.id = allocate(prefix);
1911
+ else if (fresh) throw new ReviewInputError("IDs are assigned by the server.");
1919
1912
  };
1913
+ assign(element, idPrefix(element));
1914
+ for (const child of children(element)) assignIds(child, allocate, fresh);
1920
1915
  if (element.type === "call_stack_diff") for (const frame of [...element.base, ...element.head]) assign(frame, "frame");
1921
1916
  if (element.type === "database_lens") for (const useCase of element.useCases) {
1922
1917
  assign(useCase, "case");
1923
1918
  for (const op of useCase.operations) assign(op, "operation");
1924
1919
  }
1925
1920
  }
1926
- function applyEdit(document, edit, allocate, options = {}) {
1921
+ function applyEdit(document, edit, allocate) {
1927
1922
  adoptFlowUnits(document, allocate);
1928
1923
  const locate = (id) => {
1929
1924
  const find = (siblings) => {
@@ -1952,7 +1947,7 @@ function applyEdit(document, edit, allocate, options = {}) {
1952
1947
  if (afterId === element.id) throw new ReviewInputError("An element cannot follow itself.");
1953
1948
  if (afterId !== void 0 && !siblings.some((s) => s.id === afterId)) throw new ReviewInputError("afterId must identify a sibling.");
1954
1949
  if (from) from.splice(from.indexOf(element), 1);
1955
- const index = afterId !== void 0 ? siblings.findIndex((s) => s.id === afterId) + 1 : !parent && !from && options.placement === "first" ? 0 : siblings.length;
1950
+ const index = afterId === void 0 ? siblings.length : siblings.findIndex((s) => s.id === afterId) + 1;
1956
1951
  siblings.splice(index, 0, element);
1957
1952
  };
1958
1953
  if (edit.type === "insert") {
@@ -2546,54 +2541,114 @@ z.strictObject({
2546
2541
  function migrateDiffSelections(input) {
2547
2542
  return migrateNode(jsonValueSchema.parse(input));
2548
2543
  }
2544
+ /** Keys whose values held retired attachments. */
2545
+ const ATTACHMENT_KEYS = [
2546
+ "source",
2547
+ "peek",
2548
+ "sources",
2549
+ "contextSources",
2550
+ "callSite"
2551
+ ];
2552
+ /** A retired attachment as a selection, or undefined for anything else. */
2553
+ function retiredSelection(value) {
2554
+ if (!isStringValue(value.file)) return void 0;
2555
+ if (isNumberValue(value.fromLine) && isNumberValue(value.toLine)) {
2556
+ const side = value.side ?? value.graph ?? "head";
2557
+ return {
2558
+ file: value.file,
2559
+ start: {
2560
+ side,
2561
+ line: value.fromLine
2562
+ },
2563
+ end: {
2564
+ side,
2565
+ line: value.toLine
2566
+ }
2567
+ };
2568
+ }
2569
+ if (isJsonObject(value.start) && isJsonObject(value.end) && "baseLine" in value.start) {
2570
+ const endpoint = (row) => isNumberValue(row.headLine) ? {
2571
+ side: "head",
2572
+ line: row.headLine
2573
+ } : {
2574
+ side: "base",
2575
+ line: row.baseLine ?? null
2576
+ };
2577
+ return {
2578
+ file: value.file,
2579
+ start: endpoint(value.start),
2580
+ end: endpoint(value.end)
2581
+ };
2582
+ }
2583
+ }
2549
2584
  function migrateNode(value, attachment = false) {
2550
2585
  if (Array.isArray(value)) return value.map((child) => migrateNode(child, attachment));
2551
2586
  if (!isJsonObject(value)) return value;
2552
- if (attachment && isStringValue(value.file)) {
2553
- if (isNumberValue(value.fromLine) && isNumberValue(value.toLine)) {
2554
- const side = value.side ?? value.graph ?? "head";
2555
- return {
2556
- file: value.file,
2557
- start: {
2558
- side,
2559
- line: value.fromLine
2560
- },
2561
- end: {
2562
- side,
2563
- line: value.toLine
2564
- }
2565
- };
2566
- }
2567
- if (isJsonObject(value.start) && isJsonObject(value.end) && "baseLine" in value.start) {
2568
- const endpoint = (row) => isNumberValue(row.headLine) ? {
2569
- side: "head",
2570
- line: row.headLine
2571
- } : {
2572
- side: "base",
2573
- line: row.baseLine
2574
- };
2575
- return {
2576
- file: value.file,
2577
- start: endpoint(value.start),
2578
- end: endpoint(value.end)
2579
- };
2580
- }
2581
- }
2582
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, migrateNode(child, [
2583
- "source",
2584
- "peek",
2585
- "sources",
2586
- "contextSources",
2587
- "callSite"
2588
- ].includes(key))]));
2587
+ const selection = attachment ? retiredSelection(value) : void 0;
2588
+ if (selection) return selection;
2589
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, migrateNode(child, ATTACHMENT_KEYS.includes(key))]));
2589
2590
  }
2590
2591
  //#endregion
2591
2592
  //#region src/stored-document-migration.ts
2592
2593
  /** Upgrade a saved or shared Review document to the current block schema at the
2593
2594
  * read/import boundary. New edits use the strict schema and never accept these
2594
- * retired forms. */
2595
+ * retired forms. One walk makes a fresh copy: retired attachments become
2596
+ * selections, anchors become strings, sections lose their status, and
2597
+ * Markdown links are spelled the current way. */
2595
2598
  function migrateStoredDocument(input) {
2596
- return anchorStrings(dropSectionStatus(migrateDiffSelections(input)));
2599
+ return upgradeNode(input, {
2600
+ attachment: false,
2601
+ anchors: true
2602
+ });
2603
+ }
2604
+ function upgradeNode(value, place) {
2605
+ if (isStringValue(value) || isBooleanValue(value) || value === null) return value;
2606
+ if (isNumberValue(value) && Number.isFinite(value)) return value;
2607
+ if (Array.isArray(value)) return value.map((child) => upgradeNode(child, place));
2608
+ if (!isJsonObject(value)) throw new TypeError("A document must be JSON.");
2609
+ const selection = place.attachment ? retiredSelection(value) : void 0;
2610
+ if (selection) return upgradeNode(selection, {
2611
+ attachment: false,
2612
+ anchors: false
2613
+ });
2614
+ const inherited = place.anchors ? value.pins ?? place.blockPins : void 0;
2615
+ let pins;
2616
+ const convert = (anchor) => {
2617
+ if (isStringValue(anchor)) return anchor;
2618
+ const parsed = selectionObjectSchema.safeParse(anchor);
2619
+ if (!parsed.success) return anchor;
2620
+ const { pins: own, ...anchorAt } = parsed.data;
2621
+ if (own) pins = own;
2622
+ return formatAnchor(anchorAt);
2623
+ };
2624
+ const entries = [];
2625
+ for (const [key, child] of Object.entries(value)) {
2626
+ if (key === "status" && value.type === "section") continue;
2627
+ if (key === "markdown" && isStringValue(child)) {
2628
+ entries.push([key, respellLinks(child)]);
2629
+ continue;
2630
+ }
2631
+ const attachment = ATTACHMENT_KEYS.includes(key);
2632
+ if (place.anchors && ANCHOR_KEYS.includes(key)) {
2633
+ const anchors = upgradeNode(child, {
2634
+ attachment,
2635
+ anchors: false
2636
+ });
2637
+ entries.push([key, Array.isArray(anchors) ? anchors.map(convert) : convert(anchors)]);
2638
+ } else entries.push([key, upgradeNode(child, {
2639
+ attachment,
2640
+ anchors: place.anchors,
2641
+ blockPins: inherited
2642
+ })]);
2643
+ }
2644
+ const upgraded = Object.fromEntries(entries);
2645
+ if (pins !== void 0 && value.kind !== "ranges" && JSON.stringify(pins) !== JSON.stringify(place.blockPins)) upgraded.pins = pins;
2646
+ return upgraded;
2647
+ }
2648
+ /** A stored document as the store reads it: retired forms upgraded, and
2649
+ * lens blocks lifted out to the snapshot's own lenses. */
2650
+ function readStoredDocument(input) {
2651
+ return liftFileLenses(migrateStoredDocument(input));
2597
2652
  }
2598
2653
  /** Lenses saved before ranges were anchor strings. */
2599
2654
  function migrateStoredLenses(input) {
@@ -2640,14 +2695,6 @@ function anchorStrings(value, blockPins) {
2640
2695
  if (pins !== void 0 && value.kind !== "ranges" && JSON.stringify(pins) !== JSON.stringify(blockPins)) migrated.pins = pins;
2641
2696
  return migrated;
2642
2697
  }
2643
- /** Sections once carried an optional `status` (pending, in_progress or
2644
- * complete). The authoring lease is now the only signal of work in progress,
2645
- * so versions saved before it was retired drop the field when read. */
2646
- function dropSectionStatus(value) {
2647
- if (Array.isArray(value)) return value.map(dropSectionStatus);
2648
- if (!isJsonObject(value)) return value;
2649
- return Object.fromEntries(Object.entries(value).filter(([key]) => !(value.type === "section" && key === "status")).map(([key, child]) => [key, dropSectionStatus(child)]));
2650
- }
2651
2698
  /** A saved `file_lens` block, from before lenses left the document. */
2652
2699
  const legacyFileLensSchema = z.object({
2653
2700
  type: z.literal("file_lens"),
@@ -2686,4 +2733,4 @@ function liftFileLenses(document) {
2686
2733
  };
2687
2734
  }
2688
2735
  //#endregion
2689
- export { intersectIntervals as $, documentAdvice as A, requireVisibleSource as B, resourceReference as C, sourceReferences as D, selectionReferences as E, lensSchema as F, comparisonKey as G, markdownNodes as H, lensSelections as I, selectionKey as J, formatAnchor as K, SourceRangeError as L, UNCATEGORIZED_LENS_ID as M, applyLensEdit as N, summarizeEdit as O, lensEditSchema as P, emptyCoverage as Q, checkSourcePath as R, publishedEditSchema as S, reviewTargetSchema as T, parseMarkdown as U, sliceSourceRange as V, anchorSelection as W, coverageSchema as X, sourceAnchors as Y, coverageSources as Z, hasCodeReferences as _, defineSoftwareMap as a, pinnedRepositoryIds as b, traceSchema as c, applyEdit as d, scopedCoverage as et, assignFreshIds as f, explicitPins as g, elements as h, SoftwareModelValidationError as i, stepSources as j, checkReferences as k, textIncludesQuote as l, editSchema as m, migrateStoredDocument as n, unionIntervals as nt, normalizedSoftwareElementSchema as o, documentSchema as p, resolveDiffSelection as q, migrateStoredLenses as r, updateCoverage as rt, normalizedSoftwareRelationshipSchema as s, liftFileLenses as t, subtractIntervals as tt, anchorPins as u, isUnit as v, resourceReferences as w, pinsSchema as x, pathTargetSchema as y, fileLineRangeSchema as z };
2736
+ export { parseMarkdown as $, resourceReferences as A, stepSources as B, pathTargetSchema as C, publishedEditSchema as D, pinsSchema as E, selectionReferences as F, lensSelections as G, applyLensEdit as H, sourceReferences as I, fileLineRangeSchema as J, SourceRangeError as K, summarizeEdit as L, respellDocumentLinks as M, respellEditLinks as N, readsAtBoardPins as O, reviewTargetSchema as P, markdownText as Q, checkReferences as R, explicitPins as S, pinnedRepositoryIds as T, lensEditSchema as U, UNCATEGORIZED_LENS_ID as V, lensSchema as W, sliceSourceRange as X, requireVisibleSource as Y, markdownNodes as Z, boardRepositoryId as _, SoftwareModelValidationError as a, editSchema as b, normalizedSoftwareRelationshipSchema as c, adoptFlowUnits as d, anchorPins as f, boardLinks as g, assignMissingIds as h, readStoredDocument as i, respellCommitLinks as j, resourceReference as k, traceSchema as l, assignFreshIds as m, migrateStoredDocument as n, defineSoftwareMap as o, applyEdit as p, checkSourcePath as q, migrateStoredLenses as r, normalizedSoftwareElementSchema as s, liftFileLenses as t, textIncludesQuote as u, commitLinkCommits as v, pinDocumentReferences as w, elements as x, documentSchema as y, documentAdvice as z };