@dev.fast/whiteboard 0.3.4-preview.20261019.126 → 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
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { type JsonValue, isStringValue } from "@dev.fast/json";
|
|
2
1
|
import {
|
|
3
2
|
type Anchor,
|
|
4
3
|
type LensSource,
|
|
5
|
-
commitLinkTarget,
|
|
6
4
|
parseAnchor,
|
|
7
5
|
selectionProblem,
|
|
8
6
|
sourceAnchors,
|
|
@@ -146,11 +144,6 @@ const targets = <Repository extends Record<string, z.ZodType>>(
|
|
|
146
144
|
repository: Repository,
|
|
147
145
|
) =>
|
|
148
146
|
z.discriminatedUnion("kind", [
|
|
149
|
-
/** The project of the repository, no change: a plan. */
|
|
150
|
-
z.strictObject({
|
|
151
|
-
kind: z.literal("project"),
|
|
152
|
-
...repository,
|
|
153
|
-
}),
|
|
154
147
|
z.strictObject({
|
|
155
148
|
kind: z.literal("worktree"),
|
|
156
149
|
...repository,
|
|
@@ -166,16 +159,8 @@ const targets = <Repository extends Record<string, z.ZodType>>(
|
|
|
166
159
|
|
|
167
160
|
export const reviewTargetSchema = targets({ repositoryId: label });
|
|
168
161
|
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
export const pathTargetSchema = z.discriminatedUnion("kind", [
|
|
172
|
-
...targets({ repositoryPath: label }).options,
|
|
173
|
-
z.strictObject({
|
|
174
|
-
kind: z.literal("pullRequest"),
|
|
175
|
-
repositoryPath: label,
|
|
176
|
-
pullRequest: z.union([label, z.number().int().positive()]),
|
|
177
|
-
}),
|
|
178
|
-
]);
|
|
162
|
+
/** How agents name a target: by the checkout's path, registered on acceptance. */
|
|
163
|
+
export const pathTargetSchema = targets({ repositoryPath: label });
|
|
179
164
|
|
|
180
165
|
export type ReviewTarget = z.infer<typeof reviewTargetSchema>;
|
|
181
166
|
|
|
@@ -190,99 +175,11 @@ const unitTypes = new Set(["step", "flow_node", "flow_edge"]);
|
|
|
190
175
|
export const isUnit = (element: Element): element is Unit =>
|
|
191
176
|
unitTypes.has(element.type);
|
|
192
177
|
|
|
193
|
-
/** Rewrites old review-source: and review-trace: links to their whiteboard: form. */
|
|
194
|
-
export function canonicalLink(href: string): string {
|
|
195
|
-
return href.replace(
|
|
196
|
-
/^review-(source|trace):/i,
|
|
197
|
-
(_, kind: string) => `whiteboard:${kind.toLowerCase()}/`,
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Markdown link destinations and definitions that begin with `target`, a
|
|
202
|
-
* regex source; the match's first group is what comes before it. */
|
|
203
|
-
const linkDestination = (target: string) =>
|
|
204
|
-
new RegExp(String.raw`(\]\(\s*<?|^\s*\[[^\]\n]+\]:\s*<?)` + target, "gim");
|
|
205
|
-
|
|
206
|
-
/** Every Markdown text in `value`, rewritten. */
|
|
207
|
-
function rewriteMarkdown<T>(
|
|
208
|
-
value: T,
|
|
209
|
-
rewrite: (markdown: string) => string,
|
|
210
|
-
): T {
|
|
211
|
-
// SAFETY: a JSON round trip of stored or parsed JSON, rewriting only
|
|
212
|
-
// Markdown text.
|
|
213
|
-
return JSON.parse(JSON.stringify(value), (key, field: JsonValue) =>
|
|
214
|
-
key === "markdown" && isStringValue(field) ? rewrite(field) : field,
|
|
215
|
-
) as T;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const retiredLinks = linkDestination("review-(source|trace):");
|
|
219
|
-
|
|
220
|
-
/** Rewrites old link prefixes in Markdown link destinations and definitions. */
|
|
221
|
-
export function respellLinks(markdown: string): string {
|
|
222
|
-
return markdown.replace(
|
|
223
|
-
retiredLinks,
|
|
224
|
-
(_, before: string, kind: string) =>
|
|
225
|
-
`${before}whiteboard:${kind.toLowerCase()}/`,
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/** Every Markdown text in `document` with its links spelled the current
|
|
230
|
-
* way. */
|
|
231
|
-
export function respellDocumentLinks<T>(document: T): T {
|
|
232
|
-
return rewriteMarkdown(document, respellLinks);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function respellEditLinks(edit: Edit): Edit {
|
|
236
|
-
return rewriteMarkdown(edit, respellLinks);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/** Names a block from outside its board. Block ids are unique only within a board. */
|
|
240
|
-
export type BlockRef = { board: string; block: string };
|
|
241
|
-
|
|
242
|
-
export type BoardRef = BlockRef | { board: string; block?: undefined };
|
|
243
|
-
|
|
244
|
-
/** Parses whiteboard:board/<id> or whiteboard:board/<id>#<blockId>. */
|
|
245
|
-
export function boardLink(href: string): BoardRef | undefined {
|
|
246
|
-
const match = /^whiteboard:board\/([^#/]+)(?:#(.+))?$/.exec(href);
|
|
247
|
-
|
|
248
|
-
if (!match) return undefined;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
const board = decodeURIComponent(match[1]!);
|
|
252
|
-
|
|
253
|
-
return match[2] === undefined
|
|
254
|
-
? { board }
|
|
255
|
-
: { board, block: decodeURIComponent(match[2]) };
|
|
256
|
-
} catch {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** A link from a block to a board or one of its blocks. */
|
|
262
|
-
export interface BoardLinkRef {
|
|
263
|
-
from: BlockRef;
|
|
264
|
-
to: BoardRef;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Every board link in a document. Malformed links are skipped; writes reject them. */
|
|
268
|
-
export function boardLinks(board: string, document: Block[]): BoardLinkRef[] {
|
|
269
|
-
return elements(document).flatMap((element) =>
|
|
270
|
-
element.type === "markdown"
|
|
271
|
-
? [...markdownNodes(parseMarkdown(element.markdown))].flatMap((node) => {
|
|
272
|
-
const to =
|
|
273
|
-
node.type === "link" ? boardLink(node.url ?? "") : undefined;
|
|
274
|
-
|
|
275
|
-
return to ? [{ from: { board, block: element.id! }, to }] : [];
|
|
276
|
-
})
|
|
277
|
-
: [],
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
178
|
/** Inline trace quotes keep paragraph/list layout while using retained resources. */
|
|
282
179
|
export function traceQuoteLink(
|
|
283
180
|
href: string,
|
|
284
181
|
): { traceId: string; eventId: string } | undefined {
|
|
285
|
-
const match = /^
|
|
182
|
+
const match = /^review-trace:([^#]+)#(.+)$/.exec(href);
|
|
286
183
|
|
|
287
184
|
return match
|
|
288
185
|
? {
|
|
@@ -318,12 +215,9 @@ export function resourceReferences(document: Block[]): Block[] {
|
|
|
318
215
|
|
|
319
216
|
return [...markdownNodes(parseMarkdown(block.markdown))].flatMap(
|
|
320
217
|
(node): Block[] => {
|
|
321
|
-
if (
|
|
322
|
-
node.type !== "link" ||
|
|
323
|
-
!canonicalLink(node.url ?? "").startsWith("whiteboard:trace/")
|
|
324
|
-
)
|
|
218
|
+
if (node.type !== "link" || !node.url?.startsWith("review-trace:"))
|
|
325
219
|
return [];
|
|
326
|
-
const quote = traceQuoteLink(node.url
|
|
220
|
+
const quote = traceQuoteLink(node.url);
|
|
327
221
|
|
|
328
222
|
if (!quote) throw new ReviewInputError("Invalid trace quote link.");
|
|
329
223
|
|
|
@@ -343,13 +237,7 @@ function documentReferences(
|
|
|
343
237
|
{
|
|
344
238
|
tolerant = false,
|
|
345
239
|
markdown = true,
|
|
346
|
-
|
|
347
|
-
}: {
|
|
348
|
-
tolerant?: boolean;
|
|
349
|
-
markdown?: boolean;
|
|
350
|
-
/** The board's repository, which commit links read from. */
|
|
351
|
-
repositoryId?: string;
|
|
352
|
-
} = {},
|
|
240
|
+
}: { tolerant?: boolean; markdown?: boolean } = {},
|
|
353
241
|
): {
|
|
354
242
|
id: string;
|
|
355
243
|
source: LensSource;
|
|
@@ -386,53 +274,27 @@ function documentReferences(
|
|
|
386
274
|
return [...markdownNodes(parseMarkdown(element.markdown))].flatMap(
|
|
387
275
|
(node) => {
|
|
388
276
|
if (node.type !== "link") return [];
|
|
389
|
-
const href =
|
|
277
|
+
const href = node.url ?? "";
|
|
390
278
|
|
|
391
|
-
if (
|
|
279
|
+
if (!/^review-source:/i.test(href)) {
|
|
392
280
|
// Trace links are checked by resourceReferences.
|
|
393
|
-
if (href.startsWith("
|
|
394
|
-
|
|
395
|
-
// Board links are checked against the store on write.
|
|
396
|
-
if (href.startsWith("whiteboard:board/"))
|
|
397
|
-
return boardLink(href)
|
|
398
|
-
? []
|
|
399
|
-
: reject(
|
|
400
|
-
`Use whiteboard:board/<sessionId> or whiteboard:board/<sessionId>#<blockId> to link a board, in block ${element.id}.`,
|
|
401
|
-
);
|
|
281
|
+
if (href.startsWith("review-trace:")) return [];
|
|
402
282
|
|
|
403
283
|
if (/^(?:https?:\/\/|mailto:|#)/i.test(href)) return [];
|
|
404
284
|
|
|
405
285
|
return reject(
|
|
406
|
-
`Unsupported Markdown link ${JSON.stringify(href)} in block ${element.id}. Use [label](
|
|
286
|
+
`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.`,
|
|
407
287
|
);
|
|
408
288
|
}
|
|
409
289
|
|
|
410
290
|
let target: string;
|
|
411
291
|
|
|
412
292
|
try {
|
|
413
|
-
target = decodeURIComponent(
|
|
414
|
-
href.slice("whiteboard:source/".length),
|
|
415
|
-
);
|
|
293
|
+
target = decodeURIComponent(href.slice("review-source:".length));
|
|
416
294
|
} catch {
|
|
417
295
|
return reject("Invalid URL encoding in source link.");
|
|
418
296
|
}
|
|
419
297
|
|
|
420
|
-
// A commit of the board's repository: the commit is the link's pins.
|
|
421
|
-
const commit = commitLinkTarget(target);
|
|
422
|
-
|
|
423
|
-
if (commit)
|
|
424
|
-
return repositoryId
|
|
425
|
-
? select(commit.anchor, {
|
|
426
|
-
repositoryId,
|
|
427
|
-
head: commit.commit,
|
|
428
|
-
}).map((source) => ({
|
|
429
|
-
id: `${element.id}:${node.url}`,
|
|
430
|
-
source,
|
|
431
|
-
}))
|
|
432
|
-
: reject(
|
|
433
|
-
`A commit link names a commit of the board's repository, and this board has none, in block ${element.id}.`,
|
|
434
|
-
);
|
|
435
|
-
|
|
436
298
|
// Links share the anchor grammar blocks use.
|
|
437
299
|
const anchor = target.replace(/^(head|base|diff)\//i, (side) =>
|
|
438
300
|
side.toLowerCase(),
|
|
@@ -440,7 +302,7 @@ function documentReferences(
|
|
|
440
302
|
|
|
441
303
|
if (!parseAnchor(anchor))
|
|
442
304
|
return reject(
|
|
443
|
-
"Use
|
|
305
|
+
"Use review-source:head/path#L10-L24 (or base, or diff/path#L84-R90 across sides) for a source link.",
|
|
444
306
|
);
|
|
445
307
|
|
|
446
308
|
return select(anchor, element.pins).map((source) => ({
|
|
@@ -552,108 +414,10 @@ function documentReferences(
|
|
|
552
414
|
/** All authored attachments, including code peeks, select the aligned diff. */
|
|
553
415
|
export const selectionReferences = documentReferences;
|
|
554
416
|
|
|
555
|
-
const commitLinkDestination = linkDestination(
|
|
556
|
-
String.raw`whiteboard:source\/([0-9a-f]{7,40})\/`,
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
/** The commits a document's Markdown commit links name, as written. */
|
|
560
|
-
export function commitLinkCommits(document: Block[]): string[] {
|
|
561
|
-
const commits = new Set<string>();
|
|
562
|
-
|
|
563
|
-
JSON.stringify(document, (key, value: JsonValue) => {
|
|
564
|
-
if (key === "markdown" && isStringValue(value))
|
|
565
|
-
for (const match of value.matchAll(commitLinkDestination))
|
|
566
|
-
commits.add(match[2]!);
|
|
567
|
-
|
|
568
|
-
return value;
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
return [...commits];
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/** Rewrites the commits Markdown commit links name, as a short commit
|
|
575
|
-
* becomes the full one on save. */
|
|
576
|
-
export function respellCommitLinks(
|
|
577
|
-
document: Block[],
|
|
578
|
-
commits: ReadonlyMap<string, string>,
|
|
579
|
-
): Block[] {
|
|
580
|
-
return rewriteMarkdown(document, (markdown) =>
|
|
581
|
-
markdown.replace(
|
|
582
|
-
commitLinkDestination,
|
|
583
|
-
(link, before: string, commit: string) =>
|
|
584
|
-
`${before}whiteboard:source/${commits.get(commit) ?? commit}/`,
|
|
585
|
-
),
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/** Blocks that carry pins of their own for the references in them. */
|
|
590
|
-
const pinnedBlockTypes = new Set([
|
|
591
|
-
"markdown",
|
|
592
|
-
"code_peek",
|
|
593
|
-
"sequence",
|
|
594
|
-
"flow_diagram",
|
|
595
|
-
"call_stack_diff",
|
|
596
|
-
"database_lens",
|
|
597
|
-
]);
|
|
598
|
-
|
|
599
|
-
const sideLinkDestination = linkDestination(
|
|
600
|
-
String.raw`whiteboard:source\/(head|base)\/`,
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* Makes every reference that reads at the document's pins carry them
|
|
605
|
-
* itself, for a board about to lose its pins: Markdown head/ and base/
|
|
606
|
-
* links become commit links, and a block whose references still lean on
|
|
607
|
-
* the document's pins (any anchor, or a diff/ link) takes them as its own.
|
|
608
|
-
* Nothing changes what it points at.
|
|
609
|
-
*/
|
|
610
|
-
export function pinDocumentReferences(document: Block[], pins: Pins): Block[] {
|
|
611
|
-
const own: SourcePins = {
|
|
612
|
-
repositoryId: pins.repositoryId,
|
|
613
|
-
base: pins.base,
|
|
614
|
-
head: pins.head,
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
const pinned = structuredClone(document);
|
|
618
|
-
|
|
619
|
-
for (const element of elements(pinned)) {
|
|
620
|
-
if (!pinnedBlockTypes.has(element.type)) continue;
|
|
621
|
-
|
|
622
|
-
// SAFETY: every pinned block type has optional source pins of its own.
|
|
623
|
-
const block = element as Block & { pins?: SourcePins };
|
|
624
|
-
|
|
625
|
-
if (block.pins) continue;
|
|
626
|
-
|
|
627
|
-
if (block.type === "markdown")
|
|
628
|
-
block.markdown = respellLinks(block.markdown).replace(
|
|
629
|
-
sideLinkDestination,
|
|
630
|
-
(_, before: string, side: string) =>
|
|
631
|
-
`${before}whiteboard:source/${side.toLowerCase() === "base" ? pins.base : pins.head}/`,
|
|
632
|
-
);
|
|
633
|
-
|
|
634
|
-
if (
|
|
635
|
-
documentReferences([block], { tolerant: true }).some(
|
|
636
|
-
(reference) => !reference.source.pins,
|
|
637
|
-
)
|
|
638
|
-
)
|
|
639
|
-
block.pins = own;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
return pinned;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/** The repository a board's commit links read from: its target's. */
|
|
646
|
-
export function boardRepositoryId(board: {
|
|
647
|
-
target?: { repositoryId: string };
|
|
648
|
-
pins?: { repositoryId: string };
|
|
649
|
-
}): string | undefined {
|
|
650
|
-
return board.target?.repositoryId ?? board.pins?.repositoryId;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
417
|
/** Per-side read coordinates for endpoint validation and retained source quotes. */
|
|
654
418
|
export function sourceReferences(
|
|
655
419
|
document: Block[],
|
|
656
|
-
options: { tolerant?: boolean
|
|
420
|
+
options: { tolerant?: boolean } = {},
|
|
657
421
|
) {
|
|
658
422
|
return selectionReferences(document, options).flatMap((ref) =>
|
|
659
423
|
sourceAnchors(ref.source).map((source) => ({ ...ref, source })),
|
|
@@ -661,16 +425,12 @@ export function sourceReferences(
|
|
|
661
425
|
}
|
|
662
426
|
|
|
663
427
|
/** Includes lenses and maps without inline ranges. */
|
|
664
|
-
|
|
665
|
-
* with pins of its own names its commits itself. */
|
|
666
|
-
export function readsAtBoardPins(review: {
|
|
428
|
+
export function hasCodeReferences(review: {
|
|
667
429
|
document: Block[];
|
|
668
430
|
lenses?: readonly Lens[];
|
|
669
431
|
}): boolean {
|
|
670
432
|
return Boolean(
|
|
671
|
-
sourceReferences(review.document
|
|
672
|
-
(reference) => !reference.source.pins,
|
|
673
|
-
) ||
|
|
433
|
+
sourceReferences(review.document).length ||
|
|
674
434
|
review.lenses?.length ||
|
|
675
435
|
resourceReferences(review.document).some(
|
|
676
436
|
(block) => block.type === "software_map",
|
|
@@ -754,8 +514,6 @@ export interface EditSummary {
|
|
|
754
514
|
units?: string[];
|
|
755
515
|
/** The agent whose courier draws this edit, when the host could tell. */
|
|
756
516
|
activityId?: string;
|
|
757
|
-
/** The Ask thread that made this edit, if any. */
|
|
758
|
-
threadId?: string;
|
|
759
517
|
}
|
|
760
518
|
|
|
761
519
|
/** A component an edit wrote, named so the author can address it. */
|
|
@@ -953,33 +711,18 @@ export function assignFreshIds(
|
|
|
953
711
|
element: Element,
|
|
954
712
|
allocate: (prefix: string) => string,
|
|
955
713
|
): void {
|
|
956
|
-
|
|
957
|
-
|
|
714
|
+
if (element.id !== undefined)
|
|
715
|
+
throw new ReviewInputError("IDs are assigned by the server.");
|
|
716
|
+
element.id = allocate(idPrefix(element));
|
|
958
717
|
|
|
959
|
-
|
|
960
|
-
* rest: a person's save names the blocks it kept and leaves new ones bare. */
|
|
961
|
-
export function assignMissingIds(
|
|
962
|
-
element: Element,
|
|
963
|
-
allocate: (prefix: string) => string,
|
|
964
|
-
): void {
|
|
965
|
-
assignIds(element, allocate, false);
|
|
966
|
-
}
|
|
718
|
+
for (const child of children(element)) assignFreshIds(child, allocate);
|
|
967
719
|
|
|
968
|
-
function assignIds(
|
|
969
|
-
element: Element,
|
|
970
|
-
allocate: (prefix: string) => string,
|
|
971
|
-
fresh: boolean,
|
|
972
|
-
): void {
|
|
973
720
|
const assign = (item: { id?: string }, prefix: string) => {
|
|
974
|
-
if (item.id
|
|
975
|
-
else if (fresh)
|
|
721
|
+
if (item.id !== undefined)
|
|
976
722
|
throw new ReviewInputError("IDs are assigned by the server.");
|
|
723
|
+
item.id = allocate(prefix);
|
|
977
724
|
};
|
|
978
725
|
|
|
979
|
-
assign(element, idPrefix(element));
|
|
980
|
-
|
|
981
|
-
for (const child of children(element)) assignIds(child, allocate, fresh);
|
|
982
|
-
|
|
983
726
|
if (element.type === "call_stack_diff")
|
|
984
727
|
for (const frame of [...element.base, ...element.head])
|
|
985
728
|
assign(frame, "frame");
|
|
@@ -992,10 +735,16 @@ function assignIds(
|
|
|
992
735
|
}
|
|
993
736
|
}
|
|
994
737
|
|
|
738
|
+
export interface ApplyEditOptions {
|
|
739
|
+
/** Where a root-level insert with no placement lands: the end by default. */
|
|
740
|
+
placement?: "first" | "last";
|
|
741
|
+
}
|
|
742
|
+
|
|
995
743
|
export function applyEdit(
|
|
996
744
|
document: Block[],
|
|
997
745
|
edit: Edit,
|
|
998
746
|
allocate: (prefix: string) => string,
|
|
747
|
+
options: ApplyEditOptions = {},
|
|
999
748
|
): Applied {
|
|
1000
749
|
adoptFlowUnits(document, allocate);
|
|
1001
750
|
|
|
@@ -1059,10 +808,14 @@ export function applyEdit(
|
|
|
1059
808
|
// Resolve the destination before detaching, then compute its final position.
|
|
1060
809
|
if (from) from.splice(from.indexOf(element), 1);
|
|
1061
810
|
|
|
811
|
+
// A running log reads newest first, so a root insert with no placement
|
|
812
|
+
// may lead the document; a move or a unit inside a diagram never does.
|
|
1062
813
|
const index =
|
|
1063
|
-
afterId
|
|
1064
|
-
? siblings.
|
|
1065
|
-
:
|
|
814
|
+
afterId !== undefined
|
|
815
|
+
? siblings.findIndex((s) => s.id === afterId) + 1
|
|
816
|
+
: !parent && !from && options.placement === "first"
|
|
817
|
+
? 0
|
|
818
|
+
: siblings.length;
|
|
1066
819
|
|
|
1067
820
|
siblings.splice(index, 0, element);
|
|
1068
821
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { parseGitRemote } from "@dev.fast/local-vcs";
|
|
2
|
-
import type { PullRequestState } from "@dev.fast/review-protocol";
|
|
3
2
|
import { errorMessage } from "@dev.fast/trace-core";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
|
|
6
5
|
import { ReviewInputError } from "./document.js";
|
|
7
6
|
import type { PullRequestProvider } from "./provider.js";
|
|
8
|
-
import {
|
|
7
|
+
import { firstLine } from "./pull-request.js";
|
|
9
8
|
|
|
10
9
|
const GH_TIMEOUT_MS = 20_000;
|
|
11
10
|
|
|
@@ -21,24 +20,8 @@ const metadataSchema = z.object({
|
|
|
21
20
|
baseRefOid: z.string().optional(),
|
|
22
21
|
headRefName: z.string().min(1),
|
|
23
22
|
isCrossRepository: z.boolean(),
|
|
24
|
-
state: z.enum(["OPEN", "CLOSED", "MERGED"]).optional(),
|
|
25
|
-
isDraft: z.boolean().optional(),
|
|
26
23
|
});
|
|
27
24
|
|
|
28
|
-
/** gh's state and draft flag as one state. */
|
|
29
|
-
function ghState(
|
|
30
|
-
state: "OPEN" | "CLOSED" | "MERGED" | undefined,
|
|
31
|
-
isDraft: boolean | undefined,
|
|
32
|
-
): PullRequestState | undefined {
|
|
33
|
-
if (state === "MERGED") return "merged";
|
|
34
|
-
|
|
35
|
-
if (state === "CLOSED") return "closed";
|
|
36
|
-
|
|
37
|
-
if (state === "OPEN") return isDraft ? "draft" : "open";
|
|
38
|
-
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
25
|
function unreadable(url: string, ghFailure: string, fallback: string) {
|
|
43
26
|
return new ReviewInputError(
|
|
44
27
|
`Could not read ${url}: gh failed (${ghFailure}) and ${fallback}. Run \`gh auth status\` and sign in with an account that can read the repository, then retry.`,
|
|
@@ -71,27 +54,24 @@ export const githubProvider: PullRequestProvider = {
|
|
|
71
54
|
[
|
|
72
55
|
"pr",
|
|
73
56
|
"view",
|
|
74
|
-
|
|
57
|
+
String(number),
|
|
75
58
|
"--repo",
|
|
76
|
-
|
|
59
|
+
`${host}/${slug}`,
|
|
77
60
|
"--json",
|
|
78
|
-
"number,title,baseRefName,baseRefOid,headRefName,isCrossRepository
|
|
61
|
+
"number,title,baseRefName,baseRefOid,headRefName,isCrossRepository",
|
|
79
62
|
],
|
|
80
63
|
{ timeoutMs: GH_TIMEOUT_MS },
|
|
81
64
|
);
|
|
82
65
|
|
|
83
|
-
const { headRefName, isCrossRepository,
|
|
66
|
+
const { headRefName, isCrossRepository, ...metadata } =
|
|
84
67
|
metadataSchema.parse(JSON.parse(stdout));
|
|
85
68
|
|
|
86
|
-
const pullRequestState = ghState(state, isDraft);
|
|
87
|
-
|
|
88
69
|
return {
|
|
89
70
|
host,
|
|
90
71
|
slug,
|
|
91
72
|
headRef,
|
|
92
73
|
...metadata,
|
|
93
74
|
headBranch: isCrossRepository ? undefined : headRefName,
|
|
94
|
-
...(pullRequestState && { state: pullRequestState }),
|
|
95
75
|
};
|
|
96
76
|
} catch (error) {
|
|
97
77
|
ghFailure = firstLine(errorMessage(error));
|
|
@@ -130,21 +110,15 @@ export const githubProvider: PullRequestProvider = {
|
|
|
130
110
|
404,
|
|
131
111
|
);
|
|
132
112
|
|
|
133
|
-
if (response.status === 403 || response.status === 429)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
response.status === 429
|
|
142
|
-
? "the unauthenticated GitHub API rate limit is exhausted"
|
|
143
|
-
: "the GitHub API refused the request",
|
|
144
|
-
).message,
|
|
145
|
-
reset > 0 ? reset * 1000 : Date.now() + 15 * 60_000,
|
|
113
|
+
if (response.status === 403 || response.status === 429)
|
|
114
|
+
throw unreadable(
|
|
115
|
+
url,
|
|
116
|
+
ghFailure,
|
|
117
|
+
response.headers.get("x-ratelimit-remaining") === "0" ||
|
|
118
|
+
response.status === 429
|
|
119
|
+
? "the unauthenticated GitHub API rate limit is exhausted"
|
|
120
|
+
: "the GitHub API refused the request",
|
|
146
121
|
);
|
|
147
|
-
}
|
|
148
122
|
|
|
149
123
|
if (!response.ok)
|
|
150
124
|
throw unreadable(
|
|
@@ -166,9 +140,6 @@ export const githubProvider: PullRequestProvider = {
|
|
|
166
140
|
ref: z.string().min(1),
|
|
167
141
|
repo: z.object({ id: z.number() }).nullable(),
|
|
168
142
|
}),
|
|
169
|
-
state: z.enum(["open", "closed"]).optional(),
|
|
170
|
-
draft: z.boolean().optional(),
|
|
171
|
-
merged_at: z.string().nullable().optional(),
|
|
172
143
|
})
|
|
173
144
|
.safeParse(await response.json().catch(() => undefined));
|
|
174
145
|
|
|
@@ -187,15 +158,6 @@ export const githubProvider: PullRequestProvider = {
|
|
|
187
158
|
parsed.data.head.repo?.id === parsed.data.base.repo.id
|
|
188
159
|
? parsed.data.head.ref
|
|
189
160
|
: undefined,
|
|
190
|
-
...(parsed.data.state && {
|
|
191
|
-
state: parsed.data.merged_at
|
|
192
|
-
? "merged"
|
|
193
|
-
: parsed.data.state === "closed"
|
|
194
|
-
? "closed"
|
|
195
|
-
: parsed.data.draft
|
|
196
|
-
? "draft"
|
|
197
|
-
: "open",
|
|
198
|
-
}),
|
|
199
161
|
};
|
|
200
162
|
},
|
|
201
163
|
|