@dev.fast/whiteboard 0.3.4-preview.20261019.126 → 0.3.4
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-B5-HYi1D.js → account-alias-CprZojXd.js} +8 -13
- package/dist/agent-cli-C6SPSII0.js +2 -0
- package/dist/{agent-cli-CohHf6Ua.js → agent-cli-CThN7Q59.js} +10 -15
- package/dist/{agent-client-Dtc-Kn_w.js → agent-client-BOOi7EUR.js} +3 -13
- package/dist/{authoring-tools-B7KU04f7.js → authoring-tools-BNwuMnz6.js} +27 -34
- package/dist/{background-server-C03RKxKS.js → background-server-CkvxA-6O.js} +5 -5
- package/dist/{background-server-CB9C4uoc.js → background-server-DLSDogkI.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-CJaEb7zC.js → cli-R4yfa5DT.js} +1 -1
- package/dist/{cli-runner-DGDIqh3V.js → cli-runner-Cuc9FSzu.js} +26 -26
- package/dist/cli.js +3 -4
- package/dist/{client-lKVF_Elo.js → client-BN_FDxFr.js} +47 -253
- package/dist/{fuzzy-match-BmVJJm9I.js → fuzzy-match-BO9bK37N.js} +4 -6
- package/dist/{headless-host-DRtjf9WA.js → headless-host-CVBOQyRS.js} +1 -1
- package/dist/{headless-host-XhL1X4vX.js → headless-host-Cog6SpS9.js} +140 -513
- package/dist/{local-data-Cpl4ENbJ.js → local-data-D_9tvdsp.js} +323 -2419
- package/dist/{mcp-ovmiUE_x.js → mcp-D1hziANa.js} +9 -5
- package/dist/{package-paths-BWxO-UEL.js → package-paths-C--__jEv.js} +1 -1
- package/dist/profile-CYPmvBoA.js +2 -0
- package/dist/{profile-BadSkCkI.js → profile-DokFw782.js} +8 -20
- package/dist/{remote-attach-Dd-YobM6.js → remote-attach-CEJ-kvDY.js} +4 -4
- package/dist/{remote-extensions-CNXwqK0D.js → remote-extensions-Blcjr9Ze.js} +3 -3
- package/dist/{remote-extensions-VP-kfq0C.js → remote-extensions-GN8Gf_qG.js} +1 -1
- package/dist/{remote-language-server-BXk87KCr.js → remote-language-server-BTKBQ7x4.js} +5 -5
- package/dist/{remote-uninstall-DuOzsvaD.js → remote-uninstall-DW7Gdryh.js} +6 -6
- package/dist/{request-origin-B9qCPiVB.js → request-origin-Zcn_CjX4.js} +12 -45
- package/dist/{review-app-launcher-C7el2Uoy.js → review-app-launcher-CkwLclzR.js} +2 -2
- package/dist/{review-home-paths-D8AoCdd2.js → review-home-paths-j2BRAaNv.js} +17 -19
- package/dist/{review-telemetry-BsoRMC2w.js → review-telemetry-BLNwYnLV.js} +5 -5
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-CsG2EWED.js → s3-DtJFWC0G.js} +1 -1
- package/dist/{s3-config-CjcLyFV5.js → s3-config-BTO12tCC.js} +2 -4
- package/dist/{s3-config-zz4GWh2T.js → s3-config-CSy3fx8H.js} +1 -1
- package/dist/server/desktop-host.js +76 -29
- package/dist/{server-discovery-CKjw3-6h.js → server-discovery-Bey8IqXH.js} +3 -3
- package/dist/sharing/index.d.ts +70 -484
- package/dist/sharing/index.js +1 -1
- package/dist/{src-WPaQn75m.js → src-BMPuxWv4.js} +73 -208
- package/dist/{src-BiWNbcF_.js → src-DsM3jO8G.js} +12 -89
- package/dist/{stored-document-migration-dFbewRTG.js → stored-document-migration-CChxtFAy.js} +269 -270
- package/dist/{thread-state-Do5LX10I.js → thread-state-5pYr6_zM.js} +2 -7
- package/dist/{tool-failure-DvI-gys2.js → tool-failure-C9MpbXJJ.js} +3 -10
- package/instructions/authoring.md +2 -3
- package/instructions/scratchpad.md +23 -0
- package/package.json +4 -4
- package/src/ask/agents.ts +1 -13
- package/src/ask/checkout-files.ts +5 -2
- package/src/ask/thread-state.ts +0 -4
- package/src/ask/thread.ts +0 -16
- package/src/ask/threads.ts +3 -24
- package/src/cli-install.ts +2 -13
- package/src/cli-runner.ts +1 -4
- package/src/cli.ts +1 -4
- package/src/diff-selection-migration.ts +34 -48
- package/src/lens-selection.ts +2 -38
- package/src/review-api/README.md +13 -52
- package/src/review-api/activity.ts +4 -1
- package/src/review-api/agent-cli.ts +1 -9
- package/src/review-api/anchor-quotes.ts +7 -14
- package/src/review-api/ask-history.ts +51 -137
- package/src/review-api/authoring-tools.ts +30 -45
- package/src/review-api/blocks/definition.ts +1 -1
- package/src/review-api/blocks/markdown.ts +2 -2
- package/src/review-api/document-headings.ts +27 -58
- package/src/review-api/document-text.ts +2 -31
- package/src/review-api/document.ts +34 -281
- package/src/review-api/github-provider.ts +13 -51
- package/src/review-api/http.ts +123 -702
- package/src/review-api/instructions.ts +19 -0
- package/src/review-api/local-data.ts +46 -310
- package/src/review-api/mcp.ts +11 -3
- package/src/review-api/onboarding.ts +1 -1
- package/src/review-api/profile.ts +6 -37
- package/src/review-api/public-tools.ts +2 -24
- package/src/review-api/pull-request.ts +49 -82
- package/src/review-api/request-origin.ts +0 -10
- package/src/review-api/review-progress.ts +27 -61
- package/src/review-api/store-schema.ts +29 -38
- package/src/review-api/store.ts +478 -2142
- package/src/review-api/worktree-events.ts +5 -3
- package/src/review-api/worktree-source.ts +7 -3
- package/src/review-api/worktree-structural.integration.ts +0 -72
- package/src/review-preferences.ts +88 -0
- package/src/review-stack.ts +23 -15
- package/src/runtime.ts +30 -4
- package/src/server/desktop-host.ts +1 -6
- package/src/server/desktop-server.ts +31 -1
- package/src/server/headless-host.ts +2 -0
- package/src/server/hono-http.ts +1 -2
- package/src/server/legacy-tutorial-cleanup.ts +3 -2
- package/src/server/review-gateway-list.ts +5 -3
- package/src/server/review-gateway-memory.ts +1 -1
- package/src/server/review-lifecycle-telemetry.ts +2 -2
- package/src/server/review-server-core.ts +5 -1
- package/src/server/structural-comparisons.ts +0 -47
- package/src/sharing/export.ts +7 -36
- package/src/sharing/host.ts +14 -34
- package/src/sharing/import.ts +24 -156
- package/src/sharing/repository.ts +20 -128
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +19 -110
- package/src/ui-telemetry-events.ts +11 -33
- package/dist/agent-cli-_YyMIMRY.js +0 -2
- package/dist/lens-selection-CyJapcsl.js +0 -214
- package/dist/profile-DJvkBJNQ.js +0 -2
- package/dist/runtime-Bj2tD33R.js +0 -27
- package/src/heading-anchors.ts +0 -42
- package/src/review-api/board-order.ts +0 -144
- package/src/review-api/checkout-groups.ts +0 -560
- package/src/review-api/foreign-references.ts +0 -328
- package/src/review-api/link-preview.ts +0 -113
- package/src/review-api/scratchpad-plan-upgrade.ts +0 -250
- package/src/review-api/store-migrations.ts +0 -357
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type JsonValue, isJsonObject } from "@dev.fast/json";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
|
|
4
3
|
import {
|
|
@@ -16,7 +15,10 @@ import { commandSchema } from "./store.js";
|
|
|
16
15
|
/** The host publishes its input schemas, except where agents write less than
|
|
17
16
|
* the host accepts (publishedEditSchema, publishedLensEditSchema) or name a
|
|
18
17
|
* checkout by path (pathTargetSchema); adapters validate nothing. */
|
|
19
|
-
export function authoringTools(
|
|
18
|
+
export function authoringTools(
|
|
19
|
+
scratchpadAvailable = false,
|
|
20
|
+
traceEnabled = false,
|
|
21
|
+
) {
|
|
20
22
|
const id = z.string().min(1);
|
|
21
23
|
const review = { reviewId: id };
|
|
22
24
|
|
|
@@ -29,21 +31,19 @@ export function authoringTools(traceEnabled = false) {
|
|
|
29
31
|
...trace.shape,
|
|
30
32
|
kind: z.enum(["image", "trace"]),
|
|
31
33
|
})
|
|
32
|
-
.omit({ repositoryId: true })
|
|
33
|
-
.extend({ repositoryPath: id })
|
|
34
34
|
.partial()
|
|
35
|
-
.required({ id: true,
|
|
35
|
+
.required({ id: true, repositoryId: true, kind: true });
|
|
36
36
|
|
|
37
37
|
const descriptions = {
|
|
38
38
|
create:
|
|
39
|
-
'Create a
|
|
39
|
+
'Create a review of saved working files, immutable commits or a GitHub PR. Revisions are resolved on acceptance. A worktree target reviews the saved files in its checkout, uncommitted ones included and untracked ones left out (git add -N a new file to include it), against base: the branch to compare against, by default the default branch of the repository. The diff starts at the merge base of base and HEAD, which follows rebases. Omitted commits base means source at head with no diff; supply the parent to review introduced changes. For a branch stacked on another, pass the branch below it as base; Desktop links reviews into a stack through their bases. For a GitHub PR, pullRequestUrl alone is enough: target and title become optional, and the host fetches the PR into a registered checkout of its repository and pins the current PR head and GitHub diff base, titled from the PR. When the existing review for that PR is in Trash, creation reports its ID; restore it with review_lifecycle state:"active" before continuing. When an active review for that PR exists, it is returned instead, reporting whether its head moved and whether an agent is working on it; update it in place, move its target with review_set_target, or create a separate review with reuseExisting. kind:"scratchpad" names the one scratchpad, which the host creates itself. The result carries review, the review as review_list shows it: its target with resolved commits, origin (its PR), repositoryName and repositoryPath, so no follow-up read is needed before diffing. When Desktop is available the review opens there and the result reports opened, softwareMapEnabled and environmentIssues, as review_open does; set open:false to author in the background without taking over Desktop.',
|
|
40
40
|
set_target:
|
|
41
|
-
"Move the review to a new target
|
|
41
|
+
"Move the review to a new target, such as the commits after a rebase or amend, preserving the document and component IDs. Revisions are resolved on acceptance. Returns warnings for retained source ranges in files the new commits changed, to verify, and resources that no longer match; fix them with review_edit. Earlier versions keep their source. Omitted pullRequestUrl keeps the PR within the same repository; changing repositories clears it. Supply a URL to replace it or null to detach.",
|
|
42
42
|
edit: [
|
|
43
|
-
"Edit one document component. content is one component, named by its type; each type's fields are in the Components section of session_get_instructions.",
|
|
44
|
-
"Anchors are head/path#L10-L20 or base/path#L7, or diff/path#L84-R90 for a range across sides, with repository-relative paths and 1-based inclusive lines. pins {head, base?} say which commits
|
|
45
|
-
"insert adds content: parentId nests it, afterId places it, and omitted placement appends. update patches a component's own fields; null removes an optional one. A section's children and a diagram's units change through their own IDs; a call stack's frames change by replacing the call stack. move and remove take a targetId; removing a flow_node removes its edges.",
|
|
46
|
-
"The host assigns short durable IDs. The result names the edited component and, for an insert or replace, its first-level children, and quotes the first and last line of each anchor the edit added or changed (the first 20; unquotedAnchors counts the rest): check they are the lines you meant. Accepted edits save at once.
|
|
43
|
+
"Edit one document component. content is one component, named by its type; each type's fields are in the Components section of session_get_instructions (authoring or scratchpad).",
|
|
44
|
+
"Anchors are head/path#L10-L20 or base/path#L7, or diff/path#L84-R90 for a range across sides, with repository-relative paths and 1-based inclusive lines. pins {repositoryId, head, base?} say which commits anchors quote: a step, frame, attachment or operation without pins uses its block's, and a block without them the review's.",
|
|
45
|
+
"insert adds content: parentId nests it, afterId places it, and omitted placement appends (on the scratchpad, it lands at the top). update patches a component's own fields; null removes an optional one. A section's children and a diagram's units change through their own IDs; a call stack's frames change by replacing the call stack. move and remove take a targetId; removing a flow_node removes its edges.",
|
|
46
|
+
"The host assigns short durable IDs. The result names the edited component and, for an insert or replace, its first-level children, and quotes the first and last line of each anchor the edit added or changed (the first 20; unquotedAnchors counts the rest): check they are the lines you meant. Accepted edits save at once.",
|
|
47
47
|
"While a reader may be watching, write small and often: one paragraph per edit. Insert a new diagram whole. Change one already drawn a unit at a time (a flow_node, flow_edge or step, with parentId naming the diagram), and link each added flow_node to a node already drawn.",
|
|
48
48
|
].join("\n"),
|
|
49
49
|
lens_edit:
|
|
@@ -56,11 +56,7 @@ export function authoringTools(traceEnabled = false) {
|
|
|
56
56
|
lifecycle:
|
|
57
57
|
"Move a review between active and trash. Trash retains the review for 30 days before automatic deletion. Moving it back to active restores it; moving it to trash again does not restart that period.",
|
|
58
58
|
delete:
|
|
59
|
-
"Permanently delete a review and its history. The review must be in Trash. This cannot be undone.
|
|
60
|
-
set_parent:
|
|
61
|
-
"Nest the review under another review of the same project, or make it top-level with parentId:null. Nesting is one level deep: the parent must be top-level, and a review with children can't become a child. The sidebar shows children under their parent. Saves no review version. Returns the review's relations: parentId, children in sibling order, and dependsOn.",
|
|
62
|
-
set_dependencies:
|
|
63
|
-
"Replace the list of reviews this one builds on: any reviews of the same project. Siblings list after what they depend on, directly or through other reviews; a PR stack is a chain. A list that would close a cycle is refused, naming the cycle. Saves no review version. Returns the review's relations.",
|
|
59
|
+
"Permanently delete a review and its history. The review must be in Trash. This cannot be undone.",
|
|
64
60
|
};
|
|
65
61
|
|
|
66
62
|
const tool = (
|
|
@@ -74,7 +70,7 @@ export function authoringTools(traceEnabled = false) {
|
|
|
74
70
|
name: `review_${name === "restore" ? "restore_version" : name}`,
|
|
75
71
|
description,
|
|
76
72
|
inputSchema: {
|
|
77
|
-
...
|
|
73
|
+
...z.toJSONSchema(schema, { io: "input" }),
|
|
78
74
|
type: "object" as const,
|
|
79
75
|
},
|
|
80
76
|
method,
|
|
@@ -96,6 +92,9 @@ export function authoringTools(traceEnabled = false) {
|
|
|
96
92
|
'Read Whiteboard\'s guidance before creating or editing a review. The default topic gives the authoring workflow; "file-lenses" covers Diff-view file lenses.' +
|
|
97
93
|
(traceEnabled
|
|
98
94
|
? ' Call review_get_instructions({topic:"trace-archaeology"}) for why code exists, what an agent was thinking, or whether an agent solved this before.'
|
|
95
|
+
: "") +
|
|
96
|
+
(scratchpadAvailable
|
|
97
|
+
? ' When the user asks in conversation to be shown how code works or wants a diagram, without asking for a review, draw it on the scratchpad rather than answering only in chat: call review_get_instructions({topic:"scratchpad"}) first. A request for a review or to use Whiteboard means authoring a review with the default topic.'
|
|
99
98
|
: ""),
|
|
100
99
|
instructionsQuerySchema.partial(),
|
|
101
100
|
"GET",
|
|
@@ -125,12 +124,10 @@ export function authoringTools(traceEnabled = false) {
|
|
|
125
124
|
...commandSchema.shape.operation.options.map((operation) => {
|
|
126
125
|
const type = operation.shape.type.value;
|
|
127
126
|
|
|
128
|
-
//
|
|
129
|
-
// and give PRs as a target.
|
|
127
|
+
// Agents name a checkout by its path; /commands registers it.
|
|
130
128
|
const {
|
|
131
129
|
type: _type,
|
|
132
130
|
repositoryId: _repositoryId,
|
|
133
|
-
pullRequestUrl: _pullRequestUrl,
|
|
134
131
|
...fields
|
|
135
132
|
}: Record<string, z.ZodType> = operation.shape;
|
|
136
133
|
|
|
@@ -140,7 +137,12 @@ export function authoringTools(traceEnabled = false) {
|
|
|
140
137
|
z.strictObject({
|
|
141
138
|
...fields,
|
|
142
139
|
...(type === "create" && {
|
|
143
|
-
target: pathTargetSchema,
|
|
140
|
+
target: pathTargetSchema.optional(),
|
|
141
|
+
repositoryPath: id
|
|
142
|
+
.optional()
|
|
143
|
+
.describe(
|
|
144
|
+
"Only with pullRequestUrl and no target: the local checkout to fetch the PR into. Default: the existing review's, else the first registered checkout with a remote for the PR's repository.",
|
|
145
|
+
),
|
|
144
146
|
open: z.boolean().optional(),
|
|
145
147
|
}),
|
|
146
148
|
...(type === "set_target" && { target: pathTargetSchema }),
|
|
@@ -194,6 +196,13 @@ export function authoringTools(traceEnabled = false) {
|
|
|
194
196
|
"POST",
|
|
195
197
|
"/workspace-cleanup",
|
|
196
198
|
),
|
|
199
|
+
tool(
|
|
200
|
+
"register_repository",
|
|
201
|
+
"Register a local Git or jj repository and return its id, for pins on scratchpad blocks. session_create and session_set_target take the checkout's path instead.",
|
|
202
|
+
z.strictObject({ path: id }),
|
|
203
|
+
"POST",
|
|
204
|
+
"/repositories",
|
|
205
|
+
),
|
|
197
206
|
tool(
|
|
198
207
|
"upload",
|
|
199
208
|
'Retain an image or trace for use in a review. kind:"image" takes base64; kind:"trace" takes trace. Reusing an upload ID requires identical content; rejected uploads are not saved.',
|
|
@@ -203,27 +212,3 @@ export function authoringTools(traceEnabled = false) {
|
|
|
203
212
|
),
|
|
204
213
|
];
|
|
205
214
|
}
|
|
206
|
-
|
|
207
|
-
/** Drops repositoryId from pins: the host fills in the board's own. */
|
|
208
|
-
function withoutRepositoryIds<T>(schema: T): T {
|
|
209
|
-
// SAFETY: a JSON round trip of a JSON Schema, dropping one property.
|
|
210
|
-
return JSON.parse(JSON.stringify(schema), (_key, value: JsonValue) => {
|
|
211
|
-
if (
|
|
212
|
-
!isJsonObject(value) ||
|
|
213
|
-
!isJsonObject(value.properties) ||
|
|
214
|
-
!("repositoryId" in value.properties) ||
|
|
215
|
-
!("head" in value.properties)
|
|
216
|
-
)
|
|
217
|
-
return value;
|
|
218
|
-
|
|
219
|
-
const { repositoryId: _repositoryId, ...properties } = value.properties;
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
...value,
|
|
223
|
-
properties,
|
|
224
|
-
...(Array.isArray(value.required) && {
|
|
225
|
-
required: value.required.filter((name) => name !== "repositoryId"),
|
|
226
|
-
}),
|
|
227
|
-
};
|
|
228
|
-
}) as T;
|
|
229
|
-
}
|
|
@@ -13,7 +13,7 @@ export const identity = { id: text.optional() };
|
|
|
13
13
|
* them reads its document's. */
|
|
14
14
|
export const elementPins = sourcePinsSchema
|
|
15
15
|
.optional()
|
|
16
|
-
.describe("Pins for the anchors here.");
|
|
16
|
+
.describe("Pins for the anchors here; needed on the scratchpad.");
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* One block kind: the strict schema the store parses with, and the rules a
|
|
@@ -6,12 +6,12 @@ export const markdown = {
|
|
|
6
6
|
type: "markdown",
|
|
7
7
|
schema: defineBlock("markdown", {
|
|
8
8
|
markdown: text.describe(
|
|
9
|
-
"Safe Markdown. Repository file links must use [label](
|
|
9
|
+
"Safe Markdown. Repository file links must use [label](review-source:head/path#L10-L24) or review-source:base/path#L10-L24, with a repository-relative path and verified line numbers. Relative paths, absolute filesystem paths, and file/editor URLs are rejected. External links use https://, http://, or mailto:; document anchors use #heading.",
|
|
10
10
|
),
|
|
11
11
|
pins: sourcePinsSchema
|
|
12
12
|
.optional()
|
|
13
13
|
.describe(
|
|
14
|
-
"Repository and commits this block's
|
|
14
|
+
"Repository and commits this block's review-source links resolve against, instead of the document's pins.",
|
|
15
15
|
),
|
|
16
16
|
}),
|
|
17
17
|
} as const;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
headingAnchors,
|
|
3
|
-
isBoardHeading,
|
|
4
|
-
markdownHeadingDepth,
|
|
5
|
-
} from "@review/heading-anchors.js";
|
|
6
1
|
import { markdownText, parseMarkdown } from "@review/markdown.js";
|
|
2
|
+
import { slugify, uniqueId } from "@review/slug.js";
|
|
7
3
|
|
|
8
|
-
import type
|
|
4
|
+
import { type Block, elements } from "./document.js";
|
|
9
5
|
|
|
10
6
|
export type ReviewHeadingLevel = "h2" | "h3";
|
|
11
7
|
|
|
@@ -18,65 +14,38 @@ export interface DocumentHeading {
|
|
|
18
14
|
block: Block;
|
|
19
15
|
/** Position among the markdown block's own headings; absent for a section. */
|
|
20
16
|
index?: number;
|
|
21
|
-
/** How deep it sits, 1 at the top: a section by its nesting, a Markdown
|
|
22
|
-
* heading one step under its section and under the block's bigger
|
|
23
|
-
* headings before it. */
|
|
24
|
-
depth: number;
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
/** Heading ids by the rule the MDX renderer published, `slugify(text)` made
|
|
28
20
|
* unique in document order, so imported `#fragment` links still land. */
|
|
29
21
|
export function documentHeadings(blocks: Block[]): DocumentHeading[] {
|
|
30
|
-
const
|
|
31
|
-
const out: DocumentHeading[] = [];
|
|
22
|
+
const used = new Set<string>();
|
|
32
23
|
|
|
33
|
-
const assign = (heading: Omit<DocumentHeading, "id">) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/** The Markdown headings open under the current section, by `#` count. */
|
|
38
|
-
let open: number[] = [];
|
|
39
|
-
|
|
40
|
-
const visit = (list: Block[], depth: number) => {
|
|
41
|
-
for (const block of list) {
|
|
42
|
-
if (block.type === "section") {
|
|
43
|
-
assign({
|
|
44
|
-
text: block.title,
|
|
45
|
-
level: depth === 1 ? "h2" : "h3",
|
|
46
|
-
block,
|
|
47
|
-
depth,
|
|
48
|
-
});
|
|
49
|
-
open = [];
|
|
50
|
-
visit(block.children, depth + 1);
|
|
51
|
-
open = [];
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (block.type === "markdown") {
|
|
56
|
-
let index = 0;
|
|
57
|
-
|
|
58
|
-
for (const node of parseMarkdown(block.markdown).children ?? []) {
|
|
59
|
-
if (node.type !== "heading" || !isBoardHeading(node.depth)) continue;
|
|
24
|
+
const assign = (heading: Omit<DocumentHeading, "id">): DocumentHeading => {
|
|
25
|
+
const id = uniqueId(slugify(heading.text) || "section", used);
|
|
26
|
+
used.add(id);
|
|
60
27
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
assign({
|
|
64
|
-
text: markdownText(node),
|
|
65
|
-
level: node.depth === 2 ? "h2" : "h3",
|
|
66
|
-
block,
|
|
67
|
-
index: index++,
|
|
68
|
-
depth: at,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if ("children" in block) visit(block.children, depth);
|
|
76
|
-
}
|
|
28
|
+
return { id, ...heading };
|
|
77
29
|
};
|
|
78
30
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
31
|
+
return elements(blocks).flatMap((block): DocumentHeading[] => {
|
|
32
|
+
if (block.type === "section")
|
|
33
|
+
return [assign({ text: block.title, level: "h2", block })];
|
|
34
|
+
|
|
35
|
+
if (block.type !== "markdown") return [];
|
|
36
|
+
let index = 0;
|
|
37
|
+
|
|
38
|
+
return (parseMarkdown(block.markdown).children ?? []).flatMap((node) =>
|
|
39
|
+
node.type === "heading" && (node.depth === 2 || node.depth === 3)
|
|
40
|
+
? [
|
|
41
|
+
assign({
|
|
42
|
+
text: markdownText(node),
|
|
43
|
+
level: node.depth === 2 ? "h2" : "h3",
|
|
44
|
+
block,
|
|
45
|
+
index: index++,
|
|
46
|
+
}),
|
|
47
|
+
]
|
|
48
|
+
: [],
|
|
49
|
+
);
|
|
50
|
+
});
|
|
82
51
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { boardTitle } from "@dev.fast/review-protocol";
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
type Element,
|
|
5
3
|
ReviewInputError,
|
|
@@ -8,22 +6,11 @@ import {
|
|
|
8
6
|
} from "./document.js";
|
|
9
7
|
import type { Snapshot } from "./store.js";
|
|
10
8
|
|
|
11
|
-
interface RelatedBoard {
|
|
12
|
-
id: string;
|
|
13
|
-
title: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
9
|
/** A reading view of saved content, not another document format to maintain. */
|
|
17
10
|
export function documentText(
|
|
18
11
|
snapshot: Snapshot,
|
|
19
12
|
targetId?: string,
|
|
20
13
|
full = false,
|
|
21
|
-
/** Where the whiteboard sits among its project's, by title. */
|
|
22
|
-
relations?: {
|
|
23
|
-
parent?: RelatedBoard;
|
|
24
|
-
children: RelatedBoard[];
|
|
25
|
-
dependsOn: RelatedBoard[];
|
|
26
|
-
},
|
|
27
14
|
): string {
|
|
28
15
|
const target =
|
|
29
16
|
targetId === undefined
|
|
@@ -35,27 +22,11 @@ export function documentText(
|
|
|
35
22
|
const detailed = full || target !== undefined;
|
|
36
23
|
|
|
37
24
|
const lines = [
|
|
38
|
-
`# ${
|
|
25
|
+
`# ${snapshot.title}`,
|
|
39
26
|
`Session ${snapshot.reviewId} · version ${snapshot.version}`,
|
|
27
|
+
"",
|
|
40
28
|
];
|
|
41
29
|
|
|
42
|
-
if (relations && targetId === undefined) {
|
|
43
|
-
const list = (boards: RelatedBoard[]) =>
|
|
44
|
-
boards
|
|
45
|
-
.map((board) => `${boardTitle(board.title)} (${board.id})`)
|
|
46
|
-
.join(", ");
|
|
47
|
-
|
|
48
|
-
if (relations.parent) lines.push(`Parent: ${list([relations.parent])}`);
|
|
49
|
-
|
|
50
|
-
if (relations.children.length)
|
|
51
|
-
lines.push(`Children, in order: ${list(relations.children)}`);
|
|
52
|
-
|
|
53
|
-
if (relations.dependsOn.length)
|
|
54
|
-
lines.push(`Depends on: ${list(relations.dependsOn)}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
lines.push("");
|
|
58
|
-
|
|
59
30
|
const write = (depth: number, text: string) => {
|
|
60
31
|
for (const line of text.split("\n")) lines.push(" ".repeat(depth) + line);
|
|
61
32
|
};
|