@dev.fast/whiteboard 0.5.2-preview.20261104.147 → 0.5.2-preview.20261109.149

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/{account-alias-DXpd0Px8.js → account-alias-Cs5YqXRj.js} +4 -4
  2. package/dist/agent-cli-DdUAmdca.js +2 -0
  3. package/dist/{agent-cli-COrZtFks.js → agent-cli-a5J-juuu.js} +8 -8
  4. package/dist/{agent-client-BFtYm3Ng.js → agent-client-CgnEo6lf.js} +3 -3
  5. package/dist/{authoring-tools-D1avYp8-.js → authoring-tools-BgZvnbjN.js} +3 -3
  6. package/dist/{background-server-HOJozY99.js → background-server-D8SaPvuC.js} +1 -1
  7. package/dist/{background-server-BiRSqjxG.js → background-server-DHEcsFwH.js} +4 -4
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-CSlwGRvH.js → cli-Bq5zVqlc.js} +1 -1
  10. package/dist/{cli-runner-CuffXA0Q.js → cli-runner-hL2efcxb.js} +21 -21
  11. package/dist/cli.js +1 -1
  12. package/dist/{fuzzy-match-DEo941za.js → fuzzy-match-DNyQDN3P.js} +2 -2
  13. package/dist/{headless-host-BCkXVVpU.js → headless-host-CGyfb5mS.js} +1 -1
  14. package/dist/{headless-host-CIuJJjmM.js → headless-host-bb09yVkX.js} +17 -15
  15. package/dist/{import-CjcW7EIQ.js → import-_XAsWqen.js} +24 -139
  16. package/dist/{local-data-B5PBZ0Ge.js → local-data-ZQIkF5k1.js} +81 -70
  17. package/dist/{mcp-Ba3WvhUh.js → mcp-B-J8P5xm.js} +4 -4
  18. package/dist/{package-paths-Dk7MOXxN.js → package-paths-wobHueCx.js} +1 -1
  19. package/dist/profile-Bi2SbpSI.js +2 -0
  20. package/dist/{profile-CNMhLUhf.js → profile-D-SbO3p9.js} +2 -2
  21. package/dist/{remote-attach-DkZoYBHV.js → remote-attach-DyIESbbf.js} +4 -4
  22. package/dist/{remote-extensions-BvV29_gk.js → remote-extensions-BHHFPyYW.js} +1 -1
  23. package/dist/{remote-extensions-L95HNwIH.js → remote-extensions-gTMcRBg-.js} +2 -2
  24. package/dist/{remote-language-server-l4ConTfj.js → remote-language-server-Pp261dqJ.js} +4 -4
  25. package/dist/{remote-uninstall-BAQM7cU8.js → remote-uninstall-D_hSuBik.js} +5 -5
  26. package/dist/{request-origin-DZexKlc4.js → request-origin-ENYd0Ymr.js} +1 -1
  27. package/dist/{review-app-launcher-B5F10y_4.js → review-app-launcher-DR5vYGEl.js} +1 -1
  28. package/dist/{review-home-paths-BmZX2qiz.js → review-home-paths-B8909D07.js} +19 -19
  29. package/dist/{review-telemetry-kQ7esDRE.js → review-telemetry-CxNvwdDS.js} +4 -4
  30. package/dist/server/desktop-host.js +74 -16
  31. package/dist/{server-discovery-DYY_xeG9.js → server-discovery-Fme7f3fb.js} +2 -2
  32. package/dist/sharing/index.d.ts +21 -21
  33. package/dist/sharing/index.js +1 -1
  34. package/dist/{src-DirPN_Qh.js → src-BPrngiWX.js} +1769 -1
  35. package/dist/{stored-document-migration-vg0NAzXG.js → stored-document-migration-BQcUyXO8.js} +4 -73
  36. package/dist/{tool-failure-BLBwcl3H.js → tool-failure-Cgjidoud.js} +5 -5
  37. package/package.json +5 -6
  38. package/src/review-api/comparison-coverage.ts +6 -20
  39. package/src/review-api/http.ts +20 -16
  40. package/src/review-api/review-progress.ts +38 -123
  41. package/src/review-api/store.ts +4 -6
  42. package/src/server/review-gateway-pushes.ts +5 -1
  43. package/src/server/review-gateway.ts +15 -1
  44. package/src/software-map-diff-counts.ts +4 -2
  45. package/src/viewed-coverage.ts +9 -252
  46. package/dist/agent-cli-x6-Eh1S-.js +0 -2
  47. package/dist/profile-BstQkIY0.js +0 -2
  48. package/src/review-api/file-lenses.ts +0 -125
  49. package/src/unified-diff.ts +0 -71
  50. package/dist/{s3-CsG2EWED.js → s3-d8RGPKwt.js} +1 -1
