@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.
Files changed (224) hide show
  1. package/README.md +241 -0
  2. package/SKILL.md +119 -0
  3. package/dist/cli.d.ts +38 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +386 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/components.d.ts +179 -0
  8. package/dist/core/components.d.ts.map +1 -0
  9. package/dist/core/components.js +40 -0
  10. package/dist/core/components.js.map +1 -0
  11. package/dist/core/fake-runtime.d.ts +138 -0
  12. package/dist/core/fake-runtime.d.ts.map +1 -0
  13. package/dist/core/fake-runtime.js +210 -0
  14. package/dist/core/fake-runtime.js.map +1 -0
  15. package/dist/core/font-resolver.d.ts +28 -0
  16. package/dist/core/font-resolver.d.ts.map +1 -0
  17. package/dist/core/font-resolver.js +30 -0
  18. package/dist/core/font-resolver.js.map +1 -0
  19. package/dist/core/geometry.d.ts +71 -0
  20. package/dist/core/geometry.d.ts.map +1 -0
  21. package/dist/core/geometry.js +44 -0
  22. package/dist/core/geometry.js.map +1 -0
  23. package/dist/core/index.d.ts +19 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/index.js +20 -0
  26. package/dist/core/index.js.map +1 -0
  27. package/dist/core/manifest.d.ts +123 -0
  28. package/dist/core/manifest.d.ts.map +1 -0
  29. package/dist/core/manifest.js +43 -0
  30. package/dist/core/manifest.js.map +1 -0
  31. package/dist/core/op-translator-pptx.d.ts +150 -0
  32. package/dist/core/op-translator-pptx.d.ts.map +1 -0
  33. package/dist/core/op-translator-pptx.js +245 -0
  34. package/dist/core/op-translator-pptx.js.map +1 -0
  35. package/dist/core/pptx-runtime.d.ts +103 -0
  36. package/dist/core/pptx-runtime.d.ts.map +1 -0
  37. package/dist/core/pptx-runtime.js +405 -0
  38. package/dist/core/pptx-runtime.js.map +1 -0
  39. package/dist/core/reconciler.d.ts +113 -0
  40. package/dist/core/reconciler.d.ts.map +1 -0
  41. package/dist/core/reconciler.js +453 -0
  42. package/dist/core/reconciler.js.map +1 -0
  43. package/dist/core/runtime.d.ts +161 -0
  44. package/dist/core/runtime.d.ts.map +1 -0
  45. package/dist/core/runtime.js +11 -0
  46. package/dist/core/runtime.js.map +1 -0
  47. package/dist/core/template.d.ts +32 -0
  48. package/dist/core/template.d.ts.map +1 -0
  49. package/dist/core/template.js +3 -0
  50. package/dist/core/template.js.map +1 -0
  51. package/dist/dev/auto-examples.d.ts +6 -0
  52. package/dist/dev/auto-examples.d.ts.map +1 -0
  53. package/dist/dev/auto-examples.js +79 -0
  54. package/dist/dev/auto-examples.js.map +1 -0
  55. package/dist/dev/bin/slides-dev.d.ts +3 -0
  56. package/dist/dev/bin/slides-dev.d.ts.map +1 -0
  57. package/dist/dev/bin/slides-dev.js +87 -0
  58. package/dist/dev/bin/slides-dev.js.map +1 -0
  59. package/dist/dev/bin/slides-dev.mjs +24 -0
  60. package/dist/dev/compose-deck.d.ts +18 -0
  61. package/dist/dev/compose-deck.d.ts.map +1 -0
  62. package/dist/dev/compose-deck.js +19 -0
  63. package/dist/dev/compose-deck.js.map +1 -0
  64. package/dist/dev/deck-viewer.d.ts +19 -0
  65. package/dist/dev/deck-viewer.d.ts.map +1 -0
  66. package/dist/dev/deck-viewer.js +237 -0
  67. package/dist/dev/deck-viewer.js.map +1 -0
  68. package/dist/dev/dev-server/client/entry.d.ts +2 -0
  69. package/dist/dev/dev-server/client/entry.d.ts.map +1 -0
  70. package/dist/dev/dev-server/client/entry.js +12 -0
  71. package/dist/dev/dev-server/client/entry.js.map +1 -0
  72. package/dist/dev/dev-server/output.d.ts +8 -0
  73. package/dist/dev/dev-server/output.d.ts.map +1 -0
  74. package/dist/dev/dev-server/output.js +32 -0
  75. package/dist/dev/dev-server/output.js.map +1 -0
  76. package/dist/dev/dev-server/server-only-stub.d.ts +7 -0
  77. package/dist/dev/dev-server/server-only-stub.d.ts.map +1 -0
  78. package/dist/dev/dev-server/server-only-stub.js +12 -0
  79. package/dist/dev/dev-server/server-only-stub.js.map +1 -0
  80. package/dist/dev/dev-server/start.d.ts +14 -0
  81. package/dist/dev/dev-server/start.d.ts.map +1 -0
  82. package/dist/dev/dev-server/start.js +135 -0
  83. package/dist/dev/dev-server/start.js.map +1 -0
  84. package/dist/dev/index.d.ts +5 -0
  85. package/dist/dev/index.d.ts.map +1 -0
  86. package/dist/dev/index.js +5 -0
  87. package/dist/dev/index.js.map +1 -0
  88. package/dist/dev/lib/cn.d.ts +3 -0
  89. package/dist/dev/lib/cn.d.ts.map +1 -0
  90. package/dist/dev/lib/cn.js +3 -0
  91. package/dist/dev/lib/cn.js.map +1 -0
  92. package/dist/dev/slide-canvas.d.ts +12 -0
  93. package/dist/dev/slide-canvas.d.ts.map +1 -0
  94. package/dist/dev/slide-canvas.js +123 -0
  95. package/dist/dev/slide-canvas.js.map +1 -0
  96. package/dist/dev/styles.css +37 -0
  97. package/dist/dev/ui/icon-button.d.ts +12 -0
  98. package/dist/dev/ui/icon-button.d.ts.map +1 -0
  99. package/dist/dev/ui/icon-button.js +6 -0
  100. package/dist/dev/ui/icon-button.js.map +1 -0
  101. package/dist/dev/ui/kbd.d.ts +6 -0
  102. package/dist/dev/ui/kbd.d.ts.map +1 -0
  103. package/dist/dev/ui/kbd.js +4 -0
  104. package/dist/dev/ui/kbd.js.map +1 -0
  105. package/dist/dev/ui/text-button.d.ts +10 -0
  106. package/dist/dev/ui/text-button.d.ts.map +1 -0
  107. package/dist/dev/ui/text-button.js +6 -0
  108. package/dist/dev/ui/text-button.js.map +1 -0
  109. package/dist/dev/url-state.d.ts +7 -0
  110. package/dist/dev/url-state.d.ts.map +1 -0
  111. package/dist/dev/url-state.js +13 -0
  112. package/dist/dev/url-state.js.map +1 -0
  113. package/dist/dev/use-keyboard-nav.d.ts +17 -0
  114. package/dist/dev/use-keyboard-nav.d.ts.map +1 -0
  115. package/dist/dev/use-keyboard-nav.js +53 -0
  116. package/dist/dev/use-keyboard-nav.js.map +1 -0
  117. package/dist/index.d.ts +17 -0
  118. package/dist/index.d.ts.map +1 -0
  119. package/dist/index.js +17 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/mcp/errors.d.ts +57 -0
  122. package/dist/mcp/errors.d.ts.map +1 -0
  123. package/dist/mcp/errors.js +44 -0
  124. package/dist/mcp/errors.js.map +1 -0
  125. package/dist/mcp/index.d.ts +29 -0
  126. package/dist/mcp/index.d.ts.map +1 -0
  127. package/dist/mcp/index.js +29 -0
  128. package/dist/mcp/index.js.map +1 -0
  129. package/dist/mcp/naming.d.ts +37 -0
  130. package/dist/mcp/naming.d.ts.map +1 -0
  131. package/dist/mcp/naming.js +43 -0
  132. package/dist/mcp/naming.js.map +1 -0
  133. package/dist/mcp/render.d.ts +45 -0
  134. package/dist/mcp/render.d.ts.map +1 -0
  135. package/dist/mcp/render.js +77 -0
  136. package/dist/mcp/render.js.map +1 -0
  137. package/dist/mcp/schema.d.ts +54 -0
  138. package/dist/mcp/schema.d.ts.map +1 -0
  139. package/dist/mcp/schema.js +55 -0
  140. package/dist/mcp/schema.js.map +1 -0
  141. package/dist/mcp/server.d.ts +63 -0
  142. package/dist/mcp/server.d.ts.map +1 -0
  143. package/dist/mcp/server.js +196 -0
  144. package/dist/mcp/server.js.map +1 -0
  145. package/dist/scaffold/index.d.ts +39 -0
  146. package/dist/scaffold/index.d.ts.map +1 -0
  147. package/dist/scaffold/index.js +84 -0
  148. package/dist/scaffold/index.js.map +1 -0
  149. package/dist/scaffold/template-base/README.md +134 -0
  150. package/dist/scaffold/template-base/_gitignore +4 -0
  151. package/dist/scaffold/template-base/package.json +35 -0
  152. package/dist/scaffold/template-base/src/components/Cover.tsx +30 -0
  153. package/dist/scaffold/template-base/src/index.ts +27 -0
  154. package/dist/scaffold/template-base/src/preview.tsx +9 -0
  155. package/dist/scaffold/template-base/tsconfig.build.json +10 -0
  156. package/dist/scaffold/template-base/tsconfig.json +18 -0
  157. package/package.json +164 -0
  158. package/src/__tests__/fixtures/test-template/index.tsx +77 -0
  159. package/src/__tests__/pptx-mcp.test.ts +85 -0
  160. package/src/__tests__/pptx-smoke.test.ts +45 -0
  161. package/src/__tests__/preview.test.ts +28 -0
  162. package/src/cli.ts +426 -0
  163. package/src/core/__snapshots__/reconciler.test.ts.snap +320 -0
  164. package/src/core/components.test.ts +57 -0
  165. package/src/core/components.ts +196 -0
  166. package/src/core/fake-runtime.test.ts +174 -0
  167. package/src/core/fake-runtime.ts +302 -0
  168. package/src/core/font-resolver.ts +46 -0
  169. package/src/core/geometry.test.ts +58 -0
  170. package/src/core/geometry.ts +91 -0
  171. package/src/core/index.ts +69 -0
  172. package/src/core/manifest.test.ts +33 -0
  173. package/src/core/manifest.ts +150 -0
  174. package/src/core/op-translator-pptx.test.ts +204 -0
  175. package/src/core/op-translator-pptx.ts +365 -0
  176. package/src/core/pptx-runtime.test.ts +137 -0
  177. package/src/core/pptx-runtime.ts +504 -0
  178. package/src/core/reconciler.test.ts +644 -0
  179. package/src/core/reconciler.ts +603 -0
  180. package/src/core/runtime.ts +150 -0
  181. package/src/core/template.test.ts +136 -0
  182. package/src/core/template.ts +37 -0
  183. package/src/dev/auto-examples.ts +89 -0
  184. package/src/dev/bin/slides-dev.mjs +24 -0
  185. package/src/dev/bin/slides-dev.ts +101 -0
  186. package/src/dev/compose-deck.test.ts +68 -0
  187. package/src/dev/compose-deck.ts +40 -0
  188. package/src/dev/deck-viewer.tsx +677 -0
  189. package/src/dev/dev-server/client/entry.tsx +15 -0
  190. package/src/dev/dev-server/client/index.html +24 -0
  191. package/src/dev/dev-server/output.ts +37 -0
  192. package/src/dev/dev-server/server-only-stub.ts +12 -0
  193. package/src/dev/dev-server/start.ts +155 -0
  194. package/src/dev/index.ts +4 -0
  195. package/src/dev/lib/cn.ts +3 -0
  196. package/src/dev/slide-canvas.test.tsx +66 -0
  197. package/src/dev/slide-canvas.tsx +170 -0
  198. package/src/dev/styles.css +37 -0
  199. package/src/dev/ui/icon-button.tsx +31 -0
  200. package/src/dev/ui/kbd.tsx +20 -0
  201. package/src/dev/ui/text-button.tsx +31 -0
  202. package/src/dev/url-state.test.ts +22 -0
  203. package/src/dev/url-state.ts +17 -0
  204. package/src/dev/use-keyboard-nav.ts +64 -0
  205. package/src/index.ts +17 -0
  206. package/src/mcp/errors.test.ts +51 -0
  207. package/src/mcp/errors.ts +76 -0
  208. package/src/mcp/index.ts +45 -0
  209. package/src/mcp/naming.test.ts +39 -0
  210. package/src/mcp/naming.ts +49 -0
  211. package/src/mcp/render.ts +110 -0
  212. package/src/mcp/schema.test.ts +86 -0
  213. package/src/mcp/schema.ts +93 -0
  214. package/src/mcp/server.test.ts +309 -0
  215. package/src/mcp/server.ts +276 -0
  216. package/src/scaffold/index.ts +102 -0
  217. package/src/scaffold/template-base/README.md +134 -0
  218. package/src/scaffold/template-base/_gitignore +4 -0
  219. package/src/scaffold/template-base/package.json +35 -0
  220. package/src/scaffold/template-base/src/components/Cover.tsx +30 -0
  221. package/src/scaffold/template-base/src/index.ts +27 -0
  222. package/src/scaffold/template-base/src/preview.tsx +9 -0
  223. package/src/scaffold/template-base/tsconfig.build.json +10 -0
  224. package/src/scaffold/template-base/tsconfig.json +18 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "outDir": "./dist",
