@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.
- package/README.md +144 -8
- package/dist/cli.js +4582 -712
- package/dist/deck-runtime.js +70 -2
- package/dist/index.js +4608 -708
- package/dist/mcp.js +4254 -759
- package/dist/types/build/files.d.ts +13 -1
- package/dist/types/deck/motion.d.ts +64 -0
- package/dist/types/deck/playback.d.ts +206 -0
- package/dist/types/deck/repack.d.ts +10 -0
- package/dist/types/deck/runtime.d.ts +96 -1
- package/dist/types/deck/subtitles.d.ts +29 -1
- package/dist/types/emit/archetypes/annotated-figure.d.ts +1 -1
- package/dist/types/emit/archetypes/bar-compare.d.ts +0 -28
- package/dist/types/emit/archetypes/callout.d.ts +0 -5
- package/dist/types/emit/archetypes/claim-figure.d.ts +15 -0
- package/dist/types/emit/archetypes/equation-morph.d.ts +40 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +60 -1
- package/dist/types/emit/archetypes/grid.d.ts +0 -14
- package/dist/types/emit/archetypes/line-chart.d.ts +0 -8
- package/dist/types/emit/archetypes/pipeline.d.ts +15 -1
- package/dist/types/emit/archetypes/stack.d.ts +1 -1
- package/dist/types/emit/composition.d.ts +47 -2
- package/dist/types/emit/faces.d.ts +22 -0
- package/dist/types/emit/fit.d.ts +127 -0
- package/dist/types/emit/kit.d.ts +104 -2
- package/dist/types/emit/look.d.ts +138 -0
- package/dist/types/emit/motion.d.ts +244 -0
- package/dist/types/emit/svg.d.ts +54 -16
- package/dist/types/emit/tex.d.ts +39 -0
- package/dist/types/emit/theme.d.ts +1 -1
- package/dist/types/emit/themes/index.d.ts +19 -1
- package/dist/types/emit/themes/packs.d.ts +15 -0
- package/dist/types/emit/themes/pick.d.ts +104 -0
- package/dist/types/emit/type.d.ts +111 -0
- package/dist/types/index.d.ts +39 -2
- package/dist/types/plan/direct.d.ts +66 -0
- package/dist/types/plan/prompt.d.ts +0 -12
- package/dist/types/prefs.d.ts +15 -1
- package/dist/types/render/capture.d.ts +24 -0
- package/dist/types/render/timing.d.ts +16 -0
- package/dist/types/source/fonts.d.ts +11 -1
- package/dist/types/types.d.ts +38 -0
- package/dist/types/verify/budget.d.ts +8 -1
- package/dist/types/verify/fidelity.d.ts +53 -0
- package/dist/types/verify/fill.d.ts +205 -0
- package/dist/types/verify/index.d.ts +17 -0
- package/dist/types/verify/motion.d.ts +33 -0
- package/package.json +4 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DeckNarration } from "./emit/composition.js";
|
|
2
2
|
import type { Cut } from "./plan/select.js";
|
|
3
|
-
import { type Format, type Source, type Storyboard } from "./types.js";
|
|
3
|
+
import { type Design, type Format, type Source, type Storyboard } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Markdown (and LaTeX-ish markdown) in, typed `Source` out. The one parser we
|
|
6
6
|
* have; a consumer that already holds structured text can skip it and hand
|
|
@@ -103,10 +103,22 @@ export type { Deck, DeckNarration, DeckOptions } from "./emit/composition.js";
|
|
|
103
103
|
* itself, exported so it can be run against a budget no `Format` states.
|
|
104
104
|
*/
|
|
105
105
|
export { selectBeats } from "./plan/select.js";
|
|
106
|
+
/**
|
|
107
|
+
* `--design v2`: the Director that picks each beat's look, the look vocabulary
|
|
108
|
+
* it picks from, and the sameness numbers it reports. Exported so an eval can
|
|
109
|
+
* measure a corpus of storyboards without building each one.
|
|
110
|
+
*/
|
|
111
|
+
export { direct, summarize } from "./plan/direct.js";
|
|
112
|
+
export type { BeatLook, Direction, LookSummary } from "./plan/direct.js";
|
|
113
|
+
export { candidates, classicLook, LOOK_FILE, signature, VARIANTS } from "./emit/look.js";
|
|
114
|
+
export type { Look, Placement } from "./emit/look.js";
|
|
106
115
|
export type { Cut, Dangling, Dropped, DropRule, SelectionBudget } from "./plan/select.js";
|
|
107
116
|
/** Themes are a named, closed set; a consumer needs to enumerate and validate. */
|
|
108
117
|
export { resolveTheme, THEME_NAMES, THEMES } from "./emit/themes/index.js";
|
|
109
118
|
export type { DeckTheme } from "./emit/themes/index.js";
|
|
119
|
+
export { PACKS } from "./emit/themes/packs.js";
|
|
120
|
+
export { chooseLook, type Design, type LookChoice, pickTheme, rankPacks, } from "./emit/themes/pick.js";
|
|
121
|
+
export { TYPES, type TypeSpec } from "./emit/type.js";
|
|
110
122
|
/**
|
|
111
123
|
* The gates. `verify` is ours plus HyperFrames'; `check` is HyperFrames' alone
|
|
112
124
|
* and needs its CLI. A build that never opens a browser can still run `verify`
|
|
@@ -123,8 +135,11 @@ export { verify } from "./verify/index.js";
|
|
|
123
135
|
* `scanBeatCount` also fold into `verify`; that one needs the preferences the
|
|
124
136
|
* deck was asked for, which a built directory does not carry — and
|
|
125
137
|
* `scanUnusedFigures` folds in only where the source was passed alongside.
|
|
138
|
+
* `scanMath` does not fold into `verify`: by then the build has already said
|
|
139
|
+
* the same thing through `onBeatWarning`, and saying it twice trains people to
|
|
140
|
+
* stop reading it.
|
|
126
141
|
*/
|
|
127
|
-
export { scanBeatCount, scanNarrationDrift, scanPaperArc, scanHeadlines, scanNarrationLead, scanRepeatedObject, scanUnusedFigures, } from "./verify/index.js";
|
|
142
|
+
export { scanBeatCount, scanMath, scanNarrationDrift, scanPaperArc, scanHeadlines, scanNarrationLead, scanRepeatedObject, scanUnusedFigures, } from "./verify/index.js";
|
|
128
143
|
export { check, parseCheckReport, sampleTimes } from "./verify/check.js";
|
|
129
144
|
export type { CheckOptions } from "./verify/check.js";
|
|
130
145
|
/**
|
|
@@ -134,7 +149,25 @@ export type { CheckOptions } from "./verify/check.js";
|
|
|
134
149
|
* verdict. `verify` runs it; nothing else has to.
|
|
135
150
|
*/
|
|
136
151
|
export { gradeOverprint, MIN_OVERLAP, overprints } from "./verify/overprint.js";
|
|
152
|
+
/**
|
|
153
|
+
* Motion variety measured off a built composition: modal-entrance share, seam
|
|
154
|
+
* kinds, ease shares. The M4 exit metrics; see experiments/019-motion.
|
|
155
|
+
*/
|
|
156
|
+
export { motionStats, topTwoEaseShare } from "./verify/motion.js";
|
|
157
|
+
export type { MotionStats } from "./verify/motion.js";
|
|
137
158
|
export type { Overprint, Overprinted, TextRun } from "./verify/overprint.js";
|
|
159
|
+
/**
|
|
160
|
+
* The frame gate on its own, and the fill measure it carries: one browser over
|
|
161
|
+
* a built deck, every stop seeked and captured. `verify` runs it beside `check`;
|
|
162
|
+
* exported so an eval can read `fills` — main-axis fill at each scene's last
|
|
163
|
+
* hold, on a classic deck as on a v2 one — without running the slower half.
|
|
164
|
+
* `fillBand` and `FIT_FILE` are the definition and the manifest both sides use.
|
|
165
|
+
*/
|
|
166
|
+
export { fidelity } from "./verify/fidelity.js";
|
|
167
|
+
export type { FidelityReport } from "./verify/fidelity.js";
|
|
168
|
+
export { fillBand, FIT_FILE } from "./emit/fit.js";
|
|
169
|
+
export type { Fit, FillBand, FitManifest } from "./emit/fit.js";
|
|
170
|
+
export type { FillRow } from "./verify/fill.js";
|
|
138
171
|
/**
|
|
139
172
|
* The determinism gate: render the deck twice and compare. Separate from
|
|
140
173
|
* `verify` because it costs two full renders — minutes, not milliseconds — so
|
|
@@ -218,6 +251,10 @@ export interface BuildDeckOptions {
|
|
|
218
251
|
onBeatWarning?: (beatId: string, warning: string) => void;
|
|
219
252
|
/** Any name in `THEME_NAMES`. Overrides `storyboard.theme`. */
|
|
220
253
|
theme?: string;
|
|
254
|
+
/** `classic` (default) or `v2`. Overrides `storyboard.design`. See `designSchema`. */
|
|
255
|
+
design?: Design;
|
|
256
|
+
/** v2: the seed the style pack is ranked by. Default `storyboard.sourceId`. */
|
|
257
|
+
packSeed?: string;
|
|
221
258
|
/** Multiplies every duration and hold. 1 leaves the bytes untouched. */
|
|
222
259
|
speed?: number;
|
|
223
260
|
/** Default `FORMATS["deck-16x9"]`. */
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { EmitContext, Scene, Theme } from "../emit/kit.js";
|
|
2
|
+
import { type Look } from "../emit/look.js";
|
|
3
|
+
import type { Archetype, Beat, Design, Format, Source } from "../types.js";
|
|
4
|
+
export interface DirectOptions {
|
|
5
|
+
source: Source;
|
|
6
|
+
format: Format;
|
|
7
|
+
theme: Theme;
|
|
8
|
+
/** Normally `storyboard.sourceId`. */
|
|
9
|
+
seed: string;
|
|
10
|
+
/**
|
|
11
|
+
* The design every candidate is emitted under. `v2` from `emitDeck`, so each
|
|
12
|
+
* look is measured as the fit engine will actually draw it; absent in tests
|
|
13
|
+
* that exercise the Director on its own.
|
|
14
|
+
*/
|
|
15
|
+
design?: Design;
|
|
16
|
+
/** The emitter. `emitScene` unless a test substitutes one that misbehaves. */
|
|
17
|
+
emit?: (beat: Beat, ctx: EmitContext) => Scene;
|
|
18
|
+
}
|
|
19
|
+
/** One beat's decision, as `out/look.json` records it. */
|
|
20
|
+
export interface BeatLook {
|
|
21
|
+
beat: string;
|
|
22
|
+
archetype: Archetype;
|
|
23
|
+
variant: string;
|
|
24
|
+
placement: Look["placement"];
|
|
25
|
+
signature: string;
|
|
26
|
+
/** The chosen look's `Scene.fill`, when its emitter reports one. */
|
|
27
|
+
fill?: number;
|
|
28
|
+
/** Candidates that drew and kept the classic timing. Always ≥ 1. */
|
|
29
|
+
viable: number;
|
|
30
|
+
/** Candidates that were refused, and why — the emitter's own sentence. */
|
|
31
|
+
refused: {
|
|
32
|
+
signature: string;
|
|
33
|
+
reason: string;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
export interface Direction {
|
|
37
|
+
/** One per beat, in order: what `EmitContext.look` is set to. */
|
|
38
|
+
looks: Look[];
|
|
39
|
+
beats: BeatLook[];
|
|
40
|
+
summary: LookSummary;
|
|
41
|
+
}
|
|
42
|
+
export interface LookSummary {
|
|
43
|
+
beats: number;
|
|
44
|
+
distinct: number;
|
|
45
|
+
/** Share of beats whose chrome sits on top of the slide (title excluded from the numerator). */
|
|
46
|
+
modalChrome: number;
|
|
47
|
+
/** Adjacent pairs with the same signature. */
|
|
48
|
+
adjacentRepeats: number;
|
|
49
|
+
/** The four most-used signatures' share of beats. */
|
|
50
|
+
top4: number;
|
|
51
|
+
}
|
|
52
|
+
export declare function direct(beats: readonly Beat[], opts: DirectOptions): Direction;
|
|
53
|
+
/**
|
|
54
|
+
* The least share of the classic look's figure area another look may draw the
|
|
55
|
+
* figure at. The review (2026-10-08) measured v2's rail and foot looks shrinking
|
|
56
|
+
* the paper's own figure to 43-61% of its classic area (ja s4, ko s6, en s5) to
|
|
57
|
+
* make room for a headline: fill went up because white plate and bigger text
|
|
58
|
+
* count as ink, and the most informative thing on the slide got smaller. A
|
|
59
|
+
* look that keeps 80% or more is still a different slide; one below it is not
|
|
60
|
+
* worth the variety.
|
|
61
|
+
*/
|
|
62
|
+
export declare const FIGURE_FLOOR = 0.8;
|
|
63
|
+
/** The sameness numbers for one deck. Also used by the corpus measurement. */
|
|
64
|
+
export declare function summarize(beats: readonly Pick<BeatLook, "archetype" | "signature" | "placement">[]): LookSummary;
|
|
65
|
+
/** 32-bit FNV-1a. Deterministic, dependency-free, and enough to spread ties. */
|
|
66
|
+
export declare function fnv1a(s: string): number;
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What the model is told, and what it is told about.
|
|
3
|
-
*
|
|
4
|
-
* Planning quality is the product, and this file is where it is won or lost —
|
|
5
|
-
* the schema only guarantees the storyboard is well-formed, never that it is a
|
|
6
|
-
* good explanation of this particular document.
|
|
7
|
-
*
|
|
8
|
-
* `renderSource` deliberately puts every citable id next to its caption. The
|
|
9
|
-
* model cannot cite an id it was never shown, so the inventory is the cheapest
|
|
10
|
-
* defence against the failure mode that actually happens: a plausible-looking
|
|
11
|
-
* `figureId` that exists in no source.
|
|
12
|
-
*/
|
|
13
1
|
import type { Prefs } from "../prefs.js";
|
|
14
2
|
import { type Source } from "../types.js";
|
|
15
3
|
/**
|
package/dist/types/prefs.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
import { prefsSchema, type Source } from "./types.js";
|
|
2
|
+
import { type Design, prefsSchema, type Source } from "./types.js";
|
|
3
3
|
export type Prefs = z.infer<typeof prefsSchema>;
|
|
4
4
|
/**
|
|
5
5
|
* A partial Prefs. `narration` and `images` are the two nested objects, so they
|
|
@@ -33,6 +33,16 @@ export declare function loadPrefs(overrides?: PrefsPatch, cwd?: string, source?:
|
|
|
33
33
|
* schema, so there is exactly one place that decides what a legal tone is.
|
|
34
34
|
*/
|
|
35
35
|
export declare function prefsFromFlags(flags: PrefFlags): PrefsPatch;
|
|
36
|
+
/**
|
|
37
|
+
* The design a build uses: a `--design` flag or a config file, else what the
|
|
38
|
+
* storyboard was planned under, else `classic`.
|
|
39
|
+
*
|
|
40
|
+
* NOT `stated()`, which treats a value at its default as unsaid. `design` has
|
|
41
|
+
* no default — it is optional in the schema — so an explicit `classic` is
|
|
42
|
+
* distinguishable from silence, and it IS the rollback: it has to win over a
|
|
43
|
+
* storyboard planned under v2.
|
|
44
|
+
*/
|
|
45
|
+
export declare function designFor(prefs: Pick<Prefs, "design">, recorded?: Design): Design;
|
|
36
46
|
/** The flag surface the CLI exposes. Every one optional: absent means unstated. */
|
|
37
47
|
export interface PrefFlags {
|
|
38
48
|
slides?: string | number;
|
|
@@ -42,6 +52,10 @@ export interface PrefFlags {
|
|
|
42
52
|
genre?: string;
|
|
43
53
|
duration?: string | number;
|
|
44
54
|
theme?: string;
|
|
55
|
+
/** `--design classic | v2`. */
|
|
56
|
+
design?: string;
|
|
57
|
+
/** `--pack-seed`: what v2 hashes to pick a pack. */
|
|
58
|
+
packSeed?: string;
|
|
45
59
|
speed?: string | number;
|
|
46
60
|
narrate?: boolean;
|
|
47
61
|
voice?: string;
|
|
@@ -66,6 +66,30 @@ export interface ResolvedChrome {
|
|
|
66
66
|
export declare function resolveChrome(need?: string): Promise<ResolvedChrome>;
|
|
67
67
|
/** The path alone, for the callers that only ever wanted that. */
|
|
68
68
|
export declare function chromePath(need?: string): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Whether Chrome died for want of a sandbox. Stock Ubuntu 24.04, GitHub's
|
|
71
|
+
* `ubuntu-latest` included, sets `kernel.apparmor_restrict_unprivileged_userns=1`.
|
|
72
|
+
* Chrome's sandbox needs exactly those namespaces, so it exits at launch with
|
|
73
|
+
* `FATAL:zygote_host_impl_linux.cc(…)] No usable sandbox!`, which puppeteer
|
|
74
|
+
* quotes in the error it throws.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sandboxMissing(err: unknown): boolean;
|
|
77
|
+
/** How a person gets the sandbox back. */
|
|
78
|
+
export declare const SANDBOX_FIX = "`sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`, or give Chrome an AppArmor profile";
|
|
79
|
+
/**
|
|
80
|
+
* Launch Chrome for a page DeckSmith wrote itself: a built deck, a caption band.
|
|
81
|
+
*
|
|
82
|
+
* On a host with no usable sandbox (`sandboxMissing`) every gate that opens the
|
|
83
|
+
* deck reported `not_measured`, a warning, so `build` PASSED having measured
|
|
84
|
+
* nothing (.planning/2026-09-18-linux-determinism.md §4). The sandbox stays on
|
|
85
|
+
* wherever Chrome can have one. Only when Chrome refuses to start without it is
|
|
86
|
+
* the launch retried with `--no-sandbox`, and stderr says so. That is no wider
|
|
87
|
+
* than the render already is: hyperframes launches its own capture with
|
|
88
|
+
* `--no-sandbox` every time, on this same deck.
|
|
89
|
+
*
|
|
90
|
+
* Pages from the web never come here: `harvest` refuses instead.
|
|
91
|
+
*/
|
|
92
|
+
export declare function launchOwnPage<T>(launch: (args: string[]) => Promise<T>, args: string[], what: string): Promise<T>;
|
|
69
93
|
type Browser = Awaited<ReturnType<typeof import("puppeteer-core").launch>>;
|
|
70
94
|
type Page = Awaited<ReturnType<Browser["newPage"]>>;
|
|
71
95
|
/** An open deck, seekable and photographable. Close it. */
|
|
@@ -111,6 +111,22 @@ export interface Timing {
|
|
|
111
111
|
* written without it, and those decks did reserve nothing.
|
|
112
112
|
*/
|
|
113
113
|
captionReserve?: number;
|
|
114
|
+
/**
|
|
115
|
+
* The format the deck was built for: its id and its duration budget.
|
|
116
|
+
*
|
|
117
|
+
* The composition records only pixels, and `verify` used to find the budget
|
|
118
|
+
* by looking those up among the presets. A custom canvas matches none, so
|
|
119
|
+
* every custom build read "no duration budget applies" — true of the pixels
|
|
120
|
+
* and false of the deck, which inherits the budget of the profile it was
|
|
121
|
+
* resized from (`resizeFormat`). Optional, and absent falls back to the pixel
|
|
122
|
+
* lookup: every `timing.json` written before this lacks it.
|
|
123
|
+
*/
|
|
124
|
+
format?: {
|
|
125
|
+
id: string;
|
|
126
|
+
minWeight: number;
|
|
127
|
+
maxSeconds?: number;
|
|
128
|
+
warnSeconds?: number;
|
|
129
|
+
};
|
|
114
130
|
scenes: TimedScene[];
|
|
115
131
|
segments: TimedSegment[];
|
|
116
132
|
}
|
|
@@ -6,10 +6,20 @@ export interface FontBundle {
|
|
|
6
6
|
/** The woff2 basenames written into `dir`, beside `fonts.css`. */
|
|
7
7
|
files: string[];
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* The serif companion to `familyFor`: what a v2 pack whose type spec sets a role
|
|
11
|
+
* in a serif draws that role's CJK in. Without it every CJK deck set its
|
|
12
|
+
* headlines in Noto Sans whatever the pack said, so the font change that makes
|
|
13
|
+
* a serif pack a serif pack disappeared for three languages out of four
|
|
14
|
+
* (review, 2026-10-08).
|
|
15
|
+
*/
|
|
16
|
+
export declare function serifFamilyFor(lang: string): string | null;
|
|
9
17
|
/** The CJK family a language needs subset, or null: the deck ships Inter (`vendorInter`). */
|
|
10
18
|
export declare function familyFor(lang: string): string | null;
|
|
11
19
|
/**
|
|
12
20
|
* Write `dir/fonts.css` plus its woff2 for `lang`, covering `glyphs`. Returns
|
|
13
21
|
* null for languages the renderer already resolves, which needs no bundle.
|
|
14
22
|
*/
|
|
15
|
-
export declare function bundleFont(lang: string, glyphs: string, dir: string
|
|
23
|
+
export declare function bundleFont(lang: string, glyphs: string, dir: string, opts?: {
|
|
24
|
+
serif?: boolean;
|
|
25
|
+
}): Promise<FontBundle | null>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1001,11 +1001,35 @@ export declare const DIAGRAMMATIC: ReadonlySet<string>;
|
|
|
1001
1001
|
*/
|
|
1002
1002
|
export type ArchetypeFamily = "frame" | "structure" | "quantity" | "formal";
|
|
1003
1003
|
export declare const ARCHETYPE_FAMILY: Readonly<Record<Archetype, ArchetypeFamily>>;
|
|
1004
|
+
/**
|
|
1005
|
+
* Which generation of the look a deck is built in.
|
|
1006
|
+
*
|
|
1007
|
+
* `classic` is every deck built before 0.9 and stays byte-identical: it is what
|
|
1008
|
+
* an unset design means, so an npm user and every ink golden see no change. `v2`
|
|
1009
|
+
* opts into the redesign: the v2 player in deck.html (speed, CC, caption size,
|
|
1010
|
+
* remembered preferences — src/deck/playback.ts), the fit engine
|
|
1011
|
+
* (src/emit/fit.ts), a style pack per deck (src/emit/themes/pick.ts), varied
|
|
1012
|
+
* layouts (src/plan/direct.ts) and the motion grammar (src/emit/motion.ts). One
|
|
1013
|
+
* switch rather than one flag per feature, so a deck is always one coherent
|
|
1014
|
+
* design and `--design classic` is a complete rollback.
|
|
1015
|
+
*
|
|
1016
|
+
* OPTIONAL rather than defaulted in both schemas, so a storyboard, a config or
|
|
1017
|
+
* a pack that never mentions it serialises exactly as it did.
|
|
1018
|
+
*/
|
|
1019
|
+
export declare const designSchema: z.ZodEnum<{
|
|
1020
|
+
classic: "classic";
|
|
1021
|
+
v2: "v2";
|
|
1022
|
+
}>;
|
|
1023
|
+
export type Design = z.infer<typeof designSchema>;
|
|
1004
1024
|
export declare const storyboardSchema: z.ZodObject<{
|
|
1005
1025
|
sourceId: z.ZodString;
|
|
1006
1026
|
title: z.ZodString;
|
|
1007
1027
|
lang: z.ZodDefault<z.ZodString>;
|
|
1008
1028
|
theme: z.ZodDefault<z.ZodString>;
|
|
1029
|
+
design: z.ZodOptional<z.ZodEnum<{
|
|
1030
|
+
classic: "classic";
|
|
1031
|
+
v2: "v2";
|
|
1032
|
+
}>>;
|
|
1009
1033
|
beats: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1010
1034
|
narration: z.ZodOptional<z.ZodString>;
|
|
1011
1035
|
archetype: z.ZodLiteral<"title">;
|
|
@@ -1600,6 +1624,11 @@ export declare const prefsSchema: z.ZodObject<{
|
|
|
1600
1624
|
}>>;
|
|
1601
1625
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
1602
1626
|
theme: z.ZodDefault<z.ZodString>;
|
|
1627
|
+
design: z.ZodOptional<z.ZodEnum<{
|
|
1628
|
+
classic: "classic";
|
|
1629
|
+
v2: "v2";
|
|
1630
|
+
}>>;
|
|
1631
|
+
packSeed: z.ZodOptional<z.ZodString>;
|
|
1603
1632
|
animationSpeed: z.ZodDefault<z.ZodNumber>;
|
|
1604
1633
|
narration: z.ZodDefault<z.ZodObject<{
|
|
1605
1634
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -1739,6 +1768,11 @@ export declare const packSchema: z.ZodObject<{
|
|
|
1739
1768
|
}>>;
|
|
1740
1769
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
1741
1770
|
theme: z.ZodDefault<z.ZodString>;
|
|
1771
|
+
design: z.ZodOptional<z.ZodEnum<{
|
|
1772
|
+
classic: "classic";
|
|
1773
|
+
v2: "v2";
|
|
1774
|
+
}>>;
|
|
1775
|
+
packSeed: z.ZodOptional<z.ZodString>;
|
|
1742
1776
|
animationSpeed: z.ZodDefault<z.ZodNumber>;
|
|
1743
1777
|
narration: z.ZodDefault<z.ZodObject<{
|
|
1744
1778
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -1807,6 +1841,10 @@ export declare const packSchema: z.ZodObject<{
|
|
|
1807
1841
|
title: z.ZodString;
|
|
1808
1842
|
lang: z.ZodDefault<z.ZodString>;
|
|
1809
1843
|
theme: z.ZodDefault<z.ZodString>;
|
|
1844
|
+
design: z.ZodOptional<z.ZodEnum<{
|
|
1845
|
+
classic: "classic";
|
|
1846
|
+
v2: "v2";
|
|
1847
|
+
}>>;
|
|
1810
1848
|
beats: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1811
1849
|
narration: z.ZodOptional<z.ZodString>;
|
|
1812
1850
|
archetype: z.ZodLiteral<"title">;
|
|
@@ -26,6 +26,13 @@ export declare function readCanvas(html: string): Canvas | undefined;
|
|
|
26
26
|
* be its own is a gate people learn to ignore. Today the two 16:9 profiles have
|
|
27
27
|
* the same (absent) budget, so the reading is exact as well as safe.
|
|
28
28
|
*/
|
|
29
|
+
/** What the budget gate reads off a format, whether a preset or the one `build` recorded. */
|
|
30
|
+
export type BudgetFormat = Pick<Format, "id" | "minWeight" | "maxSeconds" | "warnSeconds">;
|
|
31
|
+
/** `timing.json`'s record of the format, with the canvas it was recorded for. */
|
|
32
|
+
export interface RecordedFormat extends BudgetFormat {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}
|
|
29
36
|
export declare function profilesFor(width: number, height: number, formats?: Readonly<Record<string, Format>>): Format[];
|
|
30
37
|
/**
|
|
31
38
|
* The budget finding for one built composition, if there is one.
|
|
@@ -46,4 +53,4 @@ export declare function profilesFor(width: number, height: number, formats?: Rea
|
|
|
46
53
|
* a table that disagrees, rather than the rule being deleted and silently absent
|
|
47
54
|
* the next time a real destination is tighter than the rest.
|
|
48
55
|
*/
|
|
49
|
-
export declare function scanBudget(html: string, storyboard?: Storyboard, kept?: readonly Beat[], formats?: Readonly<Record<string, Format
|
|
56
|
+
export declare function scanBudget(html: string, storyboard?: Storyboard, kept?: readonly Beat[], formats?: Readonly<Record<string, Format>>, recorded?: RecordedFormat): Finding[];
|
|
@@ -140,6 +140,7 @@
|
|
|
140
140
|
* a gate nobody runs; this one costs nothing, so it runs by default.
|
|
141
141
|
*/
|
|
142
142
|
import type { Finding } from "../types.js";
|
|
143
|
+
import { type FillRow } from "./fill.js";
|
|
143
144
|
/**
|
|
144
145
|
* Ink at a stop, as a fraction of the WHOLE frame — not of the measured band.
|
|
145
146
|
*
|
|
@@ -213,6 +214,11 @@ export interface FidelityOptions {
|
|
|
213
214
|
export declare function readReserve(timingText: string | null): number;
|
|
214
215
|
export interface FidelityReport {
|
|
215
216
|
stops: Measured[];
|
|
217
|
+
/**
|
|
218
|
+
* Main-axis fill at each scene's last hold — measured on every deck, graded
|
|
219
|
+
* only on a v2 one (`fit.json` present). See `./fill.ts`.
|
|
220
|
+
*/
|
|
221
|
+
fills: FillRow[];
|
|
216
222
|
/**
|
|
217
223
|
* Both gates' findings — this one's `blank_at_stop` and `overprint`'s
|
|
218
224
|
* `svg_text_overprint`.
|
|
@@ -252,6 +258,34 @@ export declare function decodePng(png: Uint8Array): Promise<Frame>;
|
|
|
252
258
|
* of those have been wrong in this project inside the last week.
|
|
253
259
|
*/
|
|
254
260
|
export declare function inkBelow(frame: Frame, bandTopPx: number): number;
|
|
261
|
+
/**
|
|
262
|
+
* Where a scene's BODY is, in device px: the part of the frame that is not its
|
|
263
|
+
* chrome. Classic chrome is a block across the top, so the body is everything
|
|
264
|
+
* below its bottom edge — exactly what `inkBelow` measured, and still is. The
|
|
265
|
+
* `--design v2` placements (src/emit/look.ts) move the chrome, and "below the
|
|
266
|
+
* headline" stops meaning "the body": under a `foot` headline there is nothing
|
|
267
|
+
* at all by design, and beside a `rail` the body is the column to its right.
|
|
268
|
+
* Measured where the body is, rather than below a headline that is no longer
|
|
269
|
+
* on top, the same floor means the same thing.
|
|
270
|
+
*/
|
|
271
|
+
export interface BodyRegion {
|
|
272
|
+
top: number;
|
|
273
|
+
left: number;
|
|
274
|
+
bottom: number;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Non-background pixels inside `region`, over the WHOLE frame's pixels.
|
|
278
|
+
*
|
|
279
|
+
* `plate` is the deck's bare background (every scene hidden), when the build
|
|
280
|
+
* captured one — a v2 deck does, for the fill gate. Against it each pixel is
|
|
281
|
+
* compared with the same pixel of the empty ground. Against the frame's modal
|
|
282
|
+
* colour alone a style pack's painted ground — signal's glow, blueprint's grid
|
|
283
|
+
* — read as ink: with every scene hidden, 4.42% of a signal frame and 2.40% of
|
|
284
|
+
* a blueprint one measured "inked", 29x and 16x `INK_FLOOR`, so a stop with
|
|
285
|
+
* nothing drawn on it passed this gate by a wide margin (review, 2026-10-08).
|
|
286
|
+
* A plate of another size is ignored.
|
|
287
|
+
*/
|
|
288
|
+
export declare function inkIn(frame: Frame, region: BodyRegion, plate?: Frame | null): number;
|
|
255
289
|
/**
|
|
256
290
|
* The stops a built deck declares, from `timing.json` if it is there and the
|
|
257
291
|
* slideshow island if it is not.
|
|
@@ -291,6 +325,25 @@ export declare function gradeFidelity(rows: readonly Measured[], floor?: number)
|
|
|
291
325
|
* thing it was told to deliver, not a note about taste.
|
|
292
326
|
*/
|
|
293
327
|
export declare function gradeReserve(rows: readonly Measured[], tolerance?: number): Finding[];
|
|
328
|
+
/** An equation the deck's own fit gave up on: the scene, and the formula. */
|
|
329
|
+
export interface Unfit {
|
|
330
|
+
sid: string;
|
|
331
|
+
tex: string;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Run IN THE PAGE: every display `equation-walk`'s fit marked as not fitting.
|
|
335
|
+
*
|
|
336
|
+
* Read once, after the ready gate — the fit runs inside it, before any frame,
|
|
337
|
+
* so the marks are the same at every time the deck could be seeked to.
|
|
338
|
+
*/
|
|
339
|
+
export declare function collectUnfit(attr: string): Unfit[];
|
|
340
|
+
/**
|
|
341
|
+
* An error per equation that does not fit its box at the type floor even broken
|
|
342
|
+
* across lines, NAMING THE FORMULA. The layout gate sees the same overflow and
|
|
343
|
+
* can only say `span.mord`, which tells nobody which of a deck's equations to
|
|
344
|
+
* shorten.
|
|
345
|
+
*/
|
|
346
|
+
export declare function gradeUnfit(rows: readonly Unfit[]): Finding[];
|
|
294
347
|
/**
|
|
295
348
|
* Seek every declared stop and count the ink below its caption.
|
|
296
349
|
*
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FILL GATE — at each scene's last hold, how much of its region did the
|
|
3
|
+
* body actually paint, and did the build predict that?
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. The founder's complaint was "the content doesn't fill the
|
|
6
|
+
* screen", and v0.8.0 passes every gate while doing exactly that: the solvers
|
|
7
|
+
* could only shrink, so a two-bar comparison drew two 96px bars in a ~700px
|
|
8
|
+
* region and `.scene` centred the rest into air. The v2 archetypes grow into
|
|
9
|
+
* their region and PREDICT the result (`Scene.fit`, `src/emit/fit.ts`); a
|
|
10
|
+
* prediction nobody checks is the green-gate-over-wrong-output shape this
|
|
11
|
+
* project keeps producing, so the browser measures the same ratio here, on the
|
|
12
|
+
* frame `fidelity` has already seeked and captured.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IS MEASURED: MAIN-AXIS FILL, IN PIXELS. The region is the room the body
|
|
15
|
+
* was given: `.scene`'s content box less the height of its `.headline` and
|
|
16
|
+
* `.eyebrow` (see `FillRegion` for why that is not "everything under the
|
|
17
|
+
* headline"). The body's extent is the first to the last pixel ROW, from the
|
|
18
|
+
* chrome's bottom edge down to the frame's bottom edge, that holds at least
|
|
19
|
+
* `MIN_ROW_INK` pixels off the frame's modal background (the same
|
|
20
|
+
* background-relative test `fidelity` uses, so it is theme-independent). Fill is
|
|
21
|
+
* that extent over the region's height. Scanning past the content box is
|
|
22
|
+
* deliberate: a body that spills into the padding reads above 1, which is the
|
|
23
|
+
* SPILLAGE band rather than a silently clipped 1.0.
|
|
24
|
+
*
|
|
25
|
+
* PIXELS, NOT DOM BOXES, for the reason `fidelity.ts` gives at length: a
|
|
26
|
+
* wrapper's box is the size of its slot, not of what it shows — `.titleslide` is
|
|
27
|
+
* `height:100%`, `.panels` is `flex:1` — and visibility read off the DOM has been
|
|
28
|
+
* wrong in this project before. A panel wash too close to the background to
|
|
29
|
+
* clear `INK_DELTA` does not count; its border and its text do.
|
|
30
|
+
*
|
|
31
|
+
* ALSO REPORTED, for the eval and never graded: `cross`, the same ratio across
|
|
32
|
+
* the region's width, and `canvas`, the union box of ALL ink on the frame
|
|
33
|
+
* (chrome included) over the frame's area — the "bbox" share the 2026-10-07
|
|
34
|
+
* density audit quoted for bar-compare and pipeline (51%).
|
|
35
|
+
*
|
|
36
|
+
* AND TWO AREA MEASURES, because `fill` is an EXTENT and cannot see area: two
|
|
37
|
+
* thin bars spanning a 1700px axis, a white figure plate, or cards with empty
|
|
38
|
+
* bottom halves all read full (review, 2026-10-08). `occupancy` cuts the whole
|
|
39
|
+
* frame (less a 3% border) into 40px cells, at 1080p scale, and reports the
|
|
40
|
+
* share holding ink (`cells`) and the share whose pixels VARY (`detail`): a
|
|
41
|
+
* flat run of colour — a plate's empty white, a panel's wash, a bar's inside —
|
|
42
|
+
* is ink but not detail. Both are placement-independent: they do not move when
|
|
43
|
+
* a look redefines where its body region is.
|
|
44
|
+
*
|
|
45
|
+
* GRADED ONLY ON A V2 DECK, i.e. one whose build wrote `fit.json`. A classic deck
|
|
46
|
+
* is measured (so the eval can compare the two designs on the same storyboard)
|
|
47
|
+
* but never gets a finding: v0.8.0's verdicts do not move. Both findings are
|
|
48
|
+
* WARNINGS, as the plan's M1 sets them — a new gate starts by reporting:
|
|
49
|
+
*
|
|
50
|
+
* hollow_at_hold measured fill under 0.70, the EMPTY band
|
|
51
|
+
* fill_model_disagrees |predicted − measured| over 0.20 — the build-time
|
|
52
|
+
* arithmetic and the browser disagree, which is a bug
|
|
53
|
+
* in the model, not a matter of taste
|
|
54
|
+
*/
|
|
55
|
+
import { type FitManifest } from "../emit/fit.js";
|
|
56
|
+
import type { Finding } from "../types.js";
|
|
57
|
+
/** A row counts as painted from this many off-background pixels: one is a stray fringe. */
|
|
58
|
+
export declare const MIN_ROW_INK = 2;
|
|
59
|
+
/** Predicted and measured fill may differ by this much before the model is called wrong. */
|
|
60
|
+
export declare const FILL_TOLERANCE = 0.2;
|
|
61
|
+
/** Device px, in the frame's own coordinates. */
|
|
62
|
+
export interface Box {
|
|
63
|
+
left: number;
|
|
64
|
+
right: number;
|
|
65
|
+
top: number;
|
|
66
|
+
bottom: number;
|
|
67
|
+
}
|
|
68
|
+
/** The pixel test `fidelity` uses: what counts as ink, and against what. */
|
|
69
|
+
export interface InkTest {
|
|
70
|
+
bg: readonly [number, number, number];
|
|
71
|
+
delta: number;
|
|
72
|
+
/**
|
|
73
|
+
* The page's background as a FRAME — every scene hidden, the body's own paint
|
|
74
|
+
* left — compared pixel for pixel instead of against `bg`. A v2 style pack
|
|
75
|
+
* paints its ground with gradients, a grid or dots (src/emit/themes/packs.ts),
|
|
76
|
+
* so no single colour is the background: a 7% cyan grid line on blueprint is
|
|
77
|
+
* 12 levels off its navy, and atlas's vignette darkens the corners by far more.
|
|
78
|
+
* Against one colour those read as ink and a sparse slide measured full. When
|
|
79
|
+
* its size differs from the frame's it is ignored and `bg` is used.
|
|
80
|
+
*/
|
|
81
|
+
plate?: FrameLike;
|
|
82
|
+
}
|
|
83
|
+
interface FrameLike {
|
|
84
|
+
width: number;
|
|
85
|
+
height: number;
|
|
86
|
+
channels: number;
|
|
87
|
+
pixels: Uint8Array;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The extent of ink inside `box`, or null when there is none. Pure, so what the
|
|
91
|
+
* gate counts can be tested without a browser.
|
|
92
|
+
*
|
|
93
|
+
* A row is painted when it holds `MIN_ROW_INK` ink pixels between `box.left` and
|
|
94
|
+
* `box.right`; the vertical extent is the first and last such row. The
|
|
95
|
+
* horizontal extent is then read over those rows only, by the same rule turned
|
|
96
|
+
* ninety degrees.
|
|
97
|
+
*/
|
|
98
|
+
export declare function inkExtent(frame: FrameLike, test: InkTest, box: Box): Box | null;
|
|
99
|
+
/**
|
|
100
|
+
* A scene's body region, as the page reports it: where to LOOK for the body's
|
|
101
|
+
* ink (`top` is the chrome's bottom edge, `bottom` the content box's), and how
|
|
102
|
+
* tall the region IS (`height`).
|
|
103
|
+
*
|
|
104
|
+
* THE TWO DIFFER ON PURPOSE, and the difference is the defect being measured.
|
|
105
|
+
* `.scene` centres its column, so a short body pushes the headline DOWN by half
|
|
106
|
+
* the slack — and a region drawn from the headline's bottom edge to the content
|
|
107
|
+
* bottom silently drops the other half, the band of nothing ABOVE the headline.
|
|
108
|
+
* The first version did exactly that and called a two-bar chart with 250px of
|
|
109
|
+
* empty canvas over its eyebrow 70% full. So the height is the content box less
|
|
110
|
+
* the chrome's own height, wherever the centring put it: the room the body was
|
|
111
|
+
* given, which is the same number `bodyBudget` hands the emitter.
|
|
112
|
+
*/
|
|
113
|
+
export interface FillRegion extends Box {
|
|
114
|
+
height: number;
|
|
115
|
+
/**
|
|
116
|
+
* How far down body ink is looked for. Absent: the frame's bottom edge, so a
|
|
117
|
+
* body that spills past its region is measured, not clipped. A `foot` slide
|
|
118
|
+
* sets it to the region's bottom, because what lies under that region is the
|
|
119
|
+
* slide's own headline, not spill.
|
|
120
|
+
*/
|
|
121
|
+
scanBottom?: number;
|
|
122
|
+
/**
|
|
123
|
+
* The page's own background, `[r, g, b]`, or null when it is not an opaque
|
|
124
|
+
* colour. What "ink" is measured against — see `fillInk`.
|
|
125
|
+
*/
|
|
126
|
+
bg: [number, number, number] | null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run IN THE PAGE: the scene's body region, in device px. Null when the scene
|
|
130
|
+
* is not in the document.
|
|
131
|
+
*
|
|
132
|
+
* The content box is `.scene`'s padding box, read off computed style rather
|
|
133
|
+
* than restated from `kit.ts`, so a deck built with a caption reserve (bottom
|
|
134
|
+
* padding grows) is measured against the box it actually laid out in.
|
|
135
|
+
*/
|
|
136
|
+
export declare function collectFillRegion(sid: string): FillRegion | null;
|
|
137
|
+
/**
|
|
138
|
+
* The ink test for a fill measurement: the page's declared background when the
|
|
139
|
+
* region reported one, else the frame's modal colour, at `delta`.
|
|
140
|
+
*
|
|
141
|
+
* NOT the modal colour by default, and the reason is this gate's own success.
|
|
142
|
+
* `fidelity` takes the background to be the frame's most common colour, which
|
|
143
|
+
* holds while a slide is mostly air — "80–98% of every frame measured". A v2
|
|
144
|
+
* callout whose panels grew into their region covers MORE of the frame with
|
|
145
|
+
* panel wash than with background; the modal colour is then the panel's, the
|
|
146
|
+
* real background reads as ink, and a 0.95 hold measured 1.107 with a canvas
|
|
147
|
+
* share of 1.0 (HypePaper ko deck 894a874a, s18, 2026-10-07). The body
|
|
148
|
+
* stylesheet says what the background is, so that is what is asked.
|
|
149
|
+
*/
|
|
150
|
+
export declare function fillInk(region: FillRegion | null, modal: readonly [number, number, number], delta: number, plate?: FrameLike | null): InkTest;
|
|
151
|
+
/**
|
|
152
|
+
* Run IN THE PAGE: hide (`true`) or restore (`false`) every scene, so one
|
|
153
|
+
* screenshot between the two is the deck's bare background — `InkTest.plate`.
|
|
154
|
+
* Opacity on the scene roots rather than visibility, because GSAP's `autoAlpha`
|
|
155
|
+
* writes `visibility:visible` on descendants and would show through.
|
|
156
|
+
*/
|
|
157
|
+
export declare function hideScenes(on: boolean): void;
|
|
158
|
+
/** One scene's last hold, measured. */
|
|
159
|
+
export interface FillRow {
|
|
160
|
+
sid: string;
|
|
161
|
+
t: number;
|
|
162
|
+
/** Main-axis (vertical) fill: body ink extent over region height. Above 1 spills. */
|
|
163
|
+
fill: number;
|
|
164
|
+
/** The same across the region's width. Reported, not graded. */
|
|
165
|
+
cross: number;
|
|
166
|
+
/** Union box of every ink pixel on the frame, chrome included, over the frame's area. */
|
|
167
|
+
canvas: number;
|
|
168
|
+
/** Region height in device px — 0 when the scene had no region to measure. */
|
|
169
|
+
region: number;
|
|
170
|
+
/** Share of the frame's cells holding ink, and of cells whose pixels vary. See `occupancy`. */
|
|
171
|
+
cells: number;
|
|
172
|
+
detail: number;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* How much of the frame carries content, by area. Pure.
|
|
176
|
+
*
|
|
177
|
+
* `cells`: share of cells with at least `CELL_MIN_INK` of their pixels off the
|
|
178
|
+
* ground (the ink test `fidelity` uses — the bare-background plate when there is
|
|
179
|
+
* one). `detail`: share of cells that hold ink AND whose pixels span more than
|
|
180
|
+
* the ink threshold in luma — an edge, a glyph, a picture — so a flat slab of
|
|
181
|
+
* one colour counts as ink and not as detail.
|
|
182
|
+
*/
|
|
183
|
+
export declare function occupancy(frame: FrameLike, test: InkTest): {
|
|
184
|
+
cells: number;
|
|
185
|
+
detail: number;
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Fill at one stop, from a decoded frame and the region the page reported.
|
|
189
|
+
* Pure. A scene with no region (not found) or no ink reads 0, which grades as
|
|
190
|
+
* hollow — "nothing painted below the headline" is exactly what that is.
|
|
191
|
+
*/
|
|
192
|
+
export declare function measureFill(frame: FrameLike, test: InkTest, region: FillRegion | null, stop: {
|
|
193
|
+
sid: string;
|
|
194
|
+
t: number;
|
|
195
|
+
}): FillRow;
|
|
196
|
+
/** The last declared stop of each scene: the final hold the fill is judged at. */
|
|
197
|
+
export declare function finalStops<S extends {
|
|
198
|
+
sid: string;
|
|
199
|
+
t: number;
|
|
200
|
+
}>(stops: readonly S[]): S[];
|
|
201
|
+
/** Parse `fit.json`. Null when the deck has none — a classic build — or it does not parse. */
|
|
202
|
+
export declare function readFitManifest(text: string | null): FitManifest | null;
|
|
203
|
+
/** Findings for a v2 deck's measured fills. Nothing at all for a classic deck. */
|
|
204
|
+
export declare function gradeFill(rows: readonly FillRow[], manifest: FitManifest | null): Finding[];
|
|
205
|
+
export {};
|