@dev.fast/whiteboard 0.4.0 → 0.4.1-preview.20261026.138

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 (78) hide show
  1. package/dist/{account-alias-2yTAxoBx.js → account-alias-BPlPnzQM.js} +7 -7
  2. package/dist/{agent-cli-CHNDd4Ga.js → agent-cli-BbCf9uid.js} +11 -11
  3. package/dist/agent-cli-ipN_JmzG.js +2 -0
  4. package/dist/{agent-client-Dc0vHFJt.js → agent-client-DfpM8F5N.js} +3 -3
  5. package/dist/{authoring-tools-D8EkIDjm.js → authoring-tools-bm39DRoV.js} +23 -10
  6. package/dist/{background-server-DyWnPJyZ.js → background-server--0EeCCeV.js} +1 -1
  7. package/dist/{background-server-DHWHzpo5.js → background-server-B--5p72o.js} +6 -6
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-qZomwJmK.js → cli-DBTQIroM.js} +2 -2
  10. package/dist/{cli-runner-DFBaVGij.js → cli-runner-CRFCdb7G.js} +25 -25
  11. package/dist/cli.js +3 -3
  12. package/dist/{fuzzy-match-CfSxqzBF.js → fuzzy-match-l3mJZ1Ge.js} +5 -5
  13. package/dist/{headless-host-dwlqhqMY.js → headless-host-5ei3nHS8.js} +1 -1
  14. package/dist/{headless-host-DJhcab6r.js → headless-host-BkJ9OUQY.js} +131 -299
  15. package/dist/{client-p9WB8Sj0.js → import-Bwvf9Wos.js} +505 -390
  16. package/dist/{lens-selection-9byzEgvU.js → lens-selection-EuZdag4Q.js} +1 -1
  17. package/dist/{local-data-DHedfhxw.js → local-data-BCSeTyic.js} +98 -154
  18. package/dist/{mcp-Br-r_wwR.js → mcp-_Xg1gQuA.js} +17 -8
  19. package/dist/{package-paths-BeYHYjeR.js → package-paths-Bg731FHS.js} +2 -2
  20. package/dist/profile-C8zL4u3L.js +2 -0
  21. package/dist/{profile-D9wdlPgw.js → profile-xzrUFgbT.js} +4 -4
  22. package/dist/{remote-attach-DSGpTOJ4.js → remote-attach-DF25Epoa.js} +4 -4
  23. package/dist/{remote-extensions-WtJO7pkb.js → remote-extensions-BJamgsCe.js} +1 -1
  24. package/dist/{remote-extensions-CsI_BJ_f.js → remote-extensions-CeywZL-Z.js} +4 -4
  25. package/dist/{remote-language-server-Dx13Mg2m.js → remote-language-server-CH8sG_CU.js} +6 -6
  26. package/dist/{remote-uninstall-Dp2o3qz0.js → remote-uninstall-Cq-lkBY7.js} +7 -7
  27. package/dist/{request-origin-CNSLVn7x.js → request-origin-CargJFvH.js} +14 -3
  28. package/dist/{review-app-launcher-BZAnR8Vp.js → review-app-launcher-D8ApNDUV.js} +3 -3
  29. package/dist/{review-home-paths-CG-sb9Cw.js → review-home-paths-DcB4xLD-.js} +2 -2
  30. package/dist/{review-telemetry-BM-_KdLi.js → review-telemetry-BW7gJrSo.js} +7 -7
  31. package/dist/{runtime-C6_1K-3Z.js → runtime-DY6Ctyog.js} +1 -1
  32. package/dist/runtime.js +1 -1
  33. package/dist/{s3-CsG2EWED.js → s3-BIWv4h1J.js} +2 -2
  34. package/dist/{s3-config-zz4GWh2T.js → s3-config-Du3RBIvv.js} +1 -1
  35. package/dist/server/desktop-host.js +51 -17
  36. package/dist/{server-discovery-DKLIOgg3.js → server-discovery-aHK_1r3U.js} +5 -5
  37. package/dist/sharing/index.d.ts +209 -98
  38. package/dist/sharing/index.js +1 -1
  39. package/dist/{src-DYHVzX3b.js → src-DEOc8bvC.js} +63 -2
  40. package/dist/{src-Z1x4-Qmb.js → src-DjRaCctk.js} +6 -6
  41. package/dist/{stored-document-migration-DEt8iwem.js → stored-document-migration-CwmTwTb8.js} +139 -13
  42. package/dist/{thread-state-CO97FUqK.js → thread-state-CbLB0jUA.js} +1 -1
  43. package/dist/{tool-failure-C1L1vJyz.js → tool-failure-DZaQzi4f.js} +2 -2
  44. package/instructions/animation.md +345 -0
  45. package/instructions/authoring.md +2 -2
  46. package/package.json +6 -6
  47. package/src/fixtures/blocks/animation.json +19 -0
  48. package/src/review-api/authoring-tools.ts +17 -6
  49. package/src/review-api/blocks/animation.ts +98 -0
  50. package/src/review-api/blocks/index.ts +3 -0
  51. package/src/review-api/blocks/sequence.ts +3 -1
  52. package/src/review-api/comparison-coverage.ts +120 -89
  53. package/src/review-api/component-reference.ts +16 -0
  54. package/src/review-api/document-text.ts +11 -0
  55. package/src/review-api/document.ts +26 -5
  56. package/src/review-api/http.ts +31 -2
  57. package/src/review-api/instructions.ts +11 -2
  58. package/src/review-api/local-data.ts +21 -4
  59. package/src/review-api/mcp.ts +22 -14
  60. package/src/review-api/onboarding.ts +100 -26
  61. package/src/review-preferences.ts +43 -0
  62. package/src/server/desktop-server.ts +24 -0
  63. package/src/server/json-review-reporting.ts +1 -1
  64. package/src/server/review-server-core.ts +2 -0
  65. package/src/server/structural-comparisons.ts +5 -11
  66. package/src/server/structural-diff.ts +8 -2
  67. package/src/sharing/cli.ts +1 -1
  68. package/src/sharing/client.ts +54 -222
  69. package/src/sharing/export.ts +172 -114
  70. package/src/sharing/host.ts +32 -36
  71. package/src/sharing/import.ts +93 -147
  72. package/src/sharing/index.ts +1 -6
  73. package/src/sharing/routes.ts +18 -17
  74. package/src/ui-telemetry-events.ts +11 -0
  75. package/dist/agent-cli-Cn4-16XA.js +0 -2
  76. package/dist/profile-BopGMZUh.js +0 -2
  77. package/dist/{s3-config-CjcLyFV5.js → s3-config-D7Yt0VhS.js} +1 -1
  78. package/dist/{src-X9ZebeOD.js → src-DXnn7RrI.js} +1 -1
