@dev.fast/whiteboard 0.4.0 → 0.4.1-preview.20261026.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-alias-2yTAxoBx.js → account-alias-BpH8VvTR.js} +2 -2
- package/dist/agent-cli-DFWrNoGJ.js +2 -0
- package/dist/{agent-cli-CHNDd4Ga.js → agent-cli-DckMzOU_.js} +6 -6
- package/dist/{agent-client-Dc0vHFJt.js → agent-client-D4FSEDX4.js} +3 -3
- package/dist/{authoring-tools-D8EkIDjm.js → authoring-tools-DmIJZq0b.js} +21 -8
- package/dist/{background-server-DHWHzpo5.js → background-server-B1uwDQTr.js} +2 -2
- package/dist/{background-server-DyWnPJyZ.js → background-server-BPZ3GQan.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-runner-DFBaVGij.js → cli-runner-DVEDbjXo.js} +17 -17
- package/dist/{cli-qZomwJmK.js → cli-tWBQpq27.js} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{client-p9WB8Sj0.js → client-C8vT0mhW.js} +3 -3
- package/dist/{fuzzy-match-CfSxqzBF.js → fuzzy-match-BNLXmpbv.js} +2 -2
- package/dist/{headless-host-dwlqhqMY.js → headless-host-B63e2z2j.js} +1 -1
- package/dist/{headless-host-DJhcab6r.js → headless-host-Dl9g9Fvd.js} +37 -17
- package/dist/{lens-selection-9byzEgvU.js → lens-selection-EuZdag4Q.js} +1 -1
- package/dist/{local-data-DHedfhxw.js → local-data-C0OJttzT.js} +92 -63
- package/dist/{mcp-Br-r_wwR.js → mcp-D74cFQNM.js} +17 -8
- package/dist/profile-CAn8Qdeh.js +2 -0
- package/dist/{profile-D9wdlPgw.js → profile-DS1F6PBF.js} +1 -1
- package/dist/{remote-attach-DSGpTOJ4.js → remote-attach-DTByuwBs.js} +3 -3
- package/dist/{remote-language-server-Dx13Mg2m.js → remote-language-server-DQfg07BB.js} +1 -1
- package/dist/{remote-uninstall-Dp2o3qz0.js → remote-uninstall-aXApHJPy.js} +3 -3
- package/dist/{request-origin-CNSLVn7x.js → request-origin-BIBW432J.js} +12 -3
- package/dist/{review-app-launcher-BZAnR8Vp.js → review-app-launcher-Brn60fPu.js} +2 -2
- package/dist/{review-telemetry-BM-_KdLi.js → review-telemetry-ChlsTRPN.js} +2 -2
- package/dist/server/desktop-host.js +44 -10
- package/dist/{server-discovery-DKLIOgg3.js → server-discovery-BchUrwFW.js} +1 -1
- package/dist/sharing/index.d.ts +53 -3
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DYHVzX3b.js → src-DEOc8bvC.js} +63 -2
- package/dist/{stored-document-migration-DEt8iwem.js → stored-document-migration-BCFx07Bq.js} +52 -11
- package/dist/{thread-state-CO97FUqK.js → thread-state-CbLB0jUA.js} +1 -1
- package/dist/{tool-failure-C1L1vJyz.js → tool-failure-DlU9hn7_.js} +2 -2
- package/instructions/animation.md +345 -0
- package/instructions/authoring.md +2 -2
- package/package.json +7 -7
- package/src/fixtures/blocks/animation.json +19 -0
- package/src/review-api/authoring-tools.ts +17 -6
- package/src/review-api/blocks/animation.ts +98 -0
- package/src/review-api/blocks/index.ts +3 -0
- package/src/review-api/blocks/sequence.ts +3 -1
- package/src/review-api/comparison-coverage.ts +120 -89
- package/src/review-api/component-reference.ts +16 -0
- package/src/review-api/document-text.ts +11 -0
- package/src/review-api/document.ts +26 -5
- package/src/review-api/http.ts +31 -2
- package/src/review-api/instructions.ts +11 -2
- package/src/review-api/local-data.ts +21 -4
- package/src/review-api/mcp.ts +22 -14
- package/src/review-preferences.ts +43 -0
- package/src/server/desktop-server.ts +24 -0
- package/src/server/review-server-core.ts +2 -0
- package/src/server/structural-comparisons.ts +5 -11
- package/src/server/structural-diff.ts +8 -2
- package/src/ui-telemetry-events.ts +9 -0
- package/dist/agent-cli-Cn4-16XA.js +0 -2
- package/dist/profile-BopGMZUh.js +0 -2
|
@@ -4,10 +4,12 @@ import path from "node:path";
|
|
|
4
4
|
import { findReviewPackageRoot } from "@review/package-paths.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { componentReference } from "./component-reference.js";
|
|
8
|
+
import { ReviewInputError } from "./input-error.js";
|
|
8
9
|
|
|
9
10
|
export const INSTRUCTION_TOPICS = [
|
|
10
11
|
"authoring",
|
|
12
|
+
"animation",
|
|
11
13
|
"file-lenses",
|
|
12
14
|
"trace-archaeology",
|
|
13
15
|
] as const;
|
|
@@ -20,6 +22,7 @@ export const instructionsQuerySchema = z.strictObject({
|
|
|
20
22
|
|
|
21
23
|
export interface InstructionContext {
|
|
22
24
|
traceEnabled: boolean;
|
|
25
|
+
animationsEnabled?: boolean;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
const cache = new Map<string, Promise<string>>();
|
|
@@ -44,6 +47,12 @@ export async function renderInstructions(
|
|
|
44
47
|
context: InstructionContext,
|
|
45
48
|
root = findReviewPackageRoot(import.meta.url),
|
|
46
49
|
): Promise<string> {
|
|
50
|
+
if (topic === "animation" && !context.animationsEnabled)
|
|
51
|
+
throw new ReviewInputError(
|
|
52
|
+
"Animation authoring is disabled. Enable Animations in Settings under Experimental Features.",
|
|
53
|
+
409,
|
|
54
|
+
);
|
|
55
|
+
|
|
47
56
|
if (topic === "trace-archaeology" && !context.traceEnabled)
|
|
48
57
|
return "Trace capture is off on this machine, so no agent traces are available. It can be turned on in Whiteboard Desktop Settings under Experimental Features.";
|
|
49
58
|
|
|
@@ -59,7 +68,7 @@ export async function renderInstructions(
|
|
|
59
68
|
|
|
60
69
|
return [
|
|
61
70
|
await read(root, "authoring"),
|
|
62
|
-
|
|
71
|
+
componentReference(context.animationsEnabled === true),
|
|
63
72
|
...(context.traceEnabled
|
|
64
73
|
? [
|
|
65
74
|
'## Traces\n\nAt the end, run `whiteboard trace list --session <sessionId> --json`, which covers every commit in the review. If it lists no sessions, there are no traces to quote: skip this step. Otherwise read `session_get_instructions({topic:"trace-archaeology"})` and rewrite as much as possible of the what/why, design, and requirements sections in terms of literal trace quotes from the user.',
|
|
@@ -1817,6 +1817,8 @@ export class LocalReviewData {
|
|
|
1817
1817
|
state: "pending" | "ready" | "error";
|
|
1818
1818
|
error?: unknown;
|
|
1819
1819
|
partial?: ComparisonCoverage;
|
|
1820
|
+
abort: AbortController;
|
|
1821
|
+
worktree?: string;
|
|
1820
1822
|
}
|
|
1821
1823
|
>();
|
|
1822
1824
|
private readonly coverageAbort = new AbortController();
|
|
@@ -1877,11 +1879,24 @@ export class LocalReviewData {
|
|
|
1877
1879
|
let entry = this.coverageCache.get(key);
|
|
1878
1880
|
|
|
1879
1881
|
if (!entry || entry.state === "error") {
|
|
1882
|
+
const worktree = pins.worktreeRevision
|
|
1883
|
+
? JSON.stringify({ ...pins, worktreeRevision: undefined })
|
|
1884
|
+
: undefined;
|
|
1885
|
+
|
|
1886
|
+
// A newer worktree revision cancels older ones.
|
|
1887
|
+
for (const [cachedKey, cached] of this.coverageCache)
|
|
1888
|
+
if (worktree && cached.worktree === worktree) {
|
|
1889
|
+
cached.abort.abort();
|
|
1890
|
+
this.coverageCache.delete(cachedKey);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const abort = new AbortController();
|
|
1894
|
+
|
|
1880
1895
|
const promise = comparisonCoverage(
|
|
1881
1896
|
this,
|
|
1882
1897
|
reviewId,
|
|
1883
1898
|
pins,
|
|
1884
|
-
this.coverageAbort.signal,
|
|
1899
|
+
AbortSignal.any([this.coverageAbort.signal, abort.signal]),
|
|
1885
1900
|
(partial) => {
|
|
1886
1901
|
const current = this.coverageCache.get(key);
|
|
1887
1902
|
|
|
@@ -1890,7 +1905,7 @@ export class LocalReviewData {
|
|
|
1890
1905
|
},
|
|
1891
1906
|
);
|
|
1892
1907
|
|
|
1893
|
-
entry = { promise, state: "pending" };
|
|
1908
|
+
entry = { promise, state: "pending", abort, worktree };
|
|
1894
1909
|
this.coverageCache.set(key, entry);
|
|
1895
1910
|
const current = entry;
|
|
1896
1911
|
|
|
@@ -1910,7 +1925,7 @@ export class LocalReviewData {
|
|
|
1910
1925
|
|
|
1911
1926
|
void promise.then(
|
|
1912
1927
|
(value) => {
|
|
1913
|
-
current.partial
|
|
1928
|
+
current.partial ??= value;
|
|
1914
1929
|
|
|
1915
1930
|
if (!this.closed)
|
|
1916
1931
|
this.store.setDiffStats(pins, {
|
|
@@ -1931,7 +1946,9 @@ export class LocalReviewData {
|
|
|
1931
1946
|
);
|
|
1932
1947
|
}
|
|
1933
1948
|
|
|
1934
|
-
return entry.
|
|
1949
|
+
return entry.state === "ready" && entry.partial
|
|
1950
|
+
? Promise.resolve(entry.partial)
|
|
1951
|
+
: entry.promise;
|
|
1935
1952
|
}
|
|
1936
1953
|
|
|
1937
1954
|
changes(pins: Pins): Promise<LocalVcsDiffFileSummary[]>;
|
package/src/review-api/mcp.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { type JsonValue, isJsonObject } from "@dev.fast/json";
|
|
4
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import {
|
|
7
7
|
CallToolRequestSchema,
|
|
8
|
+
type CallToolResult,
|
|
8
9
|
ListToolsRequestSchema,
|
|
9
10
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
11
|
import type { ReviewToolCall } from "@review/review-telemetry.js";
|
|
@@ -13,6 +14,7 @@ import type { ReviewSessionAgent } from "@review/ui-telemetry-events.js";
|
|
|
13
14
|
import {
|
|
14
15
|
type AuthoringTool,
|
|
15
16
|
type ConnectedReview,
|
|
17
|
+
ToolText,
|
|
16
18
|
toolResultText,
|
|
17
19
|
} from "./agent-client.js";
|
|
18
20
|
import { authoringTools } from "./authoring-tools.js";
|
|
@@ -37,6 +39,7 @@ function mcpAuthoringGuidance(context: { traceEnabled: boolean }): string {
|
|
|
37
39
|
'For why code exists, what an agent was thinking, or whether an agent solved something before, call session_get_instructions({topic:"trace-archaeology"}).',
|
|
38
40
|
]
|
|
39
41
|
: []),
|
|
42
|
+
"For programmatic MCP calls, use structuredContent when present; otherwise parse JSON from the text content when the result is JSON. An insert through session_edit uses edit.content for the actual document component, never the MCP transport wrapper containing content or structuredContent.",
|
|
40
43
|
"Never read or write Whiteboard files or SQL.",
|
|
41
44
|
].join(" ");
|
|
42
45
|
}
|
|
@@ -168,20 +171,20 @@ export async function serveReviewMcp(
|
|
|
168
171
|
)
|
|
169
172
|
return { content: [{ type: "text" as const, text: RECOVERY }] };
|
|
170
173
|
|
|
171
|
-
if (request.params.name === REVIEW_STATUS_TOOL.name && offlineStatus)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
error instanceof Error ? error.message : String(error),
|
|
179
|
-
),
|
|
180
|
-
),
|
|
181
|
-
},
|
|
182
|
-
],
|
|
174
|
+
if (request.params.name === REVIEW_STATUS_TOOL.name && offlineStatus) {
|
|
175
|
+
const result = await offlineStatus(
|
|
176
|
+
error instanceof Error ? error.message : String(error),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const response: CallToolResult = {
|
|
180
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
183
181
|
};
|
|
184
182
|
|
|
183
|
+
if (isJsonObject(result)) response.structuredContent = result;
|
|
184
|
+
|
|
185
|
+
return response;
|
|
186
|
+
}
|
|
187
|
+
|
|
185
188
|
throw error;
|
|
186
189
|
}
|
|
187
190
|
|
|
@@ -203,7 +206,7 @@ export async function serveReviewMcp(
|
|
|
203
206
|
const text = toolResultText(tool, result);
|
|
204
207
|
report();
|
|
205
208
|
|
|
206
|
-
|
|
209
|
+
const response: CallToolResult = {
|
|
207
210
|
content: [
|
|
208
211
|
{
|
|
209
212
|
type: "text",
|
|
@@ -214,6 +217,11 @@ export async function serveReviewMcp(
|
|
|
214
217
|
},
|
|
215
218
|
],
|
|
216
219
|
};
|
|
220
|
+
|
|
221
|
+
if (!(result instanceof ToolText) && isJsonObject(result))
|
|
222
|
+
response.structuredContent = result;
|
|
223
|
+
|
|
224
|
+
return response;
|
|
217
225
|
} catch (error) {
|
|
218
226
|
report(toolFailure(error, phase));
|
|
219
227
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { isJsonObject, parseJsonText } from "@dev.fast/review-protocol";
|
|
5
|
+
import { writePrivateJsonAtomic } from "@dev.fast/trace-core";
|
|
6
|
+
|
|
7
|
+
import { devReviewHome } from "./review-home-paths";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Machine-wide Review preferences the server itself needs. Workbench settings
|
|
11
|
+
* do not work here: the review server never reads the workbench
|
|
12
|
+
* configuration.
|
|
13
|
+
*/
|
|
14
|
+
function reviewPreferencesPath(devHome = devReviewHome()): string {
|
|
15
|
+
return path.join(devHome, "preferences.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Whether agents are offered animation authoring; existing animations
|
|
19
|
+
* always render. A missing or unreadable file means off; it never throws. */
|
|
20
|
+
export async function readAnimationsEnabled(
|
|
21
|
+
devHome?: string,
|
|
22
|
+
): Promise<boolean> {
|
|
23
|
+
try {
|
|
24
|
+
const raw = parseJsonText(
|
|
25
|
+
await readFile(reviewPreferencesPath(devHome), "utf8"),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return isJsonObject(raw) && raw.animationsEnabled === true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function writeAnimationsEnabled(
|
|
35
|
+
enabled: boolean,
|
|
36
|
+
devHome?: string,
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
await writePrivateJsonAtomic(reviewPreferencesPath(devHome), {
|
|
39
|
+
animationsEnabled: enabled,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return enabled;
|
|
43
|
+
}
|
|
@@ -46,6 +46,10 @@ import {
|
|
|
46
46
|
reviewInstanceDiscoveryPath,
|
|
47
47
|
reviewLegacyDiscoveryPath,
|
|
48
48
|
} from "@review/review-home-paths";
|
|
49
|
+
import {
|
|
50
|
+
readAnimationsEnabled,
|
|
51
|
+
writeAnimationsEnabled,
|
|
52
|
+
} from "@review/review-preferences";
|
|
49
53
|
import {
|
|
50
54
|
ReviewTelemetry,
|
|
51
55
|
type ReviewTelemetryContext,
|
|
@@ -173,6 +177,10 @@ export function createGlobalReviewServer(
|
|
|
173
177
|
let closing = false;
|
|
174
178
|
const cliPath = path.join(input.packageRoot, "dist", "cli.js");
|
|
175
179
|
|
|
180
|
+
// The animations preference, read once at listen and kept current by the
|
|
181
|
+
// settings endpoint below: this server is the only writer while it runs.
|
|
182
|
+
let animationsEnabled = false;
|
|
183
|
+
|
|
176
184
|
const discovery: ReviewDesktopDiscovery = {
|
|
177
185
|
version: REVIEW_DESKTOP_DISCOVERY_VERSION,
|
|
178
186
|
instanceId,
|
|
@@ -205,6 +213,7 @@ export function createGlobalReviewServer(
|
|
|
205
213
|
relay,
|
|
206
214
|
token,
|
|
207
215
|
instanceId,
|
|
216
|
+
animations: () => animationsEnabled,
|
|
208
217
|
status: () => {
|
|
209
218
|
const { key, channel, checkout, appVersion, cliVersion, instanceId } =
|
|
210
219
|
discovery;
|
|
@@ -257,6 +266,20 @@ export function createGlobalReviewServer(
|
|
|
257
266
|
app.all("/reviews-api", (context) => gateway.fetch(context.req.raw));
|
|
258
267
|
app.all("/reviews-api/*", (context) => gateway.fetch(context.req.raw));
|
|
259
268
|
app.get("/remote-hosts", () => serverJson(200, gateway.hosts()));
|
|
269
|
+
app.get("/preferences/animations", () =>
|
|
270
|
+
serverJson(200, { enabled: animationsEnabled }),
|
|
271
|
+
);
|
|
272
|
+
app.put("/preferences/animations", async (context) => {
|
|
273
|
+
const request = z
|
|
274
|
+
.object({ enabled: z.boolean() })
|
|
275
|
+
.safeParse(await readBoundedRequestJson(context.req.raw));
|
|
276
|
+
|
|
277
|
+
if (!request.success)
|
|
278
|
+
throw new ReviewServerError("enabled must be a boolean.", 400);
|
|
279
|
+
animationsEnabled = await writeAnimationsEnabled(request.data.enabled);
|
|
280
|
+
|
|
281
|
+
return serverJson(200, { enabled: animationsEnabled });
|
|
282
|
+
});
|
|
260
283
|
app.post("/app/focus", async () => {
|
|
261
284
|
const result = await relay.dispatch({
|
|
262
285
|
name: "focusWindow",
|
|
@@ -499,6 +522,7 @@ export function createGlobalReviewServer(
|
|
|
499
522
|
},
|
|
500
523
|
listen: async () => {
|
|
501
524
|
await migrateDiffrConfig();
|
|
525
|
+
animationsEnabled = await readAnimationsEnabled();
|
|
502
526
|
boundPort = await listen(loopback.server, input.port);
|
|
503
527
|
discovery.url = urlForBoundPort();
|
|
504
528
|
|
|
@@ -132,6 +132,7 @@ export interface WhiteboardCoreInput {
|
|
|
132
132
|
token: string;
|
|
133
133
|
instanceId: string;
|
|
134
134
|
softwareMapEnabled?: boolean;
|
|
135
|
+
animations?: () => boolean;
|
|
135
136
|
status: () => JsonObject;
|
|
136
137
|
hooks?: ReviewApiHooks;
|
|
137
138
|
ask?: {
|
|
@@ -198,6 +199,7 @@ export function createWhiteboardCore(input: WhiteboardCoreInput) {
|
|
|
198
199
|
threads: askThreads,
|
|
199
200
|
agents: async () => detectAskAgents(await ask?.env),
|
|
200
201
|
},
|
|
202
|
+
input.animations,
|
|
201
203
|
);
|
|
202
204
|
|
|
203
205
|
// A shared store mounts the publisher with the rest of sharing.
|
|
@@ -42,19 +42,16 @@ export class StructuralComparisons {
|
|
|
42
42
|
} finally {
|
|
43
43
|
entry.readers--;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
entry.failed
|
|
49
|
-
) {
|
|
45
|
+
// Summaries arrive after the files, so a run nobody reads stops. Full
|
|
46
|
+
// comparisons keep coverage as a reader until diffr completes.
|
|
47
|
+
if ((!entry.done && !entry.readers) || entry.error || entry.failed) {
|
|
50
48
|
entry.abort.abort();
|
|
51
49
|
this.entries.delete(key);
|
|
52
50
|
}
|
|
53
|
-
// Keep the most recently read idle comparisons, including background enrichment.
|
|
54
|
-
// Coverage can finish before an editor subscribes; it must not cancel summaries.
|
|
55
51
|
|
|
52
|
+
// Keep the most recently read finished comparisons.
|
|
56
53
|
const idle = [...this.entries].filter(
|
|
57
|
-
([, value]) =>
|
|
54
|
+
([, value]) => value.done && !value.readers,
|
|
58
55
|
);
|
|
59
56
|
|
|
60
57
|
for (const [oldKey, old] of idle.slice(0, -IDLE_COMPARISONS)) {
|
|
@@ -124,7 +121,6 @@ class Comparison {
|
|
|
124
121
|
done = false;
|
|
125
122
|
error: unknown;
|
|
126
123
|
failed = false;
|
|
127
|
-
initialReady = false;
|
|
128
124
|
private remaining: Set<string> | undefined;
|
|
129
125
|
private readonly listeners = new Set<() => void>();
|
|
130
126
|
|
|
@@ -144,13 +140,11 @@ class Comparison {
|
|
|
144
140
|
(entry) => (entry.file.rhs ?? entry.file.lhs)!.path,
|
|
145
141
|
),
|
|
146
142
|
);
|
|
147
|
-
this.initialReady = this.remaining.size === 0;
|
|
148
143
|
} else if (event.type === "file") {
|
|
149
144
|
const path = (event.file.rhs ?? event.file.lhs)!.path;
|
|
150
145
|
|
|
151
146
|
if (!this.remaining?.delete(path))
|
|
152
147
|
throw new Error(`Unexpected structural result: ${path}`);
|
|
153
|
-
this.initialReady = this.remaining.size === 0;
|
|
154
148
|
}
|
|
155
149
|
|
|
156
150
|
if (event.type === "complete")
|
|
@@ -61,7 +61,7 @@ export async function* structuralDiff(
|
|
|
61
61
|
args.push(`${comparison.base}...${comparison.head}`);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
args.push("--", ...(input.paths ?? []));
|
|
64
|
+
args.push("--defer", "--", ...(input.paths ?? []));
|
|
65
65
|
|
|
66
66
|
// Plugins can exceed two minutes between records. Apply this timeout only
|
|
67
67
|
// until the manifest arrives; plugins enforce their own request timeouts.
|
|
@@ -101,6 +101,7 @@ export async function* structuralDiff(
|
|
|
101
101
|
let completed = false;
|
|
102
102
|
let aborted: StructuralProblem | undefined;
|
|
103
103
|
let failed = 0;
|
|
104
|
+
const sent = new Set<string>();
|
|
104
105
|
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
105
106
|
|
|
106
107
|
try {
|
|
@@ -131,7 +132,12 @@ export async function* structuralDiff(
|
|
|
131
132
|
|
|
132
133
|
failed = event.failed;
|
|
133
134
|
aborted = event.aborted;
|
|
134
|
-
} else if (event.type
|
|
135
|
+
} else if (event.type === "file") {
|
|
136
|
+
sent.add((event.file.rhs ?? event.file.lhs)!.path);
|
|
137
|
+
} else if (
|
|
138
|
+
event.type !== "update" ||
|
|
139
|
+
!sent.has((event.file.rhs ?? event.file.lhs)!.path)
|
|
140
|
+
) {
|
|
135
141
|
throw new Error(`Unexpected diffr event: ${event.type}`);
|
|
136
142
|
}
|
|
137
143
|
|
|
@@ -621,6 +621,15 @@ export const UI_TELEMETRY_EVENTS = {
|
|
|
621
621
|
event: "review_diff_failed",
|
|
622
622
|
properties: { reason: DIFF_FAILED_REASON, files: "number" },
|
|
623
623
|
},
|
|
624
|
+
diff_loaded: {
|
|
625
|
+
event: "review_diff_loaded",
|
|
626
|
+
properties: {
|
|
627
|
+
first_file_ms: "number",
|
|
628
|
+
updates_ms: "number",
|
|
629
|
+
files: "number",
|
|
630
|
+
update_errors: "number",
|
|
631
|
+
},
|
|
632
|
+
},
|
|
624
633
|
discord_clicked: {
|
|
625
634
|
event: "review_discord_clicked",
|
|
626
635
|
properties: { via: DISCORD_VIA },
|
package/dist/profile-BopGMZUh.js
DELETED