@tycoworks/tycoslide 0.8.0 → 0.10.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 (54) hide show
  1. package/README.md +12 -9
  2. package/SKILL.md +6 -7
  3. package/dist/cli.js +39 -59
  4. package/dist/engine/fillers/filler.d.ts +21 -13
  5. package/dist/engine/fillers/filler.js +21 -24
  6. package/dist/engine/generate.d.ts +23 -17
  7. package/dist/engine/generate.js +157 -70
  8. package/dist/engine/index.d.ts +1 -1
  9. package/dist/engine/types.d.ts +39 -9
  10. package/dist/index.d.ts +15 -21
  11. package/dist/index.js +63 -88
  12. package/dist/manifest.js +17 -25
  13. package/dist/markdown/blocks/code.d.ts +17 -0
  14. package/dist/markdown/blocks/code.js +65 -0
  15. package/dist/markdown/blocks/image.d.ts +2 -0
  16. package/dist/markdown/blocks/image.js +9 -0
  17. package/dist/markdown/blocks/mermaid.d.ts +15 -0
  18. package/dist/markdown/blocks/mermaid.js +227 -0
  19. package/dist/markdown/{resolvers → blocks}/mermaidTheme.d.ts +1 -1
  20. package/dist/markdown/{resolvers → blocks}/mermaidTheme.js +1 -1
  21. package/dist/markdown/blocks/registry.d.ts +16 -0
  22. package/dist/markdown/blocks/registry.js +44 -0
  23. package/dist/markdown/blocks/table.d.ts +2 -0
  24. package/dist/markdown/blocks/table.js +23 -0
  25. package/dist/markdown/blocks/text.d.ts +12 -0
  26. package/dist/markdown/blocks/text.js +90 -0
  27. package/dist/markdown/deckCompiler.d.ts +17 -20
  28. package/dist/markdown/deckCompiler.js +143 -113
  29. package/dist/markdown/index.d.ts +11 -11
  30. package/dist/markdown/index.js +9 -8
  31. package/dist/markdown/inline.d.ts +26 -0
  32. package/dist/markdown/inline.js +136 -0
  33. package/dist/markdown/mdast.d.ts +25 -0
  34. package/dist/markdown/mdast.js +49 -0
  35. package/dist/markdown/schema/deckSchema.d.ts +30 -0
  36. package/dist/markdown/schema/deckSchema.js +51 -0
  37. package/dist/markdown/schema/strict.d.ts +9 -0
  38. package/dist/markdown/schema/strict.js +18 -0
  39. package/dist/markdown/schema/themeConfigSchema.d.ts +106 -0
  40. package/dist/markdown/schema/themeConfigSchema.js +147 -0
  41. package/dist/markdown/types.d.ts +194 -134
  42. package/dist/markdown/types.js +34 -23
  43. package/dist/skillZip.d.ts +17 -0
  44. package/dist/skillZip.js +35 -0
  45. package/package.json +7 -3
  46. package/syntax.md +1 -1
  47. package/dist/markdown/parsers.d.ts +0 -32
  48. package/dist/markdown/parsers.js +0 -233
  49. package/dist/markdown/resolvers/code.d.ts +0 -17
  50. package/dist/markdown/resolvers/code.js +0 -44
  51. package/dist/markdown/resolvers/mermaid.d.ts +0 -14
  52. package/dist/markdown/resolvers/mermaid.js +0 -81
  53. package/dist/markdown/resolvers/resolver.d.ts +0 -42
  54. package/dist/markdown/resolvers/resolver.js +0 -52
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # tycoslide
2
2
 
3
- Let AI agents build slides from existing PowerPoint (`.pptx`) templates.
3
+ Create editable, on-brand PowerPoint slides from markdown.
4
4
 
5
5
  > **Early release** — tycoslide is under active development.
6
6
 
7
7
  ## How it works
8
8
 
9
- 1. **tycoslide wraps your PowerPoint file as an agent skill.**
10
- 2. **Your agents use the skill to write slides in markdown.**
11
- 3. **tycoslide builds a finished PowerPoint file from the markdown.**
9
+ 1. **tycoslide wraps existing PowerPoint files as reusable templates.**
10
+ 2. **You (or an agent) write slides in markdown.**
11
+ 3. **tycoslide builds new PowerPoint files.**
12
12
 
