@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 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; `n` toggles presenter notes; `f` is fullscreen; `m` mutes the voice and
203
- `s` hides the subtitles. Every step is deep-linkable (`#3` is slide 3, `#3.2` its second
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.** Capture seeks rather than plays, and `seek()`
1003
- passes `suppressEvents` so `onUpdate`, `onStart` and `onComplete` never fire while a
1004
- frame is being taken. Motion applied from a callback therefore looks right in a browser
1005
- and renders a **frozen video**, with every gate green: lint, check, the type floor, and
1006
- even `drift`, which passes twice over because both renders freeze identically. State
1007
- must be applied by the thing being seeked tween the property. This is the most
1008
- dangerous failure shape in the project, and no automated gate can see it.
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 ten verbs, argv and stderr
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