@dev.fast/whiteboard 0.3.4 → 0.3.5-preview.20261021.131

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 (120) hide show
  1. package/dist/{account-alias-CprZojXd.js → account-alias-CisiYUZT.js} +13 -8
  2. package/dist/agent-cli-BglNRDZN.js +2 -0
  3. package/dist/{agent-cli-CThN7Q59.js → agent-cli-TJ7_5_B3.js} +15 -10
  4. package/dist/{agent-client-BOOi7EUR.js → agent-client-H53ajvrm.js} +13 -3
  5. package/dist/{authoring-tools-BNwuMnz6.js → authoring-tools-BCquUsB3.js} +34 -27
  6. package/dist/{background-server-DLSDogkI.js → background-server-BSYXD9au.js} +1 -1
  7. package/dist/{background-server-CkvxA-6O.js → background-server-Xom8cA_u.js} +5 -5
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-R4yfa5DT.js → cli-BOnV4cCh.js} +1 -1
  10. package/dist/{cli-runner-Cuc9FSzu.js → cli-runner-cRY5NOno.js} +70 -34
  11. package/dist/cli.js +4 -3
  12. package/dist/{client-BN_FDxFr.js → client-UH1bEG7Z.js} +260 -49
  13. package/dist/{fuzzy-match-BO9bK37N.js → fuzzy-match-D_x3wumC.js} +6 -4
  14. package/dist/{headless-host-CVBOQyRS.js → headless-host-BEe-aFtB.js} +1 -1
  15. package/dist/{headless-host-Cog6SpS9.js → headless-host-CEb5d-A0.js} +563 -175
  16. package/dist/lens-selection-BxPk53c8.js +214 -0
  17. package/dist/{local-data-D_9tvdsp.js → local-data-mbCb1HDH.js} +2448 -347
  18. package/dist/{mcp-D1hziANa.js → mcp-cBtzXsmN.js} +9 -12
  19. package/dist/{package-paths-C--__jEv.js → package-paths-BkeQsdaw.js} +1 -1
  20. package/dist/profile-Dbyidrcr.js +2 -0
  21. package/dist/{profile-DokFw782.js → profile-duqp5uPa.js} +20 -8
  22. package/dist/{remote-attach-CEJ-kvDY.js → remote-attach-BeKuMm_x.js} +4 -4
  23. package/dist/{remote-extensions-GN8Gf_qG.js → remote-extensions-CX2fAec1.js} +1 -1
  24. package/dist/{remote-extensions-Blcjr9Ze.js → remote-extensions-CYWNTVSf.js} +3 -3
  25. package/dist/{remote-language-server-BTKBQ7x4.js → remote-language-server-DLWluAeR.js} +5 -5
  26. package/dist/{remote-uninstall-DW7Gdryh.js → remote-uninstall-Y3S0YmJs.js} +6 -6
  27. package/dist/{request-origin-Zcn_CjX4.js → request-origin-icXPv6jh.js} +55 -18
  28. package/dist/{review-app-launcher-CkwLclzR.js → review-app-launcher-DZvG5ZAh.js} +2 -2
  29. package/dist/{review-home-paths-j2BRAaNv.js → review-home-paths-BwH08ihj.js} +19 -17
  30. package/dist/{review-telemetry-BLNwYnLV.js → review-telemetry-AZ0imsmJ.js} +6 -6
  31. package/dist/runtime-CF4JXdb8.js +27 -0
  32. package/dist/runtime.d.ts +1 -1
  33. package/dist/runtime.js +1 -1
  34. package/dist/{s3-DtJFWC0G.js → s3-CsG2EWED.js} +1 -1
  35. package/dist/{s3-config-BTO12tCC.js → s3-config-CjcLyFV5.js} +4 -2
  36. package/dist/{s3-config-CSy3fx8H.js → s3-config-zz4GWh2T.js} +1 -1
  37. package/dist/server/desktop-host.js +28 -104
  38. package/dist/{server-discovery-Bey8IqXH.js → server-discovery-DeVTdcqK.js} +3 -3
  39. package/dist/sharing/index.d.ts +484 -74
  40. package/dist/sharing/index.js +1 -1
  41. package/dist/{src-DsM3jO8G.js → src-DYZLCUP_.js} +98 -18
  42. package/dist/{src-BMPuxWv4.js → src-DtfSndqS.js} +269 -227
  43. package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-B29lve9-.js} +318 -271
  44. package/dist/{thread-state-5pYr6_zM.js → thread-state-BPpvKFwQ.js} +12 -2
  45. package/dist/{tool-failure-C9MpbXJJ.js → tool-failure-CQhyLht3.js} +10 -3
  46. package/instructions/authoring.md +20 -5
  47. package/package.json +4 -4
  48. package/src/agent-selection.ts +6 -0
  49. package/src/ask/agents.ts +13 -1
  50. package/src/ask/checkout-files.ts +2 -5
  51. package/src/ask/thread-state.ts +4 -0
  52. package/src/ask/thread.ts +61 -31
  53. package/src/ask/threads.ts +24 -3
  54. package/src/cli-install.ts +13 -2
  55. package/src/cli-runner.ts +4 -1
  56. package/src/cli.ts +4 -1
  57. package/src/diff-selection-migration.ts +48 -34
  58. package/src/heading-anchors.ts +42 -0
  59. package/src/lens-selection.ts +38 -2
  60. package/src/markdown.ts +42 -0
  61. package/src/posthog-capture-client.ts +1 -1
  62. package/src/review-api/README.md +52 -13
  63. package/src/review-api/activity.ts +1 -4
  64. package/src/review-api/agent-cli.ts +9 -1
  65. package/src/review-api/anchor-quotes.ts +14 -7
  66. package/src/review-api/ask-history.ts +118 -51
  67. package/src/review-api/authoring-tools.ts +45 -30
  68. package/src/review-api/azure-devops-provider.ts +2 -20
  69. package/src/review-api/azure-remote.ts +21 -0
  70. package/src/review-api/blocks/definition.ts +1 -1
  71. package/src/review-api/blocks/markdown.ts +2 -2
  72. package/src/review-api/board-order.ts +144 -0
  73. package/src/review-api/checkout-groups.ts +586 -0
  74. package/src/review-api/document-headings.ts +58 -27
  75. package/src/review-api/document-text.ts +31 -2
  76. package/src/review-api/document.ts +362 -67
  77. package/src/review-api/foreign-references.ts +328 -0
  78. package/src/review-api/github-provider.ts +51 -13
  79. package/src/review-api/http.ts +734 -135
  80. package/src/review-api/instructions.ts +0 -19
  81. package/src/review-api/link-preview.ts +113 -0
  82. package/src/review-api/local-data.ts +310 -46
  83. package/src/review-api/mcp.ts +7 -13
  84. package/src/review-api/onboarding.ts +1 -1
  85. package/src/review-api/profile.ts +37 -6
  86. package/src/review-api/public-tools.ts +24 -2
  87. package/src/review-api/pull-request.ts +82 -49
  88. package/src/review-api/request-origin.ts +10 -0
  89. package/src/review-api/review-progress.ts +61 -27
  90. package/src/review-api/scratchpad-plan-upgrade.ts +250 -0
  91. package/src/review-api/store-migrations.ts +355 -0
  92. package/src/review-api/store-schema.ts +38 -29
  93. package/src/review-api/store.ts +2176 -495
  94. package/src/review-api/worktree-events.ts +3 -5
  95. package/src/review-api/worktree-source.ts +3 -7
  96. package/src/review-api/worktree-structural.integration.ts +72 -0
  97. package/src/review-stack.ts +15 -23
  98. package/src/runtime.ts +4 -30
  99. package/src/server/desktop-host.ts +6 -1
  100. package/src/server/desktop-server.ts +1 -36
  101. package/src/server/headless-host.ts +0 -2
  102. package/src/server/hono-http.ts +2 -1
  103. package/src/server/review-gateway-list.ts +3 -5
  104. package/src/server/review-gateway-memory.ts +1 -1
  105. package/src/server/review-lifecycle-telemetry.ts +2 -2
  106. package/src/server/review-server-core.ts +1 -5
  107. package/src/server/structural-comparisons.ts +47 -0
  108. package/src/sharing/export.ts +36 -7
  109. package/src/sharing/host.ts +34 -14
  110. package/src/sharing/import.ts +156 -24
  111. package/src/sharing/repository.ts +143 -24
  112. package/src/startup-trace.ts +3 -3
  113. package/src/stored-document-migration.ts +110 -19
  114. package/src/ui-telemetry-events.ts +44 -18
  115. package/dist/agent-cli-C6SPSII0.js +0 -2
  116. package/dist/profile-CYPmvBoA.js +0 -2
  117. package/dist/runtime-CKiWhGrf.js +0 -52
  118. package/instructions/scratchpad.md +0 -23
  119. package/src/review-preferences.ts +0 -88
  120. package/src/server/legacy-tutorial-cleanup.ts +0 -38