@@ -1,28 +1,28 @@
1
- import { b as resolveRepoContext, d as git, r as defaultBranch, w as runVcs } from "./src-X9ZebeOD.js";
1
+ import { b as resolveRepoContext, d as git, r as defaultBranch, w as runVcs } from "./src-DXnn7RrI.js";
2
2
  import { c as jsonString, m as isStringValue, n as isJsonObject, p as isObjectValue, u as parseJsonText } from "./src-X9phtB2j.js";
3
- import { a as sharedReviewId, c as verifyShareRepository, d as validateShareSources, f as StreamLimitError, h as shareIdSchema, l as exportShare, m as parseShareLink, n as ShareClient, p as readBoundedStream, r as SharePreflightError, s as readShareRepository, t as ShareAuthError, u as referenceCommits } from "./client-p9WB8Sj0.js";
4
- import { d as textualRows, f as invalidateStructuralComparisons, h as readGitHubPullRequests, i as inspectSnapshot, m as diffrMissingError, p as diffrExecutable, r as commandSchema } from "./local-data-DHedfhxw.js";
5
- import { N as reviewDiffrSummarizerInputSchema, O as ReviewFeedbackContactSchema, P as decodeStructuralDiffEvent, a as parseReviewVerbRequest, c as boardIconKind, i as parseReviewDesktopVerbResult, j as ReviewOnboardingRunRequestSchema, k as ReviewFeedbackSchema, l as boardTitle, m as REVIEW_CLIENT_REMOTE, p as REVIEW_CLIENT_HEADER, y as REVIEW_HOST_HEADER } from "./src-DYHVzX3b.js";
6
- import { $ as runStoreLogin, M as selectTraceStorage, N as TraceStorageDeniedError, Q as readStoreAuth, X as DEFAULT_STORE_ORIGIN, Z as clearStoreAuth, b as listReviewTraceSessions, j as resolveTraceStorage, k as isTraceStorageConfigured, q as shellQuote, v as traceMachineEnabled, x as loadReviewAgentTrace } from "./src-Z1x4-Qmb.js";
7
- import { _ as scopedCoverage, h as emptyCoverage, i as anchorSelection, l as resolveDiffSelection, m as coverageSources, n as SelectionUnavailableError, o as comparisonKey, s as formatAnchor, u as selectionKey, v as subtractIntervals, y as unionIntervals } from "./lens-selection-9byzEgvU.js";
8
- import { D as boardRepositoryId, E as boardLinks, G as selectionReferences, S as anchorPins, X as stepSources, Z as UNCATEGORIZED_LENS_ID, f as sourceBranchName, it as fileLineRangeSchema, j as elements, l as pullRequestKey, p as pullRequestProvider, tt as lensSelections } from "./stored-document-migration-DEt8iwem.js";
3
+ import { _ as readBoundedStream, a as verifyShareRepository, c as exportShare, d as legacyModeSchema, f as lensReport, g as StreamLimitError, h as uncategorizedReport, i as readShareRepository, l as referenceCommits, m as reviewProgress, n as sharedReviewId, o as ShareAuthError, p as progressUpdateSchema, s as ShareClient, u as validateShareSources, v as parseShareLink, y as shareIdSchema } from "./import-Bwvf9Wos.js";
4
+ import { d as invalidateStructuralComparisons, f as diffrExecutable, i as inspectSnapshot, m as readGitHubPullRequests, p as diffrMissingError, r as commandSchema } from "./local-data-BCSeTyic.js";
5
+ import { F as decodeStructuralDiffEvent, N as reviewDiffrSummarizerInputSchema, O as ReviewFeedbackContactSchema, a as parseReviewVerbRequest, c as boardIconKind, i as parseReviewDesktopVerbResult, j as ReviewOnboardingRunRequestSchema, k as ReviewFeedbackSchema, l as boardTitle, m as REVIEW_CLIENT_REMOTE, p as REVIEW_CLIENT_HEADER, y as REVIEW_HOST_HEADER } from "./src-DEOc8bvC.js";
6
+ import { $ as runStoreLogin, M as selectTraceStorage, N as TraceStorageDeniedError, Q as readStoreAuth, X as DEFAULT_STORE_ORIGIN, Z as clearStoreAuth, b as listReviewTraceSessions, j as resolveTraceStorage, k as isTraceStorageConfigured, q as shellQuote, v as traceMachineEnabled, x as loadReviewAgentTrace } from "./src-DjRaCctk.js";
7
+ import { _ as scopedCoverage, s as formatAnchor, u as selectionKey } from "./lens-selection-EuZdag4Q.js";
8
+ import { N as elements, O as boardLinks, Q as stepSources, f as sourceBranchName, k as boardRepositoryId, l as pullRequestKey, p as pullRequestProvider, q as selectionReferences, w as anchorPins } from "./stored-document-migration-CwmTwTb8.js";
9
9
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
10
- import { A as processStartIdentity, L as writePrivateJsonAtomic, N as devReviewHome, h as TraceConfigurationError, i as isS3MockMode, j as withFileLock } from "./s3-config-CjcLyFV5.js";
10
+ import { A as processStartIdentity, L as writePrivateJsonAtomic, N as devReviewHome, h as TraceConfigurationError, i as isS3MockMode, j as withFileLock } from "./s3-config-D7Yt0VhS.js";
11
11
  import { t as errorMessage } from "./error-message-OtiDonty.js";