13
13
  ## Quick Start
14
14
 
@@ -42,16 +42,15 @@ title: Highlights
42
42
  Build:
43
43
 
44
44
  ```bash
45
- tycoslide build deck.md # → deck.pptx
45
+ npx tycoslide build deck.md # → deck.pptx
46
46
  ```
47
47
 
48
48
  ## CLI
49
49
 
50
50
  ```bash
51
- tycoslide build deck.md # markdown → PPTX (theme resolved from deck frontmatter)
52
- tycoslide build deck.md --no-notes # omit speaker notes from the output
53
- tycoslide plugin # generate AI agent plugin package
54
- tycoslide manifest # print layout + asset catalog to stdout
51
+ npx tycoslide build deck.md # markdown → PPTX (theme resolved from deck frontmatter)
52
+ npx tycoslide build deck.md --no-notes # omit speaker notes from the output
53
+ npx tycoslide package # generate the Agent Skill (skill.md, syntax.md, manifest.json at the theme root) + an uploadable <package-name>.zip
55
54
  ```
56
55
 
57
56
  ## Theme Structure
@@ -65,6 +64,10 @@ my-theme/
65
64
  assets/icons/
66
65
  theme.json
67
66
  package.json
67
+ skill.md # generated by `tycoslide package`
68
+ syntax.md # generated by `tycoslide package`
69
+ manifest.json # generated by `tycoslide package`
70
+ <package-name>.zip # uploadable Agent Skill bundle
68
71
  ```
69
72
 
70
73
  **Template** — the PPTX file with named shapes that tycoslide fills.
package/SKILL.md CHANGED
@@ -1,17 +1,15 @@
1
1
  ---
2
2
  name: slides
3
3
  description: >
4
- Use this skill any time the user wants to create branded slides, presentations,
5
- pitch decks, or sales collateral as a .pptx. Trigger whenever the user mentions "deck," "slides,"
6
- "presentation," "pitch," or .pptx output. Also trigger when the user says "build me a deck,"
7
- "make slides about X," or "turn this into a presentation."
4
+ Build branded slides, presentations, pitch decks, or sales collateral as a .pptx.
5
+ Trigger on "deck," "slides," "presentation," "pitch," ".pptx," or "build me a deck."
8
6
  ---
9
7
 
10
8
  # slides
11
9
 
12
10
  ## Setup
13
11
 
14
- Before first use, install dependencies from the plugin root:
12
+ Before first use, install dependencies from the theme root:
15
13
 
16
14
  ```bash
17
15
  npm install
@@ -48,7 +46,7 @@ A layout's inputs split two ways (see [syntax.md](syntax.md) for details):
48
46
 
49
47
  A single physical slide may back multiple layouts. When two manifest entries share the same `slideNumber`, they render into the same underlying PPTX shapes but declare their fill differently -- e.g. one layout exposing the fill as an `image` parameter and a sibling exposing it as a `mermaid` slot. Pick between them by naming the layout you want in frontmatter (`layout: Full bleed diagram` vs `layout: Full bleed image`); the declaration is unambiguous per layout, so the compiler always knows how to interpret the content you provide.
50
48
 
51
- Study each layout's `whenToUse` and `limit`s before writing any slides. These are your primary guide for matching content to layouts.
49
+ Study each layout's `slots` and `limit`s before writing any slides.
52
50
 
53
51
  ---
54
52
 
@@ -131,7 +129,7 @@ The deck is written to your current working directory (not inside the skill).
131
129
 
132
130
  ### For Each Slide
133
131
 
134
- **Every slide communicates one idea.** If you're writing more than 5 bullets or 3 paragraphs, split into two slides.
132
+ **Every slide communicates one idea.** If you're writing more than 5 bullets or 3 paragraphs, split into two slides. Put the takeaway in the headline, then support it.
135
133
 
136
134
  Check each layout's `limit` in the manifest for content density constraints. When content overflows, split across slides.
137
135
 
@@ -143,6 +141,7 @@ Check each layout's `limit` in the manifest for content density constraints. Whe
143
141
  - **Don't open with a body/content layout** -- use the Title layout for impact
144
142
  - **Don't skip section dividers** -- for decks over 5 slides, use Section title layouts to group sections
145
143
  - **Don't restyle the layout** -- the theme owns all design; you only fill slots