@@ -1,18 +1,124 @@
1
- import { type JsonValue, isJsonObject } from "@dev.fast/review-protocol";
1
+ import {
2
+ type JsonObject,
3
+ type JsonValue,
4
+ isBooleanValue,
5
+ isJsonObject,
6
+ isNumberValue,
7
+ isStringValue,
8
+ } from "@dev.fast/review-protocol";
2
9
  import { z } from "zod";
3
10
 
4
- import { migrateDiffSelections } from "./diff-selection-migration.js";
11
+ import {
12
+ ATTACHMENT_KEYS,
13
+ migrateDiffSelections,
14
+ retiredSelection,
15
+ } from "./diff-selection-migration.js";
5
16
  import { formatAnchor } from "./lens-selection.js";
6
17
  import { label } from "./review-api/blocks/definition.js";
7
18
  import { type Lens, lensTargetsSchema } from "./review-api/diff-lenses.js";
19
+ import { respellLinks } from "./review-api/document.js";
8
20
 
9
21
  /** Upgrade a saved or shared Review document to the current block schema at the
10
22
  * read/import boundary. New edits use the strict schema and never accept these
11
- * retired forms. */
23
+ * retired forms. One walk makes a fresh copy: retired attachments become
24
+ * selections, anchors become strings, sections lose their status, and
25
+ * Markdown links are spelled the current way. */
12
26
  // This is the decoder boundary for stored documents in retired wire formats.