@@ -1,82 +1,13 @@
1
1
  import { C as renderVcsArgs, v as parseGitRemote, w as runVcs } from "./src-Y30gweiK.js";
2
2
  import { d as isBooleanValue, f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
3
- import { C as respellLinks, H as lensTargetsSchema, W as label, et as formatAnchor, ot as unionIntervals, st as ReviewInputError } from "./src-DirPN_Qh.js";
4
- import "./src-B-91Rd2d.js";
5
- import "./review-home-paths-BmZX2qiz.js";
3
+ import { E as respellLinks, G as lensTargetsSchema, X as label, at as formatAnchor, mt as ReviewInputError } from "./src-BPrngiWX.js";
4
+ import "./review-home-paths-B8909D07.js";
6
5
  import { t as errorMessage } from "./error-message-OtiDonty.js";
6
+ import "./src-B-91Rd2d.js";
7
7
  import { randomUUID } from "node:crypto";
8
8
  import { z } from "zod";
9
9
  import { execFile } from "node:child_process";
10
10
  import { promisify } from "node:util";
11
- //#region src/viewed-coverage.ts
12
- const coverageSchema = z.object({
13
- base: z.array(z.tuple([z.number().int().nonnegative(), z.number().int().nonnegative()])),
14
- head: z.array(z.tuple([z.number().int().nonnegative(), z.number().int().nonnegative()]))
15
- });
16
- const emptyCoverage = () => ({
17
- base: [],
18
- head: []
19
- });
20
- function intersectIntervals(left, right) {
21
- const result = [];
22
- const a = unionIntervals(left), b = unionIntervals(right);
23
- let i = 0, j = 0;
24
- while (i < a.length && j < b.length) {
25
- const start = Math.max(a[i][0], b[j][0]), end = Math.min(a[i][1], b[j][1]);
26
- if (start < end) result.push([start, end]);
27
- if (a[i][1] < b[j][1]) i++;
28
- else j++;
29
- }
30
- return result;
31
- }
32
- function subtractIntervals(left, right) {
33
- const result = [];
34
- const removed = unionIntervals(right);
35
- for (const [start, end] of unionIntervals(left)) {
36
- let cursor = start;
37
- for (const [from, to] of removed) {
38
- if (to <= cursor) continue;
39
- if (from >= end) break;
40
- if (from > cursor) result.push([cursor, from]);
41
- cursor = Math.min(end, Math.max(cursor, to));
42
- }
43
- if (cursor < end) result.push([cursor, end]);
44
- }
45
- return result;
46
- }
47
- function updateCoverage(current, scope, viewed) {
48
- const apply = (side) => viewed ? unionIntervals([...current[side], ...scope[side]]) : subtractIntervals(current[side], scope[side]);
49
- return {
50
- base: apply("base"),
51
- head: apply("head")
52
- };
53
- }
54
- function scopedCoverage(file, sources) {
55
- if (!sources) return file.changed;
56
- 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]] : []));
57
- return {
58
- base: side("base"),
59
- head: side("head")
60
- };
61
- }
62
- /** Each file's own sources, grouped once. */
63
- function sourcesForFile(sources) {
64
- if (!sources) return () => void 0;
65
- const byFile = Map.groupBy(sources, (source) => source.file);
66
- return (file) => {
67
- const base = byFile.get(file.previousPath ?? file.path) ?? [];
68
- return file.previousPath && file.previousPath !== file.path ? [...base, ...byFile.get(file.path) ?? []] : base;
69
- };
70
- }
71
- function coverageSources(file, coverage = file.viewed) {
72
- return ["base", "head"].flatMap((side) => coverage[side].map(([start, end]) => ({
73
- side,
74
- file: side === "base" ? file.previousPath ?? file.path : file.path,
75
- fromLine: start + 1,
76
- toLine: end
77
- })));
78
- }
79
- //#endregion
80
11
  //#region src/evidence.ts
