@dev.fast/whiteboard 0.3.3 → 0.3.4-preview.20261019.125
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/dist/{account-alias-xOdmMnDH.js → account-alias-ClRtWF_-.js} +8 -6
- package/dist/{agent-cli-DT7MdaqL.js → agent-cli-C6AM9dml.js} +15 -10
- package/dist/agent-cli-DKpWo-4F.js +2 -0
- package/dist/{agent-client-BRBzv3AV.js → agent-client-G4KxB8zB.js} +13 -3
- package/dist/{authoring-tools-DGSEag8y.js → authoring-tools-Dfo0uOSj.js} +34 -27
- package/dist/{background-server-WKzI_a98.js → background-server-BF_lLFxY.js} +5 -5
- package/dist/{background-server-C_REWFkG.js → background-server-DJfYWRoN.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-BBSDi_dX.js → cli-CqOiiTW_.js} +1 -1
- package/dist/{cli-runner-DshTBske.js → cli-runner-D8CsXff4.js} +26 -26
- package/dist/cli.js +2 -2
- package/dist/{client-BN_FDxFr.js → client-lKVF_Elo.js} +253 -47
- package/dist/{fuzzy-match-bFX6wm8j.js → fuzzy-match-DH9lZivo.js} +42 -14
- package/dist/{headless-host-CS4Gv0c5.js → headless-host-C4bhVPZx.js} +513 -140
- package/dist/{headless-host-CzvYpKOX.js → headless-host-CeNnXxiL.js} +1 -1
- package/dist/lens-selection-CyJapcsl.js +214 -0
- package/dist/{local-data-DtQTIzHX.js → local-data-BdqqCYUD.js} +2434 -328
- package/dist/{mcp-DpKnnmHH.js → mcp-B2mvDEhC.js} +5 -9
- package/dist/{package-paths-jMg6JxDP.js → package-paths-Cm1tR8c4.js} +1 -1
- package/dist/{profile-DaqmODhc.js → profile-DcigfSms.js} +20 -8
- package/dist/profile-DthRnZ7x.js +2 -0
- package/dist/{remote-attach-DCngg8cl.js → remote-attach-DGwVRmY5.js} +4 -4
- package/dist/{remote-extensions-BCAbQZTv.js → remote-extensions-CferWwM9.js} +3 -3
- package/dist/{remote-extensions-Cf3L-ZGG.js → remote-extensions-D8W2IgKH.js} +1 -1
- package/dist/{remote-language-server-UWQgpoh-.js → remote-language-server-By-rEosC.js} +5 -5
- package/dist/{remote-uninstall-CcDOcGyi.js → remote-uninstall-DgHnDAFH.js} +6 -6
- package/dist/{request-origin-Zcn_CjX4.js → request-origin-DDmfnytX.js} +38 -10
- package/dist/{review-app-launcher-tqSQPlbu.js → review-app-launcher-CG3TFZEx.js} +2 -2
- package/dist/{review-home-paths-Lc02WFuX.js → review-home-paths-D1JVitqN.js} +19 -17
- package/dist/{review-telemetry-BHWJsnoD.js → review-telemetry-Myzm7o6-.js} +5 -5
- package/dist/runtime-Bj2tD33R.js +27 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-77-qkEhs.js → s3-DtJFWC0G.js} +1 -1
- package/dist/{s3-config-DDWWMh5x.js → s3-config-BTO12tCC.js} +1 -1
- package/dist/{s3-config-0xuCh-F_.js → s3-config-CSy3fx8H.js} +1 -1
- package/dist/server/desktop-host.js +29 -76
- package/dist/{server-discovery-BVqrE1_-.js → server-discovery-Bwa_6Tsj.js} +3 -3
- package/dist/sharing/index.d.ts +486 -70
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DsM3jO8G.js → src-BiWNbcF_.js} +89 -12
- package/dist/{src-BMPuxWv4.js → src-WPaQn75m.js} +208 -73
- package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-dFbewRTG.js} +270 -269
- package/dist/{thread-state-5pYr6_zM.js → thread-state-Do5LX10I.js} +7 -2
- package/dist/{tool-failure-BFt2xj9i.js → tool-failure-C9J3r7KD.js} +10 -3
- package/instructions/authoring.md +3 -2
- package/package.json +4 -4
- package/src/agent-connect.ts +8 -2
- package/src/ask/agents.ts +51 -9
- package/src/ask/checkout-files.ts +2 -5
- package/src/ask/thread-state.ts +4 -0
- package/src/ask/thread.ts +16 -0
- package/src/ask/threads.ts +24 -3
- package/src/cli-runner.ts +4 -1
- package/src/diff-selection-migration.ts +48 -34
- package/src/heading-anchors.ts +42 -0
- package/src/lens-selection.ts +38 -2
- package/src/review-api/README.md +52 -13
- package/src/review-api/activity.ts +1 -4
- package/src/review-api/agent-cli.ts +9 -1
- package/src/review-api/anchor-quotes.ts +14 -7
- package/src/review-api/ask-history.ts +137 -51
- package/src/review-api/authoring-tools.ts +45 -30
- package/src/review-api/blocks/definition.ts +1 -1
- package/src/review-api/blocks/markdown.ts +2 -2
- package/src/review-api/board-order.ts +144 -0
- package/src/review-api/checkout-groups.ts +560 -0
- package/src/review-api/document-headings.ts +58 -27
- package/src/review-api/document-text.ts +31 -2
- package/src/review-api/document.ts +281 -34
- package/src/review-api/foreign-references.ts +328 -0
- package/src/review-api/github-provider.ts +51 -13
- package/src/review-api/http.ts +702 -123
- package/src/review-api/instructions.ts +0 -19
- package/src/review-api/link-preview.ts +113 -0
- package/src/review-api/local-data.ts +310 -46
- package/src/review-api/mcp.ts +3 -11
- package/src/review-api/onboarding.ts +1 -1
- package/src/review-api/profile.ts +37 -6
- package/src/review-api/public-tools.ts +24 -2
- package/src/review-api/pull-request.ts +82 -49
- package/src/review-api/request-origin.ts +10 -0
- package/src/review-api/review-progress.ts +61 -27
- package/src/review-api/scratchpad-plan-upgrade.ts +250 -0
- package/src/review-api/store-migrations.ts +357 -0
- package/src/review-api/store-schema.ts +38 -29
- package/src/review-api/store.ts +2144 -480
- package/src/review-api/workspaces.ts +52 -13
- package/src/review-api/worktree-events.ts +3 -5
- package/src/review-api/worktree-source.ts +3 -7
- package/src/review-api/worktree-structural.integration.ts +72 -0
- package/src/review-stack.ts +15 -23
- package/src/runtime.ts +4 -30
- package/src/server/desktop-host.ts +6 -1
- package/src/server/desktop-server.ts +1 -31
- package/src/server/headless-host.ts +0 -2
- package/src/server/hono-http.ts +2 -1
- package/src/server/legacy-tutorial-cleanup.ts +2 -3
- package/src/server/review-gateway-list.ts +3 -5
- package/src/server/review-gateway-memory.ts +1 -1
- package/src/server/review-lifecycle-telemetry.ts +2 -2
- package/src/server/review-server-core.ts +1 -5
- package/src/server/structural-comparisons.ts +47 -0
- package/src/sharing/export.ts +36 -7
- package/src/sharing/host.ts +34 -14
- package/src/sharing/import.ts +156 -24
- package/src/sharing/repository.ts +128 -20
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +110 -19
- package/src/ui-telemetry-events.ts +25 -9
- package/dist/agent-cli-DeCrUC2s.js +0 -2
- package/dist/profile-Cz7L781D.js +0 -2
- package/dist/runtime-CKiWhGrf.js +0 -52
- package/instructions/scratchpad.md +0 -23
- package/src/review-preferences.ts +0 -88
package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-dFbewRTG.js}
RENAMED
|
@@ -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-
|
|
1
|
+
import { d as isBooleanValue, f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
|
|
2
|
+
import "./src-BiWNbcF_.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-CyJapcsl.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
|
*
|
|
@@ -1036,7 +852,7 @@ const identity = { id: text$1.optional() };
|
|
|
1036
852
|
/** The repository and commits an element's anchors quote. A step, frame,
|
|
1037
853
|
* attachment or operation without them reads its block's; a block without
|
|
1038
854
|
* them reads its document's. */
|
|
1039
|
-
const elementPins = sourcePinsSchema.optional().describe("Pins for the anchors here
|
|
855
|
+
const elementPins = sourcePinsSchema.optional().describe("Pins for the anchors here.");
|
|
1040
856
|
/** The strict schema for a block's own fields; id and type are added here. */
|
|
1041
857
|
function defineBlock(type, props) {
|
|
1042
858
|
return z.strictObject({
|
|
@@ -1376,8 +1192,8 @@ const image = {
|
|
|
1376
1192
|
const markdown = {
|
|
1377
1193
|
type: "markdown",
|
|
1378
1194
|
schema: defineBlock("markdown", {
|
|
1379
|
-
markdown: text$1.describe("Safe Markdown. Repository file links must use [label](
|
|
1380
|
-
pins: sourcePinsSchema.optional().describe("Repository and commits this block's
|
|
1195
|
+
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."),
|
|
1196
|
+
pins: sourcePinsSchema.optional().describe("Repository and commits this block's whiteboard:source/ links resolve against, instead of the document's pins.")
|
|
1381
1197
|
})
|
|
1382
1198
|
};
|
|
1383
1199
|
//#endregion
|
|
@@ -1581,28 +1397,91 @@ function pinnedRepositoryIds(document) {
|
|
|
1581
1397
|
})) if (source.pins) ids.add(source.pins.repositoryId);
|
|
1582
1398
|
return ids;
|
|
1583
1399
|
}
|
|
1584
|
-
const targets = (repository) => z.discriminatedUnion("kind", [
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
}),
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
})
|
|
1400
|
+
const targets = (repository) => z.discriminatedUnion("kind", [
|
|
1401
|
+
z.strictObject({
|
|
1402
|
+
kind: z.literal("project"),
|
|
1403
|
+
...repository
|
|
1404
|
+
}),
|
|
1405
|
+
z.strictObject({
|
|
1406
|
+
kind: z.literal("worktree"),
|
|
1407
|
+
...repository,
|
|
1408
|
+
base: label.optional()
|
|
1409
|
+
}),
|
|
1410
|
+
z.strictObject({
|
|
1411
|
+
kind: z.literal("commits"),
|
|
1412
|
+
...repository,
|
|
1413
|
+
head: label,
|
|
1414
|
+
base: label.optional()
|
|
1415
|
+
})
|
|
1416
|
+
]);
|
|
1594
1417
|
const reviewTargetSchema = targets({ repositoryId: label });
|
|
1595
|
-
/**
|
|
1596
|
-
|
|
1418
|
+
/** Targets as agents write them: a checkout path instead of a repository id,
|
|
1419
|
+
* plus a PR by URL or number. */
|
|
1420
|
+
const pathTargetSchema = z.discriminatedUnion("kind", [...targets({ repositoryPath: label }).options, z.strictObject({
|
|
1421
|
+
kind: z.literal("pullRequest"),
|
|
1422
|
+
repositoryPath: label,
|
|
1423
|
+
pullRequest: z.union([label, z.number().int().positive()])
|
|
1424
|
+
})]);
|
|
1597
1425
|
const unitTypes = /* @__PURE__ */ new Set([
|
|
1598
1426
|
"step",
|
|
1599
1427
|
"flow_node",
|
|
1600
1428
|
"flow_edge"
|
|
1601
1429
|
]);
|
|
1602
1430
|
const isUnit = (element) => unitTypes.has(element.type);
|
|
1431
|
+
/** Rewrites old review-source: and review-trace: links to their whiteboard: form. */
|
|
1432
|
+
function canonicalLink(href) {
|
|
1433
|
+
return href.replace(/^review-(source|trace):/i, (_, kind) => `whiteboard:${kind.toLowerCase()}/`);
|
|
1434
|
+
}
|
|
1435
|
+
/** Markdown link destinations and definitions that begin with `target`, a
|
|
1436
|
+
* regex source; the match's first group is what comes before it. */
|
|
1437
|
+
const linkDestination = (target) => new RegExp(String.raw`(\]\(\s*<?|^\s*\[[^\]\n]+\]:\s*<?)` + target, "gim");
|
|
1438
|
+
/** Every Markdown text in `value`, rewritten. */
|
|
1439
|
+
function rewriteMarkdown(value, rewrite) {
|
|
1440
|
+
return JSON.parse(JSON.stringify(value), (key, field) => key === "markdown" && isStringValue(field) ? rewrite(field) : field);
|
|
1441
|
+
}
|
|
1442
|
+
const retiredLinks = linkDestination("review-(source|trace):");
|
|
1443
|
+
/** Rewrites old link prefixes in Markdown link destinations and definitions. */
|
|
1444
|
+
function respellLinks(markdown) {
|
|
1445
|
+
return markdown.replace(retiredLinks, (_, before, kind) => `${before}whiteboard:${kind.toLowerCase()}/`);
|
|
1446
|
+
}
|
|
1447
|
+
/** Every Markdown text in `document` with its links spelled the current
|
|
1448
|
+
* way. */
|
|
1449
|
+
function respellDocumentLinks(document) {
|
|
1450
|
+
return rewriteMarkdown(document, respellLinks);
|
|
1451
|
+
}
|
|
1452
|
+
function respellEditLinks(edit) {
|
|
1453
|
+
return rewriteMarkdown(edit, respellLinks);
|
|
1454
|
+
}
|
|
1455
|
+
/** Parses whiteboard:board/<id> or whiteboard:board/<id>#<blockId>. */
|
|
1456
|
+
function boardLink(href) {
|
|
1457
|
+
const match = /^whiteboard:board\/([^#/]+)(?:#(.+))?$/.exec(href);
|
|
1458
|
+
if (!match) return void 0;
|
|
1459
|
+
try {
|
|
1460
|
+
const board = decodeURIComponent(match[1]);
|
|
1461
|
+
return match[2] === void 0 ? { board } : {
|
|
1462
|
+
board,
|
|
1463
|
+
block: decodeURIComponent(match[2])
|
|
1464
|
+
};
|
|
1465
|
+
} catch {
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
/** Every board link in a document. Malformed links are skipped; writes reject them. */
|
|
1470
|
+
function boardLinks(board, document) {
|
|
1471
|
+
return elements(document).flatMap((element) => element.type === "markdown" ? [...markdownNodes(parseMarkdown(element.markdown))].flatMap((node) => {
|
|
1472
|
+
const to = node.type === "link" ? boardLink(node.url ?? "") : void 0;
|
|
1473
|
+
return to ? [{
|
|
1474
|
+
from: {
|
|
1475
|
+
board,
|
|
1476
|
+
block: element.id
|
|
1477
|
+
},
|
|
1478
|
+
to
|
|
1479
|
+
}] : [];
|
|
1480
|
+
}) : []);
|
|
1481
|
+
}
|
|
1603
1482
|
/** Inline trace quotes keep paragraph/list layout while using retained resources. */
|
|
1604
1483
|
function traceQuoteLink(href) {
|
|
1605
|
-
const match = /^
|
|
1484
|
+
const match = /^whiteboard:trace\/([^#]+)#(.+)$/.exec(canonicalLink(href));
|
|
1606
1485
|
return match ? {
|
|
1607
1486
|
traceId: decodeURIComponent(match[1]),
|
|
1608
1487
|
eventId: decodeURIComponent(match[2])
|
|
@@ -1629,8 +1508,8 @@ function resourceReferences(document) {
|
|
|
1629
1508
|
return elements(document).flatMap((block) => {
|
|
1630
1509
|
if (block.type !== "markdown") return block.type === "image" || block.type === "trace_quote" || block.type === "software_map" ? [block] : [];
|
|
1631
1510
|
return [...markdownNodes(parseMarkdown(block.markdown))].flatMap((node) => {
|
|
1632
|
-
if (node.type !== "link" || !node.url
|
|
1633
|
-
const quote = traceQuoteLink(node.url);
|
|
1511
|
+
if (node.type !== "link" || !canonicalLink(node.url ?? "").startsWith("whiteboard:trace/")) return [];
|
|
1512
|
+
const quote = traceQuoteLink(node.url ?? "");
|
|
1634
1513
|
if (!quote) throw new ReviewInputError("Invalid trace quote link.");
|
|
1635
1514
|
return [{
|
|
1636
1515
|
type: "trace_quote",
|
|
@@ -1645,7 +1524,7 @@ function resourceReferences(document) {
|
|
|
1645
1524
|
* `tolerant` skips malformed Markdown source links instead of rejecting, for
|
|
1646
1525
|
* content that is already stored.
|
|
1647
1526
|
*/
|
|
1648
|
-
function documentReferences(document, { tolerant = false, markdown = true } = {}) {
|
|
1527
|
+
function documentReferences(document, { tolerant = false, markdown = true, repositoryId } = {}) {
|
|
1649
1528
|
const reject = (message) => {
|
|
1650
1529
|
if (tolerant) return [];
|
|
1651
1530
|
throw new ReviewInputError(message);
|
|
@@ -1664,20 +1543,29 @@ function documentReferences(document, { tolerant = false, markdown = true } = {}
|
|
|
1664
1543
|
if (element.type === "markdown" && !markdown) return [];
|
|
1665
1544
|
if (element.type === "markdown") return [...markdownNodes(parseMarkdown(element.markdown))].flatMap((node) => {
|
|
1666
1545
|
if (node.type !== "link") return [];
|
|
1667
|
-
const href = node.url ?? "";
|
|
1668
|
-
if (
|
|
1669
|
-
if (href.startsWith("
|
|
1546
|
+
const href = canonicalLink(node.url ?? "");
|
|
1547
|
+
if (!href.startsWith("whiteboard:source/")) {
|
|
1548
|
+
if (href.startsWith("whiteboard:trace/")) return [];
|
|
1549
|
+
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
1550
|
if (/^(?:https?:\/\/|mailto:|#)/i.test(href)) return [];
|
|
1671
|
-
return reject(`Unsupported Markdown link ${JSON.stringify(href)} in block ${element.id}. Use [label](
|
|
1551
|
+
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
1552
|
}
|
|
1673
1553
|
let target;
|
|
1674
1554
|
try {
|
|
1675
|
-
target = decodeURIComponent(href.slice(
|
|
1555
|
+
target = decodeURIComponent(href.slice(18));
|
|
1676
1556
|
} catch {
|
|
1677
1557
|
return reject("Invalid URL encoding in source link.");
|
|
1678
1558
|
}
|
|
1559
|
+
const commit = commitLinkTarget(target);
|
|
1560
|
+
if (commit) return repositoryId ? select(commit.anchor, {
|
|
1561
|
+
repositoryId,
|
|
1562
|
+
head: commit.commit
|
|
1563
|
+
}).map((source) => ({
|
|
1564
|
+
id: `${element.id}:${node.url}`,
|
|
1565
|
+
source
|
|
1566
|
+
})) : reject(`A commit link names a commit of the board's repository, and this board has none, in block ${element.id}.`);
|
|
1679
1567
|
const anchor = target.replace(/^(head|base|diff)\//i, (side) => side.toLowerCase());
|
|
1680
|
-
if (!parseAnchor(anchor)) return reject("Use
|
|
1568
|
+
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
1569
|
return select(anchor, element.pins).map((source) => ({
|
|
1682
1570
|
id: `${element.id}:${node.url}`,
|
|
1683
1571
|
source
|
|
@@ -1738,6 +1626,58 @@ function documentReferences(document, { tolerant = false, markdown = true } = {}
|
|
|
1738
1626
|
}
|
|
1739
1627
|
/** All authored attachments, including code peeks, select the aligned diff. */
|
|
1740
1628
|
const selectionReferences = documentReferences;
|
|
1629
|
+
const commitLinkDestination = linkDestination(String.raw`whiteboard:source\/([0-9a-f]{7,40})\/`);
|
|
1630
|
+
/** The commits a document's Markdown commit links name, as written. */
|
|
1631
|
+
function commitLinkCommits(document) {
|
|
1632
|
+
const commits = /* @__PURE__ */ new Set();
|
|
1633
|
+
JSON.stringify(document, (key, value) => {
|
|
1634
|
+
if (key === "markdown" && isStringValue(value)) for (const match of value.matchAll(commitLinkDestination)) commits.add(match[2]);
|
|
1635
|
+
return value;
|
|
1636
|
+
});
|
|
1637
|
+
return [...commits];
|
|
1638
|
+
}
|
|
1639
|
+
/** Rewrites the commits Markdown commit links name, as a short commit
|
|
1640
|
+
* becomes the full one on save. */
|
|
1641
|
+
function respellCommitLinks(document, commits) {
|
|
1642
|
+
return rewriteMarkdown(document, (markdown) => markdown.replace(commitLinkDestination, (link, before, commit) => `${before}whiteboard:source/${commits.get(commit) ?? commit}/`));
|
|
1643
|
+
}
|
|
1644
|
+
/** Blocks that carry pins of their own for the references in them. */
|
|
1645
|
+
const pinnedBlockTypes = /* @__PURE__ */ new Set([
|
|
1646
|
+
"markdown",
|
|
1647
|
+
"code_peek",
|
|
1648
|
+
"sequence",
|
|
1649
|
+
"flow_diagram",
|
|
1650
|
+
"call_stack_diff",
|
|
1651
|
+
"database_lens"
|
|
1652
|
+
]);
|
|
1653
|
+
const sideLinkDestination = linkDestination(String.raw`whiteboard:source\/(head|base)\/`);
|
|
1654
|
+
/**
|
|
1655
|
+
* Makes every reference that reads at the document's pins carry them
|
|
1656
|
+
* itself, for a board about to lose its pins: Markdown head/ and base/
|
|
1657
|
+
* links become commit links, and a block whose references still lean on
|
|
1658
|
+
* the document's pins (any anchor, or a diff/ link) takes them as its own.
|
|
1659
|
+
* Nothing changes what it points at.
|
|
1660
|
+
*/
|
|
1661
|
+
function pinDocumentReferences(document, pins) {
|
|
1662
|
+
const own = {
|
|
1663
|
+
repositoryId: pins.repositoryId,
|
|
1664
|
+
base: pins.base,
|
|
1665
|
+
head: pins.head
|
|
1666
|
+
};
|
|
1667
|
+
const pinned = structuredClone(document);
|
|
1668
|
+
for (const element of elements(pinned)) {
|
|
1669
|
+
if (!pinnedBlockTypes.has(element.type)) continue;
|
|
1670
|
+
const block = element;
|
|
1671
|
+
if (block.pins) continue;
|
|
1672
|
+
if (block.type === "markdown") block.markdown = respellLinks(block.markdown).replace(sideLinkDestination, (_, before, side) => `${before}whiteboard:source/${side.toLowerCase() === "base" ? pins.base : pins.head}/`);
|
|
1673
|
+
if (documentReferences([block], { tolerant: true }).some((reference) => !reference.source.pins)) block.pins = own;
|
|
1674
|
+
}
|
|
1675
|
+
return pinned;
|
|
1676
|
+
}
|
|
1677
|
+
/** The repository a board's commit links read from: its target's. */
|
|
1678
|
+
function boardRepositoryId(board) {
|
|
1679
|
+
return board.target?.repositoryId ?? board.pins?.repositoryId;
|
|
1680
|
+
}
|
|
1741
1681
|
/** Per-side read coordinates for endpoint validation and retained source quotes. */
|
|
1742
1682
|
function sourceReferences(document, options = {}) {
|
|
1743
1683
|
return selectionReferences(document, options).flatMap((ref) => sourceAnchors(ref.source).map((source) => ({
|
|
@@ -1746,8 +1686,10 @@ function sourceReferences(document, options = {}) {
|
|
|
1746
1686
|
})));
|
|
1747
1687
|
}
|
|
1748
1688
|
/** Includes lenses and maps without inline ranges. */
|
|
1749
|
-
|
|
1750
|
-
|
|
1689
|
+
/** Whether a board reads code at its own pins: a commit link or a block
|
|
1690
|
+
* with pins of its own names its commits itself. */
|
|
1691
|
+
function readsAtBoardPins(review) {
|
|
1692
|
+
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
1693
|
}
|
|
1752
1694
|
const documentSchema = z.array(blockSchema);
|
|
1753
1695
|
/** Discriminated by type, like blockSchema, so a malformed insert reports
|
|
@@ -1910,20 +1852,27 @@ function adoptFlowUnits(document, allocate) {
|
|
|
1910
1852
|
/** Mutate a private candidate. Only the store owns allocation and commits. */
|
|
1911
1853
|
/** Assign server ids to an element tree that arrives without any. */
|
|
1912
1854
|
function assignFreshIds(element, allocate) {
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1855
|
+
assignIds(element, allocate, true);
|
|
1856
|
+
}
|
|
1857
|
+
/** Give ids to the parts of an element tree that have none, keeping the
|
|
1858
|
+
* rest: a person's save names the blocks it kept and leaves new ones bare. */
|
|
1859
|
+
function assignMissingIds(element, allocate) {
|
|
1860
|
+
assignIds(element, allocate, false);
|
|
1861
|
+
}
|
|
1862
|
+
function assignIds(element, allocate, fresh) {
|
|
1916
1863
|
const assign = (item, prefix) => {
|
|
1917
|
-
if (item.id
|
|
1918
|
-
|
|
1864
|
+
if (item.id === void 0) item.id = allocate(prefix);
|
|
1865
|
+
else if (fresh) throw new ReviewInputError("IDs are assigned by the server.");
|
|
1919
1866
|
};
|
|
1867
|
+
assign(element, idPrefix(element));
|
|
1868
|
+
for (const child of children(element)) assignIds(child, allocate, fresh);
|
|
1920
1869
|
if (element.type === "call_stack_diff") for (const frame of [...element.base, ...element.head]) assign(frame, "frame");
|
|
1921
1870
|
if (element.type === "database_lens") for (const useCase of element.useCases) {
|
|
1922
1871
|
assign(useCase, "case");
|
|
1923
1872
|
for (const op of useCase.operations) assign(op, "operation");
|
|
1924
1873
|
}
|
|
1925
1874
|
}
|
|
1926
|
-
function applyEdit(document, edit, allocate
|
|
1875
|
+
function applyEdit(document, edit, allocate) {
|
|
1927
1876
|
adoptFlowUnits(document, allocate);
|
|
1928
1877
|
const locate = (id) => {
|
|
1929
1878
|
const find = (siblings) => {
|
|
@@ -1952,7 +1901,7 @@ function applyEdit(document, edit, allocate, options = {}) {
|
|
|
1952
1901
|
if (afterId === element.id) throw new ReviewInputError("An element cannot follow itself.");
|
|
1953
1902
|
if (afterId !== void 0 && !siblings.some((s) => s.id === afterId)) throw new ReviewInputError("afterId must identify a sibling.");
|
|
1954
1903
|
if (from) from.splice(from.indexOf(element), 1);
|
|
1955
|
-
const index = afterId
|
|
1904
|
+
const index = afterId === void 0 ? siblings.length : siblings.findIndex((s) => s.id === afterId) + 1;
|
|
1956
1905
|
siblings.splice(index, 0, element);
|
|
1957
1906
|
};
|
|
1958
1907
|
if (edit.type === "insert") {
|
|
@@ -2546,54 +2495,114 @@ z.strictObject({
|
|
|
2546
2495
|
function migrateDiffSelections(input) {
|
|
2547
2496
|
return migrateNode(jsonValueSchema.parse(input));
|
|
2548
2497
|
}
|
|
2498
|
+
/** Keys whose values held retired attachments. */
|
|
2499
|
+
const ATTACHMENT_KEYS = [
|
|
2500
|
+
"source",
|
|
2501
|
+
"peek",
|
|
2502
|
+
"sources",
|
|
2503
|
+
"contextSources",
|
|
2504
|
+
"callSite"
|
|
2505
|
+
];
|
|
2506
|
+
/** A retired attachment as a selection, or undefined for anything else. */
|
|
2507
|
+
function retiredSelection(value) {
|
|
2508
|
+
if (!isStringValue(value.file)) return void 0;
|
|
2509
|
+
if (isNumberValue(value.fromLine) && isNumberValue(value.toLine)) {
|
|
2510
|
+
const side = value.side ?? value.graph ?? "head";
|
|
2511
|
+
return {
|
|
2512
|
+
file: value.file,
|
|
2513
|
+
start: {
|
|
2514
|
+
side,
|
|
2515
|
+
line: value.fromLine
|
|
2516
|
+
},
|
|
2517
|
+
end: {
|
|
2518
|
+
side,
|
|
2519
|
+
line: value.toLine
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
if (isJsonObject(value.start) && isJsonObject(value.end) && "baseLine" in value.start) {
|
|
2524
|
+
const endpoint = (row) => isNumberValue(row.headLine) ? {
|
|
2525
|
+
side: "head",
|
|
2526
|
+
line: row.headLine
|
|
2527
|
+
} : {
|
|
2528
|
+
side: "base",
|
|
2529
|
+
line: row.baseLine ?? null
|
|
2530
|
+
};
|
|
2531
|
+
return {
|
|
2532
|
+
file: value.file,
|
|
2533
|
+
start: endpoint(value.start),
|
|
2534
|
+
end: endpoint(value.end)
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2549
2538
|
function migrateNode(value, attachment = false) {
|
|
2550
2539
|
if (Array.isArray(value)) return value.map((child) => migrateNode(child, attachment));
|
|
2551
2540
|
if (!isJsonObject(value)) return value;
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
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))]));
|
|
2541
|
+
const selection = attachment ? retiredSelection(value) : void 0;
|
|
2542
|
+
if (selection) return selection;
|
|
2543
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, migrateNode(child, ATTACHMENT_KEYS.includes(key))]));
|
|
2589
2544
|
}
|
|
2590
2545
|
//#endregion
|
|
2591
2546
|
//#region src/stored-document-migration.ts
|
|
2592
2547
|
/** Upgrade a saved or shared Review document to the current block schema at the
|
|
2593
2548
|
* read/import boundary. New edits use the strict schema and never accept these
|
|
2594
|
-
* retired forms.
|
|
2549
|
+
* retired forms. One walk makes a fresh copy: retired attachments become
|
|
2550
|
+
* selections, anchors become strings, sections lose their status, and
|
|
2551
|
+
* Markdown links are spelled the current way. */
|
|
2595
2552
|
function migrateStoredDocument(input) {
|
|
2596
|
-
return
|
|
2553
|
+
return upgradeNode(input, {
|
|
2554
|
+
attachment: false,
|
|
2555
|
+
anchors: true
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
function upgradeNode(value, place) {
|
|
2559
|
+
if (isStringValue(value) || isBooleanValue(value) || value === null) return value;
|
|
2560
|
+
if (isNumberValue(value) && Number.isFinite(value)) return value;
|
|
2561
|
+
if (Array.isArray(value)) return value.map((child) => upgradeNode(child, place));
|
|
2562
|
+
if (!isJsonObject(value)) throw new TypeError("A document must be JSON.");
|
|
2563
|
+
const selection = place.attachment ? retiredSelection(value) : void 0;
|
|
2564
|
+
if (selection) return upgradeNode(selection, {
|
|
2565
|
+
attachment: false,
|
|
2566
|
+
anchors: false
|
|
2567
|
+
});
|
|
2568
|
+
const inherited = place.anchors ? value.pins ?? place.blockPins : void 0;
|
|
2569
|
+
let pins;
|
|
2570
|
+
const convert = (anchor) => {
|
|
2571
|
+
if (isStringValue(anchor)) return anchor;
|
|
2572
|
+
const parsed = selectionObjectSchema.safeParse(anchor);
|
|
2573
|
+
if (!parsed.success) return anchor;
|
|
2574
|
+
const { pins: own, ...anchorAt } = parsed.data;
|
|
2575
|
+
if (own) pins = own;
|
|
2576
|
+
return formatAnchor(anchorAt);
|
|
2577
|
+
};
|
|
2578
|
+
const entries = [];
|
|
2579
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2580
|
+
if (key === "status" && value.type === "section") continue;
|
|
2581
|
+
if (key === "markdown" && isStringValue(child)) {
|
|
2582
|
+
entries.push([key, respellLinks(child)]);
|
|
2583
|
+
continue;
|
|
2584
|
+
}
|
|
2585
|
+
const attachment = ATTACHMENT_KEYS.includes(key);
|
|
2586
|
+
if (place.anchors && ANCHOR_KEYS.includes(key)) {
|
|
2587
|
+
const anchors = upgradeNode(child, {
|
|
2588
|
+
attachment,
|
|
2589
|
+
anchors: false
|
|
2590
|
+
});
|
|
2591
|
+
entries.push([key, Array.isArray(anchors) ? anchors.map(convert) : convert(anchors)]);
|
|
2592
|
+
} else entries.push([key, upgradeNode(child, {
|
|
2593
|
+
attachment,
|
|
2594
|
+
anchors: place.anchors,
|
|
2595
|
+
blockPins: inherited
|
|
2596
|
+
})]);
|
|
2597
|
+
}
|
|
2598
|
+
const upgraded = Object.fromEntries(entries);
|
|
2599
|
+
if (pins !== void 0 && value.kind !== "ranges" && JSON.stringify(pins) !== JSON.stringify(place.blockPins)) upgraded.pins = pins;
|
|
2600
|
+
return upgraded;
|
|
2601
|
+
}
|
|
2602
|
+
/** A stored document as the store reads it: retired forms upgraded, and
|
|
2603
|
+
* lens blocks lifted out to the snapshot's own lenses. */
|
|
2604
|
+
function readStoredDocument(input) {
|
|
2605
|
+
return liftFileLenses(migrateStoredDocument(input));
|
|
2597
2606
|
}
|
|
2598
2607
|
/** Lenses saved before ranges were anchor strings. */
|
|
2599
2608
|
function migrateStoredLenses(input) {
|
|
@@ -2640,14 +2649,6 @@ function anchorStrings(value, blockPins) {
|
|
|
2640
2649
|
if (pins !== void 0 && value.kind !== "ranges" && JSON.stringify(pins) !== JSON.stringify(blockPins)) migrated.pins = pins;
|
|
2641
2650
|
return migrated;
|
|
2642
2651
|
}
|
|
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
2652
|
/** A saved `file_lens` block, from before lenses left the document. */
|
|
2652
2653
|
const legacyFileLensSchema = z.object({
|
|
2653
2654
|
type: z.literal("file_lens"),
|
|
@@ -2686,4 +2687,4 @@ function liftFileLenses(document) {
|
|
|
2686
2687
|
};
|
|
2687
2688
|
}
|
|
2688
2689
|
//#endregion
|
|
2689
|
-
export {
|
|
2690
|
+
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 };
|