13
27
  // oxlint-disable-next-line anti-slop/no-unknown-parameters
14
28
  export function migrateStoredDocument(input: unknown): JsonValue {
15
- return anchorStrings(dropSectionStatus(migrateDiffSelections(input)));
29
+ return upgradeNode(input, { attachment: false, anchors: true });
30
+ }
31
+
32
+ /** Where a node sits: under a key that held a retired attachment, and
33
+ * whether its anchors still need converting (not inside an anchor). */
34
+ interface NodePlace {
35
+ attachment: boolean;
36
+ anchors: boolean;
37
+ blockPins?: JsonValue;
38
+ }
39
+
40
+ // oxlint-disable-next-line anti-slop/no-unknown-parameters -- Decoder boundary: every value is checked to be JSON as it is copied.
41
+ function upgradeNode(value: unknown, place: NodePlace): JsonValue {
42
+ if (isStringValue(value) || isBooleanValue(value) || value === null)
43
+ return value;
44
+
45
+ if (isNumberValue(value) && Number.isFinite(value)) return value;
46
+
47
+ if (Array.isArray(value))
48
+ return value.map((child) => upgradeNode(child, place));
49
+
50
+ if (!isJsonObject(value)) throw new TypeError("A document must be JSON.");
51
+
52
+ const selection = place.attachment ? retiredSelection(value) : undefined;
53
+
54
+ if (selection)
55
+ return upgradeNode(selection, { attachment: false, anchors: false });
56
+
57
+ const inherited = place.anchors ? (value.pins ?? place.blockPins) : undefined;
58
+ let pins: JsonValue | undefined;
59
+
60
+ const convert = (anchor: JsonValue): JsonValue => {
61
+ // Most anchors are strings already.
62
+ if (isStringValue(anchor)) return anchor;
63
+
64
+ const parsed = selectionObjectSchema.safeParse(anchor);
65
+
66
+ if (!parsed.success) return anchor;
67
+
68
+ const { pins: own, ...anchorAt } = parsed.data;
69
+
70
+ if (own) pins = own;
71
+
72
+ return formatAnchor(anchorAt);
73
+ };
74
+
75
+ const entries: [string, JsonValue][] = [];
76
+
77
+ for (const [key, child] of Object.entries(value)) {
78
+ if (key === "status" && value.type === "section") continue;
79
+
80
+ if (key === "markdown" && isStringValue(child)) {
81
+ entries.push([key, respellLinks(child)]);
82
+ continue;
83
+ }
84
+
85
+ const attachment = ATTACHMENT_KEYS.includes(key);
86
+
87
+ if (place.anchors && ANCHOR_KEYS.includes(key)) {
88
+ const anchors = upgradeNode(child, { attachment, anchors: false });
89
+
90
+ entries.push([
91
+ key,
92
+ Array.isArray(anchors) ? anchors.map(convert) : convert(anchors),
93
+ ]);
94
+ } else
95
+ entries.push([
96
+ key,
97
+ upgradeNode(child, {
98
+ attachment,
99
+ anchors: place.anchors,
100
+ blockPins: inherited,
101
+ }),
102
+ ]);
103
+ }
104
+
105
+ const upgraded: JsonObject = Object.fromEntries(entries);
106
+
107
+ if (
108
+ pins !== undefined &&
109
+ value.kind !== "ranges" &&
110
+ JSON.stringify(pins) !== JSON.stringify(place.blockPins)
111
+ )
112
+ upgraded.pins = pins;
113
+
114
+ return upgraded;
115
+ }
116
+
117
+ /** A stored document as the store reads it: retired forms upgraded, and
118
+ * lens blocks lifted out to the snapshot's own lenses. */
119
+ // oxlint-disable-next-line anti-slop/no-unknown-parameters -- Decoder boundary for stored documents in retired formats.
120
+ export function readStoredDocument(input: unknown): LiftedLenses {
121
+ return liftFileLenses(migrateStoredDocument(input));
16
122
  }
