@sanity-labs/slides 0.0.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 +241 -0
- package/SKILL.md +119 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +386 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/components.d.ts +179 -0
- package/dist/core/components.d.ts.map +1 -0
- package/dist/core/components.js +40 -0
- package/dist/core/components.js.map +1 -0
- package/dist/core/fake-runtime.d.ts +138 -0
- package/dist/core/fake-runtime.d.ts.map +1 -0
- package/dist/core/fake-runtime.js +210 -0
- package/dist/core/fake-runtime.js.map +1 -0
- package/dist/core/font-resolver.d.ts +28 -0
- package/dist/core/font-resolver.d.ts.map +1 -0
- package/dist/core/font-resolver.js +30 -0
- package/dist/core/font-resolver.js.map +1 -0
- package/dist/core/geometry.d.ts +71 -0
- package/dist/core/geometry.d.ts.map +1 -0
- package/dist/core/geometry.js +44 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +20 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/manifest.d.ts +123 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +43 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/op-translator-pptx.d.ts +150 -0
- package/dist/core/op-translator-pptx.d.ts.map +1 -0
- package/dist/core/op-translator-pptx.js +245 -0
- package/dist/core/op-translator-pptx.js.map +1 -0
- package/dist/core/pptx-runtime.d.ts +103 -0
- package/dist/core/pptx-runtime.d.ts.map +1 -0
- package/dist/core/pptx-runtime.js +405 -0
- package/dist/core/pptx-runtime.js.map +1 -0
- package/dist/core/reconciler.d.ts +113 -0
- package/dist/core/reconciler.d.ts.map +1 -0
- package/dist/core/reconciler.js +453 -0
- package/dist/core/reconciler.js.map +1 -0
- package/dist/core/runtime.d.ts +161 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +11 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/template.d.ts +32 -0
- package/dist/core/template.d.ts.map +1 -0
- package/dist/core/template.js +3 -0
- package/dist/core/template.js.map +1 -0
- package/dist/dev/auto-examples.d.ts +6 -0
- package/dist/dev/auto-examples.d.ts.map +1 -0
- package/dist/dev/auto-examples.js +79 -0
- package/dist/dev/auto-examples.js.map +1 -0
- package/dist/dev/bin/slides-dev.d.ts +3 -0
- package/dist/dev/bin/slides-dev.d.ts.map +1 -0
- package/dist/dev/bin/slides-dev.js +87 -0
- package/dist/dev/bin/slides-dev.js.map +1 -0
- package/dist/dev/bin/slides-dev.mjs +24 -0
- package/dist/dev/compose-deck.d.ts +18 -0
- package/dist/dev/compose-deck.d.ts.map +1 -0
- package/dist/dev/compose-deck.js +19 -0
- package/dist/dev/compose-deck.js.map +1 -0
- package/dist/dev/deck-viewer.d.ts +19 -0
- package/dist/dev/deck-viewer.d.ts.map +1 -0
- package/dist/dev/deck-viewer.js +237 -0
- package/dist/dev/deck-viewer.js.map +1 -0
- package/dist/dev/dev-server/client/entry.d.ts +2 -0
- package/dist/dev/dev-server/client/entry.d.ts.map +1 -0
- package/dist/dev/dev-server/client/entry.js +12 -0
- package/dist/dev/dev-server/client/entry.js.map +1 -0
- package/dist/dev/dev-server/output.d.ts +8 -0
- package/dist/dev/dev-server/output.d.ts.map +1 -0
- package/dist/dev/dev-server/output.js +32 -0
- package/dist/dev/dev-server/output.js.map +1 -0
- package/dist/dev/dev-server/server-only-stub.d.ts +7 -0
- package/dist/dev/dev-server/server-only-stub.d.ts.map +1 -0
- package/dist/dev/dev-server/server-only-stub.js +12 -0
- package/dist/dev/dev-server/server-only-stub.js.map +1 -0
- package/dist/dev/dev-server/start.d.ts +14 -0
- package/dist/dev/dev-server/start.d.ts.map +1 -0
- package/dist/dev/dev-server/start.js +135 -0
- package/dist/dev/dev-server/start.js.map +1 -0
- package/dist/dev/index.d.ts +5 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +5 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/lib/cn.d.ts +3 -0
- package/dist/dev/lib/cn.d.ts.map +1 -0
- package/dist/dev/lib/cn.js +3 -0
- package/dist/dev/lib/cn.js.map +1 -0
- package/dist/dev/slide-canvas.d.ts +12 -0
- package/dist/dev/slide-canvas.d.ts.map +1 -0
- package/dist/dev/slide-canvas.js +123 -0
- package/dist/dev/slide-canvas.js.map +1 -0
- package/dist/dev/styles.css +37 -0
- package/dist/dev/ui/icon-button.d.ts +12 -0
- package/dist/dev/ui/icon-button.d.ts.map +1 -0
- package/dist/dev/ui/icon-button.js +6 -0
- package/dist/dev/ui/icon-button.js.map +1 -0
- package/dist/dev/ui/kbd.d.ts +6 -0
- package/dist/dev/ui/kbd.d.ts.map +1 -0
- package/dist/dev/ui/kbd.js +4 -0
- package/dist/dev/ui/kbd.js.map +1 -0
- package/dist/dev/ui/text-button.d.ts +10 -0
- package/dist/dev/ui/text-button.d.ts.map +1 -0
- package/dist/dev/ui/text-button.js +6 -0
- package/dist/dev/ui/text-button.js.map +1 -0
- package/dist/dev/url-state.d.ts +7 -0
- package/dist/dev/url-state.d.ts.map +1 -0
- package/dist/dev/url-state.js +13 -0
- package/dist/dev/url-state.js.map +1 -0
- package/dist/dev/use-keyboard-nav.d.ts +17 -0
- package/dist/dev/use-keyboard-nav.d.ts.map +1 -0
- package/dist/dev/use-keyboard-nav.js +53 -0
- package/dist/dev/use-keyboard-nav.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +57 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +44 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/index.d.ts +29 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +29 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/naming.d.ts +37 -0
- package/dist/mcp/naming.d.ts.map +1 -0
- package/dist/mcp/naming.js +43 -0
- package/dist/mcp/naming.js.map +1 -0
- package/dist/mcp/render.d.ts +45 -0
- package/dist/mcp/render.d.ts.map +1 -0
- package/dist/mcp/render.js +77 -0
- package/dist/mcp/render.js.map +1 -0
- package/dist/mcp/schema.d.ts +54 -0
- package/dist/mcp/schema.d.ts.map +1 -0
- package/dist/mcp/schema.js +55 -0
- package/dist/mcp/schema.js.map +1 -0
- package/dist/mcp/server.d.ts +63 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +196 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/scaffold/index.d.ts +39 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +84 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/template-base/README.md +134 -0
- package/dist/scaffold/template-base/_gitignore +4 -0
- package/dist/scaffold/template-base/package.json +35 -0
- package/dist/scaffold/template-base/src/components/Cover.tsx +30 -0
- package/dist/scaffold/template-base/src/index.ts +27 -0
- package/dist/scaffold/template-base/src/preview.tsx +9 -0
- package/dist/scaffold/template-base/tsconfig.build.json +10 -0
- package/dist/scaffold/template-base/tsconfig.json +18 -0
- package/package.json +164 -0
- package/src/__tests__/fixtures/test-template/index.tsx +77 -0
- package/src/__tests__/pptx-mcp.test.ts +85 -0
- package/src/__tests__/pptx-smoke.test.ts +45 -0
- package/src/__tests__/preview.test.ts +28 -0
- package/src/cli.ts +426 -0
- package/src/core/__snapshots__/reconciler.test.ts.snap +320 -0
- package/src/core/components.test.ts +57 -0
- package/src/core/components.ts +196 -0
- package/src/core/fake-runtime.test.ts +174 -0
- package/src/core/fake-runtime.ts +302 -0
- package/src/core/font-resolver.ts +46 -0
- package/src/core/geometry.test.ts +58 -0
- package/src/core/geometry.ts +91 -0
- package/src/core/index.ts +69 -0
- package/src/core/manifest.test.ts +33 -0
- package/src/core/manifest.ts +150 -0
- package/src/core/op-translator-pptx.test.ts +204 -0
- package/src/core/op-translator-pptx.ts +365 -0
- package/src/core/pptx-runtime.test.ts +137 -0
- package/src/core/pptx-runtime.ts +504 -0
- package/src/core/reconciler.test.ts +644 -0
- package/src/core/reconciler.ts +603 -0
- package/src/core/runtime.ts +150 -0
- package/src/core/template.test.ts +136 -0
- package/src/core/template.ts +37 -0
- package/src/dev/auto-examples.ts +89 -0
- package/src/dev/bin/slides-dev.mjs +24 -0
- package/src/dev/bin/slides-dev.ts +101 -0
- package/src/dev/compose-deck.test.ts +68 -0
- package/src/dev/compose-deck.ts +40 -0
- package/src/dev/deck-viewer.tsx +677 -0
- package/src/dev/dev-server/client/entry.tsx +15 -0
- package/src/dev/dev-server/client/index.html +24 -0
- package/src/dev/dev-server/output.ts +37 -0
- package/src/dev/dev-server/server-only-stub.ts +12 -0
- package/src/dev/dev-server/start.ts +155 -0
- package/src/dev/index.ts +4 -0
- package/src/dev/lib/cn.ts +3 -0
- package/src/dev/slide-canvas.test.tsx +66 -0
- package/src/dev/slide-canvas.tsx +170 -0
- package/src/dev/styles.css +37 -0
- package/src/dev/ui/icon-button.tsx +31 -0
- package/src/dev/ui/kbd.tsx +20 -0
- package/src/dev/ui/text-button.tsx +31 -0
- package/src/dev/url-state.test.ts +22 -0
- package/src/dev/url-state.ts +17 -0
- package/src/dev/use-keyboard-nav.ts +64 -0
- package/src/index.ts +17 -0
- package/src/mcp/errors.test.ts +51 -0
- package/src/mcp/errors.ts +76 -0
- package/src/mcp/index.ts +45 -0
- package/src/mcp/naming.test.ts +39 -0
- package/src/mcp/naming.ts +49 -0
- package/src/mcp/render.ts +110 -0
- package/src/mcp/schema.test.ts +86 -0
- package/src/mcp/schema.ts +93 -0
- package/src/mcp/server.test.ts +309 -0
- package/src/mcp/server.ts +276 -0
- package/src/scaffold/index.ts +102 -0
- package/src/scaffold/template-base/README.md +134 -0
- package/src/scaffold/template-base/_gitignore +4 -0
- package/src/scaffold/template-base/package.json +35 -0
- package/src/scaffold/template-base/src/components/Cover.tsx +30 -0
- package/src/scaffold/template-base/src/index.ts +27 -0
- package/src/scaffold/template-base/src/preview.tsx +9 -0
- package/src/scaffold/template-base/tsconfig.build.json +10 -0
- package/src/scaffold/template-base/tsconfig.json +18 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reconciler — walks a React element tree and emits a sequence of
|
|
3
|
+
* `SlideOp`s plus a generation manifest.
|
|
4
|
+
*
|
|
5
|
+
* # Why a custom walker (not `react-reconciler`)
|
|
6
|
+
*
|
|
7
|
+
* The natural reach is for the `react-reconciler` package — it's what
|
|
8
|
+
* react-three-fiber, react-pdf, and Ink use. But the constraints of this
|
|
9
|
+
* surface make a custom walker a strictly simpler fit:
|
|
10
|
+
*
|
|
11
|
+
* 1. **No state.** Template components are pure. No hooks, no effects, no refs.
|
|
12
|
+
* The whole reason to use `react-reconciler` is the scheduler + commit
|
|
13
|
+
* phases that make state-driven re-renders work. We don't have state.
|
|
14
|
+
* 2. **Write-once.** A `renderToOps()` call produces one batch and exits.
|
|
15
|
+
* There's no incremental update model to honor.
|
|
16
|
+
* 3. **No host node mutation.** Output is an immutable op list. The 30+ host-
|
|
17
|
+
* config methods `react-reconciler` requires (`appendChild`,
|
|
18
|
+
* `removeChild`, `commitMount`, etc.) all collapse to "push to an array."
|
|
19
|
+
* 4. **Tree shape is shallow and known.** `Slide` → `Box` | `Image` →
|
|
20
|
+
* text runs. The walker can encode this structure directly with much
|
|
21
|
+
* better error messages than a generic reconciler would surface.
|
|
22
|
+
*
|
|
23
|
+
* If we ever add state-driven re-renders (probably never; the docs commit to
|
|
24
|
+
* forward-only generation), we can swap in `react-reconciler` behind the same
|
|
25
|
+
* `renderToOps` signature. The signature is the contract.
|
|
26
|
+
*
|
|
27
|
+
* # What the walker does
|
|
28
|
+
*
|
|
29
|
+
* 1. Resolve function components by invoking them with their props (no hooks).
|
|
30
|
+
* 2. Flatten fragments and arrays.
|
|
31
|
+
* 3. For each `<Slide>`, emit `createSlide`, then walk its children for
|
|
32
|
+
* `<Box>`s and `<Image>`s.
|
|
33
|
+
* 4. For each `<Box>`, emit `createShape` (TEXT_BOX), then — if `fill` is
|
|
34
|
+
* set — `updateShapeProperties` carrying the resolved fill color, then
|
|
35
|
+
* collect its text runs into a single `insertText` + per-run
|
|
36
|
+
* `updateTextStyle` calls.
|
|
37
|
+
* 5. For each `<Image>`, emit `createImage` with the resolved URL and (if
|
|
38
|
+
* no slotId is set) the user-supplied alt-text. Record the artifact
|
|
39
|
+
* reference in the manifest, deduped by identifier.
|
|
40
|
+
*
|
|
41
|
+
* Errors throw with paths like "Slide[0] > Box[1] > unexpected child" so the
|
|
42
|
+
* brand-component author has enough information to localize the problem.
|
|
43
|
+
*
|
|
44
|
+
* # Determinism
|
|
45
|
+
*
|
|
46
|
+
* Object IDs are generated from a counter seeded per call. Same input tree →
|
|
47
|
+
* same output ops → same snapshot. This is what makes layer-2 golden tests in
|
|
48
|
+
* `docs/testing-strategy.md` work.
|
|
49
|
+
*/
|
|
50
|
+
import { Children, Fragment, isValidElement, } from 'react';
|
|
51
|
+
import { Box, Color, Text, Image, Slide, isPrimitive, } from './components.js';
|
|
52
|
+
import { isFontRole, resolveFontRole } from './font-resolver.js';
|
|
53
|
+
import { ptToEmu } from './geometry.js';
|
|
54
|
+
/**
|
|
55
|
+
* Render a React tree to ops + manifest.
|
|
56
|
+
*
|
|
57
|
+
* The function is synchronous: function components are invoked inline, and the
|
|
58
|
+
* walk is purely structural. There's no scheduling, no async boundaries.
|
|
59
|
+
*/
|
|
60
|
+
export const renderToOps = (input) => {
|
|
61
|
+
const ctx = {
|
|
62
|
+
template: input.template,
|
|
63
|
+
ops: [],
|
|
64
|
+
slots: new Map(),
|
|
65
|
+
artifacts: new Map(),
|
|
66
|
+
idCounter: 0,
|
|
67
|
+
currentSlideIndex: -1,
|
|
68
|
+
currentBoxIndex: -1,
|
|
69
|
+
};
|
|
70
|
+
// Seed with caller-supplied artifacts first so reconciler-discovered
|
|
71
|
+
// artifacts on the same identifier overwrite (more recent / context-bound
|
|
72
|
+
// resolution wins). Insertion order is preserved.
|
|
73
|
+
if (input.artifacts !== undefined) {
|
|
74
|
+
for (const artifact of input.artifacts) {
|
|
75
|
+
ctx.artifacts.set(artifact.identifier, artifact);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const slides = collectSlides(input.tree, ctx);
|
|
79
|
+
slides.forEach((slide, index) => {
|
|
80
|
+
ctx.currentSlideIndex = index;
|
|
81
|
+
ctx.currentBoxIndex = -1;
|
|
82
|
+
walkSlide(slide, index, ctx);
|
|
83
|
+
});
|
|
84
|
+
const manifest = {
|
|
85
|
+
manifestVersion: '1',
|
|
86
|
+
generatedBy: 'react-pptx',
|
|
87
|
+
generatedAt: (input.now ?? defaultNow)(),
|
|
88
|
+
templateName: input.template.name,
|
|
89
|
+
deckId: input.deckId,
|
|
90
|
+
slots: Object.fromEntries(ctx.slots),
|
|
91
|
+
artifacts: [...ctx.artifacts.values()],
|
|
92
|
+
};
|
|
93
|
+
return { ops: ctx.ops, manifest };
|
|
94
|
+
};
|
|
95
|
+
const defaultNow = () => new Date().toISOString();
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Tree walk
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a node to its concrete element form, invoking function components.
|
|
101
|
+
*
|
|
102
|
+
* Returns:
|
|
103
|
+
* - An array of resolved primitives if the node is a fragment / array / function
|
|
104
|
+
* component that returned multiple children.
|
|
105
|
+
* - A single primitive element if the node resolves to one.
|
|
106
|
+
* - Empty array for falsy / boolean / null nodes (React's "render nothing").
|
|
107
|
+
*
|
|
108
|
+
* This is what makes `<Cover/>` work: `Cover` is a function component that
|
|
109
|
+
* returns `<Slide>...</Slide>`, so resolving it yields the underlying Slide.
|
|
110
|
+
*/
|
|
111
|
+
const resolveNode = (node, ctx) => {
|
|
112
|
+
if (node === null || node === undefined || node === false || node === true) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
116
|
+
throw new ReconcilerError(`Unexpected text node "${node}" at top level — text must live inside a <Box>.`, ctx);
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(node)) {
|
|
119
|
+
return node.flatMap((child) => resolveNode(child, ctx));
|
|
120
|
+
}
|
|
121
|
+
if (!isValidElement(node)) {
|
|
122
|
+
throw new ReconcilerError(`Unsupported child of type ${typeof node}.`, ctx);
|
|
123
|
+
}
|
|
124
|
+
// React.Fragment: descend into children.
|
|
125
|
+
if (node.type === Fragment) {
|
|
126
|
+
const props = node.props;
|
|
127
|
+
return resolveNode(props.children, ctx);
|
|
128
|
+
}
|
|
129
|
+
// Primitive host element: pass through.
|
|
130
|
+
if (isPrimitive(node.type)) {
|
|
131
|
+
return [node];
|
|
132
|
+
}
|
|
133
|
+
// Function component: invoke with its props (no hooks; brand components are pure).
|
|
134
|
+
if (typeof node.type === 'function') {
|
|
135
|
+
const Component = node.type;
|
|
136
|
+
const result = invokeComponent(Component, node.props, ctx);
|
|
137
|
+
return resolveNode(result, ctx);
|
|
138
|
+
}
|
|
139
|
+
// Class components, intrinsic strings, etc.: not supported.
|
|
140
|
+
const typeName = describeType(node.type);
|
|
141
|
+
throw new ReconcilerError(`Unsupported element type ${typeName}.`, ctx);
|
|
142
|
+
};
|
|
143
|
+
const collectSlides = (tree, ctx) => {
|
|
144
|
+
const resolved = resolveNode(tree, ctx);
|
|
145
|
+
for (const el of resolved) {
|
|
146
|
+
if (el.type !== Slide) {
|
|
147
|
+
throw new ReconcilerError(`Top-level children must be <Slide> elements; got <${describeType(el.type)}>.`, ctx);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return resolved;
|
|
151
|
+
};
|
|
152
|
+
const walkSlide = (slide, index, ctx) => {
|
|
153
|
+
const props = slide.props;
|
|
154
|
+
const slideId = makeId('slide', ctx);
|
|
155
|
+
ctx.ops.push({
|
|
156
|
+
type: 'createSlide',
|
|
157
|
+
slideId,
|
|
158
|
+
insertAt: index,
|
|
159
|
+
});
|
|
160
|
+
const children = resolveNode(props.children, ctx);
|
|
161
|
+
children.forEach((child, childIndex) => {
|
|
162
|
+
ctx.currentBoxIndex = childIndex;
|
|
163
|
+
if (child.type === Box) {
|
|
164
|
+
walkBox(child, slideId, ctx);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (child.type === Image) {
|
|
168
|
+
walkImage(child, slideId, ctx);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
throw new ReconcilerError(`<Slide> children must be <Box> or <Image> elements; got <${describeType(child.type)}>.`, ctx);
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
const walkBox = (box, slideId, ctx) => {
|
|
175
|
+
const props = box.props;
|
|
176
|
+
const shapeId = makeId('shape', ctx);
|
|
177
|
+
ctx.ops.push({
|
|
178
|
+
type: 'createShape',
|
|
179
|
+
slideId,
|
|
180
|
+
shapeId,
|
|
181
|
+
shape: 'TEXT_BOX',
|
|
182
|
+
rect: rectToEmu(props.rect),
|
|
183
|
+
});
|
|
184
|
+
// Fill applies to the shape itself, before any text. Order matters:
|
|
185
|
+
// createShape → updateShapeProperties → insertText → updateTextStyle
|
|
186
|
+
// This way an empty Box with a fill is a valid full-bleed colored
|
|
187
|
+
// background, and a Box with both fill and text gets the fill behind the
|
|
188
|
+
// text without any z-order ambiguity.
|
|
189
|
+
if (props.fill !== undefined) {
|
|
190
|
+
const properties = boxFillToShapeProperties(props.fill);
|
|
191
|
+
ctx.ops.push({
|
|
192
|
+
type: 'updateShapeProperties',
|
|
193
|
+
objectId: shapeId,
|
|
194
|
+
properties,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (props.slotId !== undefined) {
|
|
198
|
+
if (ctx.slots.has(props.slotId)) {
|
|
199
|
+
throw new ReconcilerError(`Duplicate slotId "${props.slotId}" — slot IDs must be unique within a deck.`, ctx);
|
|
200
|
+
}
|
|
201
|
+
ctx.slots.set(props.slotId, shapeId);
|
|
202
|
+
// Slot identity is encoded into shape alt-text per generation-model.md.
|
|
203
|
+
// We re-use `updateShapeProperties` semantics by piggybacking through a
|
|
204
|
+
// dedicated op-shape — here, alt-text is set via a property update at the
|
|
205
|
+
// runtime layer. The op carries no fillColor/outlineColor, signalling
|
|
206
|
+
// "alt text only." If alt-text ever deserves its own op type, this is the
|
|
207
|
+
// keeps the SlideOp union narrow.
|
|
208
|
+
//
|
|
209
|
+
// NOTE: `updateShapeProperties` doesn't currently carry alt-text; the
|
|
210
|
+
// runtime adapter resolves slot alt-text from the manifest's `slots` map,
|
|
211
|
+
// not from an op. The manifest is the source of truth for slot identity.
|
|
212
|
+
// This is intentional: ops express "what to do," manifest expresses "what
|
|
213
|
+
// is true after." Encoding alt-text into ops would duplicate the manifest
|
|
214
|
+
// entry.
|
|
215
|
+
}
|
|
216
|
+
// Default styles to apply across the whole shape after text is inserted.
|
|
217
|
+
const text = collectTextRuns(props.children, ctx);
|
|
218
|
+
if (text.runs.length === 0) {
|
|
219
|
+
// Empty Box is permitted (colored rectangle, full-bleed background).
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
ctx.ops.push({ type: 'insertText', objectId: shapeId, text: text.full });
|
|
223
|
+
if (props.textStyle !== undefined && Object.keys(props.textStyle).length > 0) {
|
|
224
|
+
ctx.ops.push({
|
|
225
|
+
type: 'updateTextStyle',
|
|
226
|
+
objectId: shapeId,
|
|
227
|
+
range: { start: 0, end: text.full.length },
|
|
228
|
+
style: resolveTextStyleFonts(props.textStyle, ctx.template, ctx),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (props.paragraphStyle !== undefined && Object.keys(props.paragraphStyle).length > 0) {
|
|
232
|
+
ctx.ops.push({
|
|
233
|
+
type: 'updateParagraphStyle',
|
|
234
|
+
objectId: shapeId,
|
|
235
|
+
range: { start: 0, end: text.full.length },
|
|
236
|
+
style: props.paragraphStyle,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
for (const run of text.runs) {
|
|
240
|
+
if (Object.keys(run.style).length === 0)
|
|
241
|
+
continue;
|
|
242
|
+
ctx.ops.push({
|
|
243
|
+
type: 'updateTextStyle',
|
|
244
|
+
objectId: shapeId,
|
|
245
|
+
range: run.range,
|
|
246
|
+
style: resolveTextStyleFonts(run.style, ctx.template, ctx),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Walk an `<Image>` element: emit a `createImage` op and record the artifact.
|
|
252
|
+
*
|
|
253
|
+
* Slot/altText interaction: the reconciler emits `createImage` with the
|
|
254
|
+
* user-supplied `altText` (if any) — that's what lands in the FakeRuntime's
|
|
255
|
+
* in-memory model and what the live runtime's translator will set as the
|
|
256
|
+
* page-element description initially. If a `slotId` is also set, the
|
|
257
|
+
* runtime adapter's slot-stamping post-pass
|
|
258
|
+
* (`op-translator.slotRegistryToAltTextRequests`) overwrites the live
|
|
259
|
+
* page-element alt-text with the slot tag, because slot identity is required
|
|
260
|
+
* for re-fill and wins. The op stream still carries the user altText, so the
|
|
261
|
+
* substrate doesn't lose it.
|
|
262
|
+
*/
|
|
263
|
+
const walkImage = (image, slideId, ctx) => {
|
|
264
|
+
const props = image.props;
|
|
265
|
+
const imageId = makeId('image', ctx);
|
|
266
|
+
ctx.ops.push({
|
|
267
|
+
type: 'createImage',
|
|
268
|
+
slideId,
|
|
269
|
+
imageId,
|
|
270
|
+
url: props.image.url,
|
|
271
|
+
rect: rectToEmu(props.rect),
|
|
272
|
+
...(props.altText !== undefined ? { altText: props.altText } : {}),
|
|
273
|
+
});
|
|
274
|
+
// Dedup by identifier: a logo or texture referenced from many slides should
|
|
275
|
+
// appear in manifest.artifacts exactly once. Last-wins on conflicts (later
|
|
276
|
+
// resolution overrides earlier) — typically a no-op since the brand
|
|
277
|
+
// resolver is deterministic per identifier.
|
|
278
|
+
ctx.artifacts.set(props.image.artifact.identifier, props.image.artifact);
|
|
279
|
+
if (props.slotId !== undefined) {
|
|
280
|
+
if (ctx.slots.has(props.slotId)) {
|
|
281
|
+
throw new ReconcilerError(`Duplicate slotId "${props.slotId}" — slot IDs must be unique within a deck.`, ctx);
|
|
282
|
+
}
|
|
283
|
+
ctx.slots.set(props.slotId, imageId);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Resolve a `BoxFill` discriminated union to the shape-properties subset the
|
|
288
|
+
* reconciler emits via `updateShapeProperties`.
|
|
289
|
+
*
|
|
290
|
+
* The discriminant (`kind`) is exhaustively switched so adding a new fill
|
|
291
|
+
* variant in `components.ts` (e.g., `'texture'`) is a compile-time prompt to
|
|
292
|
+
* extend this resolver as well.
|
|
293
|
+
*/
|
|
294
|
+
const boxFillToShapeProperties = (fill) => {
|
|
295
|
+
switch (fill.kind) {
|
|
296
|
+
case 'solid':
|
|
297
|
+
return { fillColor: fill.color };
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Recursively collect text runs from a Box's children.
|
|
302
|
+
*
|
|
303
|
+
* Rules:
|
|
304
|
+
* - Strings/numbers contribute raw text with no style.
|
|
305
|
+
* - `<Text>` nests its own style and contributes its children's text.
|
|
306
|
+
* - `<Color>` is sugar for `<Text textStyle={{ foregroundColor }}>`.
|
|
307
|
+
* - Nested function components are resolved (so `<Bullet>foo</Bullet>` works
|
|
308
|
+
* if `Bullet` is a brand component returning `<Text>foo</Text>`).
|
|
309
|
+
* - Anything else is an error.
|
|
310
|
+
*/
|
|
311
|
+
const collectTextRuns = (node, ctx) => {
|
|
312
|
+
const acc = { full: '', runs: [] };
|
|
313
|
+
const append = (text, style) => {
|
|
314
|
+
if (text.length === 0)
|
|
315
|
+
return;
|
|
316
|
+
const start = acc.full.length;
|
|
317
|
+
acc.full += text;
|
|
318
|
+
acc.runs.push({ range: { start, end: start + text.length }, style });
|
|
319
|
+
};
|
|
320
|
+
walkText(node, {}, append, ctx);
|
|
321
|
+
return acc;
|
|
322
|
+
};
|
|
323
|
+
const walkText = (node, inheritedStyle, append, ctx) => {
|
|
324
|
+
if (node === null || node === undefined || node === false || node === true) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (typeof node === 'string') {
|
|
328
|
+
append(node, inheritedStyle);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (typeof node === 'number') {
|
|
332
|
+
append(String(node), inheritedStyle);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (Array.isArray(node)) {
|
|
336
|
+
Children.forEach(node, (child) => walkText(child, inheritedStyle, append, ctx));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (!isValidElement(node)) {
|
|
340
|
+
throw new ReconcilerError(`Unsupported text child of type ${typeof node}.`, ctx);
|
|
341
|
+
}
|
|
342
|
+
if (node.type === Fragment) {
|
|
343
|
+
const props = node.props;
|
|
344
|
+
walkText(props.children, inheritedStyle, append, ctx);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (node.type === Text) {
|
|
348
|
+
const props = node.props;
|
|
349
|
+
const merged = { ...inheritedStyle, ...(props.textStyle ?? {}) };
|
|
350
|
+
walkText(props.children, merged, append, ctx);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (node.type === Color) {
|
|
354
|
+
const props = node.props;
|
|
355
|
+
const merged = { ...inheritedStyle, foregroundColor: props.color };
|
|
356
|
+
walkText(props.children, merged, append, ctx);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (node.type === Slide || node.type === Box || node.type === Image) {
|
|
360
|
+
throw new ReconcilerError(`<${describeType(node.type)}> cannot appear inside a <Box>.`, ctx);
|
|
361
|
+
}
|
|
362
|
+
if (typeof node.type === 'function') {
|
|
363
|
+
const Component = node.type;
|
|
364
|
+
const result = invokeComponent(Component, node.props, ctx);
|
|
365
|
+
walkText(result, inheritedStyle, append, ctx);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
throw new ReconcilerError(`Unsupported text element <${describeType(node.type)}>.`, ctx);
|
|
369
|
+
};
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
// Helpers
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
/**
|
|
374
|
+
* Resolve any role-string `fontFamily` in a `TextStyle` to a literal family
|
|
375
|
+
* name using the brand's font stack. Returns the same object reference if the
|
|
376
|
+
* style has no role-string `fontFamily` (the common case for literal-family
|
|
377
|
+
* passes — preserves snapshot stability and avoids unnecessary allocation).
|
|
378
|
+
*
|
|
379
|
+
* Resolution semantics (no backend querying):
|
|
380
|
+
* role keyword in → `brand.fonts[role][0]` out (the brand's first preference).
|
|
381
|
+
*
|
|
382
|
+
* If the brand declares an empty stack for the requested role, this throws
|
|
383
|
+
* with a clear "brand defines no <role> font" error. Strict-fail beats
|
|
384
|
+
* silent-Arial when the source of truth is misconfigured.
|
|
385
|
+
*
|
|
386
|
+
* The reconciler invokes this immediately before pushing every text-style op,
|
|
387
|
+
* so downstream translators never see a role keyword as a literal family.
|
|
388
|
+
*/
|
|
389
|
+
const resolveTextStyleFonts = (style, template, ctx) => {
|
|
390
|
+
const family = style.fontFamily;
|
|
391
|
+
if (family === undefined)
|
|
392
|
+
return style;
|
|
393
|
+
if (!isFontRole(family))
|
|
394
|
+
return style; // literal family — pass through.
|
|
395
|
+
try {
|
|
396
|
+
return { ...style, fontFamily: resolveFontRole(template.fonts, family) };
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
if (err instanceof Error && err.name === 'EmptyFontStackError') {
|
|
400
|
+
throw new ReconcilerError(`Template "${template.name}" defines no ${family} font (fonts.${family} is empty). Every role needs at least one entry, with a system-safe last entry (e.g., "Arial").`, ctx);
|
|
401
|
+
}
|
|
402
|
+
throw err;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const rectToEmu = (rect) => ({
|
|
406
|
+
x: ptToEmu(rect.x),
|
|
407
|
+
y: ptToEmu(rect.y),
|
|
408
|
+
w: ptToEmu(rect.w),
|
|
409
|
+
h: ptToEmu(rect.h),
|
|
410
|
+
});
|
|
411
|
+
const makeId = (prefix, ctx) => {
|
|
412
|
+
ctx.idCounter += 1;
|
|
413
|
+
return `${prefix}_${ctx.idCounter}`;
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* Invoke a function component with its props and return its rendered children.
|
|
417
|
+
*
|
|
418
|
+
* Async components are not supported. React 19 widened the function-component
|
|
419
|
+
* return type to `ReactNode | Promise<ReactNode>`; we narrow back to
|
|
420
|
+
* `ReactNode` here and throw if a Promise is returned.
|
|
421
|
+
*/
|
|
422
|
+
const invokeComponent = (Component, props, ctx) => {
|
|
423
|
+
const result = Component(props);
|
|
424
|
+
if (typeof result === 'object' && result !== null && 'then' in result) {
|
|
425
|
+
throw new ReconcilerError(`<${describeType(Component)}> returned a Promise. Template components must be synchronous (no async, no Suspense).`, ctx);
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
};
|
|
429
|
+
const describeType = (type) => {
|
|
430
|
+
if (typeof type === 'string')
|
|
431
|
+
return type;
|
|
432
|
+
if (typeof type === 'function') {
|
|
433
|
+
const named = type;
|
|
434
|
+
return named.displayName ?? named.name ?? 'anonymous';
|
|
435
|
+
}
|
|
436
|
+
if (type === Fragment)
|
|
437
|
+
return 'Fragment';
|
|
438
|
+
return String(type);
|
|
439
|
+
};
|
|
440
|
+
/** Error thrown by the reconciler with location info pre-formatted. */
|
|
441
|
+
export class ReconcilerError extends Error {
|
|
442
|
+
constructor(message, ctx) {
|
|
443
|
+
const path = [];
|
|
444
|
+
if (ctx.currentSlideIndex >= 0)
|
|
445
|
+
path.push(`Slide[${ctx.currentSlideIndex}]`);
|
|
446
|
+
if (ctx.currentBoxIndex >= 0)
|
|
447
|
+
path.push(`Box[${ctx.currentBoxIndex}]`);
|
|
448
|
+
const prefix = path.length > 0 ? `${path.join(' > ')}: ` : '';
|
|
449
|
+
super(`${prefix}${message}`);
|
|
450
|
+
this.name = 'ReconcilerError';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.js","sourceRoot":"","sources":["../../src/core/reconciler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the reconciler and the slide runtime backend.
|
|
3
|
+
*
|
|
4
|
+
* Production wires `PptxSlidesRuntime` into the reconciler. Tests wire
|
|
5
|
+
* `FakeSlidesRuntime` which records every operation and builds an in-memory
|
|
6
|
+
* deck model.
|
|
7
|
+
*
|
|
8
|
+
* See `docs/testing-strategy.md` for the rationale.
|
|
9
|
+
*/
|
|
10
|
+
import type { Emu } from './geometry.js';
|
|
11
|
+
import type { GenerationManifest } from './manifest.js';
|
|
12
|
+
/**
|
|
13
|
+
* A typed Slides API operation the reconciler emits.
|
|
14
|
+
*
|
|
15
|
+
* This is intentionally NOT a 1:1 mapping of any specific slide API — it's
|
|
16
|
+
* the *intent* the reconciler emits, with EMU and IDs already resolved. The
|
|
17
|
+
* runtime adapter turns these into backend-specific operations.
|
|
18
|
+
*
|
|
19
|
+
* Keeping ops at this layer (vs raw API requests) means:
|
|
20
|
+
* 1. Goldens read clearly. `{ type: "createShape", ... }` beats a 50-line nested object.
|
|
21
|
+
* 2. The API can evolve underneath without churning every test snapshot.
|
|
22
|
+
* 3. Template-agnostic; nothing here is Sanity-specific.
|
|
23
|
+
*/
|
|
24
|
+
export type SlideOp = {
|
|
25
|
+
type: 'createSlide';
|
|
26
|
+
slideId: string;
|
|
27
|
+
insertAt?: number;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'createShape';
|
|
30
|
+
slideId: string;
|
|
31
|
+
shapeId: string;
|
|
32
|
+
shape: ShapeKind;
|
|
33
|
+
rect: EmuRect;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'insertText';
|
|
36
|
+
objectId: string;
|
|
37
|
+
text: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'updateTextStyle';
|
|
40
|
+
objectId: string;
|
|
41
|
+
range: TextRange;
|
|
42
|
+
style: TextStyle;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'updateParagraphStyle';
|
|
45
|
+
objectId: string;
|
|
46
|
+
range: TextRange;
|
|
47
|
+
style: ParagraphStyle;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'createImage';
|
|
50
|
+
slideId: string;
|
|
51
|
+
imageId: string;
|
|
52
|
+
url: string;
|
|
53
|
+
rect: EmuRect;
|
|
54
|
+
altText?: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'updateShapeProperties';
|
|
57
|
+
objectId: string;
|
|
58
|
+
properties: ShapeProperties;
|
|
59
|
+
};
|
|
60
|
+
/** A rectangle in EMU — the reconciler converts pt → EMU before emitting. */
|
|
61
|
+
export interface EmuRect {
|
|
62
|
+
x: Emu;
|
|
63
|
+
y: Emu;
|
|
64
|
+
w: Emu;
|
|
65
|
+
h: Emu;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Shape kinds the reconciler emits. Names mirror the Office Open XML /
|
|
69
|
+
* Google Slides API enum so translators on either side can pass through
|
|
70
|
+
* without a lookup table.
|
|
71
|
+
*/
|
|
72
|
+
export type ShapeKind = 'TEXT_BOX' | 'RECTANGLE' | 'ELLIPSE' | 'LINE';
|
|
73
|
+
/** Inclusive-exclusive index range over a string of text. */
|
|
74
|
+
export interface TextRange {
|
|
75
|
+
start: number;
|
|
76
|
+
end: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A font role keyword. Resolved against `Template.fonts[role][0]` at the
|
|
80
|
+
* reconciler boundary; the role keywords (`'display'` / `'body'` / `'mono'`)
|
|
81
|
+
* are reserved — a brand cannot have a literal font family named "display."
|
|
82
|
+
*
|
|
83
|
+
* See `reconciler.ts` for resolution semantics; the role keywords mirror
|
|
84
|
+
* `FontStack` keys so adding a role to one ripples to the other.
|
|
85
|
+
*/
|
|
86
|
+
export type FontRole = 'display' | 'body' | 'mono';
|
|
87
|
+
/** Text-style properties supported at the reconciler boundary. */
|
|
88
|
+
export interface TextStyle {
|
|
89
|
+
/**
|
|
90
|
+
* Font family. Either:
|
|
91
|
+
* - A role keyword (`'display'` / `'body'` / `'mono'`), resolved against
|
|
92
|
+
* `Template.fonts[role][0]` at the reconciler boundary before the op is
|
|
93
|
+
* emitted. This is what brand components and `<Text/>` consumers
|
|
94
|
+
* should pass — it keeps the brand's font choice load-bearing.
|
|
95
|
+
* - A literal family name (e.g., `'Geist'`, `'Inter'`), passed through
|
|
96
|
+
* verbatim. Template authors with a specific family in mind use this.
|
|
97
|
+
*
|
|
98
|
+
* The role keywords are reserved — they shadow any literal family of the
|
|
99
|
+
* same name. If a future brand needed a literal `'display'` family, the
|
|
100
|
+
* shape would need to migrate to a discriminated union.
|
|
101
|
+
*
|
|
102
|
+
* Why a string union (vs. discriminated): the role keywords are vanishingly
|
|
103
|
+
* unlikely to ever be a real family; a discriminated union would force every
|
|
104
|
+
* literal-family consumer to write `{ kind: 'family', family: ... }` for no
|
|
105
|
+
* win. `flatten-for-brand.ts:inferFontRole` returns role strings directly,
|
|
106
|
+
* which compose cleanly into this shape.
|
|
107
|
+
*/
|
|
108
|
+
fontFamily?: FontRole | string;
|
|
109
|
+
fontSize?: number;
|
|
110
|
+
bold?: boolean;
|
|
111
|
+
italic?: boolean;
|
|
112
|
+
underline?: boolean;
|
|
113
|
+
foregroundColor?: HexColor;
|
|
114
|
+
backgroundColor?: HexColor;
|
|
115
|
+
}
|
|
116
|
+
/** Paragraph-style properties supported at the reconciler boundary. */
|
|
117
|
+
export interface ParagraphStyle {
|
|
118
|
+
alignment?: 'START' | 'CENTER' | 'END' | 'JUSTIFIED';
|
|
119
|
+
lineSpacing?: number;
|
|
120
|
+
spaceAbove?: number;
|
|
121
|
+
spaceBelow?: number;
|
|
122
|
+
}
|
|
123
|
+
/** Shape-level visual properties. */
|
|
124
|
+
export interface ShapeProperties {
|
|
125
|
+
fillColor?: HexColor;
|
|
126
|
+
outlineColor?: HexColor;
|
|
127
|
+
outlineWeight?: number;
|
|
128
|
+
}
|
|
129
|
+
/** A 24-bit hex color, e.g., "#FF5500". The reconciler converts to RGB at emit. */
|
|
130
|
+
export type HexColor = `#${string}`;
|
|
131
|
+
/**
|
|
132
|
+
* The runtime contract every slide backend satisfies. This is the seam that
|
|
133
|
+
* makes layered testing work — see `docs/testing-strategy.md`.
|
|
134
|
+
*/
|
|
135
|
+
export interface SlidesRuntime {
|
|
136
|
+
/** Apply a sequence of slide operations to a deck. */
|
|
137
|
+
applyOps(deckId: string, ops: readonly SlideOp[]): Promise<ApplyOpsResult>;
|
|
138
|
+
/**
|
|
139
|
+
* Create a new deck, optionally seeded from a template reference.
|
|
140
|
+
*
|
|
141
|
+
* `masterRef` is opaque to the substrate; runtimes that don't have a
|
|
142
|
+
* master-template concept ignore it and initialize a blank deck.
|
|
143
|
+
*/
|
|
144
|
+
createDeckFromMaster(masterRef: string, title: string): Promise<{
|
|
145
|
+
deckId: string;
|
|
146
|
+
}>;
|
|
147
|
+
/** Write the deck to a file on disk. Returns the absolute path. */
|
|
148
|
+
write(deckId: string): Promise<{
|
|
149
|
+
filePath: string;
|
|
150
|
+
}>;
|
|
151
|
+
/** Attach a manifest to a deck (for later retrieval). */
|
|
152
|
+
attachManifest(deckId: string, manifest: GenerationManifest): void;
|
|
153
|
+
}
|
|
154
|
+
/** What the runtime returns after applying a batch of ops. */
|
|
155
|
+
export interface ApplyOpsResult {
|
|
156
|
+
/** IDs of objects created during this batch, keyed by the requested ID. */
|
|
157
|
+
createdObjectIds: Readonly<Record<string, string>>;
|
|
158
|
+
/** Revision token for optimistic-concurrency on the next call. */
|
|
159
|
+
revisionId?: string;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/core/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the reconciler and the slide runtime backend.
|
|
3
|
+
*
|
|
4
|
+
* Production wires `PptxSlidesRuntime` into the reconciler. Tests wire
|
|
5
|
+
* `FakeSlidesRuntime` which records every operation and builds an in-memory
|
|
6
|
+
* deck model.
|
|
7
|
+
*
|
|
8
|
+
* See `docs/testing-strategy.md` for the rationale.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/core/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|