6
+ "declaration": true,
7
+ "sourceMap": true
8
+ },
9
+ "include": ["src/**/*"]
10
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "resolveJsonModule": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true
16
+ },
17
+ "include": ["src/**/*"]
18
+ }
package/package.json ADDED
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "@sanity-labs/slides",
3
+ "version": "0.0.0",
4
+ "description": "Brand-locked PowerPoint generation for LLMs. Write a slide template in React; Claude (or any MCP client) writes the deck through the bundled MCP server without drifting on fonts, colors, or layout.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "react",
9
+ "pptx",
10
+ "powerpoint",
11
+ "mcp",
12
+ "model-context-protocol",
13
+ "claude",
14
+ "anthropic",
15
+ "llm",
16
+ "ai",
17
+ "presentations",
18
+ "react-reconciler",
19
+ "typescript"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/sanity-labs/slides.git"
24
+ },
25
+ "homepage": "https://github.com/sanity-labs/slides#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/sanity-labs/slides/issues"
28
+ },
29
+ "main": "./src/index.ts",
30
+ "types": "./src/index.ts",
31
+ "exports": {
32
+ ".": "./src/index.ts",
33
+ "./mcp": "./src/mcp/index.ts",
34
+ "./dev": "./src/dev/index.ts",
35
+ "./scaffold": "./src/scaffold/index.ts",
36
+ "./skill": "./SKILL.md",
37
+ "./package.json": "./package.json"
38
+ },
39
+ "bin": {
40
+ "slidesctl": "./dist/cli.js",
41
+ "slides-dev": "./dist/dev/bin/slides-dev.mjs"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src",
46
+ "SKILL.md",
47
+ "README.md"
48
+ ],
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json && node ./scripts/copy-static-assets.mjs",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "typecheck": "tsc --noEmit -p tsconfig.json",
54
+ "lint": "eslint .",
55
+ "format": "prettier --write .",
56
+ "format:check": "prettier --check .",
57
+ "knip": "knip",
58
+ "verify-bins": "bash scripts/verify-bins.sh",
59
+ "verify": "pnpm run typecheck && pnpm run lint && pnpm run format:check && pnpm run build && pnpm run test && pnpm run knip && pnpm run verify-bins",
60
+ "changeset": "changeset",
61
+ "changeset:version": "changeset version",
62
+ "changeset:publish": "pnpm run build && changeset publish"
63
+ },
64
+ "dependencies": {
65
+ "@modelcontextprotocol/sdk": "^1.29.0",
66
+ "@resvg/resvg-js": "^2.6.2",
67
+ "picocolors": "^1.0.0",
68
+ "pptxgenjs": "^4.0.1",
69
+ "zod": "^3.23.0",
70
+ "zod-to-json-schema": "^3.24.0"
71
+ },
72
+ "peerDependencies": {
73
+ "@tailwindcss/vite": "^4.0.0",
74
+ "@vitejs/plugin-react": "^4.3.0",
75
+ "clsx": "^2.1.0",
76
+ "lucide-react": "^0.511.0",
77
+ "react": "^19.0.0",
78
+ "react-dom": "^19.0.0",
79
+ "react-zoom-pan-pinch": "^4.0.0",
80
+ "tailwindcss": "^4.0.0",
81
+ "tsx": "^4.7.0",
82
+ "vite": "^5.4.0"
83
+ },
84
+ "peerDependenciesMeta": {
85
+ "@tailwindcss/vite": {
86
+ "optional": true
87
+ },
88
+ "@vitejs/plugin-react": {
89
+ "optional": true
90
+ },
91
+ "clsx": {
92
+ "optional": true
93
+ },
94
+ "lucide-react": {
95
+ "optional": true
96
+ },
97
+ "react-dom": {
98
+ "optional": true
99
+ },
100
+ "react-zoom-pan-pinch": {
101
+ "optional": true
102
+ },
103
+ "tailwindcss": {
104
+ "optional": true
105
+ },
106
+ "tsx": {
107
+ "optional": true
108
+ },
109
+ "vite": {
110
+ "optional": true
111
+ }
112
+ },
113
+ "devDependencies": {
114
+ "@changesets/changelog-github": "^0.7.0",
115
+ "@changesets/cli": "^2.31.0",
116
+ "@tailwindcss/vite": "^4.0.0",
117
+ "@types/node": "^22.0.0",
118
+ "@types/react": "^19.0.0",
119
+ "@types/react-dom": "^19.0.0",
120
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
121
+ "@typescript-eslint/parser": "^8.0.0",
122
+ "@vitejs/plugin-react": "^4.3.0",
123
+ "clsx": "^2.1.0",
124
+ "eslint": "^9.0.0",
125
+ "knip": "^5.0.0",
126
+ "lucide-react": "^0.511.0",
127
+ "prettier": "^3.0.0",
128
+ "react": "^19.2.5",
129
+ "react-dom": "^19.2.5",
130
+ "react-zoom-pan-pinch": "^4.0.0",
131
+ "tailwindcss": "^4.0.0",
132
+ "tsx": "^4.19.0",
133
+ "typescript": "5.9.3",
134
+ "vite": "^5.4.0",
135
+ "vitest": "2.1.9"
136
+ },
137
+ "publishConfig": {
138
+ "access": "public",
139
+ "exports": {
140
+ ".": {
141
+ "types": "./dist/index.d.ts",
142
+ "import": "./dist/index.js"
143
+ },
144
+ "./mcp": {
145
+ "types": "./dist/mcp/index.d.ts",
146
+ "import": "./dist/mcp/index.js"
147
+ },
148
+ "./dev": {
149
+ "types": "./dist/dev/index.d.ts",
150
+ "import": "./dist/dev/index.js"
151
+ },
152
+ "./scaffold": {
153
+ "types": "./dist/scaffold/index.d.ts",
154
+ "import": "./dist/scaffold/index.js"
155
+ },
156
+ "./skill": "./SKILL.md",
157
+ "./package.json": "./package.json"
158
+ }
159
+ },
160
+ "engines": {
161
+ "node": ">=20"
162
+ },
163
+ "packageManager": "pnpm@9.0.0"
164
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Minimal brand-free template fixture, used by the framework's own
3
+ * end-to-end tests (smoke, MCP, preview, verify-bins). One slide type
4
+ * (`Cover`), plain colors, no chrome, no embedded raster assets.
5
+ *
6
+ * The richer Sanity-branded template lives in its own repo
7
+ * (`sanity-labs/slides-template`) and consumes `@sanity-labs/slides` as a
8
+ * regular external user — so the framework gets a clean canary AND the
9
+ * brand experience proves the public API end-to-end.
10
+ */
11
+
12
+ import type { ReactElement } from 'react';
13
+ import { z } from 'zod';
14
+ import {
15
+ Box,
16
+ CANVAS_16_9,
17
+ defineTemplate,
18
+ defineTemplateComponent,
19
+ Slide,
20
+ Text,
21
+ type Template,
22
+ } from '../../../index.js';
23
+
24
+ export const CoverSchema = z
25
+ .object({
26
+ title: z.string().min(1).describe('The deck title.'),
27
+ subtitle: z.string().optional().describe('Optional subtitle.'),
28
+ })
29
+ .strict();
30
+
31
+ type CoverProps = z.infer<typeof CoverSchema>;
32
+
33
+ export const Cover = ({ title, subtitle }: CoverProps): ReactElement => (
34
+ <Slide>
35
+ <Box rect={{ x: 0, y: 0, w: 960, h: 540 }} fill={{ kind: 'solid', color: '#0b0b0b' }} />
36
+ <Box rect={{ x: 40, y: 60, w: 880, h: 100 }}>
37
+ <Text textStyle={{ fontFamily: 'display', fontSize: 48, foregroundColor: '#ffffff' }}>
38
+ {title}
39
+ </Text>
40
+ </Box>
41
+ {subtitle ? (
42
+ <Box rect={{ x: 40, y: 180, w: 880, h: 40 }}>
43
+ <Text textStyle={{ fontFamily: 'body', fontSize: 20, foregroundColor: '#cccccc' }}>
44
+ {subtitle}
45
+ </Text>
46
+ </Box>
47
+ ) : null}
48
+ </Slide>
49
+ );
50
+
51
+ export const preview = (): ReactElement => (
52
+ <>
53
+ <Cover title="Test deck" subtitle="Fixture used by the framework's own tests." />
54
+ <Cover title="Second slide" />
55
+ </>
56
+ );
57
+
58
+ export const testTemplate: Template = defineTemplate({
59
+ name: 'test-template',
60
+ canvas: CANVAS_16_9,
61
+ fonts: {
62
+ display: ['Arial', 'Helvetica'],
63
+ body: ['Arial', 'Helvetica'],
64
+ mono: ['Courier New'],
65
+ },
66
+ colors: {},
67
+ typography: {},
68
+ spacing: {},
69
+ components: {
70
+ Cover: defineTemplateComponent({
71
+ component: Cover,
72
+ schema: CoverSchema,
73
+ description: 'Use as the first slide of a deck. Plain title + optional subtitle.',
74
+ }),
75
+ },
76
+ preview,
77
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Integration test for the `slides_create` MCP tool against the
3
+ * synthetic fixture template. Exercises `createSlideServer` with a
4
+ * `PptxSlidesRuntime`, calls the tool, and asserts the response carries
5
+ * a real .pptx file path.
6
+ *
7
+ * Brand-specific template tests live in the Sanity template repo
8
+ * (`sanity-labs/slides-template`).
9
+ */
10
+
11
+ import { promises as fs } from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import { describe, expect, it } from 'vitest';
15
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
16
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
17
+ import { PptxSlidesRuntime } from '../index.js';
18
+ import { createSlideServer } from '../mcp/index.js';
19
+ import { testTemplate } from './fixtures/test-template/index.js';
20
+
21
+ describe('slides_create integration (fixture template)', () => {
22
+ it('end-to-end: tool call → .pptx file on disk', async () => {
23
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'slides-mcp-pptx-'));
24
+ try {
25
+ const runtime = new PptxSlidesRuntime({ outputDir: dir });
26
+ const server = createSlideServer({ template: testTemplate, runtime });
27
+
28
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
29
+ await server.connect(serverTransport);
30
+ const client = new Client({ name: 'test', version: '0.0.0' });
31
+ await client.connect(clientTransport);
32
+
33
+ const list = await client.listTools();
34
+ const names = list.tools.map((t) => t.name);
35
+ expect(names).toContain('slides_create');
36
+ expect(names).toContain('slides_list');
37
+
38
+ const response = await client.callTool({
39
+ name: 'slides_create',
40
+ arguments: {
41
+ title: 'Q2 Review',
42
+ slides: [{ component: 'Cover', props: { title: 'Q2 Review', subtitle: 'How we did' } }],
43
+ },
44
+ });
45
+
46
+ expect(response.isError).toBeFalsy();
47
+ const out = response.structuredContent as { filePath: string; slideCount: number };
48
+ expect(out.filePath).toMatch(/Q2-Review\.pptx$/);
49
+ expect(out.slideCount).toBe(1);
50
+ const buf = await fs.readFile(out.filePath);
51
+ expect(buf[0]).toBe(0x50);
52
+ expect(buf[1]).toBe(0x4b);
53
+
54
+ await server.close();
55
+ } finally {
56
+ await fs.rm(dir, { recursive: true, force: true });
57
+ }
58
+ }, 30_000);
59
+
60
+ it('rejects unknown component with a structured error', async () => {
61
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'slides-mcp-pptx-'));
62
+ try {
63
+ const runtime = new PptxSlidesRuntime({ outputDir: dir });
64
+ const server = createSlideServer({ template: testTemplate, runtime });
65
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
66
+ await server.connect(serverTransport);
67
+ const client = new Client({ name: 'test', version: '0.0.0' });
68
+ await client.connect(clientTransport);
69
+
70
+ const response = await client.callTool({
71
+ name: 'slides_create',
72
+ arguments: {
73
+ title: 'X',
74
+ slides: [{ component: 'Bogus', props: {} }],
75
+ },
76
+ });
77
+ expect(response.isError).toBe(true);
78
+ const text = (response.content as Array<{ text: string }>)[0]?.text ?? '';
79
+ expect(text).toMatch(/Bogus/);
80
+ await server.close();
81
+ } finally {
82
+ await fs.rm(dir, { recursive: true, force: true });
83
+ }
84
+ }, 30_000);
85
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * End-to-end smoke test: synthetic fixture template → reconciler →
3
+ * `PptxSlidesRuntime` → real `.pptx` file on disk.
4
+ *
5
+ * Asserts the offline export path produces a valid PPTX (ZIP magic
6
+ * number + non-zero size). Uses the in-repo brand-free fixture so the
7
+ * framework doesn't depend on the Sanity brand template for its own
8
+ * canary coverage.
9
+ */
10
+
11
+ import { promises as fs } from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import { createElement } from 'react';
15
+ import { describe, expect, it } from 'vitest';
16
+ import { PptxSlidesRuntime, renderToOps } from '../index.js';
17
+ import { Cover, testTemplate } from './fixtures/test-template/index.js';
18
+
19
+ describe('framework smoke test (fixture template → PptxSlidesRuntime → .pptx)', () => {
20
+ it('writes a real .pptx (ZIP magic, non-zero size) using the fixture template', async () => {
21
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'slides-pptx-smoke-'));
22
+ try {
23
+ const runtime = new PptxSlidesRuntime({ outputDir: dir });
24
+ const tree = createElement(Cover, { title: 'Smoke Test', subtitle: 'fixture' });
25
+ const result = renderToOps({
26
+ tree,
27
+ template: testTemplate,
28
+ deckId: null,
29
+ now: () => '2026-05-06T00:00:00.000Z',
30
+ });
31
+
32
+ const { deckId } = await runtime.createDeckFromMaster('test:cover-v1', 'Smoke Test');
33
+ const apply = await runtime.applyOps(deckId, result.ops);
34
+ expect(Object.keys(apply.createdObjectIds).length).toBeGreaterThanOrEqual(2);
35
+
36
+ const { filePath } = await runtime.write(deckId);
37
+ const buf = await fs.readFile(filePath);
38
+ expect(buf[0]).toBe(0x50);
39
+ expect(buf[1]).toBe(0x4b);
40
+ expect(buf.length).toBeGreaterThan(2000);
41
+ } finally {
42
+ await fs.rm(dir, { recursive: true, force: true });
43
+ }
44
+ }, 30_000);
45
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Smoke test for `composeDeck` and the auto-derived preview fallback.
3
+ *
4
+ * Uses the synthetic fixture template — confirms the dev-viewer's
5
+ * `composeDeck` produces a non-empty deck from a template's `preview()`,
6
+ * and that `deriveAutoPreview` synthesizes one slide per component when
7
+ * a template doesn't ship its own preview.
8
+ */
9
+
10
+ import { describe, expect, test } from 'vitest';
11
+ import { composeDeck } from '../dev/compose-deck.js';
12
+ import { deriveAutoPreview } from '../dev/auto-examples.js';
13
+ import { testTemplate } from './fixtures/test-template/index.js';
14
+
15
+ describe('preview composition', () => {
16
+ test('template.preview() composes into a multi-slide deck', async () => {
17
+ const preview = testTemplate.preview;
18
+ if (!preview) throw new Error('fixture template should define preview()');
19
+ const result = await composeDeck({ tree: preview(), template: testTemplate });
20
+ expect(result.deck.slideOrder.length).toBeGreaterThan(1);
21
+ });
22
+
23
+ test('deriveAutoPreview renders one slide per component', async () => {
24
+ const tree = deriveAutoPreview(testTemplate);
25
+ const result = await composeDeck({ tree, template: testTemplate });
26
+ expect(result.deck.slideOrder.length).toBe(Object.keys(testTemplate.components).length);
27
+ });
28
+ });