@dev.fast/whiteboard 0.5.1 → 0.5.2-preview.20261104.146
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-Bw3PT3Qo.js → account-alias-BlYamXAG.js} +5 -5
- package/dist/{agent-cli-kHRrwO4n.js → agent-cli-D7fF5xmm.js} +9 -9
- package/dist/agent-cli-DLibAL4u.js +2 -0
- package/dist/{agent-client-CMacGbMp.js → agent-client-BCEbSPWU.js} +4 -4
- package/dist/{authoring-tools-CiLy46kh.js → authoring-tools-CkfxTVJm.js} +12 -8
- package/dist/{background-server-eZWXoVcI.js → background-server-BraP8pJ9.js} +4 -4
- package/dist/{background-server-BLNHbksB.js → background-server-CwToVN1g.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-Bxmiqutp.js → cli-BpadBaGU.js} +1 -1
- package/dist/{cli-runner-DQPuu7kJ.js → cli-runner-BebYF50a.js} +22 -22
- package/dist/cli.js +2 -2
- package/dist/{fuzzy-match-DZjeOaPY.js → fuzzy-match-CxcsLQGx.js} +3 -3
- package/dist/{headless-host-Dl2KZ9IT.js → headless-host-FAlbekFD.js} +561 -415
- package/dist/{headless-host-B4r0s1ks.js → headless-host-cLipIufH.js} +1 -1
- package/dist/{import-YAIU6rB0.js → import-CZ1958L4.js} +234 -62
- package/dist/{local-data-C58vzLZR.js → local-data-DdFkxFK_.js} +41 -16
- package/dist/{mcp-COYWiGVn.js → mcp-Claiy9xE.js} +9 -6
- package/dist/{package-paths-Cg6fnaGp.js → package-paths-Dk7MOXxN.js} +1 -1
- package/dist/{profile--BYIjPkc.js → profile-Bsgzi9PA.js} +2 -2
- package/dist/profile-BxdzBxHW.js +2 -0
- package/dist/{remote-attach-C1prLjmj.js → remote-attach-LGTpwpUv.js} +4 -4
- package/dist/{remote-extensions-Bw029iIS.js → remote-extensions-BvV29_gk.js} +1 -1
- package/dist/{remote-extensions-MUUgbLJn.js → remote-extensions-L95HNwIH.js} +2 -2
- package/dist/{remote-language-server-z3rdHcLL.js → remote-language-server-BAznkpeu.js} +4 -4
- package/dist/{remote-uninstall-B3Slwor1.js → remote-uninstall-CLP79Cuk.js} +5 -5
- package/dist/{request-origin-B3_XI2VA.js → request-origin-D6B-zrL8.js} +27 -5
- package/dist/{review-app-launcher-BZEbDKuK.js → review-app-launcher-tTe8bTwg.js} +2 -2
- package/dist/{review-home-paths-LdGS4SpR.js → review-home-paths-BmZX2qiz.js} +1 -1
- package/dist/{review-telemetry-x8u-9EZb.js → review-telemetry-DACDMfqq.js} +4 -4
- package/dist/server/desktop-host.js +26 -38
- package/dist/{server-discovery-WmuFl2S3.js → server-discovery-CV5qf1J8.js} +2 -2
- package/dist/sharing/index.d.ts +137 -14
- package/dist/sharing/index.js +1 -1
- package/dist/{src-B2b8cCoI.js → src-BQDlJXkG.js} +44 -5
- package/dist/{src-DLWol7GZ.js → src-CrrW7lcY.js} +16 -1
- package/dist/{stored-document-migration-iwIQ4l5G.js → stored-document-migration-D1wK4yID.js} +3 -3
- package/dist/{thread-state-B7L1mpN_.js → thread-state-WTqSborE.js} +1 -1
- package/dist/{tool-failure-RLR8m9hq.js → tool-failure-YOFGHJCZ.js} +319 -55
- package/instructions/animation.md +69 -5
- package/instructions/animations/before-after-structure/README.md +27 -0
- package/instructions/animations/before-after-structure/animation.html +89 -0
- package/instructions/animations/before-after-structure/block.json +10 -0
- package/instructions/animations/before-after-structure/frames.json +313 -0
- package/instructions/animations/cache-fill-evict/README.md +24 -0
- package/instructions/animations/cache-fill-evict/animation.html +81 -0
- package/instructions/animations/cache-fill-evict/block.json +13 -0
- package/instructions/animations/cache-fill-evict/frames.json +79 -0
- package/instructions/animations/cache-fill-evict/scenarios.json +17 -0
- package/instructions/animations/log-replay/README.md +25 -0
- package/instructions/animations/log-replay/animation.html +86 -0
- package/instructions/animations/log-replay/block.json +10 -0
- package/instructions/animations/log-replay/frames.json +143 -0
- package/instructions/animations/log-replay/scenarios.json +18 -0
- package/instructions/animations/queue-workers/README.md +23 -0
- package/instructions/animations/queue-workers/animation.html +107 -0
- package/instructions/animations/queue-workers/block.json +13 -0
- package/instructions/animations/queue-workers/frames.json +119 -0
- package/instructions/animations/queue-workers/scenarios.json +18 -0
- package/instructions/animations/race-two-lanes/README.md +22 -0
- package/instructions/animations/race-two-lanes/animation.html +97 -0
- package/instructions/animations/race-two-lanes/block.json +1 -0
- package/instructions/animations/race-two-lanes/frames.json +234 -0
- package/instructions/animations/race-two-lanes/scenarios.json +19 -0
- package/instructions/animations/request-across-layers/README.md +24 -0
- package/instructions/animations/request-across-layers/animation.html +85 -0
- package/instructions/animations/request-across-layers/block.json +17 -0
- package/instructions/animations/request-across-layers/frames.json +58 -0
- package/instructions/animations/request-across-layers/scenarios.json +12 -0
- package/instructions/animations/request-timing/README.md +22 -0
- package/instructions/animations/request-timing/animation.html +120 -0
- package/instructions/animations/request-timing/block.json +21 -0
- package/instructions/animations/request-timing/markers.json +32 -0
- package/instructions/animations/request-timing/scenarios.json +4 -0
- package/instructions/animations/retry-backoff/README.md +22 -0
- package/instructions/animations/retry-backoff/animation.html +122 -0
- package/instructions/animations/retry-backoff/block.json +10 -0
- package/instructions/animations/retry-backoff/frames.json +65 -0
- package/instructions/animations/retry-backoff/scenarios.json +12 -0
- package/instructions/animations/state-machine/README.md +24 -0
- package/instructions/animations/state-machine/animation.html +95 -0
- package/instructions/animations/state-machine/block.json +9 -0
- package/instructions/animations/state-machine/frames.json +79 -0
- package/instructions/animations/state-machine/scenarios.json +12 -0
- package/instructions/animations/video-platform-architecture/README.md +24 -0
- package/instructions/animations/video-platform-architecture/animation.html +76 -0
- package/instructions/animations/video-platform-architecture/block.json +17 -0
- package/instructions/animations/video-platform-architecture/frames.json +97 -0
- package/instructions/animations/video-platform-architecture/scenarios.json +28 -0
- package/instructions/whiteboard.d.ts +168 -14
- package/package.json +4 -4
- package/src/animation-stage-kit.ts +150 -10
- package/src/cli-runner.ts +4 -1
- package/src/fixtures/blocks/clip.json +19 -0
- package/src/review-api/README.md +1 -0
- package/src/review-api/agent-cli.ts +3 -2
- package/src/review-api/animation-drafts.ts +151 -47
- package/src/review-api/animation-references.ts +92 -0
- package/src/review-api/animation-render.ts +106 -0
- package/src/review-api/authoring-tools.ts +10 -3
- package/src/review-api/document-text.ts +27 -12
- package/src/review-api/http.ts +52 -6
- package/src/review-api/instructions.ts +1 -1
- package/src/review-api/mcp.ts +10 -5
- package/src/review-api/public-tools.ts +71 -2
- package/src/review-api/store.ts +89 -12
- package/src/server/desktop-server.ts +17 -5
- package/src/server/headless-host.ts +4 -0
- package/src/server/review-gateway.ts +1 -0
- package/src/server/review-server-core.ts +71 -12
- package/src/sharing/client.ts +129 -32
- package/src/sharing/export.ts +42 -5
- package/src/sharing/file.ts +8 -0
- package/src/sharing/host.ts +202 -73
- package/src/sharing/import.ts +125 -33
- package/src/ui-telemetry-events.ts +25 -1
- package/dist/agent-cli-B8KBHuh_.js +0 -2
- package/dist/profile-BjcXi-oH.js +0 -2
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"key": "draft",
|
|
4
|
+
"title": "A new order starts as a draft",
|
|
5
|
+
"label": "DRAFT",
|
|
6
|
+
"caption": "Only a draft can be edited. Every later state is reached through one named transition.",
|
|
7
|
+
"data": { "state": "draft" }
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"key": "submitted",
|
|
11
|
+
"title": "Submit queues it for payment",
|
|
12
|
+
"label": "SUBMIT",
|
|
13
|
+
"caption": "submit moves draft to queued; the line items are now fixed.",
|
|
14
|
+
"data": {
|
|
15
|
+
"state": "queued",
|
|
16
|
+
"transition": { "event": "submit", "from": "draft", "to": "queued" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "paid",
|
|
21
|
+
"title": "The payment webhook marks it paid",
|
|
22
|
+
"label": "PAY",
|
|
23
|
+
"caption": "pay is legal only from queued, so a duplicate webhook cannot charge twice.",
|
|
24
|
+
"data": {
|
|
25
|
+
"state": "paid",
|
|
26
|
+
"transition": { "event": "pay", "from": "queued", "to": "paid" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"key": "shipped",
|
|
31
|
+
"title": "Fulfilment ships it",
|
|
32
|
+
"label": "SHIP",
|
|
33
|
+
"caption": "shipped is final: no transition leaves it.",
|
|
34
|
+
"data": {
|
|
35
|
+
"state": "shipped",
|
|
36
|
+
"transition": { "event": "ship", "from": "paid", "to": "shipped" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"key": "cancel-refused",
|
|
41
|
+
"title": "Cancelling a shipped order is refused",
|
|
42
|
+
"label": "CANCEL REFUSED",
|
|
43
|
+
"caption": "The table has no cancel from shipped, so the API answers 409 and the status stays shipped.",
|
|
44
|
+
"data": {
|
|
45
|
+
"state": "shipped",
|
|
46
|
+
"transition": {
|
|
47
|
+
"event": "cancel",
|
|
48
|
+
"from": "shipped",
|
|
49
|
+
"to": "cancelled",
|
|
50
|
+
"refused": "already shipped"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"key": "cancelled",
|
|
56
|
+
"title": "The customer cancels before paying",
|
|
57
|
+
"label": "CANCEL",
|
|
58
|
+
"caption": "cancel is legal from queued and from paid; from paid it also starts a refund.",
|
|
59
|
+
"data": {
|
|
60
|
+
"state": "cancelled",
|
|
61
|
+
"transition": { "event": "cancel", "from": "queued", "to": "cancelled" }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"key": "pay-refused",
|
|
66
|
+
"title": "A late payment is refused",
|
|
67
|
+
"label": "PAY REFUSED",
|
|
68
|
+
"caption": "A webhook that arrives after the cancel tries pay from cancelled. It is refused and the order stays cancelled.",
|
|
69
|
+
"data": {
|
|
70
|
+
"state": "cancelled",
|
|
71
|
+
"transition": {
|
|
72
|
+
"event": "pay",
|
|
73
|
+
"from": "cancelled",
|
|
74
|
+
"to": "paid",
|
|
75
|
+
"refused": "order cancelled"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# How a video gets from upload to playback
|
|
2
|
+
|
|
3
|
+
An architecture scene: the client, the edge, our services and our storage as four groups, with an upload walking down through them and a playback walking back out through the CDN.
|
|
4
|
+
|
|
5
|
+
## Use when
|
|
6
|
+
|
|
7
|
+
A change moves work between layers of a system: a new service, a queue between two steps, a cache in front of storage, a third party taking over a step. The reviewer needs to see which layer owns each step and where a request leaves our code.
|
|
8
|
+
|
|
9
|
+
## Shows
|
|
10
|
+
|
|
11
|
+
- Upload: the gateway routes to the upload service, the original lands in blob storage, a transcode job waits in a queue, a worker renders renditions and the catalog marks the video ready.
|
|
12
|
+
- Playback: the catalog resolves a manifest, the app fetches segments from the CDN, and the first fetch in a region misses to blob storage; later ones never touch our services.
|
|
13
|
+
- Each layer is a `group`; each card wears its kind as a `form`; the storage cards share `teal`; the queue is `violet`; the CDN, which we do not run, is dashed. A `legend` decodes them.
|
|
14
|
+
|
|
15
|
+
## Technique
|
|
16
|
+
|
|
17
|
+
A scene of six half-step columns, every card two wide so the two edge cards sit centred under the one client, and four rows, one row per layer, placed so that every hop is between neighbouring cells: the app over the gateway, the services under the gateway's row, each store under the service that uses it. `s.group` draws the four layers behind their cells: the client in `accent`, the edge plain, services plain, storage in `teal`. Cards take `form`: `window` for the app, `bars` for our services, `drum` with `outline: "dashed"` for the CDN, `cylinder` for the stores and `stack` for the queue. One `send` per frame carries the request or the data between layers; `on` changes notes and tones as a layer takes over; `highlight` rings the card doing the work. `s.legend` lists the six marks once, under the scene.
|
|
18
|
+
|
|
19
|
+
## Adapt
|
|
20
|
+
|
|
21
|
+
- Rename the layers and cards to your system; keep three card columns (six half-step ones when a row must centre under another) so the groups stay one row each, and give each layer its own row. Place a card under the one that calls it: an edge between neighbouring cells reads, one that crosses a card bows. Twelve nodes in four columns tangle; nine in three read.
|
|
22
|
+
- Put each real step's range in `code` and name it on the frames that show it.
|
|
23
|
+
- A layer with one card still deserves its group when the boundary is the point.
|
|
24
|
+
- Keep one `send` per frame; a step that fans out is two frames.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// Six half-step columns, every card two wide, so a row of two can sit
|
|
3
|
+
// centred under a row of one.
|
|
4
|
+
const s = whiteboard.scene({ columns: 6 });
|
|
5
|
+
|
|
6
|
+
// Four layers, one row each, each a group behind its cells. Every hop in
|
|
7
|
+
// the story is between neighbouring cells, so no edge crosses a card.
|
|
8
|
+
s.group({ id: "clients", at: [3, 1, 1, 2], title: "Client", color: "accent" });
|
|
9
|
+
s.group({ id: "edge", at: [2, 2, 1, 4], title: "Edge" });
|
|
10
|
+
s.group({ id: "services", at: [1, 3, 1, 6], title: "Our services" });
|
|
11
|
+
s.group({ id: "storage", at: [1, 4, 1, 6], title: "Storage", color: "teal" });
|
|
12
|
+
|
|
13
|
+
// Each card wears its kind: a window for the app, bars for our services,
|
|
14
|
+
// a drum for the CDN's cache, dashed because we do not run it, cylinders
|
|
15
|
+
// for the stores and a stack for the queue.
|
|
16
|
+
const app = s.actor({ id: "app", at: [3, 1, 1, 2], title: "Video app", form: "window", highlight: ["post", "watch", "play"] });
|
|
17
|
+
const gateway = s.actor({ id: "gateway", at: [2, 2, 1, 2], title: "API gateway", form: "bars", highlight: ["receive", "deliver"] });
|
|
18
|
+
const cdn = s.store({ id: "cdn", at: [4, 2, 1, 2], title: "CDN", note: "edge cache · third party", form: "drum", outline: "dashed", highlight: ["segment", "edge-miss"] });
|
|
19
|
+
|
|
20
|
+
const upload = s.actor({ id: "upload", at: [1, 3, 1, 2], title: "Upload service", form: "bars", code: "upload", highlight: ["store", "enqueue"] });
|
|
21
|
+
const transcoder = s.actor({ id: "transcoder", at: [3, 3, 1, 2], title: "Transcoder", note: "worker pool", form: "bars", code: "transcode", highlight: ["transcode", "renditions"] });
|
|
22
|
+
const catalog = s.actor({ id: "catalog", at: [5, 3, 1, 2], title: "Catalog service", form: "bars", code: "watch", highlight: ["ready", "lookup", "resolve"] });
|
|
23
|
+
|
|
24
|
+
const queue = s.store({ id: "queue", at: [1, 4, 1, 2], title: "Transcode queue", form: "stack", color: "violet" });
|
|
25
|
+
const blob = s.store({ id: "blob", at: [3, 4, 1, 2], title: "Blob store", note: "originals · renditions", form: "cylinder", color: "teal" });
|
|
26
|
+
const db = s.store({ id: "db", at: [5, 4, 1, 2], title: "Catalog DB", note: "videos · renditions", form: "cylinder", color: "teal" });
|
|
27
|
+
|
|
28
|
+
s.legend([
|
|
29
|
+
{ form: "window", label: "Client" },
|
|
30
|
+
{ form: "bars", label: "Our service" },
|
|
31
|
+
{ form: "cylinder", color: "teal", label: "Store" },
|
|
32
|
+
{ form: "stack", color: "violet", label: "Queue" },
|
|
33
|
+
{ form: "drum", label: "Cache" },
|
|
34
|
+
{ outline: "dashed", label: "Third party" },
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// The catalog always reads its database; the other edges arrive with sends.
|
|
38
|
+
s.link(catalog, db);
|
|
39
|
+
|
|
40
|
+
// Upload: down through the layers, then ready.
|
|
41
|
+
s.send(app, gateway, "POST /videos", "post");
|
|
42
|
+
s.send(gateway, upload, "video.mp4", "receive");
|
|
43
|
+
s.on("receive", upload, { text: "receiving 1.2 GB" });
|
|
44
|
+
s.send(upload, blob, "original", "store");
|
|
45
|
+
s.on("store", blob, { text: "v1/original.mp4", tone: "added" });
|
|
46
|
+
s.on("enqueue", upload, { text: "202 Accepted", tone: "added" });
|
|
47
|
+
queue.item("v1", { enter: "enqueue", exit: "transcode", tone: "added" });
|
|
48
|
+
s.send(upload, queue, "transcode v1", "enqueue");
|
|
49
|
+
s.send(queue, transcoder, "job v1", "transcode");
|
|
50
|
+
s.on("transcode", transcoder, { text: "rendering 360p · 720p · 1080p", tone: "accent" });
|
|
51
|
+
s.on("transcode", upload, { tone: "none" });
|
|
52
|
+
s.send(transcoder, blob, "renditions", "renditions");
|
|
53
|
+
s.on("renditions", blob, { text: "v1/{360,720,1080}p" });
|
|
54
|
+
s.send(transcoder, catalog, "v1 ready", "ready");
|
|
55
|
+
s.on("ready", transcoder, { text: "idle", tone: "none" });
|
|
56
|
+
s.on("ready", catalog, { text: "v1: ready", tone: "added" });
|
|
57
|
+
s.on("ready", db, { text: "videos.v1 = ready", tone: "added" });
|
|
58
|
+
|
|
59
|
+
// Playback: resolved by our services once, then served from the edge.
|
|
60
|
+
s.on("watch", catalog, { tone: "none" });
|
|
61
|
+
s.on("watch", db, { tone: "none" });
|
|
62
|
+
s.send(app, gateway, "GET /watch/v1", "watch");
|
|
63
|
+
s.send(gateway, catalog, "watch v1", "lookup");
|
|
64
|
+
s.on("lookup", db, { text: "read videos.v1" });
|
|
65
|
+
s.send(catalog, gateway, "manifest", "resolve");
|
|
66
|
+
s.on("resolve", catalog, { text: "manifest → CDN URLs" });
|
|
67
|
+
s.send(gateway, app, "manifest", "deliver");
|
|
68
|
+
s.on("deliver", app, { text: "playing v1", tone: "accent" });
|
|
69
|
+
s.send(app, cdn, "GET seg-0001", "segment");
|
|
70
|
+
s.on("segment", cdn, { text: "miss", tone: "modified" });
|
|
71
|
+
s.send(cdn, blob, "fetch seg-0001", "edge-miss");
|
|
72
|
+
s.on("edge-miss", blob, { text: "v1/720p/seg-0001" });
|
|
73
|
+
cdn.item("seg-0001", { enter: "edge-miss", tone: "added" });
|
|
74
|
+
s.send(cdn, app, "seg-0001", "play");
|
|
75
|
+
s.on("play", cdn, { text: "cached at the edge", tone: "none" });
|
|
76
|
+
</script>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "How a video gets from upload to playback",
|
|
3
|
+
"code": {
|
|
4
|
+
"upload": {
|
|
5
|
+
"source": "head/services/upload/src/handler.ts#L18-L64",
|
|
6
|
+
"label": "handleUpload"
|
|
7
|
+
},
|
|
8
|
+
"transcode": {
|
|
9
|
+
"source": "head/services/transcoder/src/worker.ts#L30-L92",
|
|
10
|
+
"label": "TranscodeWorker.run"
|
|
11
|
+
},
|
|
12
|
+
"watch": {
|
|
13
|
+
"source": "head/services/catalog/src/watch.ts#L12-L58",
|
|
14
|
+
"label": "resolveWatch"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"key": "post",
|
|
4
|
+
"title": "A creator uploads a video",
|
|
5
|
+
"label": "POST",
|
|
6
|
+
"caption": "The app posts the file to the API gateway, which fronts every service.",
|
|
7
|
+
"code": ["upload"]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"key": "receive",
|
|
11
|
+
"title": "The gateway hands it to the upload service",
|
|
12
|
+
"label": "RECEIVE",
|
|
13
|
+
"caption": "Routing by path: /videos belongs to the upload service.",
|
|
14
|
+
"code": ["upload"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"key": "store",
|
|
18
|
+
"title": "The original goes to blob storage",
|
|
19
|
+
"label": "STORE",
|
|
20
|
+
"caption": "handleUpload streams the original into the blob store before anything else happens.",
|
|
21
|
+
"code": ["upload"]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "enqueue",
|
|
25
|
+
"title": "A transcode job is queued",
|
|
26
|
+
"label": "ENQUEUE",
|
|
27
|
+
"caption": "The upload service answers the creator now; the heavy work waits in the queue.",
|
|
28
|
+
"code": ["upload"]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"key": "transcode",
|
|
32
|
+
"title": "A transcoder takes the job",
|
|
33
|
+
"label": "TRANSCODE",
|
|
34
|
+
"caption": "A worker takes the job and renders the renditions clients can stream.",
|
|
35
|
+
"code": ["transcode"]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"key": "renditions",
|
|
39
|
+
"title": "Renditions go back to the blob store",
|
|
40
|
+
"label": "RENDITIONS",
|
|
41
|
+
"caption": "Each rendition is written beside the original, keyed by video and quality.",
|
|
42
|
+
"code": ["transcode"]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "ready",
|
|
46
|
+
"title": "The catalog marks the video ready",
|
|
47
|
+
"label": "READY",
|
|
48
|
+
"caption": "The transcoder tells the catalog service, which updates the catalog database; the video is now playable.",
|
|
49
|
+
"code": ["transcode"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"key": "watch",
|
|
53
|
+
"title": "A viewer opens a video",
|
|
54
|
+
"label": "WATCH",
|
|
55
|
+
"caption": "The app asks the gateway for /watch/v1.",
|
|
56
|
+
"code": ["watch"]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"key": "lookup",
|
|
60
|
+
"title": "The catalog service resolves it",
|
|
61
|
+
"label": "LOOKUP",
|
|
62
|
+
"caption": "resolveWatch reads the video's row: its renditions and where they live.",
|
|
63
|
+
"code": ["watch"]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"key": "resolve",
|
|
67
|
+
"title": "A manifest comes back",
|
|
68
|
+
"label": "RESOLVE",
|
|
69
|
+
"caption": "The manifest lists segment URLs on the CDN, not the blob store.",
|
|
70
|
+
"code": ["watch"]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"key": "deliver",
|
|
74
|
+
"title": "The gateway returns the manifest",
|
|
75
|
+
"label": "DELIVER",
|
|
76
|
+
"caption": "The app can now fetch segments without touching our services again.",
|
|
77
|
+
"code": ["watch"]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"key": "segment",
|
|
81
|
+
"title": "The app fetches a segment from the CDN",
|
|
82
|
+
"label": "SEGMENT",
|
|
83
|
+
"caption": "Segments are plain files: the edge serves them, not the catalog."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"key": "edge-miss",
|
|
87
|
+
"title": "The edge misses and fetches from blob storage",
|
|
88
|
+
"label": "EDGE MISS",
|
|
89
|
+
"caption": "The first viewer in a region pays the fetch from our blob store; the CDN keeps the segment."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"key": "play",
|
|
93
|
+
"title": "Segments stream from the edge",
|
|
94
|
+
"label": "PLAY",
|
|
95
|
+
"caption": "Later viewers near this edge are served from the CDN's cache, without touching our services."
|
|
96
|
+
}
|
|
97
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"key": "upload",
|
|
4
|
+
"label": "Upload",
|
|
5
|
+
"frames": [
|
|
6
|
+
"post",
|
|
7
|
+
"receive",
|
|
8
|
+
"store",
|
|
9
|
+
"enqueue",
|
|
10
|
+
"transcode",
|
|
11
|
+
"renditions",
|
|
12
|
+
"ready"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"key": "playback",
|
|
17
|
+
"label": "Playback",
|
|
18
|
+
"frames": [
|
|
19
|
+
"watch",
|
|
20
|
+
"lookup",
|
|
21
|
+
"resolve",
|
|
22
|
+
"deliver",
|
|
23
|
+
"segment",
|
|
24
|
+
"edge-miss",
|
|
25
|
+
"play"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Frames are states, not events: whatever a frame shows must also be right
|
|
7
7
|
* when the reader jumps to it or steps back, not only when it plays.
|
|
8
|
+
*
|
|
9
|
+
* A clip has no frames: its script builds one timeline with clip(), places
|
|
10
|
+
* the block's markers on it, and the reader plays it or jumps to a marker.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
/* oxlint-disable anti-slop/no-unknown-parameters, anti-slop/no-object-parameters -- Documents a script API; anime.js parses its own targets and params. */
|
|
@@ -12,6 +15,37 @@
|
|
|
12
15
|
/** Colors any scene part, arrow, token, or kit element (as data-tone). */
|
|
13
16
|
export type Tone = "accent" | "added" | "removed" | "modified" | "muted";
|
|
14
17
|
|
|
18
|
+
/** A card's silhouette, so a reader tells kinds apart before reading a
|
|
19
|
+
* title. Pick by what the card stands for: `cylinder` for a database,
|
|
20
|
+
* `drum` for a cache, `stack` for a queue or topic, `window` for a client
|
|
21
|
+
* (a browser, an app), `bars` for a service or worker of this codebase,
|
|
22
|
+
* `pill` for a start or end, `cut` for a branch. Any kit element takes it as
|
|
23
|
+
* data-form. */
|
|
24
|
+
export type Form =
|
|
25
|
+
| "box"
|
|
26
|
+
| "pill"
|
|
27
|
+
| "cut"
|
|
28
|
+
| "cylinder"
|
|
29
|
+
| "drum"
|
|
30
|
+
| "stack"
|
|
31
|
+
| "window"
|
|
32
|
+
| "bars";
|
|
33
|
+
|
|
34
|
+
/** A border's line. `dashed` reads as outside the system: a third-party
|
|
35
|
+
* service or API. As data-outline on a kit element. */
|
|
36
|
+
export type Outline = "solid" | "dashed" | "dotted";
|
|
37
|
+
|
|
38
|
+
/** A border's colour at rest, for grouping cards of a kind: `teal` suits
|
|
39
|
+
* stores, `violet` queues, `accent` the part under discussion. A tone from
|
|
40
|
+
* on() takes over while set. As data-color on a kit element. */
|
|
41
|
+
export type Color = "ink" | "accent" | "teal" | "violet";
|
|
42
|
+
|
|
43
|
+
/** What the change under review did to the part a card stands for. A
|
|
44
|
+
* resting mark in the change colours, its dot and a stripe down its left
|
|
45
|
+
* edge, that a frame's `tone` does not replace; a removed part's title is
|
|
46
|
+
* struck through. As data-change on a kit element. */
|
|
47
|
+
export type Change = "added" | "modified" | "removed";
|
|
48
|
+
|
|
15
49
|
/** One frame, as your scripts read it. */
|
|
16
50
|
export interface Frame {
|
|
17
51
|
key: string;
|
|
@@ -35,6 +69,41 @@ export interface FrameChange {
|
|
|
35
69
|
instant: boolean;
|
|
36
70
|
}
|
|
37
71
|
|
|
72
|
+
/** One of a clip's markers, as your scripts read it. */
|
|
73
|
+
export interface Marker {
|
|
74
|
+
key: string;
|
|
75
|
+
title: string;
|
|
76
|
+
caption?: string;
|
|
77
|
+
/** The code names the marker lists, for data-wb-open. */
|
|
78
|
+
code?: string[];
|
|
79
|
+
/** Milliseconds into the active timeline, once the scripts have run. */
|
|
80
|
+
at?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A marker the playhead reached: placed, with its place in time order. */
|
|
84
|
+
export interface ReachedMarker extends Marker {
|
|
85
|
+
at: number;
|
|
86
|
+
index: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** What a marker handler learns as a clip's playhead reaches its marker. */
|
|
90
|
+
export interface MarkerChange {
|
|
91
|
+
marker: ReachedMarker;
|
|
92
|
+
/** A jump to the marker, the first draw, or reduced motion: draw the
|
|
93
|
+
* marker's state at once. */
|
|
94
|
+
instant: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** What a paint handler learns as the block paints its first frame in. */
|
|
98
|
+
export interface PaintChange {
|
|
99
|
+
/** The first frame, already drawn in its end state. A clip has none: it
|
|
100
|
+
* paints in at time 0. */
|
|
101
|
+
frame?: Frame;
|
|
102
|
+
/** Skip motion and leave everything in its end state: the block loaded
|
|
103
|
+
* without painting in, or the reader prefers reduced motion. */
|
|
104
|
+
instant: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
38
107
|
/** Return the animations a handler starts, or a finite promise, so playback
|
|
39
108
|
* waits for them. */
|
|
40
109
|
export type FrameResult =
|
|
@@ -44,7 +113,8 @@ export type FrameResult =
|
|
|
44
113
|
| Promise<void>;
|
|
45
114
|
|
|
46
115
|
export interface Whiteboard {
|
|
47
|
-
/** The active path's frames, in order: a scenario's, or all of them.
|
|
116
|
+
/** The active path's frames, in order: a scenario's, or all of them. A
|
|
117
|
+
* frame outside the active scenario is not listed. */
|
|
48
118
|
readonly frames: readonly Frame[];
|
|
49
119
|
/** The current frame object; read `.key` for its name. */
|
|
50
120
|
readonly frame: Frame;
|
|
@@ -57,12 +127,25 @@ export interface Whiteboard {
|
|
|
57
127
|
* helpers do not manage when it is true. */
|
|
58
128
|
readonly reducedMotion: boolean;
|
|
59
129
|
|
|
130
|
+
/** Paint the first frame in yourself, in place of the default painter. Runs
|
|
131
|
+
* once, after the first frame is drawn: with motion when an agent writes
|
|
132
|
+
* the block, with `instant: true` on a cold load or under reduced motion,
|
|
133
|
+
* when it must leave everything in its end state. Painting ends when what
|
|
134
|
+
* it returns has finished. */
|
|
135
|
+
on(key: "paint", handler: (paint: PaintChange) => FrameResult): () => void;
|
|
60
136
|
/** Run when a frame shows; "*" runs on every frame, before frame-specific
|
|
61
137
|
* handlers, and again when reduced motion changes. Cancel earlier work when
|
|
62
138
|
* another frame shows. Returns an unsubscribe function. */
|
|
63
139
|
on(key: string, handler: (change: FrameChange) => FrameResult): () => void;
|
|
140
|
+
/** In a clip: run when the playhead crosses the marker playing forward, or
|
|
141
|
+
* lands on it after a jump (`instant: true`); "*" runs on every marker.
|
|
142
|
+
* What a marker sets must also be right when the reader jumps there.
|
|
143
|
+
* Returns an unsubscribe function. */
|
|
144
|
+
on(key: string, handler: (change: MarkerChange) => FrameResult): () => void;
|
|
64
145
|
/** element.animate that holds its end state, lands at the end on a jump, and
|
|
65
|
-
* skips motion under reduced motion.
|
|
146
|
+
* skips motion under reduced motion. Give every keyframe its start value, and
|
|
147
|
+
* measure positions before starting one: a started animation already
|
|
148
|
+
* reports its transform. */
|
|
66
149
|
animate(
|
|
67
150
|
target: Element | string,
|
|
68
151
|
keyframes: Keyframe[] | PropertyIndexedKeyframes,
|
|
@@ -70,8 +153,22 @@ export interface Whiteboard {
|
|
|
70
153
|
): Animation;
|
|
71
154
|
/** The frame's part of one anime.js timeline: it plays when the reader steps
|
|
72
155
|
* into the frame and lands at its end on a jump. A tween starts where the
|
|
73
|
-
* frames before it left the element, so write only where it goes.
|
|
156
|
+
* frames before it left the element, so write only where it goes. Add to it
|
|
157
|
+
* while the script first runs, not from a frame handler. "paint" is a
|
|
158
|
+
* timeline of its own that plays once, after the first frame is drawn, as
|
|
159
|
+
* the block paints in, in place of the default painter; it lands at its end
|
|
160
|
+
* otherwise. Numeric positions count from its start. */
|
|
74
161
|
timeline(key: string): FrameTimeline;
|
|
162
|
+
/** A clip's one timeline: build it while the script first runs and place
|
|
163
|
+
* every marker on it. With scenarios, call it once per scenario key, each
|
|
164
|
+
* a timeline of its own; one built without a key serves every scenario
|
|
165
|
+
* without its own. Throws in an animation. */
|
|
166
|
+
clip(scenario?: string): ClipTimeline;
|
|
167
|
+
/** A clip's markers as declared, each with `at` once placed. Empty in an
|
|
168
|
+
* animation. */
|
|
169
|
+
readonly markers: readonly Marker[];
|
|
170
|
+
/** The marker a clip's playhead last reached, if any. */
|
|
171
|
+
readonly marker: ReachedMarker | undefined;
|
|
75
172
|
/** anime.js v4, also the global `anime`: easings, stagger, utils. */
|
|
76
173
|
readonly anime: unknown;
|
|
77
174
|
|
|
@@ -104,9 +201,10 @@ export interface Whiteboard {
|
|
|
104
201
|
/** A dot, or a pill with a label, that travel() moves along arrows. */
|
|
105
202
|
token(options?: TokenOptions): HTMLElement;
|
|
106
203
|
/** Move a token along an arrow during a frame, at the arrow's live geometry.
|
|
107
|
-
* It
|
|
204
|
+
* It waits at the head until the next frame unless `stay` is false. Trips append
|
|
108
205
|
* to the frame; `position: 0` starts trips together, a number of
|
|
109
|
-
* milliseconds sequences one after another.
|
|
206
|
+
* milliseconds sequences one after another. Declare trips while the script
|
|
207
|
+
* first runs, not from a frame handler. */
|
|
110
208
|
travel(
|
|
111
209
|
token: HTMLElement | string,
|
|
112
210
|
arrow: SVGGElement,
|
|
@@ -133,6 +231,25 @@ export interface FrameTimeline {
|
|
|
133
231
|
call(callback: () => void, position?: number | string): FrameTimeline;
|
|
134
232
|
}
|
|
135
233
|
|
|
234
|
+
/** A clip's timeline. Numeric positions count milliseconds from its start;
|
|
235
|
+
* "<" starts at the previous call's end and "<<" at its beginning. */
|
|
236
|
+
export interface ClipTimeline {
|
|
237
|
+
add(
|
|
238
|
+
targets: unknown,
|
|
239
|
+
params: object,
|
|
240
|
+
position?: number | string,
|
|
241
|
+
): ClipTimeline;
|
|
242
|
+
set(
|
|
243
|
+
targets: unknown,
|
|
244
|
+
params: object,
|
|
245
|
+
position?: number | string,
|
|
246
|
+
): ClipTimeline;
|
|
247
|
+
call(callback: () => void, position?: number | string): ClipTimeline;
|
|
248
|
+
/** Place a declared marker at the timeline's end so far, or at an anime.js
|
|
249
|
+
* position. Every marker must be placed once on each timeline. */
|
|
250
|
+
marker(key: string, position?: number | string): ClipTimeline;
|
|
251
|
+
}
|
|
252
|
+
|
|
136
253
|
export interface TravelOptions {
|
|
137
254
|
duration?: number;
|
|
138
255
|
ease?: string;
|
|
@@ -140,7 +257,9 @@ export interface TravelOptions {
|
|
|
140
257
|
stay?: boolean;
|
|
141
258
|
}
|
|
142
259
|
|
|
143
|
-
/** A scene card, its id, or an element.
|
|
260
|
+
/** A scene card, its id, or an element. In `whiteboard.arrow` and `travel`
|
|
261
|
+
* a string is a CSS selector (`"#api"`); in a scene's methods it is a card
|
|
262
|
+
* id. */
|
|
144
263
|
export type CardRef = SceneCard | string | Element;
|
|
145
264
|
|
|
146
265
|
/** 1-based column and row, with optional row and column spans: [1, 2, 1, 4] is
|
|
@@ -154,12 +273,11 @@ export type Cell = [
|
|
|
154
273
|
|
|
155
274
|
export interface SceneOptions {
|
|
156
275
|
/** Declared columns; default 3. Three cards with short labels fit a row at
|
|
157
|
-
* the document's width; past four, use a second row.
|
|
276
|
+
* the document's width; past four, use a second row. The scene keeps its
|
|
277
|
+
* columns at any width: a narrower block scrolls it sideways. */
|
|
158
278
|
columns?: number;
|
|
159
279
|
/** Where the scene goes; defaults to the animation's body. */
|
|
160
280
|
container?: string | HTMLElement;
|
|
161
|
-
/** Fold into fewer columns when narrow, then one. Default true. */
|
|
162
|
-
responsive?: boolean;
|
|
163
281
|
}
|
|
164
282
|
|
|
165
283
|
export interface Scene {
|
|
@@ -177,14 +295,25 @@ export interface Scene {
|
|
|
177
295
|
): HTMLElement;
|
|
178
296
|
/** A pill above the scene, e.g. a version; on() changes its text. */
|
|
179
297
|
badge(options: { text?: string; tone?: Tone }): HTMLElement;
|
|
298
|
+
/** A washed box behind the cards in its cells, titled in its corner: a
|
|
299
|
+
* boundary such as a service, a process or a layer. Declare it before or
|
|
300
|
+
* after its cards. */
|
|
301
|
+
group(options: SceneGroupOptions): HTMLElement;
|
|
302
|
+
/** A row under the scene saying what the cards' marks mean: one glyph,
|
|
303
|
+
* drawn like a card with the entry's marks, and a label each. Name only
|
|
304
|
+
* what the reader must decode. */
|
|
305
|
+
legend(entries: SceneLegendEntry[]): HTMLElement;
|
|
180
306
|
/** Change a card's note, or an item's or badge's text, or a tone, from this
|
|
181
307
|
* frame on, until a later frame changes it. Follows the active path. */
|
|
182
308
|
on(frame: string, part: CardRef, change: SceneChange): void;
|
|
183
309
|
/** A straight edge between two cards that follows them. It bows around
|
|
184
310
|
* cards in the way; keep the cells between linked cards empty. */
|
|
185
311
|
link(from: CardRef, to: CardRef, options?: SceneLinkOptions): SVGGElement;
|
|
186
|
-
/** A
|
|
187
|
-
*
|
|
312
|
+
/** A message from one card to another in a frame, drawing the edge if there
|
|
313
|
+
* is none. While the frame shows, the message is the edge's label and the
|
|
314
|
+
* edge takes the marker colour, however the frame was reached; stepping
|
|
315
|
+
* into the frame rides a chip from the sender that settles into the label.
|
|
316
|
+
* Keep labels to about 12 characters. */
|
|
188
317
|
send(
|
|
189
318
|
from: CardRef,
|
|
190
319
|
to: CardRef,
|
|
@@ -227,12 +356,39 @@ export interface SceneCardOptions extends Lifecycle {
|
|
|
227
356
|
/** The second line; on() changes it per frame. */
|
|
228
357
|
note?: string;
|
|
229
358
|
tone?: Tone;
|
|
359
|
+
/** What the card is, by silhouette; see Form. Default box. */
|
|
360
|
+
form?: Form;
|
|
361
|
+
/** The border's line; see Outline. Default solid. */
|
|
362
|
+
outline?: Outline;
|
|
363
|
+
/** The border's colour at rest; see Color. */
|
|
364
|
+
color?: Color;
|
|
365
|
+
/** What the change under review did to this part; see Change. */
|
|
366
|
+
change?: Change;
|
|
230
367
|
/** Frames during which the card is ringed. */
|
|
231
368
|
highlight?: string[];
|
|
232
369
|
/** The code the card opens when clicked: a name in the block's code. */
|
|
233
370
|
code?: string;
|
|
234
371
|
}
|
|
235
372
|
|
|
373
|
+
export interface SceneGroupOptions extends Lifecycle {
|
|
374
|
+
id: string;
|
|
375
|
+
/** The cells the group covers; its cards sit on its wash. */
|
|
376
|
+
at: Cell;
|
|
377
|
+
/** A caps title in its top-left corner, e.g. SEARCH SERVICE. */
|
|
378
|
+
title?: string;
|
|
379
|
+
/** The wash and title colour; see Color. Default a neutral well. */
|
|
380
|
+
color?: Color;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export interface SceneLegendEntry {
|
|
384
|
+
label: string;
|
|
385
|
+
form?: Form;
|
|
386
|
+
outline?: Outline;
|
|
387
|
+
color?: Color;
|
|
388
|
+
tone?: Tone;
|
|
389
|
+
change?: Change;
|
|
390
|
+
}
|
|
391
|
+
|
|
236
392
|
export interface SceneItemOptions extends Lifecycle {
|
|
237
393
|
tone?: Tone;
|
|
238
394
|
}
|
|
@@ -254,10 +410,8 @@ export interface SceneLinkOptions extends Lifecycle {
|
|
|
254
410
|
}
|
|
255
411
|
|
|
256
412
|
export interface SceneSendOptions extends Lifecycle {
|
|
413
|
+
/** Colours the chip, and the edge and its label while the frame shows. */
|
|
257
414
|
tone?: Tone;
|
|
258
|
-
/** Keep the chip at its target instead of fading it out; scope when it shows
|
|
259
|
-
* with enter, exit or only. */
|
|
260
|
-
stay?: boolean;
|
|
261
415
|
}
|
|
262
416
|
|
|
263
417
|
export interface ArrowOptions extends Lifecycle {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev.fast/whiteboard",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2-preview.20261104.146",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
"typescript": "6.0.3",
|
|
78
78
|
"vitest": "4.1.12",
|
|
79
79
|
"@dev.fast/json": "0.0.0",
|
|
80
|
-
"@dev.fast/
|
|
81
|
-
"@dev.fast/review-share-protocol": "0.3.1",
|
|
80
|
+
"@dev.fast/review-share-protocol": "0.6.0",
|
|
82
81
|
"@dev.fast/review-protocol": "0.0.0",
|
|
83
82
|
"@dev.fast/trace-protocol": "0.5.0",
|
|
83
|
+
"@dev.fast/trace-core": "0.0.0",
|
|
84
84
|
"@dev.fast/whiteboard-document": "0.2.1"
|
|
85
85
|
},
|
|
86
86
|
"engines": {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"repo": "devdotfast/review",
|
|
92
92
|
"docs": "packages/review/instructions/authoring.md"
|
|
93
93
|
},
|
|
94
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "20f4d6dcfaa9f7a40ff77fd6e6b2467fc89e64fa",
|
|
95
95
|
"scripts": {
|
|
96
96
|
"prebuild": "pnpm run build:workspace-deps",
|
|
97
97
|
"build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",
|