@jokerized/decksmith 0.7.0 → 0.9.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.
Files changed (48) hide show
  1. package/README.md +144 -8
  2. package/dist/cli.js +4582 -712
  3. package/dist/deck-runtime.js +70 -2
  4. package/dist/index.js +4608 -708
  5. package/dist/mcp.js +4254 -759
  6. package/dist/types/build/files.d.ts +13 -1
  7. package/dist/types/deck/motion.d.ts +64 -0
  8. package/dist/types/deck/playback.d.ts +206 -0
  9. package/dist/types/deck/repack.d.ts +10 -0
  10. package/dist/types/deck/runtime.d.ts +96 -1
  11. package/dist/types/deck/subtitles.d.ts +29 -1
  12. package/dist/types/emit/archetypes/annotated-figure.d.ts +1 -1
  13. package/dist/types/emit/archetypes/bar-compare.d.ts +0 -28
  14. package/dist/types/emit/archetypes/callout.d.ts +0 -5
  15. package/dist/types/emit/archetypes/claim-figure.d.ts +15 -0
  16. package/dist/types/emit/archetypes/equation-morph.d.ts +40 -0
  17. package/dist/types/emit/archetypes/equation-walk.d.ts +60 -1
  18. package/dist/types/emit/archetypes/grid.d.ts +0 -14
  19. package/dist/types/emit/archetypes/line-chart.d.ts +0 -8
  20. package/dist/types/emit/archetypes/pipeline.d.ts +15 -1
  21. package/dist/types/emit/archetypes/stack.d.ts +1 -1
  22. package/dist/types/emit/composition.d.ts +47 -2
  23. package/dist/types/emit/faces.d.ts +22 -0
  24. package/dist/types/emit/fit.d.ts +127 -0
  25. package/dist/types/emit/kit.d.ts +104 -2
  26. package/dist/types/emit/look.d.ts +138 -0
  27. package/dist/types/emit/motion.d.ts +244 -0
  28. package/dist/types/emit/svg.d.ts +54 -16
  29. package/dist/types/emit/tex.d.ts +39 -0
  30. package/dist/types/emit/theme.d.ts +1 -1
  31. package/dist/types/emit/themes/index.d.ts +19 -1
  32. package/dist/types/emit/themes/packs.d.ts +15 -0
  33. package/dist/types/emit/themes/pick.d.ts +104 -0
  34. package/dist/types/emit/type.d.ts +111 -0
  35. package/dist/types/index.d.ts +39 -2
  36. package/dist/types/plan/direct.d.ts +66 -0
  37. package/dist/types/plan/prompt.d.ts +0 -12
  38. package/dist/types/prefs.d.ts +15 -1
  39. package/dist/types/render/capture.d.ts +24 -0
  40. package/dist/types/render/timing.d.ts +16 -0
  41. package/dist/types/source/fonts.d.ts +11 -1
  42. package/dist/types/types.d.ts +38 -0
  43. package/dist/types/verify/budget.d.ts +8 -1
  44. package/dist/types/verify/fidelity.d.ts +53 -0
  45. package/dist/types/verify/fill.d.ts +205 -0
  46. package/dist/types/verify/index.d.ts +17 -0
  47. package/dist/types/verify/motion.d.ts +33 -0
  48. package/package.json +4 -1
package/README.md CHANGED
@@ -13,13 +13,19 @@ source.
13
13
 
14
14
  ## Prerequisites
15
15
 
16
- - Node 22 or later, and nothing else to **generate** a deck. `ingest`, `build`, `pack` and
17
- `unpack` are pure Node: they read files, write files, and never open a browser. Decide
18
- your deployment on that line — a service that turns papers into decks runs in a plain
19
- Node image.
16
+ - Node 22 or later, and nothing else to **generate** a deck. `ingest` of a file, `build`,
17
+ `pack` and `unpack` run in plain Node. `build` also opens each stop in Chrome when it can
18
+ find one, and warns `not_measured` when it cannot. Decide your deployment on that line —
19
+ a service that turns papers into decks runs in a plain Node image.
20
20
  - A Chromium build and ffmpeg to **render or check** one. `verify` drives the HyperFrames
