@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
|
@@ -37,6 +37,10 @@ const StructuralDiffEventSchema = z.lazy(() => z.union([
|
|
|
37
37
|
type: z.literal("file"),
|
|
38
38
|
file: structuralPairingSchema(StructuralFileRefSchema)
|
|
39
39
|
}).and(StructuralOutcomeSchema),
|
|
40
|
+
z.object({
|
|
41
|
+
type: z.literal("update"),
|
|
42
|
+
file: structuralPairingSchema(StructuralFileRefSchema)
|
|
43
|
+
}).and(StructuralUpdateSchema),
|
|
40
44
|
z.object({
|
|
41
45
|
type: z.literal("complete"),
|
|
42
46
|
succeeded: structuralU32,
|
|
@@ -51,6 +55,20 @@ const StructuralOutcomeSchema = z.lazy(() => z.union([z.object({
|
|
|
51
55
|
error: StructuralProblemSchema,
|
|
52
56
|
diff: z.never().optional()
|
|
53
57
|
})]));
|
|
58
|
+
const StructuralUpdateSchema = z.lazy(() => z.union([z.object({
|
|
59
|
+
regions: z.array(StructuralRegionVisibilitySchema),
|
|
60
|
+
visible: StructuralLineCountsSchema,
|
|
61
|
+
error: z.never().optional()
|
|
62
|
+
}), z.object({
|
|
63
|
+
error: StructuralProblemSchema,
|
|
64
|
+
regions: z.never().optional(),
|
|
65
|
+
visible: z.never().optional()
|
|
66
|
+
})]));
|
|
67
|
+
const StructuralRegionVisibilitySchema = z.lazy(() => z.object({
|
|
68
|
+
id: structuralU32,
|
|
69
|
+
collapsed: z.boolean().optional(),
|
|
70
|
+
label: z.string().optional()
|
|
71
|
+
}));
|
|
54
72
|
const StructuralSnapshotSchema = z.lazy(() => z.union([
|
|
55
73
|
z.object({
|
|
56
74
|
type: z.literal("revision"),
|
|
@@ -174,6 +192,45 @@ const ReviewStructuralDiffErrorSchema = z.object({
|
|
|
174
192
|
message: z.string()
|
|
175
193
|
});
|
|
176
194
|
z.union([StructuralDiffEventSchema, ReviewStructuralDiffErrorSchema]);
|
|
195
|
+
/**
|
|
196
|
+
* A copy of `diff` with an update's region visibility and visible counts.
|
|
197
|
+
* `accept` may change what a region takes from its entry.
|
|
198
|
+
*/
|
|
199
|
+
function applyStructuralUpdate(diff, update, accept = (_, next) => next) {
|
|
200
|
+
const entries = new Map(update.regions.map(({ id, ...visibility }) => [id, visibility]));
|
|
201
|
+
const visit = (region) => {
|
|
202
|
+
const entry = entries.get(region.id);
|
|
203
|
+
let next = region;
|
|
204
|
+
if (entry) {
|
|
205
|
+
const { collapsed, label } = accept(region, entry);
|
|
206
|
+
const { visibility: _, ...rest } = region;
|
|
207
|
+
next = collapsed || label ? {
|
|
208
|
+
...rest,
|
|
209
|
+
visibility: {
|
|
210
|
+
...collapsed && { collapsed },
|
|
211
|
+
...label && { label }
|
|
212
|
+
}
|
|
213
|
+
} : rest;
|
|
214
|
+
}
|
|
215
|
+
return next.kind === "fold" ? {
|
|
216
|
+
...next,
|
|
217
|
+
children: next.children.map(visit)
|
|
218
|
+
} : next;
|
|
219
|
+
};
|
|
220
|
+
const side = (source) => ({
|
|
221
|
+
...source,
|
|
222
|
+
root: visit(source.root)
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
...diff,
|
|
226
|
+
...diff.lhs && { lhs: side(diff.lhs) },
|
|
227
|
+
...diff.rhs && { rhs: side(diff.rhs) },
|
|
228
|
+
stats: {
|
|
229
|
+
...diff.stats,
|
|
230
|
+
visible: update.visible
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
177
234
|
const REVIEW_CLIENT_HEADER = "x-review-client";
|
|
178
235
|
const REVIEW_CLIENT_REMOTE = "remote";
|
|
179
236
|
const REVIEW_HOST_HEADER = "x-review-host";
|
|
@@ -194,7 +251,11 @@ const reviewViewSchema = z.enum([
|
|
|
194
251
|
"map",
|
|
195
252
|
"trace"
|
|
196
253
|
]);
|
|
197
|
-
const reviewThemeSchema = z.enum([
|
|
254
|
+
const reviewThemeSchema = z.enum([
|
|
255
|
+
"light",
|
|
256
|
+
"dark",
|
|
257
|
+
"system"
|
|
258
|
+
], { error: "must be light, dark or system" });
|
|
198
259
|
function urlSchema(output, constraint) {
|
|
199
260
|
return requiredString$1.transform((value, context) => {
|
|
200
261
|
let url;
|
|
@@ -1108,4 +1169,4 @@ function formatIssuePath(path) {
|
|
|
1108
1169
|
return output;
|
|
1109
1170
|
}
|
|
1110
1171
|
//#endregion
|
|
1111
|
-
export { ReviewGatewayHostSchema as A, REVIEW_REMOTE_INSTALL_LOCK as C, ReviewConnectRequestSchema as D, ReviewCliInstallStampSchema as E,
|
|
1172
|
+
export { ReviewGatewayHostSchema as A, REVIEW_REMOTE_INSTALL_LOCK as C, ReviewConnectRequestSchema as D, ReviewCliInstallStampSchema as E, decodeStructuralDiffEvent as F, structuralChangeCounts as I, ReviewSelectedDiffSchema as M, reviewDiffrSummarizerInputSchema as N, ReviewFeedbackContactSchema as O, applyStructuralUpdate as P, REVIEW_REMOTE_ATTACH_END as S, ReviewApiSelectionSourceSchema as T, REVIEW_DIFF_LAYOUTS as _, parseReviewVerbRequest as a, REVIEW_REMOTE_AGENT_IDS as b, boardIconKind as c, ReviewBugReportRequestSchema as d, parseReviewBugReportResponse as f, REVIEW_COLOR_THEME_KINDS as g, REVIEW_CLI_INSTALL_TARGET_LABELS as h, parseReviewDesktopVerbResult as i, ReviewOnboardingRunRequestSchema as j, ReviewFeedbackSchema as k, boardTitle as l, REVIEW_CLIENT_REMOTE as m, parseReviewDesktopDiscovery as n, parseReviewVerbResponse as o, REVIEW_CLIENT_HEADER as p, parseReviewDesktopVerbFrame as r, parseZod as s, parseReviewCliInstallApplyRequest as t, ReviewBugReportMetaV2Schema as u, REVIEW_DIFF_LAYOUT_SCOPES as v, REVIEW_REMOTE_WRAPPER_MARK as w, REVIEW_REMOTE_ATTACH_BEGIN as x, REVIEW_HOST_HEADER as y };
|
package/dist/{stored-document-migration-DEt8iwem.js → stored-document-migration-BCFx07Bq.js}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { C as renderVcsArgs, v as parseGitRemote, w as runVcs } from "./src-X9ZebeOD.js";
|
|
2
2
|
import { d as isBooleanValue, f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject, t as isJsonArray } from "./src-X9phtB2j.js";
|
|
3
|
-
import "./src-
|
|
3
|
+
import "./src-DEOc8bvC.js";
|
|
4
4
|
import "./src-Z1x4-Qmb.js";
|
|
5
|
-
import { a as commitLinkTarget, c as parseAnchor, d as selectionProblem, f as sourceAnchors, i as anchorSelection, r as anchorSchema, s as formatAnchor } from "./lens-selection-
|
|
5
|
+
import { a as commitLinkTarget, c as parseAnchor, d as selectionProblem, f as sourceAnchors, i as anchorSelection, r as anchorSchema, s as formatAnchor } from "./lens-selection-EuZdag4Q.js";
|
|
6
6
|
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
7
7
|
import { t as errorMessage } from "./error-message-OtiDonty.js";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
@@ -1041,6 +1041,33 @@ const flow_diagram = {
|
|
|
1041
1041
|
}
|
|
1042
1042
|
};
|
|
1043
1043
|
//#endregion
|
|
1044
|
+
//#region src/review-api/blocks/animation.ts
|
|
1045
|
+
/** A name the stage's script passes to the `whiteboard` shim. */
|
|
1046
|
+
const handle = z.string().regex(/^[A-Za-z][\w-]{0,63}$/, "Use a short name: a letter, then letters, digits, _ or -.");
|
|
1047
|
+
const animationFrameSchema = z.strictObject({
|
|
1048
|
+
key: handle.describe("Names the frame for the stage: data-frame on <html>, data-enter/data-exit, and whiteboard.on(key, ...)."),
|
|
1049
|
+
title: label$1,
|
|
1050
|
+
label: z.string().min(1).max(24).optional().describe("A short name for the step strip under the stage, e.g. PUSH. Give every frame one, or none."),
|
|
1051
|
+
caption: text$1.optional().describe("One or two short sentences shown under the stage."),
|
|
1052
|
+
duration: z.number().int().min(300).max(3e4).optional().describe("Milliseconds this frame stays up while playing. Default 3000.")
|
|
1053
|
+
});
|
|
1054
|
+
const animation = {
|
|
1055
|
+
type: "animation",
|
|
1056
|
+
schema: defineBlock("animation", {
|
|
1057
|
+
title: label$1,
|
|
1058
|
+
html: text$1.min(1).max(2e5).describe("The stage's body markup: elements, <style> and <script>. It runs in a sandboxed frame with a global `whiteboard` shim; see session_get_instructions({topic:\"animation\"})."),
|
|
1059
|
+
frames: z.array(animationFrameSchema).min(1).max(60).describe("Every possible frame. Stage scripts may select an ordered branch with whiteboard.setSequence(keys); keys stay unique and stable."),
|
|
1060
|
+
code: z.record(handle, anchorSchema).default({}).describe("Source ranges the stage shows as code peeks, by name: whiteboard.code(name)."),
|
|
1061
|
+
height: z.union([z.literal("auto"), z.number().int().min(120).max(1200)]).default("auto").describe("Fit the stage to its content by default, or set a fixed height in CSS pixels."),
|
|
1062
|
+
pins: elementPins
|
|
1063
|
+
}),
|
|
1064
|
+
check(block) {
|
|
1065
|
+
if (new Set(block.frames.map((frame) => frame.key)).size !== block.frames.length) throw new ReviewInputError("Animation frame keys must be unique within the animation.");
|
|
1066
|
+
const labelled = block.frames.filter((frame) => frame.label).length;
|
|
1067
|
+
if (labelled && labelled !== block.frames.length) throw new ReviewInputError("Give every animation frame a label, or none: the step strip needs one per frame.");
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
//#endregion
|
|
1044
1071
|
//#region src/review-api/blocks/call_stack_diff.ts
|
|
1045
1072
|
const frameSchema = z.strictObject({
|
|
1046
1073
|
...identity,
|
|
@@ -1253,7 +1280,7 @@ const stepSchema = z.strictObject({
|
|
|
1253
1280
|
]).default("call"),
|
|
1254
1281
|
/** The call site this arrow stands for, then any ranges directly about
|
|
1255
1282
|
* that call, each shown as its own card. */
|
|
1256
|
-
source: z.union([anchorSchema, z.array(anchorSchema).min(1).max(4)]).optional(),
|
|
1283
|
+
source: z.union([anchorSchema, z.array(anchorSchema).min(1).max(4)], { error: "source must be one range or a list of 1 to 4 ranges" }).optional(),
|
|
1257
1284
|
pins: elementPins,
|
|
1258
1285
|
explanation: label$1.optional(),
|
|
1259
1286
|
code: z.strictObject(codeFields).optional()
|
|
@@ -1338,7 +1365,8 @@ const leafSchema = z.discriminatedUnion("type", [
|
|
|
1338
1365
|
image.schema,
|
|
1339
1366
|
trace_quote.schema,
|
|
1340
1367
|
software_map.schema,
|
|
1341
|
-
flow_diagram.schema
|
|
1368
|
+
flow_diagram.schema,
|
|
1369
|
+
animation.schema
|
|
1342
1370
|
]);
|
|
1343
1371
|
/** Every block kind, keyed by type. A kind without a definition is a compile error. */
|
|
1344
1372
|
const blocks = {
|
|
@@ -1353,6 +1381,7 @@ const blocks = {
|
|
|
1353
1381
|
trace_quote,
|
|
1354
1382
|
software_map,
|
|
1355
1383
|
flow_diagram,
|
|
1384
|
+
animation,
|
|
1356
1385
|
section,
|
|
1357
1386
|
callout
|
|
1358
1387
|
};
|
|
@@ -1457,6 +1486,9 @@ const unitTypes = /* @__PURE__ */ new Set([
|
|
|
1457
1486
|
"flow_edge"
|
|
1458
1487
|
]);
|
|
1459
1488
|
const isUnit = (element) => unitTypes.has(element.type);
|
|
1489
|
+
/** The reference id of a code peek an animation names: the canvas finds the
|
|
1490
|
+
* peek's resolved anchor by it. */
|
|
1491
|
+
const animationCodeId = (blockId, name) => `${blockId}:code:${name}`;
|
|
1460
1492
|
/** Rewrites old review-source: and review-trace: links to their whiteboard: form. */
|
|
1461
1493
|
function canonicalLink(href) {
|
|
1462
1494
|
return href.replace(/^review-(source|trace):/i, (_, kind) => `whiteboard:${kind.toLowerCase()}/`);
|
|
@@ -1639,6 +1671,12 @@ function documentReferences(document, { tolerant = false, markdown = true, repos
|
|
|
1639
1671
|
label: attachment.label,
|
|
1640
1672
|
peek: true
|
|
1641
1673
|
})))));
|
|
1674
|
+
if (element.type === "animation") return Object.entries(element.code).flatMap(([name, anchor]) => select(anchor, element.pins).map((source) => ({
|
|
1675
|
+
id: animationCodeId(element.id, name),
|
|
1676
|
+
source,
|
|
1677
|
+
label: name,
|
|
1678
|
+
peek: true
|
|
1679
|
+
})));
|
|
1642
1680
|
if (element.type === "call_stack_diff") return [...element.base, ...element.head].flatMap((frame) => {
|
|
1643
1681
|
const pins = frame.pins ?? element.pins;
|
|
1644
1682
|
return [
|
|
@@ -1789,12 +1827,15 @@ const writableBlockTypes = Object.keys(blocks);
|
|
|
1789
1827
|
const namedByType = (types) => z.looseObject({ type: z.enum(types) }).describe("Fields depend on type; see the tool description.");
|
|
1790
1828
|
/** What agents are shown: content names only its type. The host parses
|
|
1791
1829
|
* editSchema, reporting the named kind's issues. */
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1830
|
+
function publishedEdits(animationsEnabled = true) {
|
|
1831
|
+
const types = writableBlockTypes.filter((type) => animationsEnabled || type !== "animation");
|
|
1832
|
+
return edits(namedByType([
|
|
1833
|
+
...types,
|
|
1834
|
+
"step",
|
|
1835
|
+
"flow_node",
|
|
1836
|
+
"flow_edge"
|
|
1837
|
+
]), namedByType(types), z.unknown());
|
|
1838
|
+
}
|
|
1798
1839
|
/** The block an element belongs to: itself, or the diagram around a unit. */
|
|
1799
1840
|
function enclosingBlock(document, id) {
|
|
1800
1841
|
for (const block of elements(document)) {
|
|
@@ -3213,4 +3254,4 @@ function liftFileLenses(document) {
|
|
|
3213
3254
|
};
|
|
3214
3255
|
}
|
|
3215
3256
|
//#endregion
|
|
3216
|
-
export { lensEditSchema as $, editSchema as A, resourceReferences as B, applyEdit as C, boardRepositoryId as D, boardLinks as E, pinnedRepositoryIds as F, selectionReferences as G, respellDocumentLinks as H, pinsSchema as I, checkReferences as J, sourceReferences as K,
|
|
3257
|
+
export { lensEditSchema as $, editSchema as A, resourceReferences as B, applyEdit as C, boardRepositoryId as D, boardLinks as E, pinnedRepositoryIds as F, selectionReferences as G, respellDocumentLinks as H, pinsSchema as I, checkReferences as J, sourceReferences as K, publishedEdits as L, explicitPins as M, pathTargetSchema as N, commitLinkCommits as O, pinDocumentReferences as P, applyLensEdit as Q, readsAtBoardPins as R, anchorPins as S, assignMissingIds as T, respellEditLinks as U, respellCommitLinks as V, reviewTargetSchema as W, stepSources as X, documentAdvice as Y, UNCATEGORIZED_LENS_ID as Z, fetchPullRequest as _, SoftwareModelValidationError as a, requireVisibleSource as at, textIncludesQuote as b, traceSchema as c, markdownText as ct, setPullRequest as d, lensSchema as et, sourceBranchName as f, defaultPullRequestDeps as g, GitHubRefusedRead as h, readStoredDocument as i, fileLineRangeSchema as it, elements as j, documentSchema as k, pullRequestKey as l, parseMarkdown as lt, resolvePullRequest as m, migrateStoredDocument as n, SourceRangeError as nt, defineSoftwareMap as o, sliceSourceRange as ot, pullRequestProvider as p, summarizeEdit as q, migrateStoredLenses as r, checkSourcePath as rt, softwareModelDataSchema as s, markdownNodes as st, liftFileLenses as t, lensSelections as tt, pullRequestUrl as u, remoteUrls as v, assignFreshIds as w, adoptFlowUnits as x, azureRepositoryOf as y, resourceReference as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as ReviewSelectedDiffSchema, T as ReviewApiSelectionSourceSchema } from "./src-
|
|
1
|
+
import { M as ReviewSelectedDiffSchema, T as ReviewApiSelectionSourceSchema } from "./src-DEOc8bvC.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
/** A semantic selection, independent of comment/thread creation. */
|
|
4
4
|
const AgentSelectionSchema = z.strictObject({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
|
|
2
|
-
import { l as boardTitle } from "./src-
|
|
3
|
-
import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-
|
|
2
|
+
import { l as boardTitle } from "./src-DEOc8bvC.js";
|
|
3
|
+
import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-D4FSEDX4.js";
|
|
4
4
|
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
5
5
|
//#region src/review-api/public-tools.ts
|
|
6
6
|
function publicDescription(text) {
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
An `animation` component plays HTML you write, frame by frame, to show how a change behaves over time: a request moving through layers, state before and after, a cache filling. Use it when motion explains something a static diagram cannot. Do not use it for content a `sequence`, `flow_diagram` or `code_peek` already shows.
|
|
2
|
+
|
|
3
|
+
## Choose how to build it
|
|
4
|
+
|
|
5
|
+
Choose where to start by what changes between the frames:
|
|
6
|
+
|
|
7
|
+
| Build with | When the frames show |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| The scene (`whiteboard.scene`) | Up to about 8 cards whose notes, text and tones change, with chips moving along their edges. This is the default. |
|
|
10
|
+
| Mermaid (`whiteboard.mermaid`) | A larger diagram, or one with branches or subgroups, whose nodes and edges appear, leave or light up. Its labels stay fixed: use the scene if they must change. |
|
|
11
|
+
| The toolkit, style kit and raw HTML | Anything else: a chart, a timeline, a queue, a custom shape, or motion the scene cannot express (`arrow`, `token`, `travel`, `timeline`). |
|
|
12
|
+
|
|
13
|
+
If no part of the drawing changes between frames, it is not an animation: use a `sequence` or `flow_diagram` block, whose steps and nodes link to code. The three mix: a stage can hold a scene, a Mermaid diagram and your own HTML together.
|
|
14
|
+
|
|
15
|
+
## The component
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"type": "animation",
|
|
20
|
+
"title": "Retry with backoff",
|
|
21
|
+
"frames": [
|
|
22
|
+
{
|
|
23
|
+
"key": "send",
|
|
24
|
+
"title": "The client sends",
|
|
25
|
+
"label": "SEND",
|
|
26
|
+
"caption": "The first request times out."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"key": "wait",
|
|
30
|
+
"title": "It backs off",
|
|
31
|
+
"label": "WAIT",
|
|
32
|
+
"duration": 4000
|
|
33
|
+
},
|
|
34
|
+
{ "key": "retry", "title": "The retry succeeds", "label": "RETRY" }
|
|
35
|
+
],
|
|
36
|
+
"code": { "retry": "head/src/http/retry.ts#L12-L40" },
|
|
37
|
+
"pins": { "repositoryId": "…", "head": "…" },
|
|
38
|
+
"html": "…"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `frames`: every possible step. `key` names the frame for your markup and scripts; `title` and `caption` show under the stage. `label` is a short step name (e.g. `SEND`); give every frame one, or none, and the reader gets a named step strip instead of dots. `duration` (ms, default 3000) is how long the frame holds while playing. For a flow with alternate branches, declare every stable frame here and select the active ordered path with `whiteboard.setSequence(...)`.
|
|
43
|
+
- `code`: the source ranges the stage shows, by name. The server checks each anchor like any other reference, so verify paths and lines. Use `head/` for the new code and `base/` for the old.
|
|
44
|
+
- `pins`: as on any block that quotes code. A review with a target lends its own; a board without one needs them on the block.
|
|
45
|
+
- `html`: the stage's body. It runs in a sandboxed frame with no network: inline `<style>` and `<script>` only, no external URLs. Images must be `data:` URLs.
|
|
46
|
+
- `height`: defaults to `"auto"`, which fits the stage to its content, including host code peeks and blocks. Omit it for most animations. Set a number (120 to 1200 CSS pixels) only when the animation intentionally needs a fixed viewport; fixed-height stages clip overflow, so verify every frame at narrow widths. The width is the document column.
|
|
47
|
+
|
|
48
|
+
## Build with the scene
|
|
49
|
+
|
|
50
|
+
`whiteboard.scene` is a convenient starting point for cards and message flows. It provides grid layout, bound edges, and moving chips. You can combine it with custom HTML, SVG, CSS, or animation code whenever that better fits the explanation; the scene is optional. A complete example:
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<script>
|
|
54
|
+
const s = whiteboard.scene({ columns: 4 });
|
|
55
|
+
|
|
56
|
+
// Cards sit on a 1-based grid: [column, row].
|
|
57
|
+
const agent = s.actor({
|
|
58
|
+
id: "agent",
|
|
59
|
+
at: [1, 1],
|
|
60
|
+
title: "Agent",
|
|
61
|
+
note: "session_edit",
|
|
62
|
+
});
|
|
63
|
+
const api = s.actor({
|
|
64
|
+
id: "api",
|
|
65
|
+
at: [2, 1],
|
|
66
|
+
title: "Review API",
|
|
67
|
+
note: "POST /commands",
|
|
68
|
+
});
|
|
69
|
+
const store = s.store({
|
|
70
|
+
id: "store",
|
|
71
|
+
at: [3, 1],
|
|
72
|
+
title: "Store",
|
|
73
|
+
note: "SQLite",
|
|
74
|
+
});
|
|
75
|
+
const canvas = s.actor({ id: "canvas", at: [4, 1], title: "Canvas" });
|
|
76
|
+
|
|
77
|
+
// Edges follow their cards. They draw in on the frame you name.
|
|
78
|
+
s.link(agent, api, { label: "call", enter: "write" });
|
|
79
|
+
s.link(api, store, { label: "edit op", enter: "write" });
|
|
80
|
+
s.link(store, canvas, {
|
|
81
|
+
label: "snapshot",
|
|
82
|
+
tone: "added",
|
|
83
|
+
dashed: true,
|
|
84
|
+
enter: "watch",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// A chip rides the edge during a frame, then fades out on arrival.
|
|
88
|
+
s.send(agent, api, "edit", "write");
|
|
89
|
+
s.send(api, store, "v9", "write", { stay: true, only: ["write"] });
|
|
90
|
+
|
|
91
|
+
// Per-frame changes. Each holds until the next frame that changes it.
|
|
92
|
+
s.on("write", store, { note: "version 9", tone: "modified" });
|
|
93
|
+
s.on("watch", canvas, { note: "redrawn", tone: "added" });
|
|
94
|
+
store.item("reviews", { enter: "write", tone: "added" });
|
|
95
|
+
|
|
96
|
+
// A code peek in a cell. It spans all four columns of row 2 so the
|
|
97
|
+
// source has room: [column, row, rowSpan, colSpan].
|
|
98
|
+
s.peek("handler", { at: [1, 2, 1, 4], enter: "write" });
|
|
99
|
+
|
|
100
|
+
const etag = s.badge({ text: "etag e0" });
|
|
101
|
+
s.on("watch", etag, { text: "etag e1" });
|
|
102
|
+
</script>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A scene appends to the stage body by default. To compose it with your own headings, text or controls, pass a container selector or element: `whiteboard.scene({ columns: 3, container: "#diagram" })`. Badges stay with that container too.
|
|
106
|
+
|
|
107
|
+
When the container is too narrow for readable cards, the scene stacks its cells in row/column order. Widening restores the declared grid. Tables wrap their content. Hidden cards, store items and table rows take no space, so auto height follows visible content. Use `responsive: false` for an intentional fixed grid with your own responsive or scrolling plan.
|
|
108
|
+
|
|
109
|
+
The scene keeps three things consistent for you:
|
|
110
|
+
|
|
111
|
+
- **Spacing.** Cards sit in grid cells with a fixed gap. You choose the cell; the scene sets the gap and the padding. Four cards fit a sufficiently wide row; narrower containers stack them. Past four, prefer a second row.
|
|
112
|
+
- **Edges.** `link` draws one straight line from the center of one card's nearest side to the other's, with one weight and one arrowhead. `dashed: true` means async (a reply, a stream, a background send). A second edge between the same two cards offsets beside the first.
|
|
113
|
+
- **Moving blocks.** `send` is the only way to move a block along an edge. The chip rides the edge's line at one fixed speed, so a longer edge takes longer, and every chip moves at the same pace. It fades out on arrival unless you pass `stay: true`.
|
|
114
|
+
|
|
115
|
+
### The scene API
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
whiteboard.scene(options?: { columns?: number; container?: string | Element; responsive?: boolean }): Scene; // default 3 columns
|
|
119
|
+
|
|
120
|
+
interface Scene {
|
|
121
|
+
actor(options: CardOptions): Card; // a labeled card
|
|
122
|
+
store(options: CardOptions): Store; // a card that holds items
|
|
123
|
+
table(options: CardOptions & { columns: string[] }): Table;
|
|
124
|
+
peek(name: string, options: { at: Cell; enter?; exit?; only? }): HTMLElement;
|
|
125
|
+
badge(options: { text?: string; tone?: Tone }): HTMLElement; // a pill above the scene
|
|
126
|
+
link(from: Ref, to: Ref, options?: { label?; tone?; dashed?; arrowhead?: boolean; enter?; exit?; only? }): SVGGElement;
|
|
127
|
+
send(from: Ref, to: Ref, label: string, frame: string, options?: { tone?; stay?: boolean; enter?; exit?; only? }): void;
|
|
128
|
+
on(frame: string, part: Ref, change: { text?: string; tone?: Tone | "none" }): void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
type Cell = [column: number, row: number, rowSpan?: number, colSpan?: number]; // 1-based
|
|
132
|
+
type Ref = Card | string /* its id */ | Element;
|
|
133
|
+
type Tone = "accent" | "added" | "removed" | "modified" | "muted";
|
|
134
|
+
|
|
135
|
+
interface CardOptions {
|
|
136
|
+
id: string; // names the card for link, send and on
|
|
137
|
+
at: Cell;
|
|
138
|
+
title?: string; // the first line; defaults to the id
|
|
139
|
+
note?: string; // the second line
|
|
140
|
+
tone?: Tone;
|
|
141
|
+
enter?: string; exit?: string; only?: string[]; // which frames it shows in
|
|
142
|
+
highlight?: string[]; // frames during which it is ringed
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
store.item(label, { enter?; exit?; only?; tone? }): HTMLElement; // a chip inside the store
|
|
146
|
+
table.row(cells: string[], { enter?; exit?; only?; tone? }): HTMLTableRowElement;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `on(frame, part, { text, tone })` changes a card, item, or badge from that frame on. `text` sets a card's note line, or an item's or badge's text — `note` works too, since that is the field cards declare. The change holds until the next frame that changes the same part, so a jump lands right. An unknown field throws rather than being ignored.
|
|
150
|
+
- `send` draws its edge for you if no `link` joins the two cards yet. Keep edge and chip labels short (about 12 characters): a label sits at the middle of its edge, in the space between the two cards. `stay: true` retains the chip at its destination; use `enter`, `exit`, or `only` separately to scope when that retained chip is visible. For example, `s.send(a, b, "PACK", "upload", { stay: true, only: ["upload"] })` keeps the arrived packet visible only in `upload`.
|
|
151
|
+
- Use `tone` `added` / `removed` / `modified` for what the change adds, deletes or alters. `accent` draws the eye; `muted` recedes. In `on`, `tone: "none"` clears a part back to the neutral surface, e.g. to un-highlight the previous step.
|
|
152
|
+
- Links are directed by default. Pass `arrowhead: false` to `link` for an undirected guide, boundary, or relationship while retaining endpoint binding, routing, labels, tones, dashed styling, lifecycle, and travel support.
|
|
153
|
+
- Edges route straight, but bow clear of any card that sits on the line between their ends. Labels use their rendered bounds to avoid endpoint cards, intervening cards, and other visible edge labels, and are reconsidered as the stage resizes. Prefer a layout where the cells between two linked cards are empty; automatic routing is a fallback, not the look to design for.
|
|
154
|
+
|
|
155
|
+
### Frames
|
|
156
|
+
|
|
157
|
+
The stage sets `data-frame="<key>"` on `<html>`, and `enter` / `exit` / `only` on any scene part decide the frames it shows in. They are states, not events, so they land right when the reader jumps to a frame or steps back. With `setSequence`, `enter` / `exit` and held `scene.on` changes follow active-sequence order, not declaration order: a change whose key is outside the active path does not apply, an inactive `enter` never occurs, and an inactive `exit` does not end an already-entered part. `only` and highlight lists always match exact stable keys. Visible cards keep their declared wide-grid cells. Hidden cards, store items and table rows collapse from layout; put replacing cards in the same cell. For an intentionally stable footprint, give the container a min-height or choose a fixed stage height with an appropriate scrolling plan.
|
|
158
|
+
|
|
159
|
+
## Draw with Mermaid
|
|
160
|
+
|
|
161
|
+
`whiteboard.mermaid(text)` draws a Mermaid diagram in the stage's theme, lays it out for you, and shows its parts frame by frame. Use it when the frames change a diagram too large to place card by card: a pipeline whose stages light up in turn, a state machine stepping through its states, a graph that grows as a request spreads. Use the scene when a few cards carry chips along their edges; Mermaid has no `send`.
|
|
162
|
+
|
|
163
|
+
Mermaid nodes do not link to code. A diagram that stays the same in every frame belongs in a `flow_diagram` or `sequence` block, not in an animation.
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<script>
|
|
167
|
+
const d = whiteboard.mermaid(`flowchart LR
|
|
168
|
+
agent[Agent] e1@-->|call| api(Review API)
|
|
169
|
+
api e2@-->|edit op| store[(Store)]
|
|
170
|
+
store e3@-.->|snapshot| canvas[Canvas]`);
|
|
171
|
+
|
|
172
|
+
d.highlight("call", "agent", "e1")
|
|
173
|
+
.enter("write", "store", "e2")
|
|
174
|
+
.on("write", "store", { tone: "modified" })
|
|
175
|
+
.enter("watch", "canvas", "e3")
|
|
176
|
+
.on("watch", "canvas", { tone: "added" });
|
|
177
|
+
</script>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Name a part by its Mermaid id:
|
|
181
|
+
|
|
182
|
+
- A node by its id: `api` above.
|
|
183
|
+
- An edge by the id you give it with `@`: `a e1@--> b`. Name every edge you will reveal or light up. An unnamed flowchart edge is `L_<from>_<to>_<n>`, counting from 0.
|
|
184
|
+
- In a sequence diagram, a participant by its name, and a message as `i0`, `i1`, … in the order they appear.
|
|
185
|
+
|
|
186
|
+
`d.ids` lists every part in the drawing, and a call naming a part the drawing lacks throws with that list.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
whiteboard.mermaid(text: string, options?: { container?: string | Element }): MermaidDiagram;
|
|
190
|
+
|
|
191
|
+
interface MermaidDiagram {
|
|
192
|
+
readonly element: HTMLElement; // the diagram's box
|
|
193
|
+
readonly ids: readonly string[];
|
|
194
|
+
enter(frame: string, ...ids: string[]): MermaidDiagram; // show from this frame on
|
|
195
|
+
exit(frame: string, ...ids: string[]): MermaidDiagram; // hide from this frame on
|
|
196
|
+
only(frames: string | string[], ...ids: string[]): MermaidDiagram;
|
|
197
|
+
highlight(frames: string | string[], ...ids: string[]): MermaidDiagram; // ring during these frames
|
|
198
|
+
on(frame: string, ids: string | string[], change: { tone: Tone | "none" }): MermaidDiagram;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- The whole diagram is laid out once, from its full text, and parts that have not entered keep their place. Nothing moves as parts appear, so write the text for the last frame and reveal it frame by frame.
|
|
203
|
+
- `enter`, `exit`, `only` and `highlight` follow the same rules as on scene parts, active sequences included. A solid edge draws in along its line as it enters; a dashed one fades in. An edge's label shows and hides with it.
|
|
204
|
+
- `on` gives nodes and edges a tone from that frame on, as `scene.on` does; `"none"` clears it.
|
|
205
|
+
- The diagram follows the stage's colors, faces and theme. Leave out Mermaid's `style`, `classDef` and `%%{init}%%` theming: they fix colors that do not follow light and dark. Use tones instead. Links and `click` actions do nothing in the stage.
|
|
206
|
+
- The diagram fits its box's width and draws again when that width changes. A diagram that runs across and does not fit a narrow box is drawn running down instead of shrunk, so a `flowchart LR` may show top to bottom in the document column. Your frame changes carry over to the new drawing, but an element you hold from `d.element` does not: look parts up again inside a frame handler.
|
|
207
|
+
- Any Mermaid diagram type draws. Highlights and tones color nodes and edges. Flowchart and sequence diagrams name their parts as above. State, class and ER diagrams name nodes by their ids too, but make up their own edge ids, such as `edge0`; read them from `d.ids`.
|
|
208
|
+
- Text Mermaid cannot parse shows its error under the stage.
|
|
209
|
+
|
|
210
|
+
## Going further: the toolkit
|
|
211
|
+
|
|
212
|
+
When the scene cannot express a layout, drop to the toolkit. It is the same drawing underneath, so it mixes with a scene: pass the card returned by `scene.actor`, `scene.store`, or `scene.table` directly to `arrow`, or use its public `id` or `element` fields where another API needs one.
|
|
213
|
+
|
|
214
|
+
- Lay out by hand with the style kit's layout classes (below): `.wb-row`, `.wb-stack`, `.wb-grid`, `.wb-layers`, `.wb-lane`, with `.wb-node` or `.wb-card` boxes.
|
|
215
|
+
- `whiteboard.arrow(from, to, { label, tone, dashed, arrowhead, bend, enter, exit, only, highlight })` draws an endpoint-bound connector that follows both ends. It is directed by default; pass `arrowhead: false` for a headless connector without changing its route, label, styling, lifecycle, or use with `travel`. It does not draw while either endpoint is detached from the document, and resumes when both endpoints reconnect. A default straight arrow reduces its endpoint clearance when necessary to preserve a directed segment in a narrow positive gap; overlapping endpoints still need more authored space. Labels avoid visible form controls and host-overlay slots as well as endpoint cards, other edge labels, and the swept bounds of packets travelling along their route. If both ends share an ancestor with clipped or scrollable overflow, the edge and its label are clipped to that viewport too. `bend` is a CSS-pixel distance: positive bows left relative to the arrow's direction and negative bows right. Reciprocal arrows need the same nonzero sign to bow onto opposite sides (for example, `bend: 32` on both); opposite signs put them on the same path. Use it only for a return path the scene cannot route.
|
|
216
|
+
- `whiteboard.token({ label, tone, visible, enter, exit, only })` makes a dot or pill; labelled tokens automatically choose a readable foreground for their resolved tone color in each theme. Tokens start hidden for `travel`; pass `visible: true` for a static snapshot at its authored position. Lifecycle fields work like they do on scene parts. The token already centers itself with CSS `translate`, so position it with `left` and `top` and do not add a centering `transform` or `translate`. For example:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const packet = whiteboard.token({
|
|
220
|
+
label: "PACK",
|
|
221
|
+
tone: "accent",
|
|
222
|
+
visible: true,
|
|
223
|
+
only: ["upload"],
|
|
224
|
+
});
|
|
225
|
+
lane.append(packet); // lane has position: relative
|
|
226
|
+
Object.assign(packet.style, { left: "45%", top: "50%" });
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`whiteboard.travel(token, arrow, frame, { duration, ease, position, stay })` moves a token along an arrow. Its path coordinates are stage-relative, so leave an SDK token in its default stage overlay for travel; do not reparent the positioned snapshot from the preceding example and then travel it. You can make that SDK token square by changing its shape styles without changing its positioning. For a caller-owned travelling element, use an explicit document origin such as `position: absolute; left: 0; top: 0`, with any deliberate centering in CSS; travel preserves that authored origin and existing transforms. Static lane snapshots and travelling packets are separate layout choices, and `stay` controls arrival retention rather than frame visibility—use `enter`, `exit`, or `only` for lifecycle scope.
|
|
230
|
+
|
|
231
|
+
Trips append to the frame by default. Pass `position: 0` to start parallel trips together, or a millisecond position to sequence a reply after its request. Equivalent wide/narrow layout trips should use the same positions, so a hidden layout does not add a second wait. `position` uses the same syntax as `whiteboard.timeline` below.
|
|
232
|
+
|
|
233
|
+
The default token remains hidden until travel starts. Travel progress stays bound to the live arrow geometry, so an active or retained token follows its edge when responsive layout or code-peek sizing moves the endpoints. You may reuse a token for later trips without reparenting it; the trip active at the current timeline position owns its placement, including after jumps, backward seeks, and resizes.
|
|
234
|
+
|
|
235
|
+
- `whiteboard.timeline(frame)` is that frame's part of one anime.js timeline: `.add(targets, params, position)`, `.set(...)`, `.call(...)`. A tween starts from where the frames before it left the element, so write only where it goes: `{ translateX: 120 }`. A numeric `position` counts milliseconds from the frame's start; `"<"` starts at the previous call's end, while `"<<"` starts at its beginning.
|
|
236
|
+
- `data-enter` / `data-exit` / `data-only` / `data-highlight` work on any element you write, arrows included.
|
|
237
|
+
|
|
238
|
+
## Raw HTML
|
|
239
|
+
|
|
240
|
+
For custom layouts, charts, shapes, or interactions, write HTML, SVG and CSS. Build them from the style kit first, so a hand-made stage reads like the rest of the whiteboard, and drive them with the frame API after it.
|
|
241
|
+
|
|
242
|
+
### The style kit
|
|
243
|
+
|
|
244
|
+
Every stage loads these classes. They use the whiteboard's faces, colors and spacing in light and dark, so prefer them to your own CSS; add a `<style>` only for what the kit does not cover, and build it from the variables under "Theme and motion".
|
|
245
|
+
|
|
246
|
+
| Class | Use it for |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `.wb-row`, `.wb-stack` | A centered row or column of parts, with the scene's spacing. |
|
|
249
|
+
| `.wb-grid` | A grid of equal columns; set `style="--wb-columns: 4"` (default 3). |
|
|
250
|
+
| `.wb-layers` | Children stacked in one place, for states that replace each other. |
|
|
251
|
+
| `.wb-lane` | A `position: relative` track with a dashed center line, for a static `token` snapshot. |
|
|
252
|
+
| `.wb-card` | A box with a title and note: put `.wb-card-title` and `.wb-card-note` inside. The scene's cards are these. |
|
|
253
|
+
| `.wb-node` | A centered box for a hand-made layout; a `<small>` inside is its second line. |
|
|
254
|
+
| `.wb-well` | An inset area that groups parts, such as a process or a machine boundary. |
|
|
255
|
+
| `.wb-rule` | A divider (`<hr class="wb-rule">`). |
|
|
256
|
+
| `.wb-badge` | A pill for a status or version, e.g. `etag e1`. |
|
|
257
|
+
| `.wb-chip` | A chip in a list, such as a queued job or a cached key; `.wb-items` lays a list of them out. |
|
|
258
|
+
| `.wb-table` | A compact table of rows, such as a schema or a result set. |
|
|
259
|
+
| `.wb-title` | A heading inside the stage. |
|
|
260
|
+
| `.wb-caps` | An uppercase micro label, like the step strip's. |
|
|
261
|
+
| `.wb-prose` | A sentence of explanation, in the board's reading face. |
|
|
262
|
+
| `.wb-mono`, `.wb-code` | Ids, paths and values in the mono face; `.wb-code` is inline code on a well. |
|
|
263
|
+
| `.wb-muted`, `.wb-faint` | Text that recedes. |
|
|
264
|
+
| `.wb-shape`, `.wb-shape-text` | An SVG shape drawn like a card, and the text inside it. |
|
|
265
|
+
|
|
266
|
+
`data-tone="accent|added|removed|modified|muted"` colors any card, node, chip, badge, shape or table row, like the scene's tones. `data-highlight="frameKey …"` rings a card, node or shape during those frames.
|
|
267
|
+
|
|
268
|
+
```html
|
|
269
|
+
<div class="wb-row">
|
|
270
|
+
<div class="wb-node" data-highlight="read">Reader<small>GET /v1</small></div>
|
|
271
|
+
<div class="wb-well wb-stack">
|
|
272
|
+
<span class="wb-caps">Cache</span>
|
|
273
|
+
<div class="wb-items">
|
|
274
|
+
<span class="wb-chip" data-tone="added" data-enter="fill">user:42</span>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### The frame API
|
|
281
|
+
|
|
282
|
+
- `whiteboard.on(key, handler)` runs when a frame shows (`"*"` on every frame), including a same-frame refresh when the reduced-motion preference changes. Shared `"*"` handlers run before frame-specific handlers, so they can render content before a frame handler animates it. Connection labels hide immediately when their frame ends, while the connecting stroke can fade. The handler receives `{ frame, previous, direction, instant }`; `frame` and `previous` are frame objects, so use `frame.key` when selecting keyed state. Return the animations it starts, or a finite promise for a simulation cycle, so playback waits for the represented work. Cancel earlier work when another frame shows. Use `instant` and the live `whiteboard.reducedMotion` value to draw a consistent skipped-motion pose; normal playback still needs to advance the model.
|
|
283
|
+
- `whiteboard.animate(target, keyframes, options)` is `element.animate` that holds its end state and honors the current frame jump. After that frame settles, later button or slider callbacks animate normally. The current reduced-motion preference always skips motion, including animations started by later interactions.
|
|
284
|
+
- `whiteboard.anime` is anime.js v4 (also the global `anime`): easings, stagger, utils.
|
|
285
|
+
|
|
286
|
+
Prefer `link` / `send` (or `arrow` / `travel`) for ordinary message flows: they handle endpoint geometry, labels, and navigation. Custom connectors and motion are also supported; keep them readable, responsive, and coherent when the reader jumps frames or requests reduced motion.
|
|
287
|
+
|
|
288
|
+
## The rest of the `whiteboard` shim
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
code(name: string): HTMLElement; // a placeholder the whiteboard fills with that code peek
|
|
292
|
+
block(id: string): HTMLElement; // a placeholder for another component of this whiteboard
|
|
293
|
+
setSequence(keys: string[]): void; // select the ordered path shown by host controls
|
|
294
|
+
goto(key: string): void; next(): void; prev(): void;
|
|
295
|
+
reveal(name: string): void; // open the named code beside the whiteboard
|
|
296
|
+
readonly frames: readonly { key: string; title: string; caption?: string; index: number }[];
|
|
297
|
+
readonly frame: { key: string; title: string; caption?: string; index: number };
|
|
298
|
+
readonly theme; readonly colorScheme: "light" | "dark"; readonly reducedMotion;
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`whiteboard.frame` is the current frame object, not its key. For keyed state, read `whiteboard.frame.key` or use the handler argument: `whiteboard.on("*", ({ frame }) => render(frame.key))`.
|
|
302
|
+
|
|
303
|
+
For alternate paths, call `whiteboard.setSequence(["start", "branch-a", "done"])`. Every key must be a unique frame declared in the component. Repeating the same sequence is a no-op, so it is safe to call from a render path. The selected order controls the host step labels, titles, count, Previous/Next, keyboard navigation, Play/Replay, and `whiteboard.next()` / `prev()`; changing it stops current playback and clears its hold timer. Changing the sequence preserves the current frame when it remains in the new path; otherwise it lands instantly on the new path's first frame. `whiteboard.frames` contains the active ordered path and its `index` values, and `whiteboard.frame.index` is the current active-path position; keys remain the stable identity. `goto(key)` accepts only a frame in the active sequence and is a direct jump, while adjacent forward steps still play the destination frame's motion even when declaration indexes move backward. Call `setSequence` before `goto` when a control switches branches:
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
const delivered = ["push", "gossip", "replicate", "serve"];
|
|
307
|
+
const dropped = ["push", "gossip-dropped", "catch-up", "serve-recovered"];
|
|
308
|
+
|
|
309
|
+
function selectPath(drop) {
|
|
310
|
+
const path = drop ? dropped : delivered;
|
|
311
|
+
whiteboard.setSequence(path);
|
|
312
|
+
whiteboard.goto(path[1]);
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- `scene.peek(name, ...)`, `whiteboard.code(name)` and `<div data-wb-code="name">` all draw the real code peek over a placeholder. Give `scene.peek` a cell, and span columns (the cell's `colSpan`) when the code needs width — a one-column peek clips long lines; a raw `data-wb-code` div takes a CSS width instead. Its height follows the code, up to about 18 lines. A peek is always on top of your drawing, so do not draw or travel over it.
|
|
317
|
+
- A code peek is about 40px of header plus 18px per line, up to about 400px. Rows of cards take about 90px each. With the default `height: "auto"`, the stage grows to fit them. If you intentionally use a numeric fixed height, leave enough room because overflow clips at that height.
|
|
318
|
+
|
|
319
|
+
## Text inside shapes
|
|
320
|
+
|
|
321
|
+
Scene cards supply their own padding. For custom HTML or SVG, keep the full rendered text bounds at least 12 CSS pixels inside the container boundary at the displayed size. Check every text-box corner against a curved boundary, not only the shape’s rectangular bounding box. Wrap or shorten the label, or enlarge the shape, while keeping the font readable. Verify this inset in each frame and supported viewport.
|
|
322
|
+
|
|
323
|
+
## Theme and motion
|
|
324
|
+
|
|
325
|
+
Custom CSS should use the whiteboard's variables, never literal colors, sizes or font names, so the stage matches light and dark and the rest of the board:
|
|
326
|
+
|
|
327
|
+
- Colors: `var(--wb-ink)`, `--wb-ink-muted`, `--wb-ink-faint`, `--wb-accent`, `--wb-accent-soft`, `--wb-on-accent`, `--wb-surface`, `--wb-surface-raised`, `--wb-well`, `--wb-rule`, `--wb-rule-soft`, `--wb-added`, `--wb-removed`, `--wb-modified`, `--wb-added-bg`, `--wb-removed-bg`, `--wb-modified-bg`.
|
|
328
|
+
- Faces, by role: `--wb-font-sans` for labels and controls, `--wb-font-mono` for ids, paths and values, `--wb-font-serif` for prose, `--wb-font-code` for source text. The stage loads the whiteboard's own faces, so name no font yourself.
|
|
329
|
+
- Spacing: `--wb-space-2` to `--wb-space-20` in 2px steps, then `24`, `28`, `32`, `36`, `40`, `48`, `56`, `72`, `96`, `120`, `160`.
|
|
330
|
+
- Type: sizes `--wb-text-micro` (10px), `small`, `body` (12px), `ui`, `title`, `reading`, `heading`, `display`; line heights `--wb-leading-tight`, `normal`, `relaxed`, `prose`; weights `--wb-weight-regular`, `medium`, `semibold`, `bold`; `--wb-tracking-caps` and `--wb-tracking-chrome` for uppercase labels.
|
|
331
|
+
- Radii: `--wb-radius-small`, `control`, `surface`, `pill`, `round`.
|
|
332
|
+
- Motion: `--wb-motion-fast`, `medium`, `slow` and the easings `--wb-motion-ease`, `enter`, `settle`. `--wb-duration` (450ms) is the default fade.
|
|
333
|
+
|
|
334
|
+
`<html>` carries `data-wb-theme="light|dark"` and the matching `color-scheme`, and `whiteboard.colorScheme` reads it, for the rare drawing that needs a different value per theme.
|
|
335
|
+
|
|
336
|
+
`whiteboard.reducedMotion` reads the stage's current `prefers-reduced-motion` setting. Use it when custom rendering has motion that `whiteboard.animate` and the scene helpers do not manage.
|
|
337
|
+
|
|
338
|
+
## Checklist
|
|
339
|
+
|
|
340
|
+
- Each frame shows one idea; its title says what changed. 3 to 8 frames is usual.
|
|
341
|
+
- Omit `height` unless the animation intentionally needs a fixed viewport; verify any numeric height in every frame and narrow layout.
|
|
342
|
+
- Every frame reads correctly when reached by a jump, not only by playing.
|
|
343
|
+
- Build as "Choose how to build it" says: a scene by default, Mermaid for a larger or branching diagram, and the toolkit or raw HTML for anything else.
|
|
344
|
+
- In raw HTML, build from the style kit and the `--wb-*` variables; write no literal color, size or font name.
|
|
345
|
+
- Script errors show under the stage. After writing, read the whiteboard back and fix any.
|
|
@@ -20,7 +20,7 @@ For each board, follow these first five steps in order, with no tool calls beyon
|
|
|
20
20
|
- create the whiteboard with a `target` naming the checkout by its `repositoryPath`: `{kind:"commits", head, base?}` for commits, `{kind:"pullRequest", pullRequest}` for a PR by URL or number, or `{kind:"project"}` for a plan with no change yet. if they name none, use a `worktree` target with no `base`: it reviews the checkout, uncommitted work included, against the default branch. for uncommitted work only, pass `base: "HEAD"`. untracked files are left out: run `git add -N <file>` on new files you want reviewed
|
|
21
21
|
- for a change board only, trigger a subagent with this exact instruction: "Call `session_get_instructions({topic:\"file-lenses\"})` and follow it for whiteboard <sessionId>, whose change is `<base>..<head>` in `<repositoryPath>`."
|
|
22
22
|
- call `session_activity_begin` and pass its `activityId` on every `session_edit`
|
|
23
|
-
- for a project-target plan or overview, read the relevant source or the stack's combined diff; no diff is expected on the project target itself. For a change board, read the diff with git (or jj) in `repositoryPath`, at the commits `session_create` returned: `git diff <base> <head>`, or for a worktree target `git diff <base>` plus `git status` for untracked files. if it shows no changes, the target is wrong: fix it with `session_set_target` before writing
|
|
23
|
+
- for a project-target plan or overview, read the relevant source or the stack's combined diff; no diff is expected on the project target itself. For a change board, read the diff with git (or jj) in `repositoryPath`, at the commits `session_create` returned: `git diff <base> <head>`, or for a worktree target `git diff <base>` plus `git status` for untracked files. if it shows no changes, the target is wrong: fix it with `session_set_target` before writing. for a PR, retarget with `{kind:"pullRequest", pullRequest}`, never its branch names: those pin the branch tips, not the PR's merge base, and widen the diff
|
|
24
24
|
- immediately after reading the diff (and the PR description) - put down a first pass at the what/why section.
|
|
25
25
|
|
|
26
26
|
- whiteboard structure - each of these should be written as a top-level `section`, in this order:
|
|
@@ -41,7 +41,7 @@ For each board, follow these first five steps in order, with no tool calls beyon
|
|
|
41
41
|
|
|
42
42
|
**updating existing whiteboard**
|
|
43
43
|
|
|
44
|
-
- move the whiteboard to the new commits with `session_set_target`
|
|
44
|
+
- move the whiteboard to the new commits with `session_set_target`; for a PR, pass the `pullRequest` target again so it re-pins at the PR's head and merge base
|
|
45
45
|
- read the existing whiteboard (if you haven't already,) read the diff since last whiteboard, make any necessary updates to the whiteboard.
|
|
46
46
|
|
|
47
47
|
**guidelines**
|