81
12
  /** Quote matching shared by legacy publish and JSON accept: authors paste
82
13
  * transcript text with arbitrary line wrapping, so both sides compare on
@@ -1254,4 +1185,4 @@ function liftFileLenses(document) {
1254
1185
  };
1255
1186
  }
1256
1187
  //#endregion
1257
- export { emptyCoverage as C, subtractIntervals as D, sourcesForFile as E, updateCoverage as O, coverageSources as S, scopedCoverage as T, fetchPullRequest as _, SoftwareModelValidationError as a, textIncludesQuote as b, traceSchema as c, setPullRequest as d, sourceBranchName as f, defaultPullRequestDeps as g, GitHubRefusedRead as h, readStoredDocument as i, pullRequestKey as l, resolvePullRequest as m, migrateStoredDocument as n, defineSoftwareMap as o, pullRequestProvider as p, migrateStoredLenses as r, softwareModelDataSchema as s, liftFileLenses as t, pullRequestUrl as u, remoteUrls as v, intersectIntervals as w, coverageSchema as x, azureRepositoryOf as y };
1188
+ export { fetchPullRequest as _, SoftwareModelValidationError as a, textIncludesQuote as b, traceSchema as c, setPullRequest as d, sourceBranchName as f, defaultPullRequestDeps as g, GitHubRefusedRead as h, readStoredDocument as i, pullRequestKey as l, resolvePullRequest as m, migrateStoredDocument as n, defineSoftwareMap as o, pullRequestProvider as p, migrateStoredLenses as r, softwareModelDataSchema as s, liftFileLenses as t, pullRequestUrl as u, remoteUrls as v, azureRepositoryOf as y };
@@ -1,10 +1,10 @@
1
1
  import { m as isStringValue, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import { M as checkReferences, j as blocks, st as ReviewInputError } from "./src-DirPN_Qh.js";
3
- import { l as boardTitle } from "./src-B-91Rd2d.js";
4
- import "./review-home-paths-BmZX2qiz.js";
5
- import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-BFtYm3Ng.js";
2
+ import { F as checkReferences, P as blocks, mt as ReviewInputError } from "./src-BPrngiWX.js";
6
3
  import { N as devReviewHome } from "./s3-config-CjcLyFV5.js";
7
- import { t as findReviewPackageRoot } from "./package-paths-Dk7MOXxN.js";
4
+ import "./review-home-paths-B8909D07.js";
5
+ import { t as findReviewPackageRoot } from "./package-paths-wobHueCx.js";
6
+ import { l as boardTitle } from "./src-B-91Rd2d.js";
7
+ import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-CgnEo6lf.js";
8
8
  import { m as isMissingFileError } from "./thread-state-Jxoh1hde.js";
9
9
  import { createHash } from "node:crypto";
10
10
  import { z } from "zod";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.fast/whiteboard",
3
- "version": "0.5.2-preview.20261104.147",
3
+ "version": "0.5.2-preview.20261109.149",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -53,7 +53,6 @@
53
53
  "mdast-util-gfm": "3.1.0",
54
54
  "mdast-util-math": "3.0.0",
55
55
  "micromark-extension-gfm": "3.0.0",
56
- "minimatch": "10.2.6",
57
56
  "pi-acp": "0.0.34",
58
57
  "pino": "10.3.1",
59
58
  "semver": "7.8.5",
@@ -76,12 +75,12 @@
76
75
  "tsx": "4.21.0",
77
76
  "typescript": "6.0.3",
78
77
  "vitest": "4.1.12",
79
- "@dev.fast/review-protocol": "0.0.0",
80
78
  "@dev.fast/json": "0.0.0",
81
- "@dev.fast/review-share-protocol": "0.6.0",
82
79
  "@dev.fast/trace-core": "0.0.0",
80
+ "@dev.fast/review-share-protocol": "0.6.0",
83
81
  "@dev.fast/trace-protocol": "0.5.0",
84
- "@dev.fast/whiteboard-document": "0.2.2"
82
+ "@dev.fast/review-protocol": "0.0.0",
83
+ "@dev.fast/whiteboard-document": "0.3.0"
85
84
  },
86
85
  "engines": {
87
86
  "node": ">=24 <25"
@@ -91,7 +90,7 @@
91
90
  "repo": "devdotfast/review",
92
91
  "docs": "packages/review/instructions/authoring.md"
93
92
  },
94
- "gitHead": "07deb9e521a2e372643d2d13350f9868b41fd264",
93
+ "gitHead": "4c0a0af9c57fe774cc885574b7e4236309456dc1",
95
94
  "scripts": {
96
95
  "prebuild": "pnpm run build:workspace-deps",
97
96
  "build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",
@@ -9,20 +9,18 @@ import {
9
9
  } from "@dev.fast/review-protocol";
10
10
  import {
11
11
  type AlignmentRow,
12
- type FileLineRange,
13
- type Pins,
14
- patchRows,
15
- } from "@dev.fast/whiteboard-document";
16
- import { parseUnifiedPatch } from "@review/unified-diff.js";
17
- import {
18
12
  type Coverage,
19
13
  type CoverageFile,
14
+ type FileLineRange,
20
15
  type LineInterval,
16
+ type Pins,
21
17
  emptyCoverage,
22
18
  intersectIntervals,
19
+ patchChanges,
20
+ patchRows,
23
21
  subtractIntervals,
24
22
  unionIntervals,
25
- } from "@review/viewed-coverage.js";
23
+ } from "@dev.fast/whiteboard-document";
26
24
 
27
25
  import type { LocalReviewData } from "./local-data.js";
28
26
 
@@ -60,19 +58,7 @@ export async function comparisonCoverage(
60
58
  patches?.get(file.path) ??
61
59
  (await data.changes(pins, file.path, file.previousPath));
62
60
 
63
- const changed = emptyCoverage();
64
-
65
- for (const hunk of parseUnifiedPatch(file.path, patch))
66
- for (const line of hunk.lines) {
67
- if (line.kind === "add")
68
- changed.head.push([line.newLine! - 1, line.newLine!]);
69
-
70
- if (line.kind === "remove")
71
- changed.base.push([line.oldLine! - 1, line.oldLine!]);
72
- }
73
-
74
- changed.base = unionIntervals(changed.base);
75
- changed.head = unionIntervals(changed.head);
61
+ const changed = patchChanges(file.path, patch);
76
62
 
77
63
  const [base, head] = await Promise.all([
78
64
  file.status === "added"
@@ -25,8 +25,12 @@ import { errorMessage } from "@dev.fast/trace-core";
25
25
  import {
26
26
  ReviewInputError,
27
27
  type ReviewTarget,
28
+ type UncategorizedReport,
28
29
  boardRepositoryId,
29
30
  elements,
31
+ lensReport,
32
+ scopedCoverage,
33
+ uncategorizedReport,
30
34
  } from "@dev.fast/whiteboard-document";
31
35
  import {
32
36
  type AgentSelection,
@@ -63,7 +67,6 @@ import {
63
67
  } from "@review/sharing/host.js";
64
68
  import { forgetSharedCommits } from "@review/sharing/repository.js";
65
69
  import type { ReviewSessionAgent } from "@review/ui-telemetry-events.js";
66
- import { scopedCoverage } from "@review/viewed-coverage.js";
67
70
  import { type Context, Hono, type MiddlewareHandler } from "hono";
68
71
  import { z } from "zod";
69
72
 
@@ -91,12 +94,9 @@ import {
91
94
  reviewRequestOrigin,
92
95
  } from "./request-origin.js";
93
96
  import {
94
- type UncategorizedReport,
95
97
  legacyModeSchema,
96
- lensReport,
97
98
  progressUpdateSchema,
98
99
  reviewProgress,
99
- uncategorizedReport,
100
100
  } from "./review-progress.js";
101
101
  import {
102
102
  type Author,
@@ -456,12 +456,16 @@ export function createReviewApi(
456
456
 
457
457
  const snapshot = readReview(context.req.param("id"));
458
458
 
459
+ const progress = await reviewProgress(
460
+ store,
461
+ data,
462
+ snapshot,
463
+ context.req.raw.signal,
464
+ );
465
+
459
466
  return context.json({
460
467
  version: snapshot.version,
461
- ...lensReport(
462
- snapshot.lenses ?? [],
463
- await reviewProgress(store, data, snapshot, context.req.raw.signal),
464
- ),
468
+ ...lensReport(snapshot.lenses ?? [], progress.lenses, progress.files),
465
469
  });
466
470
  });
467
471
  app.get("/status", async (context) =>
@@ -2155,15 +2159,15 @@ export function createReviewApi(
2155
2159
  if (!data) return {};
2156
2160
 
2157
2161
  try {
2162
+ const progress = await reviewProgress(
2163
+ store,
2164
+ data,
2165
+ store.read(reviewId, version),
2166
+ request.signal,
2167
+ );
2168
+
2158
2169
  return {
2159
- uncategorized: uncategorizedReport(
2160
- await reviewProgress(
2161
- store,
2162
- data,
2163
- store.read(reviewId, version),
2164
- request.signal,
2165
- ),
2166
- ),
2170
+ uncategorized: uncategorizedReport(progress.lenses, progress.files),
2167
2171
  };
2168
2172
  } catch (error) {
2169
2173
  return {
@@ -1,9 +1,9 @@
1
1
  import {
2
- type DiffLens,
2
+ type CoverageFile,
3
3
  type FileLineRange,
4
- type Lens,
5
4
  type LensSource,
6
5
  type Pins,
6
+ type ResolvedLens,
7
7
  ReviewInputError,
8
8
  SelectionUnavailableError,
9
9
  type SelectionUnavailableReason,
@@ -11,22 +11,18 @@ import {
11
11
  anchorPins,
12
12
  boardRepositoryId,
13
13
  comparisonKey,
14
+ emptyCoverage,
14
15
  fileLineRangeSchema,
15
16
  lensSelections,
16
17
  patchRows,
17
18
  resolveDiffSelection,
19
+ resolveLenses,
18
20
  selectionKey,
19
21
  selectionReferences,
20
22
  } from "@dev.fast/whiteboard-document";
21
- import { type CoverageFile, emptyCoverage } from "@review/viewed-coverage.js";
22
23
  import { z } from "zod";
23
24
 
24
25
  import type { ComparisonCoverage } from "./comparison-coverage.js";
25
- import {
26
- changedPath,
27
- resolveFileLens,
28
- uncategorizedSources,
29
- } from "./file-lenses.js";
30
26
  import type { LocalReviewData } from "./local-data.js";
31
27
  import type { ReviewStore, Snapshot } from "./store.js";
32
28
 
@@ -60,7 +56,7 @@ export interface ReviewProgress {
60
56
  referenceFiles?: Record<string, Record<string, CoverageFile>>;
61
57
  resolvedSelections: Record<string, FileLineRange[]>;
62
58
  /** The review's file lenses, then the automatic "Uncategorized changes". */
