@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.
- package/README.md +794 -0
- package/dist/cli.js +8906 -0
- package/dist/deck-runtime.js +55 -0
- package/dist/index.js +8590 -0
- package/dist/mcp.js +7809 -0
- package/dist/server/errors.js +73 -0
- package/dist/server/http.js +504 -0
- package/dist/server/main.js +91 -0
- package/dist/server/options.js +198 -0
- package/dist/server/pipeline.js +356 -0
- package/dist/server/queue.js +195 -0
- package/dist/server/ui.js +1614 -0
- package/dist/server/upload.js +232 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/deck/runtime.d.ts +59 -0
- package/dist/types/deck/subtitles.d.ts +101 -0
- package/dist/types/emit/archetypes/annotated-figure.d.ts +103 -0
- package/dist/types/emit/archetypes/bar-compare.d.ts +30 -0
- package/dist/types/emit/archetypes/callout.d.ts +7 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +9 -0
- package/dist/types/emit/archetypes/data-table.d.ts +21 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +2 -0
- package/dist/types/emit/archetypes/grid.d.ts +16 -0
- package/dist/types/emit/archetypes/index.d.ts +19 -0
- package/dist/types/emit/archetypes/line-chart.d.ts +22 -0
- package/dist/types/emit/archetypes/pipeline.d.ts +81 -0
- package/dist/types/emit/archetypes/split-compare.d.ts +2 -0
- package/dist/types/emit/archetypes/stack.d.ts +93 -0
- package/dist/types/emit/archetypes/title.d.ts +188 -0
- package/dist/types/emit/camera.d.ts +397 -0
- package/dist/types/emit/composition.d.ts +191 -0
- package/dist/types/emit/island.d.ts +19 -0
- package/dist/types/emit/kit.d.ts +256 -0
- package/dist/types/emit/svg.d.ts +177 -0
- package/dist/types/emit/theme.d.ts +65 -0
- package/dist/types/emit/themes/index.d.ts +40 -0
- package/dist/types/emit/themes/ink.d.ts +12 -0
- package/dist/types/emit/themes/mono.d.ts +20 -0
- package/dist/types/emit/themes/paper.d.ts +18 -0
- package/dist/types/index.d.ts +200 -0
- package/dist/types/mcp/main.d.ts +2 -0
- package/dist/types/mcp/prereqs.d.ts +22 -0
- package/dist/types/mcp/tools.d.ts +212 -0
- package/dist/types/narrate/narrate.d.ts +87 -0
- package/dist/types/narrate/tts.d.ts +134 -0
- package/dist/types/narrate/voices.d.ts +29 -0
- package/dist/types/pack/media.d.ts +61 -0
- package/dist/types/pack/pack.d.ts +16 -0
- package/dist/types/plan/codex.d.ts +24 -0
- package/dist/types/plan/duration.d.ts +394 -0
- package/dist/types/plan/prompt.d.ts +47 -0
- package/dist/types/plan/refs.d.ts +22 -0
- package/dist/types/plan/select.d.ts +116 -0
- package/dist/types/prefs.d.ts +43 -0
- package/dist/types/render/captions.d.ts +108 -0
- package/dist/types/render/ffmpeg.d.ts +129 -0
- package/dist/types/render/render.d.ts +123 -0
- package/dist/types/render/timing.d.ts +290 -0
- package/dist/types/server/errors.d.ts +11 -0
- package/dist/types/server/http.d.ts +57 -0
- package/dist/types/server/main.d.ts +1 -0
- package/dist/types/server/options.d.ts +90 -0
- package/dist/types/server/pipeline.d.ts +21 -0
- package/dist/types/server/queue.d.ts +105 -0
- package/dist/types/server/ui.d.ts +9 -0
- package/dist/types/server/upload.d.ts +107 -0
- package/dist/types/source/assets.d.ts +11 -0
- package/dist/types/source/fonts.d.ts +15 -0
- package/dist/types/source/markdown.d.ts +8 -0
- package/dist/types/types.d.ts +1992 -0
- package/dist/types/verify/budget.d.ts +41 -0
- package/dist/types/verify/check.d.ts +78 -0
- package/dist/types/verify/drift.d.ts +158 -0
- package/dist/types/verify/fidelity.d.ts +247 -0
- package/dist/types/verify/index.d.ts +207 -0
- package/dist/types/verify/overprint.d.ts +133 -0
- package/dist/types/verify/typefloor.d.ts +50 -0
- 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
|
+
}
|