@dev.fast/whiteboard 0.2.4 → 0.2.5-preview.20261013.110

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 (77) hide show
  1. package/dist/{account-alias-_1_7vROx.js → account-alias-D0hF0sKk.js} +2 -3
  2. package/dist/{agent-cli-BrFqhnzM.js → agent-cli-BBPxwUOd.js} +6 -6
  3. package/dist/agent-cli-YSuhE5X8.js +2 -0
  4. package/dist/{agent-client-B_b1GtUc.js → agent-client-P3J4y8Zn.js} +3 -3
  5. package/dist/{authoring-tools-DvIO906l.js → authoring-tools-D8WXsm_g.js} +11 -10
  6. package/dist/build-info.json +1 -1
  7. package/dist/{cli-gRGxkwc0.js → cli-C0ziP6hg.js} +1 -1
  8. package/dist/{cli-runner-Cjl8h376.js → cli-runner-C1gY-HAI.js} +16 -17
  9. package/dist/cli.js +1 -1
  10. package/dist/{client-BFClfQLO.js → client-DzcSt6pV.js} +63 -13
  11. package/dist/{desktop-discovery-C-Qz54wO.js → desktop-discovery-Ba3dD5Cn.js} +1 -1
  12. package/dist/{headless-host-DcWaxx9P.js → headless-host-BHD_Vg5F.js} +4 -4
  13. package/dist/{local-data-Bm84Xdck.js → local-data-BlERtVm2.js} +281 -75
  14. package/dist/{mcp-BDy9Aybd.js → mcp-t7VN9R3k.js} +4 -4
  15. package/dist/{package-paths-B6-zxvIO.js → package-paths-B9PDoAl6.js} +1 -1
  16. package/dist/{process-error-telemetry-Dzh5a3s_.js → process-error-telemetry-C0swByfp.js} +158 -57
  17. package/dist/{profile-RDEioF8b.js → profile-BsYDBr7t.js} +2 -2
  18. package/dist/profile-j97aRTS7.js +2 -0
  19. package/dist/{review-agent-traces-DNJFYzc8.js → review-home-paths-Y9mYWkCI.js} +2600 -62
  20. package/dist/{review-telemetry-DBy3eVCo.js → review-telemetry-CmM6tCbj.js} +2 -2
  21. package/dist/server/desktop-host.js +35 -46
  22. package/dist/{server-discovery-Dbzn3w6Z.js → server-discovery-ZVi8Ay8J.js} +1 -1
  23. package/dist/sharing/index.d.ts +37 -49
  24. package/dist/sharing/index.js +1 -1
  25. package/dist/{src-CmdiBL20.js → src-DiHP11l3.js} +11 -1
  26. package/dist/{stored-document-migration-BxZWQQjv.js → stored-document-migration-u4L_1VZa.js} +15 -36
  27. package/dist/{tool-failure-DJmTieh5.js → tool-failure-CJHBCHMh.js} +2 -2
  28. package/instructions/scratchpad.md +1 -1
  29. package/package.json +4 -3
  30. package/src/ask/agents.ts +6 -0
  31. package/src/ask/thread-state.ts +17 -1
  32. package/src/ask/thread.ts +116 -22
  33. package/src/ask/threads.ts +8 -2
  34. package/src/error-telemetry.ts +13 -9
  35. package/src/fixtures/blocks/tutorial.json +3 -40
  36. package/src/review-api/README.md +10 -7
  37. package/src/review-api/ask-history.ts +37 -0
  38. package/src/review-api/authoring-tools.ts +8 -5
  39. package/src/review-api/blocks/database_lens.ts +5 -0
  40. package/src/review-api/blocks/tutorial.ts +2 -18
  41. package/src/review-api/comparison-coverage.ts +16 -9
  42. package/src/review-api/component-reference.ts +1 -1
  43. package/src/review-api/document.ts +24 -10
  44. package/src/review-api/http.ts +267 -189
  45. package/src/review-api/local-data.ts +126 -37
  46. package/src/review-api/store-schema.ts +25 -1
  47. package/src/review-api/store.ts +165 -33
  48. package/src/review-api/workspaces.ts +14 -14
  49. package/src/review-api/worktree-events.ts +46 -0
  50. package/src/review-app.ts +1 -1
  51. package/src/review-info.ts +1 -1
  52. package/src/review-stack.ts +2 -2
  53. package/src/server/review-server-core.ts +25 -1
  54. package/src/server/tutorial-service.ts +22 -46
  55. package/src/sharing/export.ts +1 -0
  56. package/src/sharing/import.ts +111 -26
  57. package/tutorial/document.json +27 -86
  58. package/tutorial/lenses.json +10 -0
  59. package/tutorial/runtime-manifest.json +2 -3
  60. package/tutorial/sample-service/src/database/drizzle-orm.d.ts +16 -0
  61. package/tutorial/sample-service/src/database/schema.ts +9 -10
  62. package/tutorial/sample-service/src/inventory/forklift-mood.ts +4 -0
  63. package/tutorial/sample-service/src/inventory/inventory-service.ts +18 -1
  64. package/tutorial/sample-service/src/inventory/warehouse-lore.ts +11 -0
  65. package/tutorial/sample-service/src/orders/order-service.ts +3 -2
  66. package/tutorial/sample-service/src/orders/order.ts +27 -5
  67. package/tutorial/sample-service/src/payments/fraud-vibes.ts +4 -0
  68. package/tutorial/sample-service/src/payments/payment-gateway.ts +19 -2
  69. package/tutorial/sample-service/src/payments/raccoon-ledger.ts +9 -0
  70. package/dist/agent-cli-BR2j-2n5.js +0 -2
  71. package/dist/input-error-OLgB_h31.js +0 -11
  72. package/dist/profile-BQGaX8aX.js +0 -2
  73. package/dist/review-home-paths-6zZH-1c9.js +0 -2560
  74. package/dist/tutorial-trace-DrBvkaeL.js +0 -43
  75. package/src/tutorial-conversation.ts +0 -18
  76. package/tutorial/software-map.json +0 -139
  77. package/tutorial/trace.json +0 -20
