@jokerized/decksmith 0.1.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 (78) hide show
  1. package/README.md +794 -0
  2. package/dist/cli.js +8906 -0
  3. package/dist/deck-runtime.js +55 -0
  4. package/dist/index.js +8590 -0
  5. package/dist/mcp.js +7809 -0
  6. package/dist/server/errors.js +73 -0
  7. package/dist/server/http.js +504 -0
  8. package/dist/server/main.js +91 -0
  9. package/dist/server/options.js +198 -0
  10. package/dist/server/pipeline.js +356 -0
  11. package/dist/server/queue.js +195 -0
  12. package/dist/server/ui.js +1614 -0
  13. package/dist/server/upload.js +232 -0
  14. package/dist/types/cli.d.ts +1 -0
  15. package/dist/types/deck/runtime.d.ts +59 -0
  16. package/dist/types/deck/subtitles.d.ts +101 -0
  17. package/dist/types/emit/archetypes/annotated-figure.d.ts +103 -0
  18. package/dist/types/emit/archetypes/bar-compare.d.ts +30 -0
  19. package/dist/types/emit/archetypes/callout.d.ts +7 -0
  20. package/dist/types/emit/archetypes/claim-figure.d.ts +9 -0
  21. package/dist/types/emit/archetypes/data-table.d.ts +21 -0
  22. package/dist/types/emit/archetypes/equation-walk.d.ts +2 -0
  23. package/dist/types/emit/archetypes/grid.d.ts +16 -0
  24. package/dist/types/emit/archetypes/index.d.ts +19 -0
  25. package/dist/types/emit/archetypes/line-chart.d.ts +22 -0
  26. package/dist/types/emit/archetypes/pipeline.d.ts +81 -0
  27. package/dist/types/emit/archetypes/split-compare.d.ts +2 -0
  28. package/dist/types/emit/archetypes/stack.d.ts +93 -0
  29. package/dist/types/emit/archetypes/title.d.ts +188 -0
  30. package/dist/types/emit/camera.d.ts +397 -0
  31. package/dist/types/emit/composition.d.ts +191 -0
  32. package/dist/types/emit/island.d.ts +19 -0
  33. package/dist/types/emit/kit.d.ts +256 -0
  34. package/dist/types/emit/svg.d.ts +177 -0
  35. package/dist/types/emit/theme.d.ts +65 -0
  36. package/dist/types/emit/themes/index.d.ts +40 -0
  37. package/dist/types/emit/themes/ink.d.ts +12 -0
  38. package/dist/types/emit/themes/mono.d.ts +20 -0
  39. package/dist/types/emit/themes/paper.d.ts +18 -0
  40. package/dist/types/index.d.ts +200 -0
  41. package/dist/types/mcp/main.d.ts +2 -0
  42. package/dist/types/mcp/prereqs.d.ts +22 -0
  43. package/dist/types/mcp/tools.d.ts +212 -0
  44. package/dist/types/narrate/narrate.d.ts +87 -0
  45. package/dist/types/narrate/tts.d.ts +134 -0
  46. package/dist/types/narrate/voices.d.ts +29 -0
  47. package/dist/types/pack/media.d.ts +61 -0
  48. package/dist/types/pack/pack.d.ts +16 -0
  49. package/dist/types/plan/codex.d.ts +24 -0
  50. package/dist/types/plan/duration.d.ts +394 -0
  51. package/dist/types/plan/prompt.d.ts +47 -0
  52. package/dist/types/plan/refs.d.ts +22 -0
  53. package/dist/types/plan/select.d.ts +116 -0
  54. package/dist/types/prefs.d.ts +43 -0
  55. package/dist/types/render/captions.d.ts +108 -0
  56. package/dist/types/render/ffmpeg.d.ts +129 -0
  57. package/dist/types/render/render.d.ts +123 -0
  58. package/dist/types/render/timing.d.ts +290 -0
  59. package/dist/types/server/errors.d.ts +11 -0
  60. package/dist/types/server/http.d.ts +57 -0
  61. package/dist/types/server/main.d.ts +1 -0
  62. package/dist/types/server/options.d.ts +90 -0
  63. package/dist/types/server/pipeline.d.ts +21 -0
  64. package/dist/types/server/queue.d.ts +105 -0
  65. package/dist/types/server/ui.d.ts +9 -0
  66. package/dist/types/server/upload.d.ts +107 -0
  67. package/dist/types/source/assets.d.ts +11 -0
  68. package/dist/types/source/fonts.d.ts +15 -0
  69. package/dist/types/source/markdown.d.ts +8 -0
  70. package/dist/types/types.d.ts +1992 -0
  71. package/dist/types/verify/budget.d.ts +41 -0
  72. package/dist/types/verify/check.d.ts +78 -0
  73. package/dist/types/verify/drift.d.ts +158 -0
  74. package/dist/types/verify/fidelity.d.ts +247 -0
  75. package/dist/types/verify/index.d.ts +207 -0
  76. package/dist/types/verify/overprint.d.ts +133 -0
  77. package/dist/types/verify/typefloor.d.ts +50 -0
  78. package/package.json +84 -0