144
+ - **Don't use an image that's wrong for the slot** -- a small slot wants a simple icon, not a dense illustration; if the build warns an image shrank to a small %, swap it for a simpler one
146
145
  - **Don't invent layout or asset names** -- only use what exists in the manifest
147
146
  - **Don't leave required parameters or slots empty** -- and don't leave a placeholder logo or dummy text in an image parameter you care about
148
147
  - **Don't mix dark and light** -- keep one variant across the deck
package/dist/cli.js CHANGED
@@ -1,31 +1,21 @@
1
- import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { Command } from "commander";
5
5
  import { buildDeck } from "./index.js";
6
6
  import { generateManifest } from "./manifest.js";
7
- import { compileDeck, parseSlideDocument, RESERVED_KEY, } from "./markdown/index.js";
7
+ import { compileDeck, loadThemeConfig, parseSlideDocument, RESERVED_KEY } from "./markdown/index.js";
8
+ import { renameSkill, zipSkill } from "./skillZip.js";
8
9
  const DEFAULT_CONFIG = "theme.json";
9
- const SKILL_DIR = "skills/slides";
10
- const PLUGIN_DIR = ".claude-plugin";
11
- const PLUGIN_FILE = "plugin.json";
12
10
  const MANIFEST_FILE = "manifest.json";
13
- const SKILL_FILE = "SKILL.md";
11
+ // The theme skill is written as lowercase skill.md (copied from tycoslide's own
12
+ // SKILL.md), so the skill folder can live at the theme repo root.
13
+ const SKILL_FILE = "skill.md";
14
14
  const SYNTAX_FILE = "syntax.md";
15
15
  const BUILD_COMMAND = "npx tycoslide build";
16
16
  const sdkDir = dirname(fileURLToPath(import.meta.url));
17
- const skillMdPath = resolve(sdkDir, "..", SKILL_FILE);
17
+ const skillMdPath = resolve(sdkDir, "..", "SKILL.md");
18
18
  const syntaxMdPath = resolve(sdkDir, "..", SYNTAX_FILE);
19
- function loadConfig(absPath) {
20
- let raw;
21
- try {
22
- raw = JSON.parse(readFileSync(absPath, "utf-8"));
23
- }
24
- catch {
25
- throw new Error(`Config file not found or invalid JSON: ${absPath}`);
26
- }
27
- return { ...raw, rootDir: dirname(absPath) };
28
- }
29
19
  const pkg = JSON.parse(readFileSync(resolve(sdkDir, "..", "package.json"), "utf-8"));
30
20
  const program = new Command().name("tycoslide").description("PPTX template engine CLI").version(pkg.version);
31
21
  program
@@ -53,56 +43,46 @@ program
53
43
  if (!absConfigPath) {
54
44
  throw new Error(`${basename(deckPath)}: missing required "${RESERVED_KEY.THEME}" in global frontmatter`);
55
45
  }
56
- const config = loadConfig(absConfigPath);
57
- const deck = compileDeck(doc, config.layouts, config.rootDir, config.assets);
46
+ const config = loadThemeConfig(absConfigPath);
47
+ const deck = await compileDeck(doc, config);
58
48
  if (!deck.output)
59
49
  deck.output = basename(deckPath).replace(/\.md$/, ".pptx");
60
50
  await buildDeck(deck, config, { excludeNotes: !opts.notes });
61
51
  });
62
52
  program
63
- .command("manifest")
64
- .description("Generate manifest.json from theme config")
53
+ .command("package")
54
+ .description("Generate the Agent Skill (manifest.json, SKILL.md, syntax.md) for AI agents")
65
55
  .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