@@ -1,8 +1,8 @@
1
1
  import { c as jsonString, l as jsonValueSchema, o as jsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
2
  import "./src-WCbabvdS.js";
3
+ import "./review-home-paths-Y9mYWkCI.js";
3
4
  import { A as devReviewHome, D as processIsAlive, M as writeFileAtomic, O as withFileLock } from "./s3-config-CAxhO9u_.js";
4
- import "./review-home-paths-6zZH-1c9.js";
5
- import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "./package-paths-B6-zxvIO.js";
5
+ import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "./package-paths-B9PDoAl6.js";
6
6
  import "./request-origin-DA4K1qWY.js";
7
7
  import { createHmac, randomUUID } from "node:crypto";
8
8
  import { z } from "zod";
@@ -1,20 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { a as diffFileSummaries, b as resolveRevision, i as diff, x as splitGitPatchFiles } from "../src-CmdiBL20.js";
2
+ import { C as ReviewInputError, a as diffFileSummaries, b as resolveRevision, i as diff, x as splitGitPatchFiles } from "../src-DiHP11l3.js";
3
3
  import { c as jsonString, i as jsonBoolean, n as isJsonObject, p as isObjectValue, s as jsonProperty, u as parseJsonText } from "../src-X9phtB2j.js";
4
- import { i as SharedReviewStore } from "../client-BFClfQLO.js";
4
+ import { i as SharedReviewStore } from "../client-DzcSt6pV.js";
5
5
  import { c as ReviewBugReportRequestSchema, l as parseReviewBugReportResponse, o as parseZod, s as ReviewBugReportMetaV2Schema, t as parseReviewCliInstallApplyRequest, v as reviewDiffrSummarizerInputSchema } from "../src-WCbabvdS.js";
6
- import { C as resourceReferences, p as documentSchema } from "../stored-document-migration-BxZWQQjv.js";
7
- import { t as ReviewInputError } from "../input-error-OLgB_h31.js";
6
+ import { et as shellQuote, i as reviewInstanceDiscoveryPath, o as reviewLegacyDiscoveryPath } from "../review-home-paths-Y9mYWkCI.js";
7
+ import { C as resourceReferences, M as lensSchema, p as documentSchema } from "../stored-document-migration-u4L_1VZa.js";
8
8
  import { A as devReviewHome, P as writePrivateJsonAtomic } from "../s3-config-CAxhO9u_.js";
9
- import { U as shellQuote, i as reviewInstanceDiscoveryPath, o as reviewLegacyDiscoveryPath } from "../review-home-paths-6zZH-1c9.js";
10
- import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "../package-paths-B6-zxvIO.js";
11
- import { c as reviewInstanceIdentity, t as REVIEW_INSTANCE_ENV } from "../desktop-discovery-C-Qz54wO.js";
12
- import { t as openReviewProfile } from "../profile-RDEioF8b.js";
13
- import { r as reviewTelemetryChannel, t as ReviewTelemetry } from "../review-telemetry-DBy3eVCo.js";
9
+ import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "../package-paths-B9PDoAl6.js";
10
+ import { c as reviewInstanceIdentity, t as REVIEW_INSTANCE_ENV } from "../desktop-discovery-Ba3dD5Cn.js";
11
+ import { t as openReviewProfile } from "../profile-BsYDBr7t.js";
12
+ import { r as reviewTelemetryChannel, t as ReviewTelemetry } from "../review-telemetry-CmM6tCbj.js";
14
13
  import { o as REVIEW_TELEMETRY_TABS } from "../request-origin-DA4K1qWY.js";
15
- import { c as removeLegacyReviewSkills, d as skipCliInstall, i as finishCliInstallUpdate, l as resetCliInstall, n as applyCliInstall, r as declineCliInstall, s as removeCliInstall, t as aliasInstallationToAccount, u as resolveCliInstallStatus } from "../account-alias-_1_7vROx.js";
16
- import { _ as setDiffrConfigValue, a as serverJson, c as recordClientError, d as HttpJsonError, f as ReviewServerError, g as saveDiffrSummarizer, h as readDiffrConfig, i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, o as captureSanitizedUiTelemetry, p as GlobalReviewDesktopVerbRelay, r as serverCrashReportPath, s as clientErrorsForSession, t as drainServerCrashReport, u as readBoundedRequestJson, v as testDiffrSummarizer } from "../process-error-telemetry-Dzh5a3s_.js";
17
- import { c as readQuerySchemas } from "../authoring-tools-DvIO906l.js";
14
+ import { c as removeLegacyReviewSkills, d as skipCliInstall, i as finishCliInstallUpdate, l as resetCliInstall, n as applyCliInstall, r as declineCliInstall, s as removeCliInstall, t as aliasInstallationToAccount, u as resolveCliInstallStatus } from "../account-alias-D0hF0sKk.js";
15
+ import { _ as setDiffrConfigValue, a as serverJson, c as recordClientError, d as HttpJsonError, f as ReviewServerError, g as saveDiffrSummarizer, h as readDiffrConfig, i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, o as captureSanitizedUiTelemetry, p as GlobalReviewDesktopVerbRelay, r as serverCrashReportPath, s as clientErrorsForSession, t as drainServerCrashReport, u as readBoundedRequestJson, v as testDiffrSummarizer } from "../process-error-telemetry-C0swByfp.js";
16
+ import { c as readQuerySchemas } from "../authoring-tools-D8WXsm_g.js";
18
17
  import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
19
18
  import { z } from "zod";
20
19
  import fs, { existsSync } from "node:fs";
@@ -814,6 +813,7 @@ const authoredSchema = z.strictObject({
814
813
  title: z.string().min(1),
815
814
  document: documentSchema
816
815
  });
816
+ const lensesSchema = z.array(lensSchema.omit({ id: true }));
817
817
  const pinsSchema = z.strictObject({
818
818
  base: z.string().regex(/^[a-f0-9]{40}$/),
819
819
  head: z.string().regex(/^[a-f0-9]{40}$/)
@@ -826,15 +826,13 @@ const stampSchema = z.object({
826
826
  async function readTutorialAssets(assetsRoot) {
827
827
  const bytes = await Promise.all([
828
828
  "document.json",
829
- "trace.json",
830
- "software-map.json",
829
+ "lenses.json",
831
830
  "pins.json"
832
831
  ].map((name) => readFile(path.join(assetsRoot, name), "utf8")));
833
832
  return {
834
833
  authored: authoredSchema.parse(JSON.parse(bytes[0])),
835
- trace: JSON.parse(bytes[1]),
836
- model: JSON.parse(bytes[2]),
837
- pins: pinsSchema.parse(JSON.parse(bytes[3])),
834
+ lenses: lensesSchema.parse(JSON.parse(bytes[1])),
835
+ pins: pinsSchema.parse(JSON.parse(bytes[2])),
838
836
  contentHash: createHash("sha256").update(JSON.stringify(bytes)).digest("hex")
839
837
  };
840
838
  }
@@ -843,31 +841,6 @@ async function createNativeTutorial(input) {
843
841
  const assets = await readTutorialAssets(input.assetsRoot);
844
842
  const repository = await input.data.register(input.sampleRoot);
845
843
  const pins = await input.data.resolvePins(repository.id, assets.pins.base, assets.pins.head);
846
- const aliases = /* @__PURE__ */ new Map();
847
- const traceId = randomUUID();
848
- await input.data.upload({
849
- id: traceId,
850
- repositoryId: repository.id,
851
- kind: "trace",
852
- trace: assets.trace
853
- });
854
- aliases.set("tutorial-trace", traceId);
855
- for (const side of ["base", "head"]) {
856
- const id = randomUUID();
857
- await input.data.upload({
858
- id,
859
- repositoryId: repository.id,
860
- kind: "map",
861
- pins,
862
- side,
863
- model: assets.model
864
- });
865
- aliases.set(`tutorial-map-${side}`, id);
866
- }
867
- for (const block of resourceReferences(assets.authored.document)) {
868
- if (block.type === "trace_quote") block.traceId = aliases.get(block.traceId) ?? block.traceId;
869
- if (block.type === "software_map") block.mapVersionId = aliases.get(block.mapVersionId) ?? block.mapVersionId;
870
- }
871
844
  const result = await input.store.execute({ operation: {
872
845
  type: "create",
873
846
  title: assets.authored.title,
@@ -879,6 +852,15 @@ async function createNativeTutorial(input) {
879
852
  document: assets.authored.document,
880
853
  origin: { tutorial: true }
881
854
  });
855
+ for (const { title, targets } of assets.lenses) await input.store.execute({ operation: {
856
+ type: "lens_edit",
857
+ reviewId: result.reviewId,
858
+ edit: {
859
+ type: "insert",
860
+ title,
861
+ targets
862
+ }
863
+ } });
882
864
  return {
883
865
  snapshot: input.store.read(result.reviewId),
884
866
  contentHash: assets.contentHash
@@ -903,10 +885,17 @@ function createTutorialService(input) {
903
885
  }
904
886
  }
905
887
  async function cleanup() {
906
- for (const reviewId of input.store.tutorialIds()) await input.store.execute({ operation: {
907
- type: "delete",
908
- reviewId
909
- } });
888
+ for (const reviewId of input.store.tutorialIds()) {
889
+ if (!input.store.read(reviewId).trashedAt) await input.store.execute({ operation: {
890
+ type: "lifecycle",
891
+ state: "trash",
892
+ reviewId
893
+ } });
894
+ await input.store.execute({ operation: {
895
+ type: "delete",
896
+ reviewId
897
+ } });
898
+ }
910
899
  for (const repository of input.store.repositories()) {
911
900
  const relative = path.relative(tutorialRoot, repository.path);
912
901
  if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) await input.data.forgetRepository(repository.id);
@@ -1,5 +1,5 @@
1
+ import "./review-home-paths-Y9mYWkCI.js";
1
2
  import { A as devReviewHome } from "./s3-config-CAxhO9u_.js";
2
- import "./review-home-paths-6zZH-1c9.js";
3
3
  import { z } from "zod";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- import { watch } from "node:fs";
3
2
  import { DatabaseSync } from "node:sqlite";
3
+ import { subscribe } from "@parcel/watcher";
4
4
  //#region ../json/src/json.d.ts
5
5
  /**
6
6
  * JSON values as they come off the wire or out of a file. Parse into one of
@@ -263,7 +263,7 @@ interface ReviewApiSummary {
263
263
  deletions: number;
264
264
  } | null;
265
265
  viewedAt: string | null;
266
- dismissedAt: string | null;
266
+ trashedAt: string | null;
267
267
  /** An agent holds a live lease; absent on shared reviews. */
268
268
  working?: boolean;
269
269
  }
@@ -425,34 +425,13 @@ interface SectionBlock {
425
425
  children: Block[];
426
426
  }
427
427
  //#endregion
428
- //#region src/tutorial-conversation.d.ts
429
- declare const tutorialAuthoringConversationSchema: z.ZodObject<{
430
- version: z.ZodLiteral<1>;
431
- title: z.ZodString;
432
- messages: z.ZodArray<z.ZodObject<{
433
- role: z.ZodEnum<{
434
- user: "user";
435
- assistant: "assistant";
436
- }>;
437
- body: z.ZodString;
438
- }, z.core.$strict>>;
439
- }, z.core.$strict>;
440
- type TutorialAuthoringConversation = z.infer<typeof tutorialAuthoringConversationSchema>;
441
- //#endregion
442
428
  //#region src/review-api/blocks/tutorial.d.ts
443
429
  type TutorialContent = {
444
430
  kind: "keymap";
445
- } | {
446
- kind: "conversation";
447
- conversation: TutorialAuthoringConversation;
448
431
  } | {
449
432
  kind: "view";
450
- view: "review" | "commits" | "diff" | "map";
433
+ view: "diff";
451
434
  label: string;
452
- } | {
453
- kind: "feature";
454
- feature: "softwareMap";
455
- children: Block[];
456
435
  };
457
436
  type TutorialBlock = {
458
437
  id?: string;
@@ -575,6 +554,7 @@ declare const leafSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
575
554
  label: z.ZodString;
576
555
  key: z.ZodOptional<z.ZodString>;
577
556
  fields: z.ZodRecord<z.ZodString, z.ZodType<DatabaseField, unknown, z.core.$ZodTypeInternals<DatabaseField, unknown>>>;
557
+ source: any;
578
558
  }, z.core.$strict>>;
579
559
  }, z.core.$strict>>;
580
560
  useCases: z.ZodArray<z.ZodObject<{
@@ -815,6 +795,7 @@ declare const editSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
815
795
  label: z.ZodString;
816
796
  key: z.ZodOptional<z.ZodString>;
817
797
  fields: z.ZodRecord<z.ZodString, z.ZodType<DatabaseField, unknown, z.core.$ZodTypeInternals<DatabaseField, unknown>>>;
798
+ source: any;
818
799
  }, z.core.$strict>>;
819
800
  }, z.core.$strict>>;
820
801
  useCases: z.ZodArray<z.ZodObject<{
@@ -914,28 +895,12 @@ declare const editSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
914
895
  type: z.ZodLiteral<"tutorial">;
915
896
  id: z.ZodOptional<z.ZodString>;
916
897
  kind: z.ZodLiteral<"keymap">;
917
- }, z.core.$strict>, z.ZodObject<{
918
- type: z.ZodLiteral<"tutorial">;
919
- id: z.ZodOptional<z.ZodString>;
920
- kind: z.ZodLiteral<"conversation">;
921
- conversation: any;
922
898
  }, z.core.$strict>, z.ZodObject<{
923
899
  type: z.ZodLiteral<"tutorial">;
924
900
  id: z.ZodOptional<z.ZodString>;
925
901
  kind: z.ZodLiteral<"view">;
926
- view: z.ZodEnum<{
927
- map: "map";
928
- commits: "commits";
929
- review: "review";
930
- diff: "diff";
931
- }>;
902
+ view: z.ZodLiteral<"diff">;
932
903
  label: z.ZodString;
933
- }, z.core.$strict>, z.ZodObject<{
934
- type: z.ZodLiteral<"tutorial">;
935
- id: z.ZodOptional<z.ZodString>;
936
- kind: z.ZodLiteral<"feature">;
937
- feature: z.ZodLiteral<"softwareMap">;
938
- children: z.ZodArray<z.ZodLazy<z.ZodType<Block, unknown, z.core.$ZodTypeInternals<Block, unknown>>>>;
939
904
  }, z.core.$strict>], "kind">, z.ZodObject<{
940
905
  type: z.ZodDefault<z.ZodLiteral<"step">>;
941
906
  from: z.ZodString;
@@ -1235,7 +1200,11 @@ type AskRecord = z.infer<typeof askRecordSchema>;
1235
1200
  * `reviews`, so there is no foreign key; deleting a review deletes its rows. */
1236
1201
  declare class AskHistory {
1237
1202
  private readonly db;
1203
+ private externalWriteGuard?;
1204
+ setExternalWriteGuard(guard: (reviewId: string) => void): void;
1238
1205
  constructor(db: DatabaseSync);
1206
+ private assertWritable;
1207
+ private assertThreadWritable;
1239
1208
  save(record: AskRecord): void;
1240
1209
  /** Whether reopening the conversation bypasses permissions. */
1241
1210
  setBypass(id: string, bypass: boolean): void;
@@ -1256,6 +1225,7 @@ declare class AskHistory {
1256
1225
  /** Newest first. */
1257
1226
  list(reviewId: string): AskHistoryEntry[];
1258
1227
  get(id: string): AskRecord | undefined;
1228
+ removeReview(reviewId: string): void;
1259
1229
  delete(id: string): void;
1260
1230
  }
1261
1231
  //#endregion
@@ -1272,6 +1242,8 @@ interface SnapshotOrigin {
1272
1242
  revision?: string;
1273
1243
  }
1274
1244
  interface Snapshot {
1245
+ /** Current lifecycle state, projected onto every version when read. */
1246
+ trashedAt?: string | null;
1275
1247
  shared?: {
1276
1248
  login?: string;
1277
1249
  sharedAt?: number;
@@ -1319,6 +1291,7 @@ interface Result {
1319
1291
  type?: Element["type"] | "lens";
1320
1292
  children?: WrittenComponent[];
1321
1293
  attention?: true;
1294
+ lifecycle?: true;
1322
1295
  deleted?: true;
1323
1296
  warnings?: string[];
1324
1297
  }
@@ -1372,6 +1345,7 @@ declare class ReviewStore {
1372
1345
  private readonly liveSources;
1373
1346
  private refreshTimer?;
1374
1347
  private refreshSubscribers;
1348
+ private readonly purgeTimer;
1375
1349
  watchWorktrees(): () => void;
1376
1350
  private projectLiveSource;
1377
1351
  private refreshPending;
@@ -1382,6 +1356,7 @@ declare class ReviewStore {
1382
1356
  private readonly externalChanges;
1383
1357
  private observedDataVersion;
1384
1358
  private observedVersions;
1359
+ private observedDeletions;
1385
1360
  subscribeCatalog(listener: () => void): () => void;
1386
1361
  /** For a host whose listing changed without the store: a preference flip. */
1387
1362
  invalidateCatalog(): void;
@@ -1389,6 +1364,7 @@ declare class ReviewStore {
1389
1364
  constructor(databasePath: string, providers: ReviewProviders);
1390
1365
  private dataVersion;
1391
1366
  private currentVersions;
1367
+ private currentDeletions;
1392
1368
  private refreshExternalChanges;
1393
1369
  /** Reader progress never creates a document version or authoring event. */
1394
1370
  viewedCoverage(reviewId: string): Map<string, {
@@ -1431,6 +1407,10 @@ declare class ReviewStore {
1431
1407
  resetServerId(): string;
1432
1408
  /** The 404 check alone, without loading a snapshot. */
1433
1409
  assertExists(id: string): void;
1410
+ assertWritable(id: string): void;
1411
+ private trashTime;
1412
+ private removePermanently;
1413
+ private purgeExpired;
1434
1414
  read(id: string, version?: number): Snapshot;
1435
1415
  setDiffStats(pins: Pins, stats: NonNullable<ReviewApiSummary["diffStats"]>, mode?: "structural" | "textual"): void;
1436
1416
  /** Managed records are discoverable even if their preparation stamp was lost. */
@@ -1465,14 +1445,15 @@ declare class ReviewStore {
1465
1445
  private startPullRequest;
1466
1446
  /** Reviews whose PR is this one, newest version first. Summaries only. */
1467
1447
  private reviewsForPullRequest;
1448
+ private assertReusableReview;
1468
1449
  /** The answer to a create that found its PR's review. Its target stays:
1469
1450
  * moving it would silently point existing links at different code. */
1470
1451
  private existingReview;
1471
1452
  private commitCommand;
1472
1453
  private assertMutation;
1473
1454
  private notify;
1474
- /** Dismissed reviews, as Home lists them; only a restore clears it, not a view. */
1475
- dismissedIds(): string[];
1455
+ /** Viewing a review in Trash does not restore it. */
1456
+ trashedIds(): string[];
1476
1457
  has(reviewId: string): boolean;
1477
1458
  private validateExternal;
1478
1459
  }
@@ -1502,15 +1483,15 @@ declare class ReviewWorkspaces {
1502
1483
  private closed;
1503
1484
  private readonly ownerId;
1504
1485
  constructor(databasePath: string, store: ReviewStore);
1505
- private dismissed?;
1486
+ private trashed?;
1506
1487
  private readonly releasing;
1507
1488
  /**
1508
- * Frees a newly dismissed review's checkouts to save disk. The review itself
1489
+ * Frees a newly trashed review's checkouts to save disk. The review itself
1509
1490
  * stays; reopening it rebuilds them. Checkouts rebuilt by peeking at a
1510
- * dismissed review are kept until the next startup or dismissal, since
1491
+ * trashed review are kept until the next startup or move to Trash, since
1511
1492
  * the Desktop's canvas tabs and source windows may still show them.
1512
1493
  */
1513
- private releaseDismissed;
1494
+ private releaseTrashed;
1514
1495
  private repositoryDirs;
1515
1496
  /** Resolves once no release of the review is pending, so an ensure cannot race it. */
1516
1497
  released(reviewId: string): Promise<void>;
@@ -1617,7 +1598,7 @@ declare class LocalReviewData {
1617
1598
  blobReaderIdleTimeoutMs?: number;
1618
1599
  workspaceDatabase?: string;
1619
1600
  manageWorkspaces?: boolean;
1620
- watch?: typeof watch;
1601
+ subscribe?: typeof subscribe;
1621
1602
  /** gh, git and GitHub API access for pull request targets. */
1622
1603
  pullRequests?: PullRequestDeps;
1623
1604
  });
@@ -1866,6 +1847,8 @@ declare class SharedReviewStore {
1866
1847
  private readonly attention;
1867
1848
  private readonly listeners;
1868
1849
  private catalogLoaded;
1850
+ private readonly purgeTimer;
1851
+ private purgePending;
1869
1852
  private readonly downloads;
1870
1853
  trackImport(job: Promise<void>): void;
1871
1854
  close(): Promise<void>;
@@ -1874,6 +1857,7 @@ declare class SharedReviewStore {
1874
1857
  private readonly jobs;
1875
1858
  private readonly states;
1876
1859
  constructor(root: string, fetchRepository?: typeof fetchPinnedRepository);
1860
+ private purgeExpired;
1877
1861
  connect(store: ReviewStore, data: LocalReviewData): void;
1878
1862
  has(id: string): boolean;
1879
1863
  status(id: string): {
@@ -1887,9 +1871,11 @@ declare class SharedReviewStore {
1887
1871
  assertReady(id: string): Promise<void>;
1888
1872
  subscribe(listener: () => void): () => void;
1889
1873
  load(): Promise<void>;
1874
+ assertWritable(id: string): void;
1890
1875
  get(id: string): {
1891
1876
  snapshot: {
1892
1877
  reviewId: string;
1878
+ trashedAt: string | null;
1893
1879
  pins: any;
1894
1880
  target: any;
1895
1881
  shared: {
@@ -1965,7 +1951,9 @@ declare class SharedReviewStore {
1965
1951
  list(mode?: "structural" | "textual"): any;
1966
1952
  readObject(id: string, objectId: string): Promise<NonSharedBuffer>;
1967
1953
  removeLocal(id: string): Promise<void>;
1968
- setAttention(id: string, action: "view" | "dismiss" | "restore"): Promise<void>;
1954
+ setAttention(id: string): Promise<void>;
1955
+ setLifecycle(id: string, state: "active" | "trash"): Promise<void>;
1956
+ private updateAttention;
1969
1957
  import(origin: string, shareId: string, bundle: ShareBundle): Promise<string>;
1970
1958
  read(id: string): Promise<ShareBundle>;
1971
1959
  private readValidated;
@@ -1,2 +1,2 @@
1
- import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-BFClfQLO.js";
1
+ import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-DzcSt6pV.js";
2
2
  export { ShareClient, SharedReviewStore, exportShare, sharedReviewId, validateShareBundle, verifyShareRepository };
@@ -29,6 +29,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  }) : target, mod));
30
30
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
31
31
  //#endregion
32
+ //#region src/review-api/input-error.ts
33
+ /** Deliberately safe to show to API clients, unlike filesystem/provider errors. */
34
+ var ReviewInputError = class extends Error {
35
+ status;
36
+ constructor(message, status = 400) {
37
+ super(message);
38
+ this.status = status;
39
+ }
40
+ };
41
+ //#endregion
32
42
  //#region ../../node_modules/protocols/lib/index.js
33
43
  var require_lib$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
34
44
  /**
@@ -3673,4 +3683,4 @@ var require_adapter = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3673
3683
  module.exports.checkSync = checkSync;
3674
3684
  })))();
3675
3685
  //#endregion
3676
- export { __commonJSMin as C, setLocalVcsCommandObserver as S, __toESM as T, readFileAtCommit as _, diffFileSummaries as a, resolveRevision as b, diffTrees as c, gitAt as d, gitCommonDir as f, parseGitRemote as g, listTrackedFilesAtCommit as h, diff as i, diffWorkingTree as l, listCommitRange as m, createBlobBatchReader as n, diffFileSummariesTrees as o, jjRevisionIsConflicted as p, detectLocalVcs as r, diffFileSummariesWorkingTree as s, LocalVcsToolsMissingError as t, git as u, resolveRepoContext as v, __require as w, splitGitPatchFiles as x, resolveRepoContextSync as y };
3686
+ export { ReviewInputError as C, __toESM as E, setLocalVcsCommandObserver as S, __require as T, readFileAtCommit as _, diffFileSummaries as a, resolveRevision as b, diffTrees as c, gitAt as d, gitCommonDir as f, parseGitRemote as g, listTrackedFilesAtCommit as h, diff as i, diffWorkingTree as l, listCommitRange as m, createBlobBatchReader as n, diffFileSummariesTrees as o, jjRevisionIsConflicted as p, detectLocalVcs as r, diffFileSummariesWorkingTree as s, LocalVcsToolsMissingError as t, git as u, resolveRepoContext as v, __commonJSMin as w, splitGitPatchFiles as x, resolveRepoContextSync as y };
@@ -1,6 +1,6 @@
1
+ import { C as ReviewInputError } from "./src-DiHP11l3.js";
1
2
  import { f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
2
3
  import "./src-WCbabvdS.js";
3
- import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
4
  import { z } from "zod";
5
5
  import { fromMarkdown } from "mdast-util-from-markdown";
6
6
  import { gfmFromMarkdown } from "mdast-util-gfm";
@@ -1305,7 +1305,8 @@ const storeSchema = z.strictObject({
1305
1305
  collections: z.record(text$1, z.strictObject({
1306
1306
  label: label$1,
1307
1307
  key: label$1.optional(),
1308
- fields: z.record(text$1, fieldSchema)
1308
+ fields: z.record(text$1, fieldSchema),
1309
+ source: anchorSchema.optional().describe("Where the table or collection is declared, such as its schema definition. Its use cases' tours start here.")
1309
1310
  }))
1310
1311
  });
1311
1312
  const operationSchema = z.strictObject({
@@ -1447,39 +1448,12 @@ const trace_quote = {
1447
1448
  })
1448
1449
  };
1449
1450
  //#endregion
1450
- //#region src/tutorial-conversation.ts
1451
- const tutorialAuthoringConversationSchema = z.strictObject({
1452
- version: z.literal(1),
1453
- title: z.string().trim().min(1),
1454
- messages: z.array(z.strictObject({
1455
- role: z.enum(["user", "assistant"]),
1456
- body: z.string().trim().min(1)
1457
- })).min(2)
1458
- });
1459
- //#endregion
1460
1451
  //#region src/review-api/blocks/tutorial.ts
1461
- const tutorialSchema = z.discriminatedUnion("kind", [
1462
- defineBlock("tutorial", { kind: z.literal("keymap") }),
1463
- defineBlock("tutorial", {
1464
- kind: z.literal("conversation"),
1465
- conversation: tutorialAuthoringConversationSchema
1466
- }),
1467
- defineBlock("tutorial", {
1468
- kind: z.literal("view"),
1469
- view: z.enum([
1470
- "review",
1471
- "commits",
1472
- "diff",
1473
- "map"
1474
- ]),
1475
- label: z.string()
1476
- }),
1477
- defineBlock("tutorial", {
1478
- kind: z.literal("feature"),
1479
- feature: z.literal("softwareMap"),
1480
- children: z.array(z.lazy(() => blockSchema))
1481
- })
1482
- ]);
1452
+ const tutorialSchema = z.discriminatedUnion("kind", [defineBlock("tutorial", { kind: z.literal("keymap") }), defineBlock("tutorial", {
1453
+ kind: z.literal("view"),
1454
+ view: z.literal("diff"),
1455
+ label: z.string()
1456
+ })]);
1483
1457
  const tutorial = {
1484
1458
  type: "tutorial",
1485
1459
  schema: tutorialSchema
@@ -1709,12 +1683,17 @@ function documentReferences(document, { tolerant = false } = {}) {
1709
1683
  })) : []
1710
1684
  ];
1711
1685
  });
1712
- if (element.type === "database_lens") return element.useCases.flatMap((useCase) => useCase.operations.flatMap((operation) => select(operation.source, operation.pins ?? element.pins).map((source) => ({
1686
+ if (element.type === "database_lens") return [...Object.entries(element.stores).flatMap(([storeKey, store]) => Object.entries(store.collections).flatMap(([key, collection]) => collection.source ? select(collection.source, element.pins).map((source) => ({
1687
+ id: `${element.id}:schema:${storeKey}:${key}`,
1688
+ source,
1689
+ label: collection.label,
1690
+ peek: true
1691
+ })) : [])), ...element.useCases.flatMap((useCase) => useCase.operations.flatMap((operation) => select(operation.source, operation.pins ?? element.pins).map((source) => ({
1713
1692
  id: operation.id,
1714
1693
  source,
1715
1694
  label: operation.label,
1716
1695
  peek: true
1717
- }))));
1696
+ }))))];
1718
1697
  return [];
1719
1698
  });
1720
1699
  }
@@ -1,6 +1,6 @@
1
+ import { C as ReviewInputError } from "./src-DiHP11l3.js";
1
2
  import { m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
2
- import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-B_b1GtUc.js";
3
- import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
3
+ import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-P3J4y8Zn.js";
4
4
  //#region src/review-api/public-tools.ts
5
5
  function publicDescription(text) {
6
6
  return text.replace(/\breview_(\w+)/g, "session_$1").replace(/\breviewId\b/g, "sessionId").replace(/\bReview\b/g, "Whiteboard").replace("The result carries review,", "The result carries session,");
@@ -2,7 +2,7 @@
2
2
 
3
3
  The scratchpad is one document in Whiteboard that is not a session for any change. It has no target, base, head or lifecycle. You draw on it the way you would sketch an explanation on a whiteboard: a short paragraph, a sequence or flow diagram, a call tree, a code peek, and the smallest view that makes the point. The user reads it in the app and copies blocks back to you.
4
4
 
5
- Its id is fixed: `scratchpad`. While it is turned on in Whiteboard Settings it always exists; never create, rename, delete, dismiss or share it.
5
+ Its id is fixed: `scratchpad`. While it is turned on in Whiteboard Settings it always exists; never create, rename, trash, delete or share it.
6
6
 
7
7
  ## When to draw here
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.fast/whiteboard",
3
- "version": "0.2.4",
3
+ "version": "0.2.5-preview.20261013.110",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,6 +46,7 @@
46
46
  "@hono/node-server": "2.0.12",
47
47
  "@img/sharp-wasm32": "0.35.5",
48
48
  "@modelcontextprotocol/sdk": "1.31.0",
49
+ "@parcel/watcher": "2.6.0",
49
50
  "commander": "14.0.3",
50
51
  "fuzzysort": "4.0.2",
51
52
  "hono": "4.13.7",
@@ -59,7 +60,7 @@
59
60
  "sharp": "0.35.5",
60
61
  "smol-toml": "1.9.0",
61
62
  "zod": "4.6.5",
62
- "@dev.fast/diffr": "0.1.17"
63
+ "@dev.fast/diffr": "0.1.19"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@dev.fast/local-vcs": "^0.1.0",
@@ -85,7 +86,7 @@
85
86
  "repo": "devdotfast/review",
86
87
  "docs": "packages/review/instructions/authoring.md"
87
88
  },
88
- "gitHead": "5b00f0e66d034e187a29d270b3690a37b57ccf7b",
89
+ "gitHead": "65c40ece4360f5dda5f8ff66cfe744a43188265c",
89
90
  "scripts": {
90
91
  "prebuild": "pnpm run build:workspace-deps",
91
92
  "build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",
package/src/ask/agents.ts CHANGED
@@ -52,6 +52,11 @@ interface AskAgentSpec {
52
52
  * read-only mode and settings, when the reviewer bypasses permissions.
53
53
  * Whiteboard also allows whatever it still asks. */
54
54
  bypass?: AskAgentBypass;
55
+ /** A follow-up asked while it answers joins that turn, when it says it
56
+ * takes one. Only for an adapter that leaves a follow-up arriving as the
57
+ * turn ends to Whiteboard: Codex's starts a turn of its own instead,
58
+ * which Whiteboard does not see end, so its follow-ups wait their turn. */
59
+ steers?: boolean;
55
60
  }
56
61
 
57
62
  interface AskAgentMcp {
@@ -81,6 +86,7 @@ export const askAgents: Record<AskAgentId, AskAgentSpec> = {
81
86
  executableEnv: "CLAUDE_CODE_EXECUTABLE",
82
87
  },
83
88
  signIn: "claude auth login",
89
+ steers: true,
84
90
  // Plan mode would end each answer asking to leave it, and it blocks the
85
91
  // Whiteboard tools that edit the review. Claude instead runs in its
86
92
  // default mode without its file tools, and without bypass, so commands
@@ -174,6 +174,9 @@ export const askUsageSchema = z.object({
174
174
 
175
175
  export type AskUsage = z.infer<typeof askUsageSchema>;
176
176
 
177
+ /** A follow-up waiting for the turn under way to end. */
178
+ const askQueuedSchema = z.object({ id: z.string(), text: z.string() });
179
+
177
180
  const statusSchema = z.enum([
178
181
  "starting",
179
182
  "running",
@@ -209,6 +212,9 @@ export const askThreadStateSchema = z.object({
209
212
  title: z.string().optional(),
210
213
  selection: z.object({ title: z.string(), quote: z.string().optional() }),
211
214
  entries: z.array(askEntrySchema),
215
+ /** Follow-ups asked while the agent answered, which it could not take
216
+ * then: each goes as its own question once the turn ends. */
217
+ queued: z.array(askQueuedSchema).optional(),
212
218
  });
213
219
 
214
220
  export type AskEntry = z.infer<typeof askEntrySchema>;
@@ -228,6 +234,8 @@ export const askChangeSchema = z.discriminatedUnion("type", [
228
234
  accepts: askAcceptsSchema.optional(),
229
235
  usage: askUsageSchema.optional(),
230
236
  title: z.string().optional(),
237
+ /** Replaces the queue; empty clears it. */
238
+ queued: z.array(askQueuedSchema).optional(),
231
239
  /** `null` clears the error; absent leaves it. */
232
240
  error: z.string().nullable().optional(),
233
241
  /** `null` clears it; absent leaves it. */
@@ -271,7 +279,12 @@ export function applyAskChange(
271
279
  ): AskThreadState {
272
280
  switch (change.type) {
273
281
  case "set": {
274
- const { error: _error, signIn: _signIn, ...rest } = state;
282
+ const {
283
+ error: _error,
284
+ signIn: _signIn,
285
+ queued: _queued,
286
+ ...rest
287
+ } = state;
275
288
 
276
289
  const next: AskThreadState = {
277
290
  ...rest,
@@ -295,6 +308,9 @@ export function applyAskChange(
295
308
  const title = change.title ?? state.title;
296
309
 
297
310
  if (title) next.title = title;
311
+ const queued = change.queued ?? state.queued;
312
+
313
+ if (queued?.length) next.queued = queued;
298
314
 
299
315
  // Absent keeps the error, null clears it, a message replaces it.
300
316
  const error = change.error === undefined ? state.error : change.error;