@jokerized/decksmith 0.3.1 → 0.3.2

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
@@ -51,7 +51,9 @@ decksmith unpack talk.deck -o reopened/
51
51
  ```
52
52
 
53
53
  - **ingest** — document to `Source`: sections, figures, equations and tables, each with a
54
- stable id so a later stage can point back at it.
54
+ stable id so a later stage can point back at it. Give it an http(s) URL instead of a
55
+ path and it reads the page in a browser first — see "Ingesting a web page" below, which
56
+ also says what happens to a video.
55
57
  - **plan** — `Source` to `Storyboard`: an ordered list of beats. Each beat carries an
56
58
  `intent` (what the viewer should understand), an optional `claim` it is accountable to,
57
59
  `evidence` refs into the Source, a `weight`, and an archetype with its parameters.
@@ -108,6 +110,77 @@ decksmith unpack talk.deck -o reopened/
108
110
  - **pack** / **unpack** — the whole deck as one `.deck` file, and back again. See "The
109
111
  `.deck` container" below.
110
112
 
113
+ ### Ingesting a web page
114
+
115
+ `ingest` takes a URL wherever it takes a file:
116
+
117
+ ```sh
118
+ decksmith ingest https://example.com/the-paper -o source.json
119
+ ```
120
+
121
+ It opens a headless Chrome, puts the page's own bytes into it, and **aborts every request
122
+ the page then makes**. A browser pointed at a stranger's URL is otherwise an open proxy —
123
+ the page says `<img src="http://169.254.169.254/latest/meta-data/">` and the browser
124
+ fetches it from inside your network. So the HTML comes through the same address guard
125
+ everything else here uses, and each figure is fetched afterwards, one at a time, through
126
+ that guard again. That has a cost, and it is stated rather than discovered: a page that assembles its body
127
+ from an external bundle harvests to almost nothing, because that bundle was one of the
128
+ aborted requests. Save the page and ingest the file.
129
+
130
+ Which part of the page becomes the document is scored the way Readability scores it: strip
131
+ what the tag, the ARIA role or the class name says is chrome, score every paragraph by its
132
+ length and its commas, propagate that up to five levels of ancestor, discount each
133
+ candidate by its link density, then merge the winner with the siblings that score near it.
134
+ That is what keeps a consent banner and a 142-comment thread out of a deck about the
135
+ article. When it is not sure it declines, says so, and leaves the page to a blunter rule —
136
+ believe `<main>`, believe `<article>`, else take the densest container. Either way the
137
+ line after it is the one to read:
138
+
139
+ ```
140
+ ingest: harvested "Sparse attention at scale" — 6 images, 1 clips
141
+ ingest: 9 sections, 6 figures, 0 equations
142
+ ```
143
+
144
+ Nine sections for a nine-section article is the extraction working. Ninety is the page.
145
+
146
+ **A video has three cases, and they are three different decks.**
147
+
148
+ - **A file the page serves** — `<video src="…mp4">`, or a link ending in one — is
149
+ downloaded, measured off its own container, and re-encoded to a VP9 webm no more than
150
+ 1280px on its longest edge, with the audio dropped and the length capped at 60 seconds.
151
+ It **plays in the presented deck and in the rendered mp4**: the composition holds a real
152
+ `<video>` seeked on the deck's own clock. The encode is why the cap exists — `render`
153
+ pre-decodes a clip to one still per output frame before capture begins, at the source's
154
+ own resolution, so a 4K original writes 4K stills into an 860px plate. **If ffmpeg is
155
+ not installed the clip is still used**, exactly as the page served it, and the harvest
156
+ says so in a warning naming what to install. `--no-transcode` asks for that on purpose;
157
+ `--max-clip-seconds` moves the cap.
158
+ - **A player-page link** — YouTube, Vimeo, Dailymotion, Loom — keeps its still and where
159
+ to watch it. The **mp4 gets the poster frame**, because that is all a captured document
160
+ can honestly hold. The **presented deck gets the real player**: `deck.html` carries the
161
+ embeddable form of that URL, and a `▶ Video` button (or `v`) opens it over the slide,
162
+ click to play, torn down when you leave the slide. Nothing is fetched until you press
163
+ it, and `index.html` never gains an iframe — a third-party frame in the captured
164
+ document would play at wall-clock speed while the deck is being seeked, and refetch
165
+ itself from the network on every render.
166
+
167
+ **The bytes are deliberately not downloaded.** They sit behind a manifest, DRM or terms,
168
+ and pulling down what a YouTube link stands for is usually against that site's terms.
169
+ It is the same `embed` policy the container uses — see "bake, link, embed" below — and
170
+ it is a property of the URL rather than a choice you can make. A host we have no
171
+ verified embed rule for keeps the poster and the link, and no frame: a URL we invented
172
+ would 404 inside the frame as a black rectangle with nothing to click.
173
+ - **No video at all** is the ordinary case, and nothing changes. A `<video>` with a poster
174
+ and no source becomes an ordinary figure, warned; one with neither becomes a line of
175
+ prose carrying the link, also warned.
176
+
177
+ The URL-only budgets, each named after the option it sets so that a refusal naming
178
+ `maxAssets` names something findable in `--help`: `--max-assets` (40), `--max-clips` (4),
179
+ `--max-bytes` (96 MB in total), `--max-seconds` (180, wall clock for the whole harvest),
180
+ `--max-clip-seconds` (60, per clip), `--no-transcode`. Every figure, clip or whole video
181
+ left out is printed verbatim before the plan is paid for, which is the only moment anyone
182
+ can act on it.
183
+
111
184
  ### What `build` writes
112
185
 
113
186
  ```
