@dev.fast/whiteboard 0.3.4-preview.20261019.127 → 0.3.4
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-B5-HYi1D.js → account-alias-CprZojXd.js} +8 -13
- package/dist/agent-cli-C6SPSII0.js +2 -0
- package/dist/{agent-cli-CohHf6Ua.js → agent-cli-CThN7Q59.js} +10 -15
- package/dist/{agent-client-Dtc-Kn_w.js → agent-client-BOOi7EUR.js} +3 -13
- package/dist/{authoring-tools-B7KU04f7.js → authoring-tools-BNwuMnz6.js} +27 -34
- package/dist/{background-server-C03RKxKS.js → background-server-CkvxA-6O.js} +5 -5
- package/dist/{background-server-CB9C4uoc.js → background-server-DLSDogkI.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-CJaEb7zC.js → cli-R4yfa5DT.js} +1 -1
- package/dist/{cli-runner-DGDIqh3V.js → cli-runner-Cuc9FSzu.js} +26 -26
- package/dist/cli.js +3 -4
- package/dist/{client-lKVF_Elo.js → client-BN_FDxFr.js} +47 -253
- package/dist/{fuzzy-match-BmVJJm9I.js → fuzzy-match-BO9bK37N.js} +4 -6
- package/dist/{headless-host-DRtjf9WA.js → headless-host-CVBOQyRS.js} +1 -1
- package/dist/{headless-host-XhL1X4vX.js → headless-host-Cog6SpS9.js} +140 -513
- package/dist/{local-data-Cpl4ENbJ.js → local-data-D_9tvdsp.js} +323 -2419
- package/dist/{mcp-ovmiUE_x.js → mcp-D1hziANa.js} +9 -5
- package/dist/{package-paths-BWxO-UEL.js → package-paths-C--__jEv.js} +1 -1
- package/dist/profile-CYPmvBoA.js +2 -0
- package/dist/{profile-BadSkCkI.js → profile-DokFw782.js} +8 -20
- package/dist/{remote-attach-Dd-YobM6.js → remote-attach-CEJ-kvDY.js} +4 -4
- package/dist/{remote-extensions-CNXwqK0D.js → remote-extensions-Blcjr9Ze.js} +3 -3
- package/dist/{remote-extensions-VP-kfq0C.js → remote-extensions-GN8Gf_qG.js} +1 -1
- package/dist/{remote-language-server-BXk87KCr.js → remote-language-server-BTKBQ7x4.js} +5 -5
- package/dist/{remote-uninstall-DuOzsvaD.js → remote-uninstall-DW7Gdryh.js} +6 -6
- package/dist/{request-origin-B9qCPiVB.js → request-origin-Zcn_CjX4.js} +12 -45
- package/dist/{review-app-launcher-C7el2Uoy.js → review-app-launcher-CkwLclzR.js} +2 -2
- package/dist/{review-home-paths-D8AoCdd2.js → review-home-paths-j2BRAaNv.js} +17 -19
- package/dist/{review-telemetry-BsoRMC2w.js → review-telemetry-BLNwYnLV.js} +5 -5
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-CsG2EWED.js → s3-DtJFWC0G.js} +1 -1
- package/dist/{s3-config-CjcLyFV5.js → s3-config-BTO12tCC.js} +2 -4
- package/dist/{s3-config-zz4GWh2T.js → s3-config-CSy3fx8H.js} +1 -1
- package/dist/server/desktop-host.js +76 -29
- package/dist/{server-discovery-CKjw3-6h.js → server-discovery-Bey8IqXH.js} +3 -3
- package/dist/sharing/index.d.ts +70 -484
- package/dist/sharing/index.js +1 -1
- package/dist/{src-WPaQn75m.js → src-BMPuxWv4.js} +73 -208
- package/dist/{src-BiWNbcF_.js → src-DsM3jO8G.js} +12 -89
- package/dist/{stored-document-migration-dFbewRTG.js → stored-document-migration-CChxtFAy.js} +269 -270
- package/dist/{thread-state-Do5LX10I.js → thread-state-5pYr6_zM.js} +2 -7
- package/dist/{tool-failure-DvI-gys2.js → tool-failure-C9MpbXJJ.js} +3 -10
- package/instructions/authoring.md +2 -3
- package/instructions/scratchpad.md +23 -0
- package/package.json +4 -4
- package/src/ask/agents.ts +1 -13
- package/src/ask/checkout-files.ts +5 -2
- package/src/ask/thread-state.ts +0 -4
- package/src/ask/thread.ts +0 -16
- package/src/ask/threads.ts +3 -24
- package/src/cli-install.ts +2 -13
- package/src/cli-runner.ts +1 -4
- package/src/cli.ts +1 -4
- package/src/diff-selection-migration.ts +34 -48
- package/src/lens-selection.ts +2 -38
- package/src/review-api/README.md +13 -52
- package/src/review-api/activity.ts +4 -1
- package/src/review-api/agent-cli.ts +1 -9
- package/src/review-api/anchor-quotes.ts +7 -14
- package/src/review-api/ask-history.ts +51 -137
- package/src/review-api/authoring-tools.ts +30 -45
- package/src/review-api/blocks/definition.ts +1 -1
- package/src/review-api/blocks/markdown.ts +2 -2
- package/src/review-api/document-headings.ts +27 -58
- package/src/review-api/document-text.ts +2 -31
- package/src/review-api/document.ts +34 -281
- package/src/review-api/github-provider.ts +13 -51
- package/src/review-api/http.ts +123 -702
- package/src/review-api/instructions.ts +19 -0
- package/src/review-api/local-data.ts +46 -310
- package/src/review-api/mcp.ts +11 -3
- package/src/review-api/onboarding.ts +1 -1
- package/src/review-api/profile.ts +6 -37
- package/src/review-api/public-tools.ts +2 -24
- package/src/review-api/pull-request.ts +49 -82
- package/src/review-api/request-origin.ts +0 -10
- package/src/review-api/review-progress.ts +27 -61
- package/src/review-api/store-schema.ts +29 -38
- package/src/review-api/store.ts +478 -2142
- package/src/review-api/worktree-events.ts +5 -3
- package/src/review-api/worktree-source.ts +7 -3
- package/src/review-api/worktree-structural.integration.ts +0 -72
- package/src/review-preferences.ts +88 -0
- package/src/review-stack.ts +23 -15
- package/src/runtime.ts +30 -4
- package/src/server/desktop-host.ts +1 -6
- package/src/server/desktop-server.ts +31 -1
- package/src/server/headless-host.ts +2 -0
- package/src/server/hono-http.ts +1 -2
- package/src/server/legacy-tutorial-cleanup.ts +3 -2
- package/src/server/review-gateway-list.ts +5 -3
- 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 +5 -1
- package/src/server/structural-comparisons.ts +0 -47
- package/src/sharing/export.ts +7 -36
- package/src/sharing/host.ts +14 -34
- package/src/sharing/import.ts +24 -156
- package/src/sharing/repository.ts +20 -128
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +19 -110
- package/src/ui-telemetry-events.ts +11 -33
- package/dist/agent-cli-_YyMIMRY.js +0 -2
- package/dist/lens-selection-CyJapcsl.js +0 -214
- package/dist/profile-DJvkBJNQ.js +0 -2
- package/dist/runtime-Bj2tD33R.js +0 -27
- package/src/heading-anchors.ts +0 -42
- package/src/review-api/board-order.ts +0 -144
- package/src/review-api/checkout-groups.ts +0 -560
- package/src/review-api/foreign-references.ts +0 -328
- package/src/review-api/link-preview.ts +0 -113
- package/src/review-api/scratchpad-plan-upgrade.ts +0 -250
- package/src/review-api/store-migrations.ts +0 -357
package/dist/{stored-document-migration-dFbewRTG.js → stored-document-migration-CChxtFAy.js}
RENAMED
|
@@ -1,12 +1,196 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./src-
|
|
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";
|
|
1
|
+
import { f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
|
|
2
|
+
import "./src-DsM3jO8G.js";
|
|
4
3
|
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
7
6
|
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
8
7
|
import { mathFromMarkdown } from "mdast-util-math";
|
|
9
8
|
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
|
+
}
|
|
10
194
|
/**
|
|
11
195
|
* Check whether a character code is a markdown line ending.
|
|
12
196
|
*
|
|
@@ -852,7 +1036,7 @@ const identity = { id: text$1.optional() };
|
|
|
852
1036
|
/** The repository and commits an element's anchors quote. A step, frame,
|
|
853
1037
|
* attachment or operation without them reads its block's; a block without
|
|
854
1038
|
* them reads its document's. */
|
|
855
|
-
const elementPins = sourcePinsSchema.optional().describe("Pins for the anchors here.");
|
|
1039
|
+
const elementPins = sourcePinsSchema.optional().describe("Pins for the anchors here; needed on the scratchpad.");
|
|
856
1040
|
/** The strict schema for a block's own fields; id and type are added here. */
|
|
857
1041
|
function defineBlock(type, props) {
|
|
858
1042
|
return z.strictObject({
|
|
@@ -1192,8 +1376,8 @@ const image = {
|
|
|
1192
1376
|
const markdown = {
|
|
1193
1377
|
type: "markdown",
|
|
1194
1378
|
schema: defineBlock("markdown", {
|
|
1195
|
-
markdown: text$1.describe("Safe Markdown. Repository file links must use [label](
|
|
1196
|
-
pins: sourcePinsSchema.optional().describe("Repository and commits this block's
|
|
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.")
|
|
1197
1381
|
})
|
|
1198
1382
|
};
|
|
1199
1383
|
//#endregion
|
|
@@ -1397,91 +1581,28 @@ function pinnedRepositoryIds(document) {
|
|
|
1397
1581
|
})) if (source.pins) ids.add(source.pins.repositoryId);
|
|
1398
1582
|
return ids;
|
|
1399
1583
|
}
|
|
1400
|
-
const targets = (repository) => z.discriminatedUnion("kind", [
|
|
1401
|
-
z.
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
z.
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
}),
|
|
1410
|
-
z.strictObject({
|
|
1411
|
-
kind: z.literal("commits"),
|
|
1412
|
-
...repository,
|
|
1413
|
-
head: label,
|
|
1414
|
-
base: label.optional()
|
|
1415
|
-
})
|
|
1416
|
-
]);
|
|
1417
|
-
const reviewTargetSchema = targets({ repositoryId: label });
|
|
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()])
|
|
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()
|
|
1424
1593
|
})]);
|
|
1594
|
+
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 });
|
|
1425
1597
|
const unitTypes = /* @__PURE__ */ new Set([
|
|
1426
1598
|
"step",
|
|
1427
1599
|
"flow_node",
|
|
1428
1600
|
"flow_edge"
|
|
1429
1601
|
]);
|
|
1430
1602
|
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
|
-
}
|
|
1482
1603
|
/** Inline trace quotes keep paragraph/list layout while using retained resources. */
|
|
1483
1604
|
function traceQuoteLink(href) {
|
|
1484
|
-
const match = /^
|
|
1605
|
+
const match = /^review-trace:([^#]+)#(.+)$/.exec(href);
|
|
1485
1606
|
return match ? {
|
|
1486
1607
|
traceId: decodeURIComponent(match[1]),
|
|
1487
1608
|
eventId: decodeURIComponent(match[2])
|
|
@@ -1508,8 +1629,8 @@ function resourceReferences(document) {
|
|
|
1508
1629
|
return elements(document).flatMap((block) => {
|
|
1509
1630
|
if (block.type !== "markdown") return block.type === "image" || block.type === "trace_quote" || block.type === "software_map" ? [block] : [];
|
|
1510
1631
|
return [...markdownNodes(parseMarkdown(block.markdown))].flatMap((node) => {
|
|
1511
|
-
if (node.type !== "link" || !
|
|
1512
|
-
const quote = traceQuoteLink(node.url
|
|
1632
|
+
if (node.type !== "link" || !node.url?.startsWith("review-trace:")) return [];
|
|
1633
|
+
const quote = traceQuoteLink(node.url);
|
|
1513
1634
|
if (!quote) throw new ReviewInputError("Invalid trace quote link.");
|
|
1514
1635
|
return [{
|
|
1515
1636
|
type: "trace_quote",
|
|
@@ -1524,7 +1645,7 @@ function resourceReferences(document) {
|
|
|
1524
1645
|
* `tolerant` skips malformed Markdown source links instead of rejecting, for
|
|
1525
1646
|
* content that is already stored.
|
|
1526
1647
|
*/
|
|
1527
|
-
function documentReferences(document, { tolerant = false, markdown = true
|
|
1648
|
+
function documentReferences(document, { tolerant = false, markdown = true } = {}) {
|
|
1528
1649
|
const reject = (message) => {
|
|
1529
1650
|
if (tolerant) return [];
|
|
1530
1651
|
throw new ReviewInputError(message);
|
|
@@ -1543,29 +1664,20 @@ function documentReferences(document, { tolerant = false, markdown = true, repos
|
|
|
1543
1664
|
if (element.type === "markdown" && !markdown) return [];
|
|
1544
1665
|
if (element.type === "markdown") return [...markdownNodes(parseMarkdown(element.markdown))].flatMap((node) => {
|
|
1545
1666
|
if (node.type !== "link") return [];
|
|
1546
|
-
const href =
|
|
1547
|
-
if (
|
|
1548
|
-
if (href.startsWith("
|
|
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}.`);
|
|
1667
|
+
const href = node.url ?? "";
|
|
1668
|
+
if (!/^review-source:/i.test(href)) {
|
|
1669
|
+
if (href.startsWith("review-trace:")) return [];
|
|
1550
1670
|
if (/^(?:https?:\/\/|mailto:|#)/i.test(href)) return [];
|
|
1551
|
-
return reject(`Unsupported Markdown link ${JSON.stringify(href)} in block ${element.id}. Use [label](
|
|
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.`);
|
|
1552
1672
|
}
|
|
1553
1673
|
let target;
|
|
1554
1674
|
try {
|
|
1555
|
-
target = decodeURIComponent(href.slice(
|
|
1675
|
+
target = decodeURIComponent(href.slice(14));
|
|
1556
1676
|
} catch {
|
|
1557
1677
|
return reject("Invalid URL encoding in source link.");
|
|
1558
1678
|
}
|
|
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}.`);
|
|
1567
1679
|
const anchor = target.replace(/^(head|base|diff)\//i, (side) => side.toLowerCase());
|
|
1568
|
-
if (!parseAnchor(anchor)) return reject("Use
|
|
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.");
|
|
1569
1681
|
return select(anchor, element.pins).map((source) => ({
|
|
1570
1682
|
id: `${element.id}:${node.url}`,
|
|
1571
1683
|
source
|
|
@@ -1626,58 +1738,6 @@ function documentReferences(document, { tolerant = false, markdown = true, repos
|
|
|
1626
1738
|
}
|
|
1627
1739
|
/** All authored attachments, including code peeks, select the aligned diff. */
|
|
1628
1740
|
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
|
-
}
|
|
1681
1741
|
/** Per-side read coordinates for endpoint validation and retained source quotes. */
|
|
1682
1742
|
function sourceReferences(document, options = {}) {
|
|
1683
1743
|
return selectionReferences(document, options).flatMap((ref) => sourceAnchors(ref.source).map((source) => ({
|
|
@@ -1686,10 +1746,8 @@ function sourceReferences(document, options = {}) {
|
|
|
1686
1746
|
})));
|
|
1687
1747
|
}
|
|
1688
1748
|
/** Includes lenses and maps without inline ranges. */
|
|
1689
|
-
|
|
1690
|
-
|
|
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"));
|
|
1749
|
+
function hasCodeReferences(review) {
|
|
1750
|
+
return Boolean(sourceReferences(review.document).length || review.lenses?.length || resourceReferences(review.document).some((block) => block.type === "software_map"));
|
|
1693
1751
|
}
|
|
1694
1752
|
const documentSchema = z.array(blockSchema);
|
|
1695
1753
|
/** Discriminated by type, like blockSchema, so a malformed insert reports
|
|
@@ -1852,27 +1910,20 @@ function adoptFlowUnits(document, allocate) {
|
|
|
1852
1910
|
/** Mutate a private candidate. Only the store owns allocation and commits. */
|
|
1853
1911
|
/** Assign server ids to an element tree that arrives without any. */
|
|
1854
1912
|
function assignFreshIds(element, allocate) {
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
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) {
|
|
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);
|
|
1863
1916
|
const assign = (item, prefix) => {
|
|
1864
|
-
if (item.id
|
|
1865
|
-
|
|
1917
|
+
if (item.id !== void 0) throw new ReviewInputError("IDs are assigned by the server.");
|
|
1918
|
+
item.id = allocate(prefix);
|
|
1866
1919
|
};
|
|
1867
|
-
assign(element, idPrefix(element));
|
|
1868
|
-
for (const child of children(element)) assignIds(child, allocate, fresh);
|
|
1869
1920
|
if (element.type === "call_stack_diff") for (const frame of [...element.base, ...element.head]) assign(frame, "frame");
|
|
1870
1921
|
if (element.type === "database_lens") for (const useCase of element.useCases) {
|
|
1871
1922
|
assign(useCase, "case");
|
|
1872
1923
|
for (const op of useCase.operations) assign(op, "operation");
|
|
1873
1924
|
}
|
|
1874
1925
|
}
|
|
1875
|
-
function applyEdit(document, edit, allocate) {
|
|
1926
|
+
function applyEdit(document, edit, allocate, options = {}) {
|
|
1876
1927
|
adoptFlowUnits(document, allocate);
|
|
1877
1928
|
const locate = (id) => {
|
|
1878
1929
|
const find = (siblings) => {
|
|
@@ -1901,7 +1952,7 @@ function applyEdit(document, edit, allocate) {
|
|
|
1901
1952
|
if (afterId === element.id) throw new ReviewInputError("An element cannot follow itself.");
|
|
1902
1953
|
if (afterId !== void 0 && !siblings.some((s) => s.id === afterId)) throw new ReviewInputError("afterId must identify a sibling.");
|
|
1903
1954
|
if (from) from.splice(from.indexOf(element), 1);
|
|
1904
|
-
const index = afterId
|
|
1955
|
+
const index = afterId !== void 0 ? siblings.findIndex((s) => s.id === afterId) + 1 : !parent && !from && options.placement === "first" ? 0 : siblings.length;
|
|
1905
1956
|
siblings.splice(index, 0, element);
|
|
1906
1957
|
};
|
|
1907
1958
|
if (edit.type === "insert") {
|
|
@@ -2495,114 +2546,54 @@ z.strictObject({
|
|
|
2495
2546
|
function migrateDiffSelections(input) {
|
|
2496
2547
|
return migrateNode(jsonValueSchema.parse(input));
|
|
2497
2548
|
}
|
|
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
|
-
}
|
|
2538
2549
|
function migrateNode(value, attachment = false) {
|
|
2539
2550
|
if (Array.isArray(value)) return value.map((child) => migrateNode(child, attachment));
|
|
2540
2551
|
if (!isJsonObject(value)) return value;
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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))]));
|
|
2544
2589
|
}
|
|
2545
2590
|
//#endregion
|
|
2546
2591
|
//#region src/stored-document-migration.ts
|
|
2547
2592
|
/** Upgrade a saved or shared Review document to the current block schema at the
|
|
2548
2593
|
* read/import boundary. New edits use the strict schema and never accept these
|
|
2549
|
-
* retired forms.
|
|
2550
|
-
* selections, anchors become strings, sections lose their status, and
|
|
2551
|
-
* Markdown links are spelled the current way. */
|
|
2594
|
+
* retired forms. */
|
|
2552
2595
|
function migrateStoredDocument(input) {
|
|
2553
|
-
return
|
|
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));
|
|
2596
|
+
return anchorStrings(dropSectionStatus(migrateDiffSelections(input)));
|
|
2606
2597
|
}
|
|
2607
2598
|
/** Lenses saved before ranges were anchor strings. */
|
|
2608
2599
|
function migrateStoredLenses(input) {
|
|
@@ -2649,6 +2640,14 @@ function anchorStrings(value, blockPins) {
|
|
|
2649
2640
|
if (pins !== void 0 && value.kind !== "ranges" && JSON.stringify(pins) !== JSON.stringify(blockPins)) migrated.pins = pins;
|
|
2650
2641
|
return migrated;
|
|
2651
2642
|
}
|
|
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
|
+
}
|
|
2652
2651
|
/** A saved `file_lens` block, from before lenses left the document. */
|
|
2653
2652
|
const legacyFileLensSchema = z.object({
|
|
2654
2653
|
type: z.literal("file_lens"),
|
|
@@ -2687,4 +2686,4 @@ function liftFileLenses(document) {
|
|
|
2687
2686
|
};
|
|
2688
2687
|
}
|
|
2689
2688
|
//#endregion
|
|
2690
|
-
export {
|
|
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 };
|