17
123
 
18
124
  /** Lenses saved before ranges were anchor strings. */
@@ -82,21 +188,6 @@ export function anchorStrings(
82
188
  return migrated;
83
189
  }
84
190
 
85
- /** Sections once carried an optional `status` (pending, in_progress or
86
- * complete). The authoring lease is now the only signal of work in progress,
87
- * so versions saved before it was retired drop the field when read. */
88
- function dropSectionStatus(value: JsonValue): JsonValue {
89
- if (Array.isArray(value)) return value.map(dropSectionStatus);
90
-
91
- if (!isJsonObject(value)) return value;
92
-
93
- return Object.fromEntries(
94
- Object.entries(value)
95
- .filter(([key]) => !(value.type === "section" && key === "status"))
96
- .map(([key, child]) => [key, dropSectionStatus(child)]),
97
- );
98
- }
99
-
100
191
  /** A saved `file_lens` block, from before lenses left the document. */
101
192
  const legacyFileLensSchema = z.object({
102
193
  type: z.literal("file_lens"),
@@ -18,9 +18,9 @@
18
18
 
19
19
  import {
20
20
  type JsonObject,
21
+ REVIEW_COLOR_THEME_KINDS,
21
22
  REVIEW_DIFF_LAYOUTS,
22
23
  REVIEW_DIFF_LAYOUT_SCOPES,
23
- REVIEW_THEME_CHOICES,
24
24
  isJsonObject,
25
25
  jsonBoolean,
26
26
  jsonNumber,
@@ -29,6 +29,7 @@ import {
29
29
  import { z } from "zod";
30
30
 
31
31
  import { askAgentIds } from "./ask/thread-state";
32
+ import { SELECTION_UNAVAILABLE_REASONS } from "./lens-selection";
32
33
  import { containsFilePath, hasPossibleUserInfo } from "./telemetry-clean-text";
33
34
 
34
35
  /** Property value validators: an enum of allowed strings, or a scalar type. */
@@ -70,6 +71,13 @@ const PEEK_VIA = [
70
71
 
71
72
  export const PEEK_ROOT_KIND = ["range"] as const;
72
73
 
74
+ /** `unresolved`: progress finished without this selection; `error`: progress failed. */
75
+ export const PEEK_FAILURE_REASON = [
76
+ ...SELECTION_UNAVAILABLE_REASONS,
77
+ "error",
78
+ "unresolved",
79
+ ] as const;
80
+
73
81
  export const LSP_FEATURE = [
74
82
  "hover",
75
83
  "goto_definition",
@@ -137,7 +145,7 @@ export const SETTING_NAME = [
137
145
  "telemetry_enabled",
138
146
  "keymap",
139
147
  "software_map_enabled",
140
- "scratchpad_enabled",
148
+ "sharing_enabled",
141
149
  "diffr_config",
142
150
  "theme",
143
151
  "ctrl_tab",
@@ -145,16 +153,19 @@ export const SETTING_NAME = [
145
153
  "ready_notification",
146
154
  "document_width",
147
155
  "code_font_size",
156
+ "document_font_size",
157
+ "reading_font",
158
+ "code_font_family",
159
+ "font_ligatures",
160
+ "diff_font_size",
161
+ "diff_word_wrap",
162
+ "diff_ignore_whitespace",
148
163
  ] as const;
149
164
 
150
165
  export const REVIEW_OPENED_VIA = ["home", "cli", "other"] as const;
151
166
 
152
- /** Where a review's source comes from: its target's kind, or the one scratchpad. */
153
- export const SESSION_SOURCE_KIND = [
154
- "worktree",
155
- "commits",
156
- "scratchpad",
157
- ] as const;
167
+ /** Where a review's source comes from: its target's kind. */
168
+ export const SESSION_SOURCE_KIND = ["worktree", "commits", "project"] as const;
158
169
 
159
170
  /**
160
171
  * The authoring agent a review session is attributed to. This is the one
@@ -246,6 +257,7 @@ export const INSTALL_LOCATION = [
246
257
 
247
258
  export const DIFF_OPENED_VIA = [
248
259
  "topbar",
260
+ "sidebar",
249
261
  "lens",
250
262
  "locate",
251
263
  "file_picker",
@@ -254,6 +266,15 @@ export const DIFF_OPENED_VIA = [
254
266
  /** Which diff opened: a commit's, or a file diff drawn plain or structural. */
255
267
  export const DIFF_OPENED_KIND = ["commit", "structural"] as const;
256
268
 
269
+ export const DIFF_FAILED_REASON = [
270
+ "missing_binary",
271
+ "process_exit",
272
+ "stream_ended",
273
+ "aborted",
274
+ "file_error",
275
+ "other",
276
+ ] as const;
277
+
257
278
  /** GitHub Enterprise is any GitHub host but github.com. */
258
279
  export const PR_PROVIDER = [
259
280
  "github",
@@ -277,7 +298,9 @@ export const ONBOARDING_STEP = ["welcome", "install", "run"] as const;
277
298
 
278
299
  export const RUN_MODE = ["draw", "copy"] as const;
279
300
 
280
- export const NEW_WHITEBOARD_VIA = ["button", "command"] as const;
301
+ export const NEW_WHITEBOARD_VIA = ["button", "command", "sidebar"] as const;
302
+
303
+ export const NEW_WHITEBOARD_MODE = [...RUN_MODE, "empty"] as const;
281
304
 
282
305
  export const FIND_SURFACE = ["review", "diff"] as const;
283
306
 
@@ -390,10 +413,6 @@ export const UI_TELEMETRY_EVENTS = {
390
413
  event: "review_map_expanded",
391
414
  properties: { level: MAP_LEVEL },
392
415
  },
393
- commit_expanded: {
394
- event: "review_commit_expanded",
395
- properties: { expanded: "boolean" },
396
- },
397
416
  commit_diff_opened: {
398
417
  event: "review_commit_diff_opened",
399
418
  properties: { via: COMMIT_DIFF_VIA },
@@ -533,11 +552,11 @@ export const UI_TELEMETRY_EVENTS = {
533
552
  },
534
553
  setting_changed: {
535
554
  event: "review_setting_changed",
536
- // value carries the chosen option for the one multi-valued setting, theme.
555
+ // value carries whether the chosen theme is dark or light.
537
556
  properties: {
538
557
  setting: SETTING_NAME,
539
558
  enabled: "boolean",
540
- value: REVIEW_THEME_CHOICES,
559
+ value: REVIEW_COLOR_THEME_KINDS,
541
560
  },
542
561
  },
543
562
  review_opened: {
@@ -594,7 +613,10 @@ export const UI_TELEMETRY_EVENTS = {
594
613
  event: "review_diff_opened",
595
614
  properties: { kind: DIFF_OPENED_KIND, via: DIFF_OPENED_VIA },
596
615
  },
597
- scratchpad_opened: { event: "review_scratchpad_opened", properties: {} },
616
+ diff_failed: {
617
+ event: "review_diff_failed",
618
+ properties: { reason: DIFF_FAILED_REASON, files: "number" },
619
+ },
598
620
  discord_clicked: {
599
621
  event: "review_discord_clicked",
600
622
  properties: { via: DISCORD_VIA },
@@ -613,7 +635,7 @@ export const UI_TELEMETRY_EVENTS = {
613
635
  },
614
636
  peek_resolve_failed: {
615
637
  event: "review_peek_resolve_failed",
616
- properties: { root_kind: PEEK_ROOT_KIND },
638
+ properties: { root_kind: PEEK_ROOT_KIND, reason: PEEK_FAILURE_REASON },
617
639
  },
618
640
  review_deleted: {
619
641
  event: "review_review_deleted",
@@ -652,7 +674,11 @@ export const UI_TELEMETRY_EVENTS = {
652
674
  },
653
675
  new_whiteboard_started: {
654
676
  event: "review_new_whiteboard_started",
655
- properties: { mode: RUN_MODE, embedded: "boolean", agent: askAgentIds },
677
+ properties: {
678
+ mode: NEW_WHITEBOARD_MODE,
679
+ embedded: "boolean",
680
+ agent: askAgentIds,
681
+ },
656
682
  },
657
683
  remote_host_installed: {
658
684
  event: "review_remote_host_installed",
@@ -1,2 +0,0 @@
1
- import { n as runReviewAgentCli } from "./agent-cli-CThN7Q59.js";
2
- export { runReviewAgentCli };
@@ -1,2 +0,0 @@
1
- import { t as openReviewProfile } from "./profile-DokFw782.js";
2
- export { openReviewProfile };
@@ -1,52 +0,0 @@
1
- import path from "node:path";
2
- import { spawn } from "node:child_process";
3
- //#region src/runtime.ts
4
- async function resolveReviewRoot(cwd, execFile = execFilePromise) {
5
- const initialCwd = path.resolve(cwd);
6
- try {
7
- const { stdout } = await execFile("jj", ["root"], { cwd: initialCwd });
8
- const root = stdout.trim();
9
- if (root) return path.resolve(root);
10
- } catch {}
11
- try {
12
- const { stdout } = await execFile("git", [
13
- "-C",
14
- initialCwd,
15
- "rev-parse",
16
- "--show-toplevel"
17
- ]);
18
- const root = stdout.trim();
19
- if (root) return path.resolve(root);
20
- } catch {}
21
- return initialCwd;
22
- }
23
- function execFilePromise(file, args, options = {}) {
24
- return execFileUntraced(file, args, options);
25
- }
26
- function execFileUntraced(file, args, options = {}) {
27
- return new Promise((resolve, reject) => {
28
- const child = spawn(file, args, {
29
- cwd: options.cwd,
30
- stdio: [
31
- "ignore",
32
- "pipe",
33
- "pipe"
34
- ]
35
- });
36
- let stdout = "";
37
- let stderr = "";
38
- child.stdout?.on("data", (chunk) => {
39
- stdout += String(chunk);
40
- });
41
- child.stderr?.on("data", (chunk) => {
42
- stderr += String(chunk);
43
- });
44
- child.on("error", reject);
45
- child.on("exit", (code) => {
46
- if (code === 0) resolve({ stdout });
47
- else reject(new Error(stderr.trim() || `${file} exited with ${code}`));
48
- });
49
- });
50
- }
51
- //#endregion
52
- export { resolveReviewRoot as t };
@@ -1,23 +0,0 @@
1
- # Scratchpad
2
-
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
-
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
-
7
- ## When to draw here
8
-
9
- - The user asks to be shown how something works, wants a picture of a flow, a call path, a data shape or a layout, or is thinking out loud with you before there is a concrete plan.
10
- - The explanation spans more than one file or more than one repository, or would be an ASCII diagram in chat.
11
-
12
- Answer in chat when one sentence or one code line does it. Do not use the scratchpad to review a change; author a session instead (`session_get_instructions({})`).
13
-
14
- ## How
15
-
16
- 1. `session_capabilities({})`. Draw only when `desktopAvailable` and `scratchpadEnabled` are true. Otherwise answer in chat; if the pad is off, say once that it can be turned on in Whiteboard Settings.
17
- 2. Every source reference needs pins. For each repository you will quote, `session_register_repository({path})` for its `repositoryId`, and `git rev-parse HEAD` (or the commit the user is looking at) for a commit id. Put `pins: {repositoryId, head}` on each block that quotes code (`code_peek`, `sequence`, `flow_diagram`, `call_stack_diff`, `database_lens` and `markdown` with `review-source:` links); a step, frame, attachment or operation that quotes other commits than its block takes its own `pins`. Add `base` only when an anchor reads the base side. A reference without pins is rejected, since the scratchpad has none to lend.
18
- 3. Read what you cite at those commits, for example `git show <head>:<path>` in the repository, so line numbers match the pins.
19
- 4. Call `session_activity_begin` and pass the `activityId` it returns on each `session_edit` as you append blocks. Omitted placement puts the block at the top of the pad, and the response's `targetId` names it. A thought that spans several blocks reads top-down only if you insert it bottom-up (last block first) or give each following block `afterId` of the block you just inserted. Draw a diagram whole in one insert; fix one you drew earlier by patching its nodes, edges or steps by ID, and `replace` a block only when the whole picture was wrong. Each block's fields are in the Components section below; read `session_get_instructions({topic:"authoring"})` for choosing components. End the activity when finished.
20
- 5. After your first insert in a session, `session_open({sessionId: "scratchpad"})` once so the pad is showing. Do not call it again for later edits; they appear live.
21
- 6. In chat, say in one line what you drew. Do not repeat the diagram there.
22
-
23
- Keep it a log, not a document: the newest thought sits at the top, above older ones. Do not build sections, outlines or status markers unless the user asks for structure. When the user asks to clear the pad, `replace` or `remove` its blocks; history keeps what was there.
@@ -1,88 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- import {
5
- type JsonValue,
6
- isJsonObject,
7
- parseJsonText,
8
- } from "@dev.fast/review-protocol";
9
- import { writePrivateJsonAtomic } from "@dev.fast/trace-core";
10
-
11
- import { devReviewHome } from "./review-home-paths";
12
-
13
- /**
14
- * Machine-wide Review preferences the server itself needs. Workbench settings
15
- * do not work here: the review server never reads the workbench
16
- * configuration.
17
- */
18
- export interface ReviewPreferences {
19
- /**
20
- * Whether the scratchpad exists on this machine. Off, the server neither
21
- * makes nor lists it and Review's instructions do not offer it to agents.
22
- */
23
- scratchpadEnabled: boolean;
24
- }
25
-
26
- const DEFAULT_REVIEW_PREFERENCES: ReviewPreferences = {
27
- scratchpadEnabled: false,
28
- };
29
-
30
- function reviewPreferencesPath(devHome = devReviewHome()): string {
31
- return path.join(devHome, "preferences.json");
32
- }
33
-
34
- /** A missing or unreadable file falls back to the defaults; it never throws. */
35
- export async function readReviewPreferences(
36
- devHome?: string,
37
- ): Promise<ReviewPreferences> {
38
- try {
39
- const raw = parseJsonText(
40
- await readFile(reviewPreferencesPath(devHome), "utf8"),
41
- );
42
-
43
- return {
44
- scratchpadEnabled: parseScratchpadEnabled(raw),
45
- };
46
- } catch {
47
- return { ...DEFAULT_REVIEW_PREFERENCES };
48
- }
49
- }
50
-
51
- export async function writeReviewPreferences(
52
- preferences: ReviewPreferences,
53
- devHome?: string,
54
- ): Promise<ReviewPreferences> {
55
- const next: ReviewPreferences = {
56
- scratchpadEnabled: preferences.scratchpadEnabled === true,
57
- };
58
-
59
- await writePrivateJsonAtomic(reviewPreferencesPath(devHome), next);
60
-
61
- return next;
62
- }
63
-
64
- /** Reads the scratchpad preference; a missing file means off. */
65
- export async function readScratchpadEnabled(
66
- devHome?: string,
67
- ): Promise<boolean> {
68
- return (await readReviewPreferences(devHome)).scratchpadEnabled;
69
- }
70
-
71
- /** Sets the scratchpad preference, keeping the other preferences as they are. */
72
- export async function writeScratchpadEnabled(
73
- enabled: boolean,
74
- devHome?: string,
75
- ): Promise<boolean> {
76
- const current = await readReviewPreferences(devHome);
77
-
78
- return (
79
- await writeReviewPreferences(
80
- { ...current, scratchpadEnabled: enabled },
81
- devHome,
82
- )
83
- ).scratchpadEnabled;
84
- }
85
-
86
- function parseScratchpadEnabled(raw: JsonValue): boolean {
87
- return isJsonObject(raw) && raw.scratchpadEnabled === true;
88
- }
@@ -1,38 +0,0 @@
1
- import { rm } from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- import type { LocalReviewData } from "@review/review-api/local-data";
5
- import type { ReviewStore } from "@review/review-api/store";
6
- import { devReviewHome } from "@review/review-home-paths";
7
-
8
- /**
9
- * Earlier versions shipped a three-minute tutorial: a managed review over a
10
- * sample checkout under ~/.dev/tutorial. Whiteboard no longer has one, so
11
- * remove what an earlier version left behind. Best effort: a failure leaves
12
- * it for the next start.
13
- */
14
- export async function removeLegacyTutorial(input: {
15
- store: ReviewStore;
16
- data: LocalReviewData;
17
- root?: string;
18
- }): Promise<void> {
19
- const tutorialRoot = input.root ?? path.join(devReviewHome(), "tutorial");
20
-
21
- try {
22
- for (const reviewId of input.store.legacyTutorialIds())
23
- await input.store.execute({ operation: { type: "delete", reviewId } });
24
-
25
- for (const repository of input.store.repositories()) {
26
- const relative = path.relative(tutorialRoot, repository.path);
27
-
28
- if (relative && !relative.startsWith("..") && !path.isAbsolute(relative))
29
- await input.data.forgetRepository(repository.id);
30
- }
31
-
32
- // Windows cannot delete a checkout while its Git reader or watchers
33
- // hold it open, and handles can outlive their close briefly.
34
- await rm(tutorialRoot, { recursive: true, force: true, maxRetries: 10 });
35
- } catch (error) {
36
- console.warn("[Whiteboard] Removing the old tutorial failed:", error);
37
- }
38
- }