12
- import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-BeYHYjeR.js";
13
- import "./review-home-paths-CG-sb9Cw.js";
14
- import { _ as reviewInstanceIdentity, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-DKLIOgg3.js";
15
- import { c as askPicksSchema, d as selectionMarkdown, l as askQuestionSchema, n as askAgentIds, r as askChoiceKinds, s as askPermissionsSchema, t as applyAskChange, u as AgentSelectionSchema } from "./thread-state-CO97FUqK.js";
16
- import { t as openReviewProfile } from "./profile-D9wdlPgw.js";
17
- import { c as REVIEW_APP_SESSION_ID_HEADER, d as sanitizeUiTelemetryEvent, f as cleanTelemetryText, o as reviewRequestOrigin, r as REVIEW_PERSON_HEADER, s as BUNDLE_FRAME_PATTERN, t as ASK_THREAD_ENV, u as isReportableCleanedMessage } from "./request-origin-CNSLVn7x.js";
18
- import { a as defaultAskPermissions, i as askAgents, l as launchAskAgent, n as askAgentSettings, o as detectAskAgents, r as askAgentTakesMcp, t as fuzzyRank } from "./fuzzy-match-CfSxqzBF.js";
19
- import { a as inspectQuerySchema, i as renderInstructions, o as queryAnchor, r as instructionsQuerySchema, s as readQuerySchemas, t as authoringTools } from "./authoring-tools-D8EkIDjm.js";
20
- import { a as headlessServerOwner, c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-DHWHzpo5.js";
12
+ import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-Bg731FHS.js";
13
+ import "./review-home-paths-DcB4xLD-.js";
14
+ import { _ as reviewInstanceIdentity, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-aHK_1r3U.js";
15
+ import { c as askPicksSchema, d as selectionMarkdown, l as askQuestionSchema, n as askAgentIds, r as askChoiceKinds, s as askPermissionsSchema, t as applyAskChange, u as AgentSelectionSchema } from "./thread-state-CbLB0jUA.js";
16
+ import { t as openReviewProfile } from "./profile-xzrUFgbT.js";
17
+ import { c as REVIEW_APP_SESSION_ID_HEADER, d as sanitizeUiTelemetryEvent, f as cleanTelemetryText, o as reviewRequestOrigin, r as REVIEW_PERSON_HEADER, s as BUNDLE_FRAME_PATTERN, t as ASK_THREAD_ENV, u as isReportableCleanedMessage } from "./request-origin-CargJFvH.js";
18
+ import { a as defaultAskPermissions, i as askAgents, l as launchAskAgent, n as askAgentSettings, o as detectAskAgents, r as askAgentTakesMcp, t as fuzzyRank } from "./fuzzy-match-l3mJZ1Ge.js";
19
+ import { a as inspectQuerySchema, i as renderInstructions, o as queryAnchor, r as instructionsQuerySchema, s as readQuerySchemas, t as authoringTools } from "./authoring-tools-bm39DRoV.js";
20
+ import { a as headlessServerOwner, c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-B--5p72o.js";
21
21
  import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
22
22
  import { z } from "zod";
23
+ import path, { join } from "node:path";
23
24
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
24
25
  import { mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
25
- import path, { join, posix } from "node:path";
26
26
  import { Writable } from "node:stream";
27
27
  import os, { tmpdir } from "node:os";
28
28
  import { execFile, spawn } from "node:child_process";
@@ -2200,244 +2200,6 @@ async function captureSanitizedUiTelemetry(telemetry, request, name, properties,
2200
2200
  }
2201
2201
  }
2202
2202
  //#endregion
2203
- //#region src/review-api/file-lenses.ts
2204
- /** Subtract authored coverage, independently of viewed state. */
2205
- function uncategorizedSources(files, sources) {
2206
- return files.flatMap((file) => {
2207
- const covered = scopedCoverage(file, sources);
2208
- return coverageSources(file, {
2209
- base: subtractIntervals(file.changed.base, covered.base),
2210
- head: subtractIntervals(file.changed.head, covered.head)
2211
- });
2212
- });
2213
- }
2214
- /** Resolve all targets into one union; a whole-file range subsumes narrower ranges. */
2215
- function resolveFileLens(lens, files, fileSources, resolve) {
2216
- const targets = lens.targets;
2217
- const selected = targets.flatMap((target) => target.kind === "ranges" ? target.sources.flatMap((source) => resolve(anchorSelection(source))) : files.filter((file) => matchesFileLens(target.patterns, file)).flatMap((file) => fileSources.get(file.path) ?? []));
2218
- const groups = /* @__PURE__ */ new Map();
2219
- for (const source of selected) {
2220
- const key = JSON.stringify([source.side, source.file]);
2221
- const group = groups.get(key) ?? [];
2222
- group.push(source);
2223
- groups.set(key, group);
2224
- }
2225
- const sources = [...groups.values()].flatMap((group) => unionIntervals(group.map((source) => [source.fromLine - 1, source.toLine])).map(([start, end]) => ({
2226
- ...group[0],
2227
- fromLine: start + 1,
2228
- toLine: end
2229
- })));
2230
- return {
2231
- sources,
2232
- fileCount: new Set(sources.map((source) => files.find((file) => source.file === (source.side === "base" ? file.previousPath ?? file.path : file.path))?.path ?? source.file)).size,
2233
- wholeFiles: targets.every((target) => target.kind === "files")
2234
- };
2235
- }
2236
- /** Evaluate authored patterns against the changed-file list, never the filesystem. */
2237
- function matchesFileLens(patterns, file) {
2238
- return patterns.some((pattern) => {
2239
- const normalized = pattern.startsWith("./") ? pattern.slice(2) : pattern;
2240
- return [file.path, file.previousPath].some((path) => path !== void 0 && (path === normalized || posix.matchesGlob(path, normalized)));
2241
- });
2242
- }
2243
- //#endregion
2244
- //#region src/review-api/review-progress.ts
2245
- /** Older clients still send `mode`. */
2246
- const legacyModeSchema = z.unknown().optional();
2247
- const progressUpdateSchema = z.strictObject({
2248
- mode: legacyModeSchema,
2249
- version: z.number().int().nonnegative(),
2250
- files: z.array(z.strictObject({
2251
- path: z.string(),
2252
- fingerprint: z.string().min(1),
2253
- sources: z.array(fileLineRangeSchema)
2254
- })).max(1e4),
2255
- viewed: z.boolean()
2256
- });
2257
- /** Coverage survives new pins only when both complete file contents are unchanged. */
2258
- async function reviewProgress(store, data, snapshot, signal = new AbortController().signal, partial) {
2259
- const marks = snapshot.shared ? /* @__PURE__ */ new Map() : store.viewedCoverage(snapshot.reviewId);
2260
- const pins = snapshot.pins ? (await data.resolveSource(snapshot)).pins : void 0;
2261
- const comparison = pins ? partial ?? await data.coverage(snapshot.reviewId, pins) : {
2262
- counted: true,
2263
- files: [],
2264
- fileSources: /* @__PURE__ */ new Map(),
2265
- alignments: /* @__PURE__ */ new Map()
2266
- };
2267
- const counting = !comparison.counted;
2268
- signal.throwIfAborted();
2269
- const documentKey = JSON.stringify(pins);
2270
- const comparisons = /* @__PURE__ */ new Map();
2271
- /** The comparison a reference's own pins name, shared across references. */
2272
- const ownComparison = (own) => {
2273
- const key = JSON.stringify(own);
2274
- let loading = comparisons.get(key);
2275
- if (loading) return loading;
2276
- if (partial) {
2277
- const state = data.coverageSnapshot(snapshot.reviewId, own);
2278
- if (state.pending) return state.comparison.counted ? Promise.resolve(state.comparison) : void 0;
2279
- }
2280
- loading = data.coverage(snapshot.reviewId, own);
2281
- comparisons.set(key, loading);
2282
- return loading;
2283
- };
2284
- const withViewed = (file) => ({
2285
- ...file,
2286
- viewed: marks.get(file.path)?.fingerprint === file.fingerprint ? marks.get(file.path).coverage : emptyCoverage()
2287
- });
2288
- const files = comparison.files.map(withViewed);
2289
- const referenceFiles = {};
2290
- const fileSources = new Map(comparison.fileSources);
2291
- const alignments = new Map(comparison.alignments);
2292
- const resolvedSelections = {};
2293
- const unavailableSelections = {};
2294
- const pendingSources = /* @__PURE__ */ new Set();
2295
- const resolve = async (source) => {
2296
- const key = selectionKey(source);
2297
- if (resolvedSelections[key]) return resolvedSelections[key];
2298
- const sourcePins = anchorPins(source, pins);
2299
- const own = JSON.stringify(sourcePins) !== documentKey;
2300
- let ownFiles = files;
2301
- let ownAlignments = alignments;
2302
- if (own) {
2303
- const loading = ownComparison(sourcePins);
2304
- if (!loading) {
2305
- pendingSources.add(key);
2306
- return [];
2307
- }
2308
- const loaded = await loading;
2309
- ownFiles = loaded.files;
2310
- ownAlignments = loaded.alignments;
2311
- }
2312
- const file = ownFiles.find((file) => source.file === file.path || source.file === file.previousPath);
2313
- if (own) {
2314
- const group = referenceFiles[comparisonKey(sourcePins)] ??= {};
2315
- if (file) group[file.path] ??= withViewed(file);
2316
- }
2317
- let rows = file && ownAlignments.get(file.path);
2318
- if (!file && counting && !own) {
2319
- pendingSources.add(key);
2320
- return [];
2321
- }
2322
- if (!file) {
2323
- const [base, head] = await Promise.all(["base", "head"].map((side) => data.file(sourcePins, side, source.file).then((value) => value.text, (error) => {
2324
- if (error instanceof ReviewInputError && error.status === 404) return null;
2325
- throw error;
2326
- })));
2327
- if (base === null && head === null) throw new SelectionUnavailableError("missing_file", "File is unavailable at these pins.");
2328
- if (base !== null && head !== null && base !== head) throw new SelectionUnavailableError("no_alignment", "Changed context file has no alignment.");
2329
- rows = textualRows(source.file, "", base?.split("\n").length ?? 0, head?.split("\n").length ?? 0);
2330
- }
2331
- if (!rows) throw new SelectionUnavailableError("no_alignment", "File has no text alignment.");
2332
- const result = resolveDiffSelection(source, rows, file ?? { path: source.file });
2333
- resolvedSelections[key] = result;
2334
- return result;
2335
- };
2336
- const resolveAvailable = async (source) => {
2337
- try {
2338
- return await resolve(source);
2339
- } catch (error) {
2340
- unavailableSelections[selectionKey(source)] = error instanceof SelectionUnavailableError ? error.reason : "other";
2341
- return [];
2342
- }
2343
- };
2344
- const lenses = await Promise.all((snapshot.lenses ?? []).map(async (authored) => {
2345
- const lens = {
2346
- id: authored.id,
2347
- title: authored.title
2348
- };
2349
- try {
2350
- await Promise.all(lensSelections([authored]).map(({ source }) => resolveAvailable(source)));
2351
- const resolved = resolveFileLens(authored, files, fileSources, (source) => resolvedSelections[selectionKey(source)] ?? []);
2352
- return {
2353
- ...lens,
2354
- ...resolved,
2355
- pending: counting,
2356
- unavailable: counting || resolved.fileCount ? void 0 : "No files match these targets"
2357
- };
2358
- } catch {
2359
- return {
2360
- ...lens,
2361
- sources: [],
2362
- unavailable: "Source links need updating at these pins"
2363
- };
2364
- }
2365
- }));
2366
- await Promise.all(selectionReferences(snapshot.document, {
2367
- tolerant: true,
2368
- repositoryId: boardRepositoryId(snapshot)
2369
- }).map(async ({ source }) => {
2370
- try {
2371
- await resolveAvailable(source);
2372
- } catch {}
2373
- }));
2374
- const uncategorized = counting ? [] : uncategorizedSources(files, lenses.filter((lens) => !lens.unavailable).flatMap((lens) => lens.sources));
2375
- lenses.push({
2376
- id: UNCATEGORIZED_LENS_ID,
2377
- title: "Uncategorized changes",
2378
- sources: uncategorized,
2379
- wholeFiles: false,
2380
- fileCount: new Set(uncategorized.map((source) => files.find((file) => source.file === (source.side === "base" ? file.previousPath ?? file.path : file.path))?.path ?? source.file)).size,
2381
- pending: counting,
2382
- unavailable: counting || uncategorized.length ? void 0 : "All changed lines are covered by lenses"
2383
- });
2384
- const progress = {
2385
- complete: !counting && !pendingSources.size,
2386
- files,
2387
- referenceFiles,
2388
- lenses,
2389
- resolvedSelections,
2390
- unavailableSelections
2391
- };
2392
- const untrackedFiles = pins ? await data.untrackedFiles(pins) : void 0;
2393
- if (untrackedFiles) progress.untrackedFiles = untrackedFiles;
2394
- return progress;
2395
- }
2396
- /** The most files an uncategorized report names; the rest are counted. */
2397
- const REPORT_FILES = 50;
2398
- /** What a lens author still has to place: the changed lines no lens selects,
2399
- * grouped by file. */
2400
- function uncategorizedReport(progress) {
2401
- const sources = progress.lenses.find((lens) => lens.id === "automatic-uncategorized")?.sources ?? [];
2402
- const byPath = /* @__PURE__ */ new Map();
2403
- for (const source of sources) {
2404
- const path = progress.files.find((file) => source.file === (source.side === "base" ? file.previousPath ?? file.path : file.path))?.path ?? source.file;
2405
- const entry = byPath.get(path) ?? {
2406
- path,
2407
- lines: 0,
2408
- ranges: []
2409
- };
2410
- entry.lines += source.toLine - source.fromLine + 1;
2411
- entry.ranges.push({
2412
- side: source.side,
2413
- fromLine: source.fromLine,
2414
- toLine: source.toLine
2415
- });
2416
- byPath.set(path, entry);
2417
- }
2418
- const files = [...byPath.values()];
2419
- const report = {
2420
- lines: files.reduce((total, file) => total + file.lines, 0),
2421
- files: files.slice(0, REPORT_FILES)
2422
- };
2423
- if (files.length > REPORT_FILES) report.moreFiles = files.length - REPORT_FILES;
2424
- return report;
2425
- }
2426
- /** Each lens as authored, with what it resolves to, for a lens author. */
2427
- function lensReport(lenses, progress) {
2428
- return {
2429
- lenses: lenses.map((lens) => {
2430
- const resolved = progress.lenses.find((item) => item.id === lens.id);
2431
- return {
2432
- ...lens,
2433
- fileCount: resolved?.fileCount ?? 0,
2434
- ...resolved?.unavailable && { unavailable: resolved.unavailable }
2435
- };
2436
- }),
2437
- uncategorized: uncategorizedReport(progress)
2438
- };
2439
- }
2440
- //#endregion
2441
2203
  //#region src/ask/checkout-files.ts
2442
2204
  /** The checkout's files relative to its root: tracked ones, and new ones git
2443
2205
  * does not ignore. None where git cannot list them. */
@@ -2883,7 +2645,7 @@ function mountSharingHost(app, store, data, shared, options = {}) {
2883
2645
  const parsed = parseShareLink(url);
2884
2646
  const account = await readStoreAuth();
2885
2647
  if (parsed.origin !== DEFAULT_STORE_ORIGIN && parsed.origin !== account?.origin) throw new ReviewInputError("This share uses an untrusted service. Sign in to that service before opening its links.", 400);
2886
- const id = sharedReviewId(parsed.origin, parsed.shareId);
2648
+ const id = sharedReviewId(parsed.origin, parsed.capability);
2887
2649
  if (!imports.has(id)) {
2888
2650
  shared.setStatus(id, "downloading");
2889
2651
  const job = (async () => {
@@ -2891,8 +2653,8 @@ function mountSharingHost(app, store, data, shared, options = {}) {
2891
2653
  await shared.prepare(id);
2892
2654
  return;
2893
2655
  }
2894
- const bundle = await new ShareClient(parsed.origin).download(parsed.shareId, parsed.capability);
2895
- await shared.import(parsed.origin, parsed.shareId, bundle);
2656
+ const { bundle, attribution } = await new ShareClient(parsed.origin).download(parsed.capability);
2657
+ await shared.import(parsed.origin, parsed.capability, bundle, attribution);
2896
2658
  })().catch((error) => {
2897
2659
  shared.setStatus(id, "error", error instanceof ReviewInputError ? error.message : "This share is unavailable, revoked, or needs a newer Whiteboard version.");
2898
2660
  }).finally(() => imports.delete(id));
@@ -2919,9 +2681,13 @@ function mountSharingPublisher(app, store, data, options = {}) {
2919
2681
  const repositoryId = snapshot.pins?.repositoryId ?? snapshot.target?.repositoryId;
2920
2682
  const root = repositoryId ? store.repositoryPath(repositoryId) : void 0;
2921
2683
  const readRepository = options.readRepository ?? readShareRepository;
2922
- const repository = !root ? void 0 : snapshot.pins ? await readRepository(root) : await (async () => {
2684
+ const commits = referenceCommits(snapshot);
2685
+ const repository = (async () => {
2686
+ if (!root) return void 0;
2923
2687
  try {
2924
- return await readRepository(root);
2688
+ const repository = await readRepository(root);
2689
+ if (snapshot.pins || commits.length) await verifyRepository(root, snapshot.pins, commits);
2690
+ return repository;
2925
2691
  } catch {
2926
2692
  return;
2927
2693
  }
@@ -2930,19 +2696,12 @@ function mountSharingPublisher(app, store, data, options = {}) {
2930
2696
  store,
2931
2697
  data,
2932
2698
  reviewId: input.reviewId,
2933
- version: snapshot.version,
2934
- repository
2699
+ version: snapshot.version
2935
2700
  });
2936
- const commits = referenceCommits(snapshot);
2937
- const verification = root && (snapshot.pins || commits.length) && repository ? verifyRepository(root, snapshot.pins, commits).then(() => ({ ok: true }), (error) => ({
2938
- ok: false,
2939
- error
2940
- })) : Promise.resolve({ ok: true });
2701
+ const verified = await repository;
2702
+ if (verified) bundle.repository = verified;
2941
2703
  try {
2942
- const result = await new ShareClient(account.origin, account.token, options.fetch).create(bundle, input.requestId ?? randomUUID(), async () => {
2943
- const result = await verification;
2944
- if (!result.ok) throw result.error;
2945
- });
2704
+ const result = await new ShareClient(account.origin, account.token, options.fetch).create(bundle, input.requestId ?? randomUUID());
2946
2705
  const url = new URL(result.url);
2947
2706
  if (process.env.DEV_FAST_REVIEW_APP_URL_PROTOCOL === "dev-fast-review-preview") url.searchParams.set("app", "preview");
2948
2707
  options.onPublished?.({
@@ -2955,7 +2714,7 @@ function mountSharingPublisher(app, store, data, options = {}) {
2955
2714
  version: snapshot.version
2956
2715
  });
2957
2716
  } catch (error) {
2958
- if (error instanceof SharePreflightError) return context.json({ error: error.message }, 422);
2717
+ if (error instanceof ReviewInputError) throw error;
2959
2718
  if (error instanceof ShareAuthError) {
2960
2719
  if (process.env.DEV_REVIEW_SHARE_TOKEN === void 0) await clearStoreAuth();
2961
2720
  throw new ReviewInputError("Your sign-in has expired. Sign in again to share.", 401);
@@ -2984,17 +2743,21 @@ var SharedReviewData = class {
2984
2743
  this.store = store;
2985
2744
  }
2986
2745
  map(id, resourceId) {
2987
- const map = this.store.get(id).presentation.maps[resourceId];
2988
- if (!map) throw new ReviewInputError("Map is not included in this share.", 404);
2989
- return map;
2746
+ const { maps } = this.store.get(id);
2747
+ if (!Object.hasOwn(maps, resourceId)) throw new ReviewInputError("Map is not included in this share.", 404);
2748
+ return maps[resourceId];
2990
2749
  }
2991
2750
  async resource(id, resourceId) {
2992
- const resource = this.store.get(id).manifest.resources.find((item) => item.id === resourceId);
2993
- if (!resource) throw new ReviewInputError("Resource is not included in this share.", 404);
2994
- return {
2995
- data: await this.store.readObject(id, resource.object),
2996
- mimeType: resource.mimeType
2751
+ const { bundle } = this.store.get(id);
2752
+ if (Object.hasOwn(bundle.images, resourceId)) return {
2753
+ data: Buffer.from(bundle.images[resourceId].png, "base64"),
2754
+ mimeType: "image/png"
2755
+ };
2756
+ if (Object.hasOwn(bundle.traces, resourceId)) return {
2757
+ data: Buffer.from(JSON.stringify(bundle.traces[resourceId])),
2758
+ mimeType: "application/json"
2997
2759
  };
2760
+ throw new ReviewInputError("Resource is not included in this share.", 404);
2998
2761
  }
2999
2762
  };
3000
2763
  //#endregion
@@ -3136,6 +2899,14 @@ function documentText(snapshot, targetId, full = false, relations) {
3136
2899
  }
3137
2900
  for (const edge of element.edges) detail(`[${edge.id}] ${edge.from} → ${edge.to}${edge.label ? `: ${edge.label}` : ""}`);
3138
2901
  break;
2902
+ case "animation":
2903
+ for (const [index, frame] of element.frames.entries()) detail(`Frame ${index + 1} [${frame.key}]: ${frame.title}${frame.caption ? ` — ${frame.caption}` : ""}`);
2904
+ for (const [name, anchor] of Object.entries(element.code)) detail(`Code ${name}: ${anchor}`);
2905
+ if (detailed) code(depth + 1, {
2906
+ language: "html",
2907
+ text: element.html
2908
+ });
2909
+ break;
3139
2910
  case "software_map": detail(`Map: ${element.mapVersionId}${element.focusElementId ? `, focus: ${element.focusElementId}` : ""}`);
3140
2911
  }
3141
2912
  };
@@ -3272,24 +3043,53 @@ async function modified(file) {
3272
3043
  return 0;
3273
3044
  }
3274
3045
  }
3046
+ /** The folder holding a bare clone's `.git` file, as in `project/.bare`;
3047
+ * else the clone itself. */
3048
+ async function bareRoot(bare) {
3049
+ const parent = path.dirname(bare);
3050
+ try {
3051
+ const gitdir = /^gitdir:\s*(.+)$/m.exec(await readFile(path.join(parent, ".git"), "utf8"))?.[1];
3052
+ if (gitdir && path.resolve(parent, gitdir.trim()) === bare) return parent;
3053
+ } catch {}
3054
+ return bare;
3055
+ }
3056
+ /** A folder's project: its main checkout, its bare clone, or itself. */
3057
+ async function projectOf(folder, run) {
3058
+ const git = (args, cwd = folder) => run("git", args, cwd).then((output) => output.trim());
3059
+ const common = path.resolve(folder, await git([
3060
+ "rev-parse",
3061
+ "--path-format=absolute",
3062
+ "--git-common-dir"
3063
+ ]));
3064
+ if (path.basename(common) === ".git") return {
3065
+ root: path.dirname(common),
3066
+ common,
3067
+ bare: false
3068
+ };
3069
+ if (await git(["rev-parse", "--is-bare-repository"], common) === "true") return {
3070
+ root: await bareRoot(common),
3071
+ common,
3072
+ bare: true
3073
+ };
3074
+ return {
3075
+ root: await git(["rev-parse", "--show-toplevel"]),
3076
+ common,
3077
+ bare: false
3078
+ };
3079
+ }
3275
3080
  /** The repository a folder belongs to, and when it last saw Git activity.
3276
3081
  * Worktrees resolve to their main checkout, which lists them as targets. */
3277
3082
  async function repositoryOf(folder, run) {
3278
3083
  try {
3279
3084
  if (!(await stat(folder)).isDirectory()) return void 0;
3280
- const common = path.resolve(folder, (await run("git", [
3281
- "rev-parse",
3282
- "--path-format=absolute",
3283
- "--git-common-dir"
3284
- ], folder)).trim());
3285
- if (path.basename(common) !== ".git") return void 0;
3085
+ const { root, common } = await projectOf(folder, run);
3286
3086
  const activity = await Promise.all([
3287
3087
  "index",
3288
3088
  "FETCH_HEAD",
3289
3089
  path.join("logs", "HEAD")
3290
3090
  ].map((file) => modified(path.join(common, file))));
3291
3091
  return {
3292
- root: path.dirname(common),
3092
+ root,
3293
3093
  active: Math.max(...activity)
3294
3094
  };
3295
3095
  } catch {
@@ -3322,9 +3122,24 @@ function parseWorktrees(output) {
3322
3122
  for (const line of output.split("\n")) if (line.startsWith("worktree ")) {
3323
3123
  current = { path: line.slice(9) };
3324
3124
  worktrees.push(current);
3325
- } else if (current && line.startsWith("branch ")) current.branch = line.slice(7).replace(/^refs\/heads\//, "");
3125
+ } else if (!current) continue;
3126
+ else if (line.startsWith("branch ")) current.branch = line.slice(7).replace(/^refs\/heads\//, "");
3127
+ else if (line === "bare") current.bare = true;
3128
+ else if (line.startsWith("prunable")) current.prunable = true;
3326
3129
  return worktrees;
3327
3130
  }
3131
+ const drawable = (worktree) => !worktree.bare && !worktree.prunable && !worktree.path.split(/[\\/]/).includes("dev-fast") && !isInside(TEMPORARY_ROOTS, worktree.path);
3132
+ /** In a bare clone, a pull request is drawn from the worktree on its branch. */
3133
+ async function checkoutOf(project, branch, run) {
3134
+ const worktrees = parseWorktrees(await run("git", [
3135
+ "worktree",
3136
+ "list",
3137
+ "--porcelain"
3138
+ ], project).catch(() => ""));
3139
+ if (!worktrees[0]?.bare) return project;
3140
+ const usable = worktrees.filter(drawable);
3141
+ return (usable.find((tree) => tree.branch === branch) ?? usable[0])?.path;
3142
+ }
3328
3143
  const pullRequestsSchema = z.array(z.object({
3329
3144
  number: z.number().int().positive(),
3330
3145
  title: z.string().min(1),
@@ -3387,13 +3202,17 @@ async function pullRequests(root, run) {
3387
3202
  * worktrees, and its open pull requests, most recently touched first. */
3388
3203
  async function onboardingTargets(directory, run = runCommand) {
3389
3204
  let root;
3205
+ let bare = false;
3390
3206
  try {
3391
3207
  root = (await run("git", ["rev-parse", "--show-toplevel"], directory)).trim();
3392
3208
  } catch {
3393
- throw new ReviewInputError("Choose a folder inside a Git repository.", 400);
3209
+ const project = await projectOf(directory, run).catch(() => void 0);
3210
+ if (!project?.bare) throw new ReviewInputError("Choose a folder inside a Git repository.", 400);
3211
+ root = project.root;
3212
+ bare = true;
3394
3213
  }
3395
3214
  const [branch, worktreeList, base, prs, times] = await Promise.all([
3396
- run("git", ["branch", "--show-current"], root).then((output) => output.trim(), () => ""),
3215
+ bare ? "" : run("git", ["branch", "--show-current"], root).then((output) => output.trim(), () => ""),
3397
3216
  run("git", [
3398
3217
  "worktree",
3399
3218
  "list",
@@ -3407,7 +3226,7 @@ async function onboardingTargets(directory, run = runCommand) {
3407
3226
  target,
3408
3227
  at: times.get(target.branch) ?? 0
3409
3228
  });
3410
- const worktrees = parseWorktrees(worktreeList).filter((worktree) => worktree.branch && path.resolve(worktree.path) !== path.resolve(root) && !worktree.path.split(/[\\/]/).includes("dev-fast") && !isInside(TEMPORARY_ROOTS, worktree.path)).map((worktree) => local({
3229
+ const worktrees = parseWorktrees(worktreeList).filter((worktree) => worktree.branch && path.resolve(worktree.path) !== path.resolve(root) && drawable(worktree)).map((worktree) => local({
3411
3230
  kind: "worktree",
3412
3231
  path: worktree.path,
3413
3232
  branch: worktree.branch ?? ""
@@ -3450,7 +3269,8 @@ function registerOnboardingRoutes(app, store, ask, host, run = runCommand) {
3450
3269
  if (!parsed.success) throw new ReviewInputError("Invalid run.", 400);
3451
3270
  const input = parsed.data;
3452
3271
  const { target } = input;
3453
- const cwd = target.kind === "pr" ? input.projectPath : target.path;
3272
+ const cwd = target.kind === "pr" ? await checkoutOf(input.projectPath, target.branch, run) : target.path;
3273
+ if (!cwd) throw new ReviewInputError("This bare clone has no worktrees. Add one with git worktree add.", 400);
3454
3274
  let head;
3455
3275
  try {
3456
3276
  head = (await run("git", ["rev-parse", "HEAD"], cwd)).trim();
@@ -3638,7 +3458,7 @@ const remoteCaller = (context) => context.req.header(REVIEW_CLIENT_HEADER) === R
3638
3458
  function createReviewApi(store, data, open, shared, capabilities = () => ({
3639
3459
  desktopAvailable: Boolean(open),
3640
3460
  softwareMapEnabled: false
3641
- }), traceEnabled = async () => false, status = () => ({}), hooks = {}, ask) {
3461
+ }), traceEnabled = async () => false, status = () => ({}), hooks = {}, ask, animationsEnabled = () => false) {
3642
3462
  const app = new Hono();
3643
3463
  app.onError((error, context) => {
3644
3464
  if (error instanceof HttpJsonError) return context.json({ error: error.message }, error.statusCode);
@@ -3729,10 +3549,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3729
3549
  };
3730
3550
  app.get("/", (context) => context.json(catalog()));
3731
3551
  app.get("/feedback-eligibility", (context) => context.json({ eligible: store.feedbackEligible() }));
3732
- const instructionContext = async () => ({ traceEnabled: await traceEnabled() });
3552
+ const instructionContext = async () => ({
3553
+ traceEnabled: await traceEnabled(),
3554
+ animationsEnabled: animationsEnabled()
3555
+ });
3733
3556
  app.get("/authoring", async (context) => {
3734
3557
  const instructions = await instructionContext();
3735
- return context.json(authoringTools(instructions.traceEnabled));
3558
+ return context.json(authoringTools(instructions.traceEnabled, instructions.animationsEnabled));
3736
3559
  });
3737
3560
  app.get("/instructions", async (context) => {
3738
3561
  const { topic } = instructionsQuerySchema.parse(context.req.query());
@@ -3786,7 +3609,10 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3786
3609
  ...status(),
3787
3610
  desktopAvailable: (await capabilities()).desktopAvailable
3788
3611
  }));
3789
- app.get("/capabilities", async (context) => context.json(await capabilities()));
3612
+ app.get("/capabilities", async (context) => context.json({
3613
+ ...await capabilities(),
3614
+ animationsEnabled: animationsEnabled()
3615
+ }));
3790
3616
  app.get("/:id/activity", (context) => {
3791
3617
  const id = context.req.param("id");
3792
3618
  readReview(id);
@@ -4721,6 +4547,12 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4721
4547
  const register = async (path) => data ? data.register(path) : store.registerRepository(path);
4722
4548
  const request = withBoardRepository(await locateRepositories(await pullRequestTargets(body, register, (id) => store.read(id)), register), (id) => store.has(id) ? store.read(id) : void 0);
4723
4549
  const input = commandSchema.parse(request);
4550
+ if (!animationsEnabled() && input.operation.type === "edit") {
4551
+ const { edit, reviewId } = input.operation;
4552
+ const incoming = "content" in edit ? elements([edit.content]) : [];
4553
+ const target = "targetId" in edit ? elements(readReview(reviewId).document).find((element) => element.id === edit.targetId) : void 0;
4554
+ if (incoming.some((element) => element.type === "animation") || target?.type === "animation") throw new ReviewInputError("Animation authoring is disabled. Enable Animations in Settings under Experimental Features to edit animations. Existing animations can still be viewed.", 409);
4555
+ }
4724
4556
  const command = sharedCommandSchema.safeParse(input);
4725
4557
  if (command.success && command.data.operation.reviewId?.startsWith("shared-")) {
4726
4558
  const parsed = commandSchema.parse(input);
@@ -5096,7 +4928,7 @@ function createWhiteboardCore(input) {
5096
4928
  const api = createReviewApi(store, data, callbacks.open, shared, callbacks.capabilities, () => traceMachineEnabled(), input.status, input.hooks, askThreads && {
5097
4929
  threads: askThreads,
5098
4930
  agents: async () => detectAskAgents(await ask?.env)
5099
- });
4931
+ }, input.animations);
5100
4932
  if (!shared) mountSharingPublisher(api, store, data);
5101
4933
  return {
5102
4934
  app,
@@ -5431,4 +5263,4 @@ async function serve(input) {
5431
5263
  }
5432
5264
  }
5433
5265
  //#endregion
5434
- export { saveDiffrSummarizer as C, cliAskTools as E, readDiffrConfig as S, testDiffrSummarizer as T, DEFAULT_MAX_REQUEST_BYTES as _, drainServerCrashReport as a, GlobalReviewDesktopVerbRelay as b, answerErrorsAsJson as c, legacyModeSchema as d, captureSanitizedUiTelemetry as f, readBoundedRequestJson as g, createNodeRequestListener as h, withHeadlessServerLock as i, createWhiteboardCore as l, recordClientError as m, runHeadlessServer as n, installProcessErrorTelemetry as o, clientErrorsForSession as p, takeOverServerHome as r, serverCrashReportPath as s, HeadlessServerBusyError as t, serverJson as u, HttpJsonError as v, setDiffrConfigValue as w, migrateDiffrConfig as x, ReviewServerError as y };
5266
+ export { setDiffrConfigValue as C, saveDiffrSummarizer as S, cliAskTools as T, HttpJsonError as _, drainServerCrashReport as a, migrateDiffrConfig as b, answerErrorsAsJson as c, captureSanitizedUiTelemetry as d, clientErrorsForSession as f, DEFAULT_MAX_REQUEST_BYTES as g, readBoundedRequestJson as h, withHeadlessServerLock as i, createWhiteboardCore as l, createNodeRequestListener as m, runHeadlessServer as n, installProcessErrorTelemetry as o, recordClientError as p, takeOverServerHome as r, serverCrashReportPath as s, HeadlessServerBusyError as t, serverJson as u, ReviewServerError as v, testDiffrSummarizer as w, readDiffrConfig as x, GlobalReviewDesktopVerbRelay as y };