63
- lenses: (DiffLens & { unavailable?: string; pending?: boolean })[];
59
+ lenses: (ResolvedLens & { pending?: boolean })[];
64
60
  /** Untracked files a live worktree review leaves out of its comparison. */
65
61
  untrackedFiles?: number;
66
62
  }
@@ -243,40 +239,17 @@ export async function reviewProgress(
243
239
  }
244
240
  };
245
241
 
246
- const lenses: ReviewProgress["lenses"] = await Promise.all(
242
+ // Explicit ranges use the same pin validation as diagram evidence.
243
+ await Promise.all(
247
244
  (snapshot.lenses ?? []).map(async (authored) => {
248
- const lens = { id: authored.id, title: authored.title };
249
-
250
245
  try {
251
- // Explicit ranges use the same pin validation as diagram evidence.
252
246
  await Promise.all(
253
247
  lensSelections([authored]).map(({ source }) =>
254
248
  resolveAvailable(source),
255
249
  ),
256
250
  );
257
-
258
- const resolved = resolveFileLens(
259
- authored,
260
- files,
261
- fileSources,
262
- (source) => resolvedSelections[selectionKey(source)] ?? [],
263
- );
264
-
265
- return {
266
- ...lens,
267
- ...resolved,
268
- pending: counting,
269
- unavailable:
270
- counting || resolved.fileCount
271
- ? undefined
272
- : "No files match these targets",
273
- };
274
251
  } catch {
275
- return {
276
- ...lens,
277
- sources: [],
278
- unavailable: "Source links need updating at these pins",
279
- };
252
+ /* resolveLenses reports a lens whose anchors don't parse. */
280
253
  }
281
254
  }),
282
255
  );
@@ -295,27 +268,19 @@ export async function reviewProgress(
295
268
  }),