66
- .option(`-o, --out <file>`, "write to file instead of stdout")
67
56
  .action(async (opts) => {
68
- const config = loadConfig(resolve(process.cwd(), opts.config));
69
- const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
70
- if (opts.out) {
71
- writeFileSync(resolve(process.cwd(), opts.out), `${json}\n`);
72
- console.log(`WROTE ${opts.out}`);
57
+ const config = loadThemeConfig(resolve(process.cwd(), opts.config));
58
+ const themePkg = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
59
+ if (!themePkg.name) {
60
+ throw new Error('Cannot name the skill: the theme\'s package.json has no "name" field.');
61
+ }
62
+ // basename drops any npm scope, e.g. "@acme/mz-slides" -> "mz-slides".
63
+ const skillName = basename(themePkg.name);
64
+ const manifestJson = `${generateManifest(config, { build: { command: BUILD_COMMAND } })}\n`;
65
+ writeFileSync(resolve(process.cwd(), MANIFEST_FILE), manifestJson);
66
+ console.log(`WROTE ${MANIFEST_FILE}`);
67
+ let skillMd;
68
+ try {
69
+ skillMd = renameSkill(readFileSync(skillMdPath, "utf-8"), skillName);
73
70
  }
74
- else {
75
- process.stdout.write(`${json}\n`);
71
+ catch (err) {
72
+ throw new Error(`${skillMdPath}: ${err.message}`);
76
73
  }
77
- });
78
- program
79
- .command("plugin")
80
- .description("Generate plugin package (plugin.json, manifest.json, SKILL.md, syntax.md) for AI agents")
81
- .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
82
- .action(async (opts) => {
83
- const config = loadConfig(resolve(process.cwd(), opts.config));
84
- const cwd = process.cwd();
85
- const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
86
- const pluginMeta = {
87
- name: pkg.name,
88
- version: pkg.version,
89
- description: pkg.description ?? "",
90
- skills: "./skills",
91
- };
92
- if (pkg.author)
93
- pluginMeta.author = pkg.author;
94
- const pluginDir = resolve(cwd, PLUGIN_DIR);
95
- mkdirSync(pluginDir, { recursive: true });
96
- writeFileSync(resolve(pluginDir, PLUGIN_FILE), `${JSON.stringify(pluginMeta, null, 2)}\n`);
97
- console.log(`WROTE ${PLUGIN_DIR}/${PLUGIN_FILE}`);
98
- const skillDir = resolve(cwd, SKILL_DIR);
99
- mkdirSync(skillDir, { recursive: true });
100
- const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
101
- writeFileSync(resolve(skillDir, MANIFEST_FILE), `${json}\n`);
102
- console.log(`WROTE ${SKILL_DIR}/${MANIFEST_FILE}`);
103
- copyFileSync(skillMdPath, resolve(skillDir, SKILL_FILE));
104
- console.log(`WROTE ${SKILL_DIR}/${SKILL_FILE}`);
105
- copyFileSync(syntaxMdPath, resolve(skillDir, SYNTAX_FILE));
106
- console.log(`WROTE ${SKILL_DIR}/${SYNTAX_FILE}`);
74
+ writeFileSync(resolve(process.cwd(), SKILL_FILE), skillMd);
75
+ console.log(`WROTE ${SKILL_FILE}`);
76
+ const syntaxMd = readFileSync(syntaxMdPath, "utf-8");
77
+ writeFileSync(resolve(process.cwd(), SYNTAX_FILE), syntaxMd);
78
+ console.log(`WROTE ${SYNTAX_FILE}`);
79
+ const zipFile = `${skillName}.zip`;
80
+ const zipBuf = await zipSkill(skillName, [
81
+ { name: SKILL_FILE, content: skillMd },
82
+ { name: SYNTAX_FILE, content: syntaxMd },
83
+ { name: MANIFEST_FILE, content: manifestJson },
84
+ ]);
85
+ writeFileSync(resolve(process.cwd(), zipFile), zipBuf);
86
+ console.log(`WROTE ${zipFile}`);
107
87
  });
108
88
  await program.parseAsync(process.argv);
@@ -1,22 +1,30 @@
1
1
  /**
2
2
  * The `Filler` strategy registry — one plain-object strategy per SlotType, each
3
- * pairing a value discriminator with a slide-level fill. The record key IS the
4
- * slot type, so a strategy carries no redundant `type` field. `generate()`
5
- * consults `FILLERS[slot.type]` once per (slot, value): `matches` validates the
6
- * value shape, then `fill` applies it to the slide.
3
+ * pairing a value discriminator with the element-level modify callbacks that
4
+ * apply it. The record key IS the slot type, so a strategy carries no redundant
5
+ * `type` field.
7
6
  *
8
- * Element-level geometry lives in the `fillX` primitives; slide-level concerns
9
- * (media pre-swap for images, relation access for body hyperlinks, column
10
- * validation for tables) live in the strategy wrappers here.
7
+ * `callbacks(value, target)` returns the `(element, relation)` callbacks that
8
+ * fill one shape. They are deliberately shape-name-agnostic beyond the `target`
9
+ * so they can be applied two ways: `slide.modifyElement(name, callbacks)` for a
10
+ * shape already on the cloned base slide, or `slide.addElement(alias, n, name,
11
+ * callbacks)` for a shape transplanted from another slide — pptx-automizer runs
12
+ * an appended shape's callbacks against the imported element itself, so the same
13
+ * callbacks refill a transplant.
14
+ *
15
+ * Element-level geometry lives in the `fillX` primitives; cross-shape concerns
16
+ * (media pre-swap for images) live in the callbacks here.
11
17
  */
12
- import { type Slot, SlotType } from "../types.js";
13
- export type FillContext = {
14
- layoutName: string;
18
+ import { SlotType } from "../types.js";
19
+ /** The shape a filler targets, plus its slot-level options (startAt for text). */
20
+ export type FillTarget = {
21
+ shapeName: string;
22
+ startAt?: number;
15
23
  };
24
+ /** A pptx-automizer element-modify callback: `(element, relation) => void`. */
25
+ export type ShapeCallback = (element: any, relation: any) => unknown;
16
26
  export interface Filler<T> {
17
27
  matches(v: unknown): v is T;
18
- /** Human name for the mismatch error — no magic string at the throw site. */
19
- label: string;
20
- fill(slide: any, slot: Slot, value: T, ctx: FillContext): void;
28
+ callbacks(value: T, target: FillTarget): ShapeCallback[];
21
29
  }
22
30
  export declare const FILLERS: Record<SlotType, Filler<any>>;
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * The `Filler` strategy registry — one plain-object strategy per SlotType, each
3
- * pairing a value discriminator with a slide-level fill. The record key IS the
4
- * slot type, so a strategy carries no redundant `type` field. `generate()`
5
- * consults `FILLERS[slot.type]` once per (slot, value): `matches` validates the
6
- * value shape, then `fill` applies it to the slide.
3
+ * pairing a value discriminator with the element-level modify callbacks that
4
+ * apply it. The record key IS the slot type, so a strategy carries no redundant
5
+ * `type` field.
7
6
  *
8
- * Element-level geometry lives in the `fillX` primitives; slide-level concerns
9
- * (media pre-swap for images, relation access for body hyperlinks, column
10
- * validation for tables) live in the strategy wrappers here.
7
+ * `callbacks(value, target)` returns the `(element, relation)` callbacks that
8
+ * fill one shape. They are deliberately shape-name-agnostic beyond the `target`
9
+ * so they can be applied two ways: `slide.modifyElement(name, callbacks)` for a
10
+ * shape already on the cloned base slide, or `slide.addElement(alias, n, name,
11
+ * callbacks)` for a shape transplanted from another slide — pptx-automizer runs
12
+ * an appended shape's callbacks against the imported element itself, so the same
13
+ * callbacks refill a transplant.
14
+ *
15
+ * Element-level geometry lives in the `fillX` primitives; cross-shape concerns
16
+ * (media pre-swap for images) live in the callbacks here.
11
17
  */
12
18
  import { basename } from "node:path";
13
19
  import { ModifyImageHelper } from "pptx-automizer";
@@ -19,32 +25,23 @@ import { fillText, isTextFill } from "./text.js";
19
25
  export const FILLERS = {
20
26
  [SlotType.Template]: {
21
27
  matches: isTemplateFill,
22
- label: "TemplateFill",
23
- fill: (slide, slot, v) => slide.modifyElement(slot.shapeName, [(el) => fillTemplate(el, v, slot.shapeName)]),
28
+ callbacks: (v, t) => [(el) => fillTemplate(el, v, t.shapeName)],
24
29
  },
25
30
  [SlotType.Text]: {
26
31
  matches: isTextFill,
27
- label: "TextFill",
28
- fill: (slide, slot, v) => {
29
- const startAt = slot.startAt ?? 0;
30
- slide.modifyElement(slot.shapeName, [
31
- (el, relation) => fillText(el, v, { startAt, relation, shapeName: slot.shapeName }),
32
- ]);
33
- },
32
+ callbacks: (v, t) => [
33
+ (el, relation) => fillText(el, v, { startAt: t.startAt ?? 0, relation, shapeName: t.shapeName }),
34
+ ],
34
35
  },
35
36
  [SlotType.Table]: {
36
37
  matches: isTableFill,
37
- label: "TableFill",
38
- fill: (slide, slot, v) => {
39
- slide.modifyElement(slot.shapeName, [(el) => fillTable(el, v, slot.shapeName)]);
40
- },
38
+ callbacks: (v, t) => [(el) => fillTable(el, v, t.shapeName)],
41
39
  },
42
40
  [SlotType.Image]: {
43
41
  matches: isImageFill,
44
- label: "ImageFill",
45
- fill: (slide, slot, v) => slide.modifyElement(slot.shapeName, [
42
+ callbacks: (v, t) => [
46
43
  ModifyImageHelper.setRelationTarget(basename(v.path)),
47
- (el) => fillImage(el, v, slot.shapeName),
48
- ]),
44
+ (el) => fillImage(el, v, t.shapeName),
45
+ ],
49
46
  },
50
47
  };
@@ -18,11 +18,15 @@
18
18
  * file (registered by generate(), swapped by the ImageFiller) and adjusts
19
19
  * geometry for the chosen fit. See fillers/image.ts.
20
20
  *
21
- * generate() loads the template, registers media, and for each DeckStep walks
22
- * the unified `layout.slots`. Each value in `step.content` is dispatched by the
23
- * slot's type via `FILLERS[slot.type]` (required, no default).
21
+ * generate() loads the template, registers media, and for each DeckStep clones
22
+ * the layout's base slide and calls `fillSlide`. Each value in `step.content`
23
+ * selects, by its own shape, the `Block` in the slot's `accepts` whose type it
24
+ * matches: a base-slide block fills in place; any other block is transplanted
25
+ * from its source slide onto the clone, then filled with the same callbacks.
26
+ *
27
+ * `generate()` is first below; its helpers follow (function declarations hoist).
24
28
  */
25
- import type { Config, Deck, Slot } from "./types.js";
29
+ import type { Config, Deck, DeckStep, Layout } from "./types.js";
26
30
  /** Options for `generate` / `buildDeck`. */
27
31
  export type GenerateOptions = {
28
32
  /**
@@ -40,22 +44,24 @@ export type GenerateOptions = {
40
44
  * 1. Load the template; register it twice (as root and under an alias).
41
45
  * 2. Pre-register every image's media buffer with pptx-automizer, walking
42
46
  * the unified `step.content` for ImageFill values.
43
- * 3. For each deck step, clone the layout's source slide, then within the
44
- * addSlide callback walk `layout.slots` once and dispatch by slot.type
45
- * via `FILLERS[slot.type]`.
47
+ * 3. For each deck step, clone the layout's base slide, then within the
48
+ * addSlide callback call `fillSlide` to dispatch each content value to the
49
+ * matching `Block` (fill in place, or transplant + fill).
46
50
  * 4. Write the output PPTX.
47
51
  */
48
52
  export declare function generate(deck: Deck, config: Config, options?: GenerateOptions): Promise<void>;
49
53
  /**
50
- * Validate that every required slot on a layout is supplied. Type/shape
51
- * validation happens in the compiler now this is a thin required-only
52
- * checker kept for tests and defensive callers.
54
+ * Fill one cloned slide. Per slot the step supplies a value for: resolve WHICH
55
+ * shape realizes it (`resolveBlock`), build WHAT to write (`FILLERS[…].callbacks`),
56
+ * and place it WHERE/HOW (`applyBlock`). Every ambiguity fails fast, naming layout
57
+ * + slot.
53
58
  *
54
- * Exported for tests; not part of the public engine surface (index.ts).
59
+ * Exported for tests; `generate()` calls it inside the `addSlide` callback.
60
+ */
61
+ export declare function fillSlide(slide: any, layout: Layout, step: DeckStep, sourceAlias: string): void;
62
+ /**
63
+ * Reject a slot whose `accepts` lists two blocks of the same type — the
64
+ * value→block lookup would silently pick the first. Called once per layout at
65
+ * build start.
55
66
  */
56
- export declare function validateContentSlots(step: {
57
- layout: string;
58
- content?: Record<string, unknown>;
59
- }, tpl: {
60
- slots: Slot[];
61
- }): void;
67
+ export declare function assertSlotsWellFormed(layout: Layout): void;