@@ -279,6 +352,8 @@ for pictures still finishes, on the tool's own SVG if it has to.
279
352
  | `GET /api/jobs/:id/events` | the same payload as SSE on every change |
280
353
  | `GET /api/formats` | the presets, themes, tones, densities and canvas bounds the picker draws from |
281
354
  | `GET /d/:id/...` | the built deck, served statically; `/d/:id/deck.html` is the player |
355
+ | `GET /player.js` | the `<decksmith-player>` element, as an ES module — see below |
356
+ | `GET /examples/embed.html` | a page that embeds two decks with it, and the file you copy |
282
357
 
283
358
  Options on `POST`, all optional, all defaulted server-side: `format`, `width`+`height`,
284
359
  `theme`, `slides`, `lang`, `tone`, `density`, `speed`, `narrate`, `voice`, `images`,
@@ -850,6 +925,42 @@ The corollary is worth internalising before you trust a green gate: `check` has
850
925
  twice on artifacts that were broken. The gates verify the mechanics of what the structure
851
926
  exposes, and a structurally wrong deck exposes nothing to check.
852
927
 
928
+ ### Embedding a deck in your own page
929
+
930
+ The same step layer is reachable from outside as a custom element. A consumer learns one
931
+ thing — where the deck is:
932
+
933
+ ```html
934
+ <script type="module" src="/player.js"></script>
935
+ <decksmith-player deck="/d/<id>/"></decksmith-player>
936
+ ```
937
+
938
+ `next()`, `prev()`, `go(i)` and `play(on)` are methods; `ds-ready`, `ds-stop` and
939
+ `ds-error` are events. `ds-ready` carries every stop the deck can land on, which is what
940
+ a jump list is built from. Setting `deck` again swaps the deck in place.
941
+
942
+ **The iframe stays, and is the module boundary.** It is tempting to mount the deck
943
+ inline instead, and three facts in this tree rule it out. `frameOf` reads
944
+ `contentDocument` and returns null cross-origin while the runtime only warns, so an
945
+ inlined deck served from a CDN would navigate perfectly and paint nothing, silently.
946
+ `customElements.define` is one registry per document, so two decks would be two vendored
947
+ hyperframes bundles and the second `define` throws. And the deck's own chrome is written
948
+ against `100vh` being the box, which is true inside a frame and quietly wrong outside it.
949
+ Keeping the frame leaves the same-origin pair as `deck.html`↔`index.html`, one directory,
950
+ always true — and makes the host link `postMessage`, which does not care about origin.
951
+
952
+ **Silence is a supported state.** A deck is a static artifact that outlives the tool that
953
+ built it, and every deck built before this change has no bridge in it. The element waits,
954
+ gives up, emits `ds-error` with reason `no-bridge`, and leaves the deck exactly as usable
955
+ as it was — still a deck in a frame, its own keyboard still working. It does not blank and
956
+ it does not throw.
957
+
958
+ `examples/embed.html` is both the demo the dev server serves at `/examples/embed.html`
959
+ and the file you copy next to a built deck. Nothing in `lint`, `check`, `verify`, `drift`
960
+ or `render` opens a deck page, so a browser pass is the only instrument that can tell you
961
+ any of this works. The design note is
962
+ [`.planning/2026-09-07-player-as-a-module.md`](.planning/2026-09-07-player-as-a-module.md).
963
+
853
964
  ## Invariants the generator enforces
854
965
 
855
966
  These were all learned by breaking them, and they are why build output is generated