@jokerized/decksmith 0.3.2 → 0.4.0
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/README.md +96 -14
- package/dist/cli.js +450 -151
- package/dist/deck-runtime.js +2 -2
- package/dist/index.js +359 -68
- package/dist/mcp.js +477 -179
- package/dist/types/deck/runtime.d.ts +127 -0
- package/dist/types/emit/camera.d.ts +14 -6
- package/dist/types/emit/composition.d.ts +13 -0
- package/dist/types/emit/kit.d.ts +33 -8
- package/dist/types/emit/svg.d.ts +27 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/tmpdir.d.ts +33 -0
- package/dist/types/types.d.ts +28 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -189,6 +189,11 @@ out/
|
|
|
189
189
|
deck.html open this: the navigable deck
|
|
190
190
|
hyperframes-player.global.js copied from the hyperframes package, so nothing needs a CDN
|
|
191
191
|
hyperframes.json
|
|
192
|
+
vendor/ the scripts the head loads, and only those: GSAP and
|
|
193
|
+
DrawSVG on every deck, MorphSVG (21,195 B) only on one
|
|
194
|
+
that reshapes, ds-morph.js only on one that morphs an
|
|
195
|
+
equation
|
|
196
|
+
katex/ katex.min.css rewritten to its woff2 faces, and those fonts
|
|
192
197
|
assets/ every figure a beat cites — generated ones included, under
|
|
193
198
|
the same name source.json records
|
|
194
199
|
audio/ only when the deck is narrated
|
|
@@ -199,9 +204,9 @@ not `file://`: the wrapper drives the composition through its iframe, and a file
|
|
|
199
204
|
iframe cannot be reached. The deck says so in the console rather than rendering blank.
|
|
200
205
|
|
|
201
206
|
Arrow keys, Space and PageUp/PageDown step; clicking the left or right third does too;
|
|
202
|
-
`Home`/`End` jump; `
|
|
203
|
-
`
|
|
204
|
-
reveal).
|
|
207
|
+
`Home`/`End` jump; `p` plays the deck by itself and pauses it again; `n` toggles presenter
|
|
208
|
+
notes; `f` is fullscreen; `m` mutes the voice and `s` hides the subtitles. Every step is
|
|
209
|
+
deep-linkable (`#3` is slide 3, `#3.2` its second reveal).
|
|
205
210
|
|
|
206
211
|
Stepping forward *plays* the reveal rather than cutting to it — the step layer sweeps the
|
|
207
212
|
composition's timelines across frames instead of seeking once. Backward steps, `Home`/`End`
|
|
@@ -563,6 +568,8 @@ const { out, files, navigable } = await buildDeck(storyboard, source, "./deck",
|
|
|
563
568
|
format: FORMATS["deck-16x9"],
|
|
564
569
|
theme: "ink",
|
|
565
570
|
assetsFrom: ".", // the directory whose assets/ holds the figures
|
|
571
|
+
onBeatError: (id, err) => {}, // the beat is not in the deck
|
|
572
|
+
onBeatWarning: (id, why) => {}, // the beat IS, minus an ornament it could not fit
|
|
566
573
|
onStep: console.log, // silent otherwise: a library that prints is one you
|
|
567
574
|
}); // cannot run inside a request handler
|
|
568
575
|
|
|
@@ -574,6 +581,11 @@ demo deck built this way is byte-identical to `decksmith build`'s. It does **not
|
|
|
574
581
|
gates, because `verify` wants a browser and a caller may have neither one nor the patience
|
|
575
582
|
for it; call `verify` yourself when you want it.
|
|
576
583
|
|
|
584
|
+
`onBeatWarning` is the only signal that a slide is finished-looking but not what was
|
|
585
|
+
planned — a `line-chart` drawn without its comparison, say. `build` prints it and the
|
|
586
|
+
server puts it in the job's warnings; a library caller that ignores it ships the same
|
|
587
|
+
silence this project keeps finding by eye.
|
|
588
|
+
|
|
577
589
|
### The surface
|
|
578
590
|
|
|
579
591
|
| Stage | Exports |
|
|
@@ -586,6 +598,7 @@ for it; call `verify` yourself when you want it.
|
|
|
586
598
|
| verify | `verify`, `check`, `parseCheckReport` |
|
|
587
599
|
| pack | `writePack`, `readPack`, `openPack`, `planMedia`, `mediaSummary` |
|
|
588
600
|
| prefs | `loadPrefs`, `CONFIG_FILE` |
|
|
601
|
+
| process | `guardTmpdir` |
|
|
589
602
|
| contract | everything in `src/types.ts` — every schema, `Source`, `Storyboard`, `Beat`, `Format`, `FORMATS`, `Verdict` |
|
|
590
603
|
|
|
591
604
|
Two of those are worth pointing at. `Runner` is exported so you can drive planning through
|
|
@@ -597,6 +610,13 @@ body rather than a filesystem: it is pure, taking strings in and returning strin
|
|
|
597
610
|
so a server's tests draw with a fake instead of spawning Codex, and a deployment can add a
|
|
598
611
|
backend this package does not ship.
|
|
599
612
|
|
|
613
|
+
`guardTmpdir` is the odd one out: it is not part of building a deck, it is what an
|
|
614
|
+
executable that is about to `mkdtemp` calls first. It deletes a `TMPDIR` that resolves
|
|
615
|
+
inside the package root and says so on stderr, and it is a no-op in an installed package.
|
|
616
|
+
Importing this module does not run it, deliberately — an absent `TMPDIR` is inherited by
|
|
617
|
+
every child process a host spawns, which is not a decision a library import gets to make
|
|
618
|
+
for its host. Our own three executables call it; nothing else does.
|
|
619
|
+
|
|
600
620
|
Anything not listed is deliberately absent, and adding to the list is a promise we cannot
|
|
601
621
|
quietly take back. `prefsFromFlags` is the clearest example: it translates commander's flag
|
|
602
622
|
object, which is the CLI's problem and nobody else's.
|
|
@@ -717,7 +737,19 @@ right up until the first stall, and a stall is exactly when a viewer looks at th
|
|
|
717
737
|
to find out what they missed. `m` mutes (captions keep tracking, and a muted element is
|
|
718
738
|
exempt from the autoplay policy, so it doubles as the escape hatch); `s` hides subtitles.
|
|
719
739
|
If the browser refuses to autoplay, the deck says `press any key for sound` once and
|
|
720
|
-
navigation carries on regardless.
|
|
740
|
+
navigation carries on regardless. A segment that will not *play* — a missing file, a
|
|
741
|
+
dropped connection — reads `narration unavailable` instead, because telling that viewer to
|
|
742
|
+
press a key names the wrong culprit and goes on naming it every time they try. A gesture
|
|
743
|
+
retries either failure; nothing retries the unplayable one unasked.
|
|
744
|
+
|
|
745
|
+
`p` plays the deck by itself, and that is a mode rather than a change to what stepping
|
|
746
|
+
means — the ordinary case is a presenter talking over it. A narrated stop is timed by its
|
|
747
|
+
own audio and waits for it to finish. A stop that is not being *heard* — no segment, or
|
|
748
|
+
one that would not play — gets the clock instead, at the gap the author left before the
|
|
749
|
+
next stop, floored at 1.5s and capped at 8s. Whether a stop is heard is asked twice, on
|
|
750
|
+
arrival and again when `play()` settles, because until it settles a missing file and a
|
|
751
|
+
working one are indistinguishable; without the second ask, autoplay waits forever for an
|
|
752
|
+
`ended` that a source which never loaded cannot fire.
|
|
721
753
|
|
|
722
754
|
Audio is content-addressed on the text, voice, rate and pitch, so re-narrating an edited
|
|
723
755
|
deck re-speaks only the sentences that moved — and two beats saying the same sentence
|
|
@@ -874,7 +906,7 @@ The explanatory vocabulary. These came out of hand-building a real deck
|
|
|
874
906
|
| `equation-walk` | an equation explained symbol by symbol | `equationId`, `terms` (1–4) |
|
|
875
907
|
| `equation-morph` | one equation becoming the next, the shared terms carried across | `fromId`, `toId`, `terms` (1–4) |
|
|
876
908
|
| `data-table` | a results table with rows revealed in argument order | `tableId`, `highlight` |
|
|
877
|
-
| `line-chart` | a trend, with per-step deltas | `points`, `deltas?`, `readout?` |
|
|
909
|
+
| `line-chart` | a trend, with per-step deltas, optionally against a baseline | `points`, `deltas?`, `readout?`, `compare?` |
|
|
878
910
|
| `callout` | 1–3 panels of prose: definitions, contrasts, takeaways | `panels`, `note?` |
|
|
879
911
|
| `pipeline` | stages in a flow, arrowed, with an optional feedback loop | `stages` (2–6), `loop?` |
|
|
880
912
|
| `annotated-figure` | a figure cropped to the panel under discussion, with leader lines | `figureId`, `crop?`, `notes` |
|
|
@@ -887,6 +919,33 @@ The last six draw the mechanism rather than describe it, and `verify` warns when
|
|
|
887
919
|
leans on the others: a deck of headlines and bullet panels is what every other slide
|
|
888
920
|
generator already makes.
|
|
889
921
|
|
|
922
|
+
`line-chart`'s `compare` is the one parameter that changes what its archetype *is*. Given
|
|
923
|
+
`{ label, points }` the chart draws the baseline first, holds it, then reshapes the curve
|
|
924
|
+
into `points` and leaves the baseline behind as a labelled ghost — so the slide asserts a
|
|
925
|
+
change in the curve's *shape*, not two numbers. Reach for it on that tell: one quantity
|
|
926
|
+
measured under two conditions.
|
|
927
|
+
|
|
928
|
+
The two series must be over the same x values in the same order, or `storyboardSchema`
|
|
929
|
+
refuses the beat before any of it is drawn. Series over different categories would reshape
|
|
930
|
+
point *i* of one condition onto point *i* of another, which is a smooth and convincing lie
|
|
931
|
+
that nothing downstream can catch: both curves fit the plot, both clear the type floor, and
|
|
932
|
+
`drift` renders the same wrong thing twice.
|
|
933
|
+
|
|
934
|
+
It costs time — the baseline draws, is held, then reshapes — so give a compare beat
|
|
935
|
+
`seconds` of 7, or 8 with a `readout`. Given fewer than it needs, the emitter draws the
|
|
936
|
+
chart *without* the comparison rather than stopping on a half-drawn one: the same beat, one
|
|
937
|
+
series, byte-for-byte the plain chart, down to MorphSVG's 21,195 bytes staying off the deck.
|
|
938
|
+
It says so on the way past, as a `build: kept <beat> — …` line, and that line is the only
|
|
939
|
+
place anyone learns the comparison is gone. Refusing instead would reach `onBeatError` and
|
|
940
|
+
drop the whole slide, which is the worse trade.
|
|
941
|
+
|
|
942
|
+
Keep `label` short. It is set at the end of the baseline and refused outright if it is
|
|
943
|
+
wider than the plot, and *that* refusal does cost the slide. Where it fits, it is placed by
|
|
944
|
+
trying four positions and rejecting any that would print through the axis names, the tick
|
|
945
|
+
labels, the category names, a value, a drawn delta or either curve; if all four are
|
|
946
|
+
rejected the label is dropped rather than overprinted, and dropped silently, because an
|
|
947
|
+
unnamed ghost is still legibly the fainter, earlier curve.
|
|
948
|
+
|
|
890
949
|
Each maps to exactly one emitter. Adding a domain means adding archetypes — the core never
|
|
891
950
|
learns what a camera frustum or an orderbook is.
|
|
892
951
|
|
|
@@ -899,7 +958,13 @@ learns what a camera frustum or an orderbook is.
|
|
|
899
958
|
inner HTML, GSAP statements, hold points, and its own CSS. It owns one scene's insides
|
|
900
959
|
and nothing else. If it draws labelled parts a camera could fly into, fill `Scene.parts`
|
|
901
960
|
in the same loop that gives them their ids — `inside.element` is an index, and that map
|
|
902
|
-
is the only thing that can tell the plan's third stage from the picture's.
|
|
961
|
+
is the only thing that can tell the plan's third stage from the picture's. If its tweens
|
|
962
|
+
need a vendored GSAP plugin, name it in `Scene.plugins`: `PLUGINS` in
|
|
963
|
+
`src/emit/composition.ts` is the one place a name resolves, and a name that table does
|
|
964
|
+
not know is refused at emit time rather than emitted as a tween that animates nothing.
|
|
965
|
+
And if it has to drop an ornament to fit the beat's length, draw the rest and say so in
|
|
966
|
+
`Scene.warnings` rather than throwing — a throw reaches `onBeatError`, which costs the
|
|
967
|
+
whole slide.
|
|
903
968
|
3. Register it in `src/emit/archetypes/index.ts`.
|
|
904
969
|
|
|
905
970
|
Nothing else changes. The document shell, the deck runtime, the format profiles and the
|
|
@@ -999,19 +1064,36 @@ mechanically rather than free-hand.
|
|
|
999
1064
|
correctly only because one wrapper happened to already be its box's width. No gate reads
|
|
1000
1065
|
CSS, so this is pinned by a test instead — `archetypes.test.ts` fails when two
|
|
1001
1066
|
archetypes say different things about the same class name.
|
|
1002
|
-
- **Nothing is driven by a GSAP callback.**
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
and
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1067
|
+
- **Nothing is driven by a GSAP callback.** Not because the video renders frozen: that was
|
|
1068
|
+
the stated reason for a long time, it was measured on 2026-09-04, and it did not hold.
|
|
1069
|
+
`hyperframes render` drives capture through Chrome's `beginFrame` rather than through a
|
|
1070
|
+
seek, and `suppressEvents` is a property of a seek — so under the renderer a callback
|
|
1071
|
+
fires and its motion ramps, in both constructions that were tried. What the callback
|
|
1072
|
+
actually costs is **reproducibility**. Measured against a control on the same deck: the
|
|
1073
|
+
demo differs on 11 frames of 3,120 as built and on 260 with one `onUpdate` tween added,
|
|
1074
|
+
so one callback multiplies the non-reproducible frames by 24 without moving the
|
|
1075
|
+
worst-case PSNR. And nothing in this stack agrees about what a callback did: `decksmith
|
|
1076
|
+
frames` *does* seek, so it passes `suppressEvents` and shows nothing; `hyperframes
|
|
1077
|
+
snapshot` shows a browser's playback; the render shows a third thing. So tween the
|
|
1078
|
+
property, and where a value is not directly tweenable, tween a proxy object and bind the
|
|
1079
|
+
property to it. `AGENTS.md` invariant 11 carries the measurement and the table.
|
|
1080
|
+
- **A vendored runtime costs bytes only on the decks that use it.** GSAP and DrawSVG are
|
|
1081
|
+
unconditional, because every drawing archetype draws something on. Everything else goes
|
|
1082
|
+
through `PLUGINS` in `src/emit/composition.ts`: a scene names what its tweens need in
|
|
1083
|
+
`Scene.plugins`, the head emits a `<script src>` and a `registerPlugin` per name **in the
|
|
1084
|
+
table's order** — not the scene's, or two storyboards differing only in beat order would
|
|
1085
|
+
emit the same tags in a different order and cost someone a day in `drift` — and
|
|
1086
|
+
`vendorScripts` copies only the files the emitted head actually references. So MorphSVG's
|
|
1087
|
+
21,195 bytes are on a deck that reshapes and on no other, and a name the table does not
|
|
1088
|
+
know is refused at emit time rather than shipped as a tween that silently animates
|
|
1089
|
+
nothing. Registration goes before the first scene script, because a scene builds its
|
|
1090
|
+
timeline inline and late registration is not late, it is nothing.
|
|
1009
1091
|
|
|
1010
1092
|
## Repo layout
|
|
1011
1093
|
|
|
1012
1094
|
```
|
|
1013
1095
|
src/index.ts the library surface — the only file consumers import
|
|
1014
|
-
src/cli.ts the
|
|
1096
|
+
src/cli.ts the eleven verbs, argv and stderr
|
|
1015
1097
|
src/types.ts the contract: Source, Storyboard, Beat, Format, Verdict
|
|
1016
1098
|
src/prefs.ts the three-layer preference resolver
|
|
1017
1099
|
src/emit/kit.ts the seam between the deck shell and the archetype emitters
|