296
269
  );
297
270
 
298
- const uncategorized = counting
299
- ? []
300
- : uncategorizedSources(
301
- files,
302
- lenses
303
- .filter((lens) => !lens.unavailable)
304
- .flatMap((lens) => lens.sources),
305
- );
306
-
307
- lenses.push({
308
- id: UNCATEGORIZED_LENS_ID,
309
- title: "Uncategorized changes",
310
- sources: uncategorized,
311
- wholeFiles: false,
312
- fileCount: new Set(uncategorized.map(changedPath(files))).size,
313
- pending: counting,
314
- unavailable:
315
- counting || uncategorized.length
316
- ? undefined
317
- : "All changed lines are covered by lenses",
318
- });
271
+ const lenses = pendingLenses(
272
+ resolveLenses(
273
+ snapshot.lenses ?? [],
274
+ files,
275
+ fileSources,
276
+ (source) =>
277
+ unavailableSelections[selectionKey(source)]
278
+ ? undefined
279
+ : (resolvedSelections[selectionKey(source)] ?? []),
280
+ { nocase: process.platform === "darwin" || process.platform === "win32" },
281
+ ),
282
+ counting,
283
+ );
319
284
 
320
285
  const progress: ReviewProgress = {
321
286
  complete: !counting && !pendingSources.size,
@@ -333,71 +298,21 @@ export async function reviewProgress(
333
298
  return progress;
334
299
  }
335
300
 
336
- /** The most files an uncategorized report names; the rest are counted. */
337
- const REPORT_FILES = 50;
338
-
339
- export interface UncategorizedReport {
340
- /** Changed lines, counting each side, that no lens selects. */
341
- lines: number;
342
- files: {
343
- path: string;
344
- lines: number;
345
- ranges: Pick<FileLineRange, "side" | "fromLine" | "toLine">[];
346
- }[];
347
- /** Files past the first REPORT_FILES, left out of `files`. */
348
- moreFiles?: number;
349
- }
350
-
351
- /** What a lens author still has to place: the changed lines no lens selects,
352
- * grouped by file. */
353
- export function uncategorizedReport(
354
- progress: ReviewProgress,
355
- ): UncategorizedReport {
356
- const sources =
357
- progress.lenses.find((lens) => lens.id === UNCATEGORIZED_LENS_ID)
358
- ?.sources ?? [];
359
-
360
- const byPath = new Map<string, UncategorizedReport["files"][number]>();
361
- const pathOf = changedPath(progress.files);
362
-
363
- for (const source of sources) {
364
- const path = pathOf(source);
365
-
366
- const entry = byPath.get(path) ?? { path, lines: 0, ranges: [] };
367
- entry.lines += source.toLine - source.fromLine + 1;
368
- entry.ranges.push({
369
- side: source.side,
370
- fromLine: source.fromLine,
371
- toLine: source.toLine,
372
- });
373
- byPath.set(path, entry);
374
- }
375
-
376
- const files = [...byPath.values()];
377
-
378
- const report: UncategorizedReport = {
379
- lines: files.reduce((total, file) => total + file.lines, 0),
380
- files: files.slice(0, REPORT_FILES),
381
- };
382
-
383
- if (files.length > REPORT_FILES)
384
- report.moreFiles = files.length - REPORT_FILES;
385
-
386
- return report;
387
- }
388
-
389
- /** Each lens as authored, with what it resolves to, for a lens author. */
390
- export function lensReport(lenses: readonly Lens[], progress: ReviewProgress) {
391
- return {
392
- lenses: lenses.map((lens) => {
393
- const resolved = progress.lenses.find((item) => item.id === lens.id);
394
-
395
- return {
396
- ...lens,
397
- fileCount: resolved?.fileCount ?? 0,
398
- ...(resolved?.unavailable && { unavailable: resolved.unavailable }),
399
- };
400
- }),
401
- uncategorized: uncategorizedReport(progress),
402
- };
301
+ /** A lens with no fileCount failed to resolve, so it is never pending. */
302
+ export function pendingLenses(
303
+ lenses: readonly ResolvedLens[],
304
+ counting: boolean,
305
+ ): ReviewProgress["lenses"] {
306
+ return lenses.map((lens) => {
307
+ if (lens.fileCount === undefined) return lens;
308
+
309
+ if (!counting) return { ...lens, pending: false };
310
+
311
+ return {
312
+ ...lens,
313
+ ...(lens.id === UNCATEGORIZED_LENS_ID && { sources: [], fileCount: 0 }),
314
+ unavailable: undefined,
315
+ pending: true,
316
+ };
317
+ });
403
318
  }
@@ -17,6 +17,7 @@ import {
17
17
  type Applied,
18
18
  type Block,
19
19
  type BoardLinkRef,
20
+ type Coverage,
20
21
  type EditSummary,
21
22
  type Element,
22
23
  type FileLineRange,
@@ -39,6 +40,7 @@ import {
39
40
  documentSchema,
40
41
  editSchema,
41
42
  elements,
43
+ emptyCoverage,
42
44
  explicitPins,
43
45
  lensEditSchema,
44
46
  lensSchema,
@@ -53,18 +55,14 @@ import {
53
55
  sourceAnchors,
54
56
  sourceReferences,
55
57
  summarizeEdit,
58
+ updateCoverage,
56
59
  } from "@dev.fast/whiteboard-document";
57
60
  import type { GitHubPullRequest } from "@review/github-pr-graph.js";
58
61
  import {
59
62
  migrateStoredLenses,
60
63
  readStoredDocument,
61
64
  } from "@review/stored-document-migration.js";
62
- import {
63
- type Coverage,
64
- coverageSchema,
65
- emptyCoverage,
66
- updateCoverage,
67
- } from "@review/viewed-coverage.js";
65
+ import { coverageSchema } from "@review/viewed-coverage.js";
68
66
  import { z } from "zod";
69
67
 
70
68
  import { ReviewActivity } from "./activity.js";
@@ -20,6 +20,7 @@ const REMOTE_VERBS = new Set<ReviewVerbRequest["name"]>([
20
20
  "authoringCapabilities",
21
21
  "openApiReview",
22
22
  "focusWindow",
23
+ "renderAnimation",
23
24
  ]);
24
25
 
25
26
  export function createGatewayPushes(input: {
@@ -41,7 +42,10 @@ export function createGatewayPushes(input: {
41
42
  error: `${request.name} is not available from another machine.`,
42
43
  };
43
44
 
44
- if (request.name === "openApiReview") {
45
+ if (
46
+ request.name === "openApiReview" ||
47
+ request.name === "renderAnimation"
48
+ ) {
45
49
  const refused = await input.claim(remote, request.args.reviewId);
46
50
 
47
51
  if (refused) return { ok: false, error: refused };
@@ -63,6 +63,20 @@ const REMOTE_ROUTES: readonly RemoteRoute[] = [
63
63
  { method: "GET", path: "diff" },
64
64
  { method: "GET", path: "structural-diff" },
65
65
  { method: "GET", path: "stack" },
66
+ { method: "GET", path: "place" },
67
+ { method: "GET", path: "project" },
68
+ { method: "GET", path: "inspect" },
69
+ { method: "GET", path: "history" },
70
+ { method: "GET", path: "lenses" },
71
+ { method: "GET", path: "activity" },
72
+ { method: "POST", path: "activity/:action{begin|update|end}" },
73
+ { method: "POST", path: "person-edit" },
74
+ { method: "POST", path: "focus" },
75
+ { method: "POST", path: "animation-run" },
76
+ { method: "GET", path: "render", slow: true },
77
+ { method: "GET", path: "links" },
78
+ { method: "GET", path: "links/peek" },
79
+ { method: "GET", path: "commit/:commit" },
66
80
  { method: "GET", path: "tree" },
67
81
  { method: "GET", path: "file" },
68
82
  { method: "GET", path: "resources/:name" },
@@ -85,7 +99,7 @@ const REMOTE_ROUTES: readonly RemoteRoute[] = [
85
99
  },
86
100
  {
87
101
  method: "POST",
88
- path: "ask/:thread/:action{prompt|permission|files|retry|cancel|close}",
102
+ path: "ask/:thread/:action{prompt|permission|files|retry|cancel|close|viewed}",
89
103
  },
90
104
  { method: "DELETE", path: "ask/:thread" },
91
105
  ];
@@ -3,8 +3,10 @@ import {
3
3
  diff as readLocalVcsDiff,
4
4
  } from "@dev.fast/local-vcs";
5
5
  import { jsonString, parseJsonText } from "@dev.fast/review-protocol";
6
-
7
- import { type DiffHunkLine, parseUnifiedPatch } from "./unified-diff";
6
+ import {
7
+ type DiffHunkLine,
8
+ parseUnifiedPatch,
9
+ } from "@dev.fast/whiteboard-document";
8
10
 
9
11
  export interface SoftwareMapDiffLineCount {
10
12
  additions: number;