@dev.fast/whiteboard 0.5.2-preview.20261104.146 → 0.5.2-preview.20261109.148

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 (52) hide show
  1. package/dist/{account-alias-BlYamXAG.js → account-alias-Cs5YqXRj.js} +5 -5
  2. package/dist/agent-cli-DdUAmdca.js +2 -0
  3. package/dist/{agent-cli-D7fF5xmm.js → agent-cli-a5J-juuu.js} +8 -8
  4. package/dist/{agent-client-BCEbSPWU.js → agent-client-CgnEo6lf.js} +4 -4
  5. package/dist/{authoring-tools-CkfxTVJm.js → authoring-tools-BgZvnbjN.js} +3 -3
  6. package/dist/{background-server-CwToVN1g.js → background-server-D8SaPvuC.js} +1 -1
  7. package/dist/{background-server-BraP8pJ9.js → background-server-DHEcsFwH.js} +4 -4
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-BpadBaGU.js → cli-Bq5zVqlc.js} +1 -1
  10. package/dist/{cli-runner-BebYF50a.js → cli-runner-hL2efcxb.js} +21 -21
  11. package/dist/cli.js +2 -2
  12. package/dist/{fuzzy-match-CxcsLQGx.js → fuzzy-match-DNyQDN3P.js} +3 -3
  13. package/dist/{headless-host-cLipIufH.js → headless-host-CGyfb5mS.js} +1 -1
  14. package/dist/{headless-host-FAlbekFD.js → headless-host-bb09yVkX.js} +18 -16
  15. package/dist/{import-CZ1958L4.js → import-_XAsWqen.js} +25 -140
  16. package/dist/{local-data-DdFkxFK_.js → local-data-ZQIkF5k1.js} +83 -13
  17. package/dist/{mcp-Claiy9xE.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-Bsgzi9PA.js → profile-D-SbO3p9.js} +2 -2
  21. package/dist/{remote-attach-LGTpwpUv.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-BAznkpeu.js → remote-language-server-Pp261dqJ.js} +4 -4
  25. package/dist/{remote-uninstall-CLP79Cuk.js → remote-uninstall-D_hSuBik.js} +5 -5
  26. package/dist/{request-origin-D6B-zrL8.js → request-origin-ENYd0Ymr.js} +4 -3
  27. package/dist/{review-app-launcher-tTe8bTwg.js → review-app-launcher-DR5vYGEl.js} +2 -2
  28. package/dist/{review-home-paths-BmZX2qiz.js → review-home-paths-B8909D07.js} +19 -19
  29. package/dist/{review-telemetry-DACDMfqq.js → review-telemetry-CxNvwdDS.js} +4 -4
  30. package/dist/server/desktop-host.js +13 -13
  31. package/dist/{server-discovery-CV5qf1J8.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-CrrW7lcY.js → src-B-91Rd2d.js} +1 -0
  35. package/dist/{src-BQDlJXkG.js → src-BPrngiWX.js} +1806 -1
  36. package/dist/{stored-document-migration-D1wK4yID.js → stored-document-migration-BQcUyXO8.js} +4 -158
  37. package/dist/{thread-state-WTqSborE.js → thread-state-Jxoh1hde.js} +1 -1
  38. package/dist/{tool-failure-YOFGHJCZ.js → tool-failure-Cgjidoud.js} +6 -6
  39. package/package.json +5 -6
  40. package/src/review-api/comparison-coverage.ts +8 -23
  41. package/src/review-api/http.ts +20 -16
  42. package/src/review-api/review-progress.ts +40 -126
  43. package/src/review-api/store.ts +4 -6
  44. package/src/software-map-diff-counts.ts +4 -2
  45. package/src/ui-telemetry-events.ts +1 -0
  46. package/src/viewed-coverage.ts +9 -252
  47. package/dist/agent-cli-DLibAL4u.js +0 -2
  48. package/dist/profile-BxdzBxHW.js +0 -2
  49. package/src/review-api/file-lenses.ts +0 -125
  50. package/src/review-api/lens-alignment.ts +0 -52
  51. package/src/unified-diff.ts +0 -71
  52. package/dist/{s3-CsG2EWED.js → s3-d8RGPKwt.js} +1 -1