21
21
  gates in a headless browser, and the MP4 encode is ffmpeg's. Both arrive with the
22
22
  hyperframes toolchain during `npm install`; there is nothing to install by hand.
23
+ - On Linux, Chrome's sandbox needs unprivileged user namespaces, and Ubuntu 23.10 and later
24
+ deny them by default. DeckSmith then opens its own pages, the gates' view of a deck and a
25
+ caption band, without the sandbox, as HyperFrames' render always does, and prints a
26
+ `chrome:` line saying so. `ingest` of a URL refuses instead, because that page comes from
27
+ the web. `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`, or an AppArmor
28
+ profile for Chrome, gives the sandbox back.
23
29
  - Three commands reach outside for their own reasons: `plan` runs the Codex CLI, `narrate`
24
30
  runs edge-tts over the network, and `illustrate` asks an image backend or that same Codex
25
31
  account for pictures — and draws its own when neither will.
@@ -214,6 +220,49 @@ Arrow keys, Space and PageUp/PageDown step; clicking the left or right third doe
214
220
  notes; `f` is fullscreen; `m` mutes the voice and `s` hides the subtitles. Every step is
215
221
  deep-linkable (`#3` is slide 3, `#3.2` its second reveal).
216
222
 
223
+ ### The v2 player (`--design v2`)
224
+
225
+ `build --design v2` (or `"design": "v2"` in the storyboard or config) gives `deck.html` the
226
+ v2 player; everything else about the build is unchanged. A deck that is already built
227
+ gets it without rebuilding:
228
+
229
+ ```bash
230
+ decksmith repack out/ # writes out/deck2.html beside deck.html, never over it
231
+ ```
232
+
233
+ `repack` swaps the inlined runtime, marks the page and checks that every JSON island is
234
+ byte-identical — no Chrome, no TTS, no re-render.
235
+
236
+ - **One control bar**: play/pause, the counter, narration speed (0.75–2×), `CC`, caption
237
+ size (S/M/L/XL) and fullscreen — or, in a window too narrow for those beside a caption,
238
+ play and one settings menu. **It never covers text.** It docks under the slide in spare
239
+ letterbox (a portrait phone); over the slide's empty bottom padding when that holds a
240
+ 40px target (a slide about 720px tall or more), where it hides 2.5s after the last
241
+ pointer move while the deck plays; otherwise in a band under the slide that it shares
242
+ side by side with the captions, whose text is padded in past the buttons.
243
+ `test/deck-page.test.ts` measures controls against caption text and against every glyph
244
+ the slide draws, at six viewports.
245
+ - **Play/pause** pauses the voice where it is and carries on from the same word. A deck
246
+ opens paused; stepping while paused moves the slide silently.
247
+ - **Speed** sets the narration's `playbackRate` live, on the sentence already playing, and
248
+ divides the reveal glides and every autoplay wait by the same factor so they keep pace.
249
+ A stop with no narration holds for its reveal and 1.5s at 1×, not the author's gap.
250
+ - **Captions** are sized from the slide (3% of its height, 13px floor; L and XL keep floors
251
+ of 16 and 19px so the size control works on a phone) and the slide gives up only one
252
+ two-line strip for them: 84% of the frame at 1080p, 83% in an 800×450 embed (v0.8.0: 77%
253
+ and 69%), 100% in a portrait letterbox. Long cues are cut to two lines of the strip's
254
+ actual width, which also splits Japanese and Chinese.
255
+ - **Keys:** Space steps forward (Space is the presenter's key, not play); `Enter` plays and
256
+ pauses; `c` (or `s`) captions; `<`/`>` speed; `+`/`-` caption size. Letter keys fall back
257
+ to the physical key under a Korean or Japanese IME. Anything held with Cmd, Ctrl or Alt
258
+ is left to the browser.
259
+ - **Preferences** — speed, captions on/off, size — come from `?speed=1.25&cc=0&ccsize=l`
260
+ (this visit only), else from `localStorage` (`decksmith.prefs.v1`), else the defaults.
261
+ When the viewer changes one, the deck posts
262
+ `{ type: "decksmith:prefs", speed, cc, ccsize }` to its parent so an embedding site can
263
+ keep it per user (a cross-site iframe's storage is partitioned), and it applies the same
264
+ message when the parent sends it. Check `event.origin` on your side.
265
+
217
266
  Stepping forward *plays* the reveal rather than cutting to it — the step layer sweeps the
218
267
  composition's timelines across frames instead of seeking once. Backward steps, `Home`/`End`
219
268
  and deep links cut, because entrance tweens run in reverse look like elements un-drawing
@@ -778,8 +827,11 @@ invalidates a storyboard you have already edited.
778
827
  | `lang` | the source's | plan | BCP-47. Drives the copy, the voice, and the font subset |
779
828
  | `tone` | `plain` | plan | `plain` · `academic` · `conversational` · `punchy` |
780
829
  | `density` | `normal` | plan | `sparse` · `normal` · `dense` — how much text a slide may carry |
781
- | `theme` | `ink` | emit | `ink` · `paper` · `mono` |
830
+ | `theme` | `ink` | emit | `ink` · `paper` · `mono`, or a v2 style pack by name (see Themes) |
831
+ | `design` | `classic` | narrate, emit | `classic` is the v0.8.0 look byte for byte · `v2` is the redesign: the v2 player (see "The v2 player") and a style pack per deck when no theme is named |
832
+ | `packSeed` | the source id | narrate, emit | what `design: v2` hashes to pick a pack; pass a paper id so its languages share one |
782
833
  | `animationSpeed` | `1` | emit | multiplies every duration, hold and beat length. Below 1 is faster |
834
+ | `design` | `classic` | emit | `classic` (the 0.8 look, byte-identical) · `v2` (the redesign — see [v2 motion](#v2-motion)) |
783
835
  | `narration.voice` | picked for `lang`+`tone` | narrate | an explicit edge-tts voice id |
784
836
  | `narration.rate` | `+0%` | narrate | edge-tts prosody |
785
837
  | `narration.pitch` | `+0Hz` | narrate | edge-tts prosody |
@@ -826,8 +878,8 @@ why `slideCount` did nothing.
826
878
  decksmith.config.json: unknown preference "narration.speed". Valid: enabled, voice, rate, pitch, subtitles.
827
879
  ```
828
880
 
829
- On the command line: `--slides --lang --tone --density` on `plan`, `--theme --speed` on
830
- `build`, `--voice --rate --pitch --no-subtitles` on `narrate`, `--images --image-provider
881
+ On the command line: `--slides --lang --tone --density` on `plan`, `--theme --design
882
+ --pack-seed --speed` on `build` (`--design --pack-seed` on `narrate` too), `--voice --rate --pitch --no-subtitles` on `narrate`, `--images --image-provider
831
883
  --image-model --image-style --image-max` on `plan` and `illustrate`, and all but the image
832
884
  flags on `pack`, which records the preferences the deck was made under — whether it was
833
885
  illustrated is read off the storyboard itself, the way `narration.enabled` is read off the
@@ -835,9 +887,43 @@ narration beside it.
835
887
 
836
888
  A preference sitting at its default says nothing, so a stored artifact wins over it and
837
889
  loses to anything you type. `plan` stamps `lang` and `theme` into the storyboard it
838
- writes; `build` then uses the storyboard's unless `--theme` or a config file restates one.
890
+ writes (and `design`, only when one was asked for); `build` then uses the storyboard's
891
+ unless `--theme`/`--design` or a config file restates one.
839
892
  Language is never overridden at build time — it describes copy that is already written.
840
893
 
894
+ `--design v2` (or `"design": "v2"` in the config file) lets the build vary each beat's
895
+ layout: bars as rows or columns, a pipeline as a row, a stair or a column, a claim beside,
896
+ mirrored against or above its figure, a comparison as columns or rows, and the headline on
897
+ top, in a left rail or under the body. The choice is made by code, deterministically, per
898
+ paper — never by the planner — and it never moves a stop, so narration stays aligned.
899
+ `build` writes the choices to `out/look.json`. Without the flag the deck is the classic one,
900
+ byte for byte. See `.planning/2026-10-07-v2-layout-director.md`.
901
+
902
+ ## v2 motion
903
+
904
+ `--design v2` replaces the 0.8 deck's single motion — every scene fading up the same
905
+ eyebrow and headline, every seam the same 0.4s dissolve, nothing moving once a slide has
906
+ built — with a grammar planned per deck (`src/emit/motion.ts`):
907
+
908
+ - **Entrances.** Six verbs — `rise`, `slide`, `snap`, `focus`, `wipe`, `mask` — one per
909
+ scene, chosen from the source id and beat id, never the same twice in a row, with the
910
+ stock `rise` capped at a quarter of the deck.
911
+ - **Seams.** `dissolve`, `push`, `lift`, `wipe`, `zoom`, picked from how two neighbouring
912
+ beats relate (same family → push, a role boundary → zoom, a title → lift, into the close
913
+ → dissolve), never repeated back to back, and at least three kinds in a deck of ten or
914
+ more beats. A beat `inside` the one before keeps the camera dive.
915
+ - **Emphasis while the narrator talks.** The part a sentence is about pulses, glows or is
916
+ underlined, starting on a cue boundary of that sentence inside the quiet stretch after
917
+ its stop, and is back at rest before the next reveal — so every frame a gate captures
918
+ at a stop is unchanged.
919
+
920
+ All of it is `fromTo` tweens with no callbacks, seeded rather than random, and keeps every
921
+ hold, every scene window and `timing.json` exactly as `classic` writes them. In `deck.html`
922
+ a v2 deck also glides through the seam into the next slide (0.8 cut to its first stop,
923
+ already built) and, while a stop's audio plays, seeks the scene through that stop's quiet
924
+ stretch on the audio clock so the emphasis lands on the same word as in the video. Both
925
+ are off under `prefers-reduced-motion`.
926
+
841
927
  ## Themes
842
928
 
843
929
  Three, each a position rather than a hue.
@@ -856,6 +942,41 @@ naming a family the deck does not declare falls back silently.
856
942
  A theme is a name and a palette, and that is the whole extension point: a new one is a
857
943
  file in `src/emit/themes/` plus a line in `THEMES`. No archetype learns it exists.
858
944
 
945
+ ### v2 style packs
946
+
947
+ Six more, each a whole look rather than a palette: its own typeface pairing and type
948
+ scale, ground and accent, eyebrow treatment, figure framing and surface.
949
+
950
+ | Pack | Ground | Headline / body | Title | Lists | Bars | Leans to |
951
+ |---|---|---|---|---|---|---|
952
+ | `signal` | violet-black, Magma accent | Space Grotesk / Inter | glowing headline | ticks | pills | foot headlines |
953
+ | `blueprint` | navy, cyan | IBM Plex Sans / IBM Plex Sans | drawing frame with corner marks | numbers | square, on dashed tracks | rail headlines |
954
+ | `atlas` | espresso, amber | Source Serif 4 / Inter | centred, frontispiece rule | dots | slightly rounded | top headlines |
955
+ | `folio` | cream, oxblood | Source Serif 4 / Source Serif 4 | masthead rules | numbers | square, no tracks | top headlines, versus |
956
+ | `chalk` | cool white, ultramarine | Space Grotesk / IBM Plex Sans | highlighter under each word | cards | rounded, outlined | foot headlines, tables |
957
+ | `journal` | sage, forest | IBM Plex Sans / Source Serif 4 | side bar down the title | ruled rows | slightly rounded, outlined tracks | rail headlines |
958
+
959
+ The forms are the pack's `forms` (list marks, Director affinity) and its `skin` (title,
960
+ bars), which may set only paint: colours, shadows, radii, outlines and SVG stroke/fill
961
+ opacity, never a box. In a Korean, Japanese or Chinese deck a pack's serif roles are set
962
+ in Noto Serif KR/JP/SC, bundled beside the sans.
963
+
964
+ `--theme <pack>` forces one. `--design v2` picks one when nobody named a theme (a
965
+ storyboard's default `ink` counts as nobody): a weighted, deterministic hash of the
966
+ source id (or `--pack-seed`), leaning mildly toward packs that suit the deck's mix of
967
+ archetypes. Over the 176 HypePaper storyboards on disk the busiest pack carries 20.5% of
968
+ decks. With narration on disk, a pack that would stage a beat with a different stop count
969
+ than the narration was recorded at, or that would leave out a beat the storyboard's own
970
+ theme draws, is skipped for the next one; if none fits, the storyboard's own theme is kept.
971
+ So `--design v2` never breaks a narrated rebuild and never costs a slide.
972
+
973
+ Each pack's faces ship beside a Latin deck, vendored from `@fontsource-variable/*` like
974
+ Inter, and are measured by their own width tables (`src/emit/faces.ts`, written by
975
+ `node scripts/measure-faces.mjs --write`). A CJK deck keeps its Noto bundle first in every
976
+ stack — Noto Serif for a role the pack sets in a serif, Noto Sans otherwise — so the glyph
977
+ shapes change with the pack in every language.
978
+ The packs' interfaces are written up in `.planning/2026-10-07-v2-style-packs.md`.
979
+
859
980
  ## Narration
860
981
 
861
982
  ```sh
@@ -1199,6 +1320,19 @@ mechanically rather than free-hand.
1199
1320
  root timeline holds only a dummy tween spanning the deck.
1200
1321
  - KaTeX renders with `output: "html"`. The default also emits a hidden MathML mirror that
1201
1322
  the layout inspector reads as overlapping text.
1323
+ - No TeX reaches a `katex.render` call that KaTeX has not already parsed in Node, with
1324
+ `throwOnError` and the deck's own options (`src/emit/tex.ts`). A formula that fails gets
1325
+ a repair only where it cannot change what the formula says — a missing `\right.`, an
1326
+ unclosed brace, a dropped `\label`, a paper's undefined macro drawn as its name — and the
1327
+ repair is printed at `plan` and again at `build`. One no repair saves is shown as its
1328
+ source in plain text by `equation-walk`, and costs the beat, by name, in
1329
+ `equation-morph`. Until this existed the first parser a formula met was the browser at
1330
+ `verify`, after the deck had been built and narrated: 26 of 92 failed HypePaper builds
1331
+ in the week to 2026-10-07, 25 of them from `wrapTerms` cutting a `\left`/`\right` pair or
1332
+ a superscript in half.
1333
+ - `equation-walk` measures its display after fonts and fits it to its box: smaller down to
1334
+ the 40px floor, then broken after top-level relations and operators. One that still does
1335
+ not fit fails `fidelity` as `math_unfit`, with the formula in the message.
1202
1336
  - No `Date.now()`, no `Math.random()`, no network at render time *in the composition*.
1203
1337
  Two renders of an image-free deck must be byte-identical. `deck.html` is exempt: it is
1204
1338
  presented, never rendered, and its subtitle loop legitimately reads a clock.
@@ -1259,6 +1393,8 @@ src/prefs.ts the three-layer preference resolver
1259
1393
  src/emit/kit.ts the seam between the deck shell and the archetype emitters
1260
1394
  src/emit/archetypes/ one emitter per archetype
1261
1395
  src/emit/themes/ one palette per file; the registry is the extension point
1396
+ src/emit/type.ts type specs: the v2 packs' faces and chrome scale, read by chromeHeight and chromeCss
1397
+ src/emit/faces.ts measured width tables for the packs' Latin faces (generated)
1262
1398
  src/images/ the three rungs a brief is drawn through, and the illustrate step
1263
1399
  src/narrate/ edge-tts, one segment per stop
1264
1400
  src/pack/ the .deck container and its bake/link/embed policy