@@ -0,0 +1,207 @@
1
+ import { type Beat, type Finding, type Storyboard, type Verdict } from "../types.js";
2
+ import { type CheckOptions } from "./check.js";
3
+ /**
4
+ * The duration budget reads the built composition, so it needs no argument the
5
+ * `verify <dir>` entry point does not already have. Exported for the same reason
6
+ * the other scans are: it is testable without a browser.
7
+ */
8
+ export { type Canvas, profilesFor, readCanvas, scanBudget } from "./budget.js";
9
+ /**
10
+ * The drift gate is deliberately not part of `verify()`. Everything above reads
11
+ * one built artifact and costs seconds; drift renders the deck twice and costs
12
+ * minutes, so folding it in would make every build pay for it. It is its own
13
+ * entry point, called on demand.
14
+ */
15
+ export { type DriftMode, type DriftOptions, type DriftReport, drift, FLOOR_DB, type Motion, type SceneWindow, } from "./drift.js";
16
+ /**
17
+ * The fidelity gate. Unlike `drift` it IS part of `verify()`: it costs one
18
+ * browser and one screenshot per declared stop — 4.3s for the twelve-beat demo's
19
+ * 37 stops against `check`'s ~60s — so folding it in is a 7% tax, and it is the
20
+ * only gate here that can see a slide that draws nothing.
21
+ */
22
+ export { type FidelityOptions, type FidelityReport, type Frame, fidelity, gradeFidelity, INK_FLOOR, inkBelow, type Measured, readStops, type Stop, } from "./fidelity.js";
23
+ /**
24
+ * The chart-collision rule, folded into `fidelity`'s report because it shares
25
+ * that gate's browser. Exported here so the pure half is testable and so a
26
+ * caller can see what the rule considers a collision.
27
+ */
28
+ export { collectSvgTextRuns, gradeOverprint, MIN_OVERLAP, type Overprint, type Overprinted, overprints, type TextRun, } from "./overprint.js";
29
+ /**
30
+ * Invariant 5. Emit enforces the floor while laying a beat out, and this checks
31
+ * that the artifact came out the way emit believed — the two are not the same
32
+ * claim while any of that enforcement is a character count rather than a width.
33
+ */
34
+ export { scanTypeFloor, TYPE_FLOOR_PX } from "./typefloor.js";
35
+ /**
36
+ * What `verify` runs on top of the HyperFrames gates.
37
+ *
38
+ * `fidelity` is opt-OUT rather than opt-in: it is the only gate that can see a
39
+ * slide the audience gets nothing from, and a gate that has to be asked for is a
40
+ * gate that is not on when it matters. The switch exists for the one honest
41
+ * case — a machine with no browser, where it would only emit its own warning.
42
+ */
43
+ export interface VerifyOptions extends CheckOptions {
44
+ /** Default true. `false` skips the frame measurement entirely. */
45
+ fidelity?: boolean;
46
+ }
47
+ /**
48
+ * Run every gate DeckSmith owns over a built project directory.
49
+ *
50
+ * The storyboard is optional because `decksmith verify <dir>` is handed a built
51
+ * directory and nothing else; pass it from `build`, where one is in hand, to get
52
+ * the beat-level gates as well. `kept` — the cut `build` emitted — is optional
53
+ * for the same reason, and for a second one: without it the budget gate falls
54
+ * back to the flat threshold, which is the right list only while the budget has
55
+ * cut nothing.
56
+ */
57
+ export declare function verify(dir: string, opts?: VerifyOptions, storyboard?: Storyboard, kept?: readonly Beat[]): Promise<Verdict>;
58
+ /**
59
+ * Every mp3 the narration island promises is actually in the deck.
60
+ *
61
+ * Nothing else notices this. `hyperframes check` never opens `deck.html`, and
62
+ * the runtime treats a missing file exactly like a browser that refused to
63
+ * autoplay — it clears the subtitles and moves on. So a deck that lost its audio
64
+ * on the way to a web host presents in silence and says nothing about why, which
65
+ * is the failure mode a gate exists for.
66
+ */
67
+ export declare function scanNarration(page: string, files: ReadonlySet<string>): Finding[];
68
+ /**
69
+ * Warn when a deck mostly talks. A deck of headlines and bullet panels is what
70
+ * every other slide generator already makes, and passes every other gate here
71
+ * with room to spare — nothing else in the pipeline notices it at all.
72
+ *
73
+ * A warning rather than an error: a genuinely shapeless source can honestly
74
+ * yield such a deck, and this is a judgement about explanation, not a broken
75
+ * build.
76
+ */
77
+ export declare function scanDiagrammatic(storyboard: Storyboard): Finding[];
78
+ /**
79
+ * Warn when a headline reads the visual's own labels back as a list.
80
+ *
81
+ * FOUND BY WATCHING A RENDER, not by a gate. "ThinkSR links encoding, windows,
82
+ * thought ticks, and decoding" sat over a pipeline whose four stages are Encoder,
83
+ * Windows, Shared DQ-CTM ticks and Decoder. Cover the four names and the sentence
84
+ * asserts nothing the arrows had not already drawn — it is RULE 8's label with
85
+ * commas where the Title Case used to be. It passes RULE 8 as written, because it
86
+ * IS a complete sentence in sentence case with a verb in it.
87
+ *
88
+ * WHY THIS IS A DETECTOR AND NOT A PROMPT RULE. It was tried as a prompt rule
89
+ * first — an example-led sharpening of RULE 8 carrying that exact bad headline and
90
+ * its good twin — and a real Codex run answered "ThinkSR runs through encoder,
91
+ * windows, ticks, and decoder". Same beat, same four labels, verb swapped. Whether
92
+ * a sentence "asserts nothing" is a judgement the writer makes about their own
93
+ * output, so it can always be met cosmetically. The label strings are not a
94
+ * judgement, so a check on them has teeth the instruction did not.
95
+ *
96
+ * TWO CONDITIONS, and it needs both. Naming your own parts is not the defect —
97
+ * "A dense carrier is read and updated by compact thought" names all three of its
98
+ * stages and is the good headline on the same archetype, because the names sit in
99
+ * different grammatical positions with a relation asserted between them. And
100
+ * coordination is not the defect either: "One pass in, one pass out, and a loop in
101
+ * the middle" is three coordinated fragments and the sharpest line in the shipped
102
+ * demo, because it names no stage at all — it says what the shape DOES. The defect
103
+ * is the two together, which is why this counts labels landing in SEPARATE
104
+ * coordinated fragments rather than counting either alone.
105
+ *
106
+ * A warning, never an error. Incidence is about one beat in twelve, the judgement
107
+ * is editorial, and `plan` already tells the author to read the storyboard before
108
+ * building — which is the moment this is for.
109
+ */
110
+ export declare function scanHeadlines(storyboard: Storyboard): Finding[];
111
+ /**
112
+ * Warn when two beats draw the SAME PICTURE from the same object (RULE 9).
113
+ *
114
+ * FOUND IN A REAL PLAN, not by a gate. `b10-params` and `b11-average` both cite
115
+ * `tbl-bench` and both emit a bar-compare over the same five methods — one
116
+ * picture, drawn twice, with a different number on the bars. RULE 9 already
117
+ * forbids it in prose ("One object, one beat... Repeating a visual to say one
118
+ * more small thing is padding, and reads as padding") and the planner did it
119
+ * anyway, which is the same shape of failure `scanHeadlines` documents: a rule
120
+ * about whether something is padding is a judgement the writer makes about their
121
+ * own output, so it can always be met cosmetically.
122
+ *
123
+ * THREE CONDITIONS, and it needs all of them, because the loose version condemns
124
+ * the shipped demo. Measured over the 134 plans in `experiments/` and `demo/`:
125
+ *
126
+ * same evidence id alone fires on 10 plans, INCLUDING demo/storyboard.json
127
+ * + same archetype + identical labels fires on 4, demo clear, every hit the same five bars
128
+ *
129
+ * What the two extra conditions buy is the difference between padding and
130
+ * progressive disclosure. `demo/storyboard.json` cites `tbl-bench` from a
131
+ * bar-compare and then a data-table — the shape, then the numbers — and that is a
132
+ * deck teaching, not repeating. So is a pipeline followed by the paper's own
133
+ * annotated figure. Two bar-compares over the identical five bars is neither;
134
+ * there is no reading of it in which the second picture shows something the first
135
+ * did not. A0-05 draws three bar-compares off one table with DIFFERENT bars in
136
+ * each and is deliberately left alone — different bars are a different picture,
137
+ * and whether three of them is too many is editorial.
138
+ *
139
+ * A camera dive is exempt: `inside` says this beat is what happens within a part
140
+ * of the one before it, so sharing the object is the entire point of the relation
141
+ * (RULE 11) rather than a repeat of it.
142
+ *
143
+ * A warning, never an error — same reason as `scanHeadlines`. Merging two beats
144
+ * into two holds of one beat is a rewrite only the author can perform.
145
+ */
146
+ export declare function scanRepeatedObject(storyboard: Storyboard): Finding[];
147
+ /**
148
+ * How far a name may precede the thing it names before a viewer notices.
149
+ *
150
+ * A second, which is generous on purpose. The word position inside a cue is
151
+ * estimated proportionally by character offset — the same model `splitCue`
152
+ * already uses — so it carries a few hundred milliseconds of error, and a
153
+ * threshold tighter than that would be reporting its own arithmetic.
154
+ */
155
+ export declare const LEAD_SECONDS = 1;
156
+ /**
157
+ * Warn when the narration names a part before that part is on screen.
158
+ *
159
+ * `scanHeadlines` one level down, and the reason it did not exist until now is
160
+ * worth keeping: THE DEFECT WAS NOT REACHABLE. Measured across all 136 plans in
161
+ * `experiments/` and `demo/` — 1103 named parts over 337 containers, every word
162
+ * placed on the scene clock at the speed a 60-second target derives — the lead
163
+ * came out at median −6.87s, p90 −2.47s, maximum +0.87s, and **zero** parts were
164
+ * named more than a second early. Transplanting §9's own defect sentence onto a
165
+ * real pipeline produced zero early names too. The build ran roughly four times
166
+ * faster than the voice, so the narration simply could not outrun the picture.
167
+ *
168
+ * `fillFactor` is what makes it reachable. Slowing each scene's build to fill its
169
+ * sentence takes that same measurement from 0 of 1103 to 222 of 1103 — the words
170
+ * stay put and the pictures they describe move later. So this ships in the same
171
+ * change as the stretch, not after it: the detector is not a smoke alarm for a
172
+ * fire already burning, it is the one fitted before the gas is turned on.
173
+ *
174
+ * CONSERVATIVE BY CONSTRUCTION, in three ways, because a warning that cries wolf
175
+ * is a warning people learn to scroll past:
176
+ * - a part is assumed to appear at the EARLIEST hold that could be its own,
177
+ * `holds[min(j, last)]`. Where an archetype spends its first hold on a
178
+ * landing rather than a part, the real appearance is later than this and the
179
+ * finding is missed rather than invented.
180
+ * - only labels the narration actually names are considered, by the same
181
+ * five-character prefix match `scanHeadlines` uses — tuned on exactly this
182
+ * problem, where the headline said "encoding" and the stage was `Encoder`.
183
+ * - the word's time inside its cue is estimated proportionally, and the
184
+ * threshold is a whole second.
185
+ *
186
+ * A warning, never an error. Which sentence describes which reveal is editorial,
187
+ * and `place` already guarantees the containment that matters — no sentence ends
188
+ * before the reveal it speaks over appears (`assertFits`).
189
+ */
190
+ export declare function scanNarrationLead(beats: readonly Beat[], timing: {
191
+ scenes: readonly {
192
+ id: string;
193
+ start: number;
194
+ holds: readonly number[];
195
+ }[];
196
+ segments: readonly {
197
+ scene: string;
198
+ start: number;
199
+ cues: readonly {
200
+ start: number;
201
+ end: number;
202
+ text: string;
203
+ }[];
204
+ }[];
205
+ }): Finding[];
206
+ /** Flag render-time nondeterminism in one composition file (invariant 7). */
207
+ export declare function scanDeterminism(html: string, file: string): Finding[];
@@ -0,0 +1,133 @@
1
+ /**
2
+ * THE COLLISION GATE — inside a chart, did two labels land on top of each other?
3
+ *
4
+ * WHY THIS EXISTS AND WHY IT IS OURS. `hyperframes check` has a rule for exactly
5
+ * this, `content_overlap`, and it is STRUCTURALLY BLIND to a chart. Two reasons,
6
+ * either of which alone is fatal:
7
+ *
8
+ * - `isManagedFlowOverlap` (layoutAudit) exempts any pair of boxes that are
9
+ * both in normal flow under a shared flex/grid ancestor, on the sound
10
+ * grounds that a flex container is entitled to lay its children out however
11
+ * it likes. An SVG child computes `position: static`, so `isInFlow` is true
12
+ * of every `<text>` in the chart, and `.chartwrap` is the flex ancestor they
13
+ * all share. Every label pair in every chart is exempt, permanently.
14
+ * - `content_overlap` is not in `OFF_CANVAS`, so even if it fired it would
15
+ * arrive as upstream's severity and could never fail a DeckSmith build.
16
+ *
17
+ * MEASURED, not reasoned: `line-chart` with the three 2026-07-31 fixes reverted,
18
+ * built as a one-beat deck and sampled at every stop inside the scene, comes
19
+ * back `layout.findings: []` with `truncated: false`. The deck it passed is a
20
+ * line chart whose value labels, delta labels and category labels print through
21
+ * each other 69 times over. Neither of those two facts is reachable from here
22
+ * without an upstream change, so the rule is ours.
23
+ *
24
+ * WHY SVG AND NOTHING ELSE. Scoping it wider would be re-implementing
25
+ * `content_overlap` badly: outside SVG, elements overlap on purpose all the time
26
+ * (a badge on a card, a caption over a plate), the flex exemption upstream
27
+ * applies is CORRECT there, and a second opinion that disagrees with the shipped
28
+ * gate is how two definitions of "broken" start drifting apart. Inside an `<svg>`
29
+ * every glyph was placed by our own arithmetic at an absolute coordinate: two
30
+ * text runs on the same pixels is never a layout engine's decision, it is always
31
+ * our emitter getting the spacing wrong. Two archetypes emit `<text>` —
32
+ * `line-chart` and `bar-compare` — so that is the whole blast radius.
33
+ *
34
+ * WHERE IT RUNS. Inside `fidelity`'s existing per-stop loop, on the page it has
35
+ * already opened and seeked. No second browser, no second page, no second
36
+ * `renderSeek`: the marginal cost is one `page.evaluate` per stop. It therefore
37
+ * rides with `--fidelity`, which is right rather than merely convenient — the
38
+ * one honest reason to pass `--no-fidelity` is a machine with no browser, and a
39
+ * machine with no browser cannot run this either.
40
+ *
41
+ * AT A STOP, NOT AT A MIDPOINT. A chart builds: at t=0 the value labels have not
42
+ * arrived and at the first hold the deltas have not. Measuring anywhere but a
43
+ * declared stop would report a collision between a label that is on screen and
44
+ * one that is fading in at opacity 0.3, which is motion, not a defect. The walk
45
+ * drops anything hidden or fully transparent for the same reason.
46
+ */
47
+ import type { Finding } from "../types.js";
48
+ /**
49
+ * How much two runs must share, in BOTH axes, before it is a collision.
50
+ *
51
+ * 8px, carried across from the calibration this was fitted in (the
52
+ * research-env perturbation sweep, 2026-07-31). Abutting boxes are the reason it
53
+ * is not zero: a `<text>` run's client rect includes the font's leading, so two
54
+ * lines of a stacked label overlap by a pixel or three while being perfectly
55
+ * legible, and at 0 the sweep called every multi-line label a defect. 8px is
56
+ * about a fifth of the 40px audience floor of invariant 5 — an overlap that
57
+ * large is a fifth of a glyph body and is visible.
58
+ */
59
+ export declare const MIN_OVERLAP = 8;
60
+ /** One painted run of text, in device pixels. */
61
+ export interface TextRun {
62
+ /**
63
+ * Which text node this rect came from, numbered in document order.
64
+ *
65
+ * IDENTITY, NOT THE STRING. Chrome hands back one client rect per line box, so
66
+ * a wrapped `<text>` yields several runs that necessarily overlap; those are
67
+ * one label, not a collision. The first version told them apart by comparing
68
+ * `text`, which also exempted two GENUINELY different labels that happened to
69
+ * agree — on the first 40 characters, since that is all `text` keeps — and
70
+ * exempted a real collision between two elements showing the same rounded
71
+ * value, which is exactly what a chart with repeated y-labels produces.
72
+ */
73
+ node: number;
74
+ /** The run's string, truncated — for the message only, never for comparison. */
75
+ text: string;
76
+ x: number;
77
+ y: number;
78
+ w: number;
79
+ h: number;
80
+ }
81
+ /** Two runs that share pixels, and how many. */
82
+ export interface Overprint {
83
+ a: string;
84
+ b: string;
85
+ /** Overlap extent, `[x, y]`, rounded. */
86
+ overlap: [number, number];
87
+ }
88
+ /**
89
+ * Serialised into the page: every visible text run inside this scene's SVGs.
90
+ *
91
+ * TEXT RANGES, NOT ELEMENT BOXES. `<g class="ptlab">` wraps every value label in
92
+ * the chart, so its own box spans the entire plot and overlaps everything;
93
+ * measuring elements would report the chart colliding with itself on every deck
94
+ * ever built. A `Range` over the text node measures the glyphs, which is what
95
+ * the audience sees overlapping.
96
+ *
97
+ * The walk starts at the SCENE and only begins collecting once it is inside an
98
+ * `<svg>`, so the visibility test is applied to every ancestor on the way down —
99
+ * a chart hidden inside a collapsed wrapper contributes nothing, rather than
100
+ * contributing runs measured at a stale position.
101
+ */
102
+ export declare function collectSvgTextRuns(sid: string): TextRun[];
103
+ /**
104
+ * Every pair of runs that shares more than `minOverlap` pixels in both axes.
105
+ *
106
+ * Pure and exported so the predicate can be tested without a browser — the same
107
+ * reason `inkBelow` is. Quadratic on purpose: a chart has tens of labels, not
108
+ * thousands, and a spatial index here would be code nobody can check by reading.
109
+ *
110
+ * ONE NODE'S OWN RECTS ARE SKIPPED. A `<text>` that Chrome breaks into two line
111
+ * boxes yields two runs which necessarily overlap; that is one label, and without
112
+ * this the sweep's very first run reported hundreds of them. The test is
113
+ * `TextRun.node`, not the string — see there for why comparing the string
114
+ * exempted real collisions too.
115
+ */
116
+ export declare function overprints(runs: readonly TextRun[], minOverlap?: number): Overprint[];
117
+ /** One scene's collisions at one stop. */
118
+ export interface Overprinted {
119
+ sid: string;
120
+ t: number;
121
+ pairs: Overprint[];
122
+ }
123
+ /**
124
+ * Findings for the scenes whose chart labels print through each other.
125
+ *
126
+ * One finding per SCENE, not per pair and not per stop — the same shape, and the
127
+ * same reason, as `gradeFidelity`. `line-chart` at sixteen points collides 69
128
+ * times at one stop; sixty-nine lines saying "two labels overlap" is a report
129
+ * nobody reads, and the defect is one defect: the chart does not have room for
130
+ * its labels. The worst stop is named because that is the frame to open, and
131
+ * three example pairs because a repair needs to know WHICH labels.
132
+ */
133
+ export declare function gradeOverprint(rows: readonly Overprinted[]): Finding[];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Invariant 5, measured on the artifact instead of guessed from the input.
3
+ *
4
+ * WHY THIS EXISTS AT ALL. The floor is enforced at emit time — seven archetypes
5
+ * refuse a beat they cannot lay out above it — but the enforcement is a mixture
6
+ * of measurement and PROXY, and the proxies are where it leaks. The clearest one
7
+ * is `src/emit/archetypes/equation-walk.ts:187`, which picks a font size from
8
+ * `tex.length`: 68px past 120 characters, 80 past 90, 92 past 55. A character
9
+ * count is not a width. `\alpha` is six characters and one glyph; `\!` is two
10
+ * characters and negative width. The review of `.planning/VOCABULARY.md` found
11
+ * the same substitution being used as a DEFECT TEST — `coherence.mjs:92` scored a
12
+ * headline as broken at "73 chars > 60" — built the deck, and found the
13
+ * 73-character headline wrapping to two lines at full size, looking correct
14
+ * (VOCABULARY-REVIEW §3.3). A rule that fires on text that renders fine is a rule
15
+ * people learn to ignore, and a rule that passes text that renders too small is
16
+ * worse. So nothing here counts characters. It reads the size the deck declares.
17
+ *
18
+ * REFERENCE SPACE, NOT CANVAS PIXELS. An archetype lays out in a unit system
19
+ * `refWidth` wide and `baseCss` zooms the finished scene onto the real canvas
20
+ * exactly once (`src/emit/kit.ts`, `REF_PULL`). Every absolute measurement in the
21
+ * vocabulary — including the floor — is therefore written in reference space and
22
+ * left untouched by the format: MEASURED on the demo, both `deck-16x9` and
23
+ * `short-9x16` declare the same smallest size, 40. At 1920 the zoom is exactly 1
24
+ * and reference px are canvas px; in portrait the same 40 deliberately lands at
25
+ * 30 canvas px, which is the whole argument of `REF_PULL` and not a violation. So
26
+ * the declared number is the number the floor is about, and the scan compares it
27
+ * to 40 as written, with no per-format normalisation to get wrong.
28
+ *
29
+ * WHAT IT DOES NOT SEE, stated because a gate that overclaims is the thing this
30
+ * file was written to stop. It reads declared sizes, so text shrunk by a
31
+ * `scale` below 1 at a hold reads as its unscaled size; and it says nothing about
32
+ * whether the text FITS — `container_overflow` and `text_occluded` in
33
+ * `hyperframes check` are the gates for that, and `check.ts` already grades them
34
+ * up to errors.
35
+ */
36
+ import type { Finding } from "../types.js";
37
+ /**
38
+ * Audience text never below this, in reference-space px (invariant 5).
39
+ *
40
+ * Inclusive: the demo declares exactly 40 in ten places, and a floor that failed
41
+ * its own reference deck would be a floor nobody could build against.
42
+ */
43
+ export declare const TYPE_FLOOR_PX = 40;
44
+ /**
45
+ * Every text size the composition declares, against the floor.
46
+ *
47
+ * `file` names the composition in the finding, the way `scanDeterminism` does —
48
+ * a deck can hold more than one and "some size is 28px" is not a repair.
49
+ */
50
+ export declare function scanTypeFloor(html: string, file: string, floorPx?: number): Finding[];
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@jokerized/decksmith",
3
+ "version": "0.1.0",
4
+ "description": "Turn a source document into an animated explanation deck.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ca1773130n/DeckSmith.git"
10
+ },
11
+ "homepage": "https://github.com/ca1773130n/DeckSmith#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/ca1773130n/DeckSmith/issues"
14
+ },
15
+ "keywords": [
16
+ "slides",
17
+ "presentation",
18
+ "deck",
19
+ "animation",
20
+ "gsap",
21
+ "video",
22
+ "paper",
23
+ "explainer",
24
+ "mcp"
25
+ ],
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "bin": {
30
+ "decksmith": "./dist/cli.js",
31
+ "decksmith-mcp": "./dist/mcp.js"
32
+ },
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/types/index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/types/index.d.ts",
38
+ "default": "./dist/index.js"
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md"
45
+ ],
46
+ "scripts": {
47
+ "build": "node scripts/build.mjs",
48
+ "prepare": "npm run build",
49
+ "typecheck": "tsc --noEmit",
50
+ "lint": "biome check .",
51
+ "format": "biome check --write .",
52
+ "test": "vitest run",
53
+ "check": "npm run typecheck && npm run lint && npm run test",
54
+ "score": "npm run build && node scripts/score.mjs",
55
+ "sweep": "npm run build && node scripts/sweep.mjs",
56
+ "build:server": "esbuild src/server/*.ts --outdir=dist/server --platform=node --target=node22 --format=esm --log-level=warning",
57
+ "build:mcp": "esbuild src/mcp/main.ts --bundle --outfile=dist/mcp.js --platform=node --target=node22 --format=esm --packages=external --log-level=warning",
58
+ "serve": "npm run build && npm run build:server && node dist/server/main.js",
59
+ "demo": "node demo/build.mjs && node dist/cli.js build demo/storyboard.json --source demo/source.json -o demo/deck && python3 -m http.server 8080 --directory demo/deck --bind 127.0.0.1"
60
+ },
61
+ "dependencies": {
62
+ "@modelcontextprotocol/sdk": "^1.30.0",
63
+ "@puppeteer/browsers": "^3.0.6",
64
+ "commander": "^14.0.1",
65
+ "fflate": "^0.8.3",
66
+ "gsap": "^3.14.2",
67
+ "hyperframes": "0.7.71",
68
+ "katex": "^0.16.11",
69
+ "puppeteer-core": "^25.3.0",
70
+ "remark-gfm": "^4.0.1",
71
+ "remark-math": "^6.0.0",
72
+ "remark-parse": "^11.0.0",
73
+ "unified": "^11.0.5",
74
+ "zod": "^4.1.12"
75
+ },
76
+ "devDependencies": {
77
+ "@biomejs/biome": "^2.3.14",
78
+ "@types/mdast": "^4.0.4",
79
+ "@types/node": "^22.19.0",
80
+ "esbuild": "^0.25.12",
81
+ "typescript": "^5.9.3",
82
+ "vitest": "^3.2.4"
83
+ }
84
+ }