@@ -1,167 +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, at as unionIntervals, et as formatAnchor, ot as ReviewInputError } from "./src-BQDlJXkG.js";
4
- import "./src-CrrW7lcY.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
- //#region src/unified-diff.ts
81
- const HUNK_HEADER = /^@@ -(?<oldStart>\d+)(?:,(?<oldLines>\d+))? \+(?<newStart>\d+)(?:,(?<newLines>\d+))? @@/;
82
- function parseUnifiedPatch(file, patch) {
83
- if (!patch) return [];
84
- const hunks = [];
85
- let current = null;
86
- let oldLine = 0;
87
- let newLine = 0;
88
- for (const row of patch.split(/\r?\n/)) {
89
- const header = HUNK_HEADER.exec(row);
90
- if (header?.groups) {
91
- current = {
92
- file,
93
- oldStart: Number(header.groups.oldStart),
94
- oldLines: Number(header.groups.oldLines ?? "1"),
95
- newStart: Number(header.groups.newStart),
96
- newLines: Number(header.groups.newLines ?? "1"),
97
- lines: []
98
- };
99
- oldLine = current.oldStart;
100
- newLine = current.newStart;
101
- hunks.push(current);
102
- continue;
103
- }
104
- if (!current || row.startsWith("\")) continue;
105
- const marker = row[0];
106
- const text = row.slice(1);
107
- let line = null;
108
- if (marker === " ") {
109
- line = {
110
- kind: "context",
111
- oldLine,
112
- newLine,
113
- text
114
- };
115
- oldLine += 1;
116
- newLine += 1;
117
- } else if (marker === "+") {
118
- line = {
119
- kind: "add",
120
- oldLine: null,
121
- newLine,
122
- text
123
- };
124
- newLine += 1;
125
- } else if (marker === "-") {
126
- line = {
127
- kind: "remove",
128
- oldLine,
129
- newLine: null,
130
- text
131
- };
132
- oldLine += 1;
133
- }
134
- if (line) current.lines.push(line);
135
- }
136
- return hunks;
137
- }
138
- //#endregion
139
- //#region src/review-api/lens-alignment.ts
140
- /** Project the already-computed patch's runs, including unchanged gaps. */
141
- function textualRows(path, patch, baseCount, headCount) {
142
- const rows = [];
143
- let base = 0, head = 0;
144
- const append = (baseEnd, headEnd) => {
145
- while (base < baseEnd || head < headEnd) rows.push([base < baseEnd ? base++ : null, head < headEnd ? head++ : null]);
146
- };
147
- for (const hunk of parseUnifiedPatch(path, patch)) {
148
- append(hunk.oldLines ? hunk.oldStart - 1 : hunk.oldStart, hunk.newLines ? hunk.newStart - 1 : hunk.newStart);
149
- let removed = 0, added = 0;
150
- const flush = () => {
151
- append(base + removed, head + added);
152
- removed = added = 0;
153
- };
154
- for (const line of hunk.lines) if (line.kind === "context") {
155
- flush();
156
- append(base + 1, head + 1);
157
- } else if (line.kind === "add") added++;
158
- else removed++;
159
- flush();
160
- }
161
- append(baseCount, headCount);
162
- return rows;
163
- }
164
- //#endregion
165
11
  //#region src/evidence.ts
166
12
  /** Quote matching shared by legacy publish and JSON accept: authors paste
167
13
  * transcript text with arbitrary line wrapping, so both sides compare on
@@ -1339,4 +1185,4 @@ function liftFileLenses(document) {
1339
1185
  };
1340
1186
  }
1341
1187
  //#endregion
1342
- export { updateCoverage as A, coverageSchema as C, scopedCoverage as D, intersectIntervals as E, sourcesForFile as O, parseUnifiedPatch as S, emptyCoverage 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, subtractIntervals as k, 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, coverageSources as w, textualRows 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,4 +1,4 @@
1
- import { E as ReviewApiSelectionSourceSchema, N as ReviewSelectedDiffSchema } from "./src-CrrW7lcY.js";
1
+ import { E as ReviewApiSelectionSourceSchema, N as ReviewSelectedDiffSchema } from "./src-B-91Rd2d.js";
2
2
  import { z } from "zod";
3
3
  import { stat } from "node:fs/promises";
4
4
  //#region src/fs-utils.ts
@@ -1,11 +1,11 @@
1
1
  import { m as isStringValue, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import { M as checkReferences, j as blocks, ot as ReviewInputError } from "./src-BQDlJXkG.js";
3
- import { l as boardTitle } from "./src-CrrW7lcY.js";
4
- import "./review-home-paths-BmZX2qiz.js";
5
- import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-BCEbSPWU.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";
8
- import { m as isMissingFileError } from "./thread-state-WTqSborE.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
+ import { m as isMissingFileError } from "./thread-state-Jxoh1hde.js";
9
9
  import { createHash } from "node:crypto";
10
10
  import { z } from "zod";
11
11
  import { chmod, cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.fast/whiteboard",
3
- "version": "0.5.2-preview.20261104.146",
3
+ "version": "0.5.2-preview.20261109.148",
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",
@@ -78,10 +77,10 @@
78
77
  "vitest": "4.1.12",
79
78
  "@dev.fast/json": "0.0.0",
80
79
  "@dev.fast/review-share-protocol": "0.6.0",
81
- "@dev.fast/review-protocol": "0.0.0",
82
- "@dev.fast/trace-protocol": "0.5.0",
83
80
  "@dev.fast/trace-core": "0.0.0",
84
- "@dev.fast/whiteboard-document": "0.2.1"
81
+ "@dev.fast/trace-protocol": "0.5.0",
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": "20f4d6dcfaa9f7a40ff77fd6e6b2467fc89e64fa",
93
+ "gitHead": "b2e15bbe59af85f4e1a721e310526de6c60a4f3b",
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",
@@ -7,23 +7,21 @@ import {
7
7
  applyStructuralUpdate,
8
8
  structuralRows,
9
9
  } from "@dev.fast/review-protocol";
10
- import type {
11
- AlignmentRow,
12
- FileLineRange,
13
- Pins,
14
- } from "@dev.fast/whiteboard-document";
15
- import { parseUnifiedPatch } from "@review/unified-diff.js";
16
10
  import {
11
+ type AlignmentRow,
17
12
  type Coverage,
18
13
  type CoverageFile,
14
+ type FileLineRange,
19
15
  type LineInterval,
16
+ type Pins,
20
17
  emptyCoverage,
21
18
  intersectIntervals,
19
+ patchChanges,
20
+ patchRows,
22
21
  subtractIntervals,
23
22
  unionIntervals,
24
- } from "@review/viewed-coverage.js";
23
+ } from "@dev.fast/whiteboard-document";
25
24
 
26
- import { textualRows } from "./lens-alignment.js";
27
25
  import type { LocalReviewData } from "./local-data.js";
28
26
 
29
27
  const hash = (parts: (string | null)[]) =>
@@ -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"
@@ -91,8 +77,7 @@ export async function comparisonCoverage(
91
77
 
92
78
  alignments.set(
93
79
  file.path,
94
- textualRows(
95
- file.path,
80
+ patchRows(
96
81
  patch,
97
82
  base === null ? 0 : base.split("\n").length,
98
83
  head === null ? 0 : head.split("\n").length,
@@ -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,
17
+ patchRows,
16
18
  resolveDiffSelection,
19
+ resolveLenses,
17
20
  selectionKey,
18
21
  selectionReferences,
19
22
  } from "@dev.fast/whiteboard-document";
20
- import { type CoverageFile, emptyCoverage } from "@review/viewed-coverage.js";
21
23
  import { z } from "zod";
22
24
 
23
25
  import type { ComparisonCoverage } from "./comparison-coverage.js";
24
- import {
25
- changedPath,
26
- resolveFileLens,
27
- uncategorizedSources,
28
- } from "./file-lenses.js";
29
- import { textualRows } from "./lens-alignment.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
  }
@@ -208,8 +204,7 @@ export async function reviewProgress(
208
204
  "no_alignment",
209
205
  "Changed context file has no alignment.",
210
206
  );
211
- rows = textualRows(
212
- source.file,
207
+ rows = patchRows(
213
208
  "",
214
209
  base?.split("\n").length ?? 0,
215
210
  head?.split("\n").length ?? 0,
@@ -244,40 +239,17 @@ export async function reviewProgress(
244
239
  }
245
240
  };
246
241
 
247
- const lenses: ReviewProgress["lenses"] = await Promise.all(
242
+ // Explicit ranges use the same pin validation as diagram evidence.
243
+ await Promise.all(
248
244
  (snapshot.lenses ?? []).map(async (authored) => {
249
- const lens = { id: authored.id, title: authored.title };
250
-
251
245
  try {
252
- // Explicit ranges use the same pin validation as diagram evidence.
253
246
  await Promise.all(
254
247
  lensSelections([authored]).map(({ source }) =>
255
248
  resolveAvailable(source),
256
249
  ),
257
250
  );
258
-
259
- const resolved = resolveFileLens(
260
- authored,
261
- files,
262
- fileSources,
263
- (source) => resolvedSelections[selectionKey(source)] ?? [],
264
- );
265
-
266
- return {
267
- ...lens,
268
- ...resolved,
269
- pending: counting,
270
- unavailable:
271
- counting || resolved.fileCount
272
- ? undefined
273
- : "No files match these targets",
274
- };
275
251
  } catch {
276
- return {
277
- ...lens,
278
- sources: [],
279
- unavailable: "Source links need updating at these pins",
280
- };
252
+ /* resolveLenses reports a lens whose anchors don't parse. */
281
253
  }
282
254
  }),
283
255
  );
@@ -296,27 +268,19 @@ export async function reviewProgress(
296
268
  }),
297
269
  );
298
270
 
299
- const uncategorized = counting
300
- ? []
301
- : uncategorizedSources(
302
- files,
303
- lenses
304
- .filter((lens) => !lens.unavailable)
305
- .flatMap((lens) => lens.sources),
306
- );
307
-
308
- lenses.push({
309
- id: UNCATEGORIZED_LENS_ID,
310
- title: "Uncategorized changes",
311
- sources: uncategorized,
312
- wholeFiles: false,
313
- fileCount: new Set(uncategorized.map(changedPath(files))).size,
314
- pending: counting,
315
- unavailable:
316
- counting || uncategorized.length
317
- ? undefined
318
- : "All changed lines are covered by lenses",
319
- });
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
+ );
320
284
 
321
285
  const progress: ReviewProgress = {
322
286
  complete: !counting && !pendingSources.size,
@@ -334,71 +298,21 @@ export async function reviewProgress(
334
298
  return progress;
335
299
  }
336
300
 
337
- /** The most files an uncategorized report names; the rest are counted. */
338
- const REPORT_FILES = 50;
339
-
340
- export interface UncategorizedReport {
341
- /** Changed lines, counting each side, that no lens selects. */
342
- lines: number;
343
- files: {
344
- path: string;
345
- lines: number;
346
- ranges: Pick<FileLineRange, "side" | "fromLine" | "toLine">[];
347
- }[];
348
- /** Files past the first REPORT_FILES, left out of `files`. */
349
- moreFiles?: number;
350
- }
351
-
352
- /** What a lens author still has to place: the changed lines no lens selects,
353
- * grouped by file. */
354
- export function uncategorizedReport(
355
- progress: ReviewProgress,
356
- ): UncategorizedReport {
357
- const sources =
358
- progress.lenses.find((lens) => lens.id === UNCATEGORIZED_LENS_ID)
359
- ?.sources ?? [];
360
-
361
- const byPath = new Map<string, UncategorizedReport["files"][number]>();
362
- const pathOf = changedPath(progress.files);
363
-
364
- for (const source of sources) {
365
- const path = pathOf(source);
366
-
367
- const entry = byPath.get(path) ?? { path, lines: 0, ranges: [] };
368
- entry.lines += source.toLine - source.fromLine + 1;
369
- entry.ranges.push({
370
- side: source.side,
371
- fromLine: source.fromLine,
372
- toLine: source.toLine,
373
- });
374
- byPath.set(path, entry);
375
- }
376
-
377
- const files = [...byPath.values()];
378
-
379
- const report: UncategorizedReport = {
380
- lines: files.reduce((total, file) => total + file.lines, 0),
381
- files: files.slice(0, REPORT_FILES),
382
- };
383
-
384
- if (files.length > REPORT_FILES)
385
- report.moreFiles = files.length - REPORT_FILES;
386
-
387
- return report;
388
- }
389
-
390
- /** Each lens as authored, with what it resolves to, for a lens author. */
391
- export function lensReport(lenses: readonly Lens[], progress: ReviewProgress) {
392
- return {
393
- lenses: lenses.map((lens) => {
394
- const resolved = progress.lenses.find((item) => item.id === lens.id);
395
-
396
- return {
397
- ...lens,
398
- fileCount: resolved?.fileCount ?? 0,
399
- ...(resolved?.unavailable && { unavailable: resolved.unavailable }),
400
- };
401
- }),
402
- uncategorized: uncategorizedReport(progress),
403
- };
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
+ });
404
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";
@@ -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;
@@ -223,6 +223,7 @@ const SOURCE_OPENED_DESTINATION = [
223
223
  "vscode",
224
224
  "cursor",
225
225
  "zed",
226
+ "intellij",
226
227
  "application",
227
228
  ] as const;
228
229