@tpitre/story-ui 4.12.1 → 4.13.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 (90) hide show
  1. package/dist/cli/generateRegistry.d.ts +23 -0
  2. package/dist/cli/generateRegistry.d.ts.map +1 -0
  3. package/dist/cli/generateRegistry.js +155 -0
  4. package/dist/cli/index.js +18 -3
  5. package/dist/cli/setup.d.ts.map +1 -1
  6. package/dist/cli/setup.js +115 -4
  7. package/dist/cli/update.d.ts.map +1 -1
  8. package/dist/cli/update.js +26 -0
  9. package/dist/mcp-server/index.js +86 -1
  10. package/dist/mcp-server/routes/canvasGenerate.d.ts +25 -0
  11. package/dist/mcp-server/routes/canvasGenerate.d.ts.map +1 -0
  12. package/dist/mcp-server/routes/canvasGenerate.js +268 -0
  13. package/dist/mcp-server/routes/canvasIntent.d.ts +15 -0
  14. package/dist/mcp-server/routes/canvasIntent.d.ts.map +1 -0
  15. package/dist/mcp-server/routes/canvasIntent.js +553 -0
  16. package/dist/mcp-server/routes/canvasPreview.d.ts +14 -0
  17. package/dist/mcp-server/routes/canvasPreview.d.ts.map +1 -0
  18. package/dist/mcp-server/routes/canvasPreview.js +25 -0
  19. package/dist/mcp-server/routes/canvasSave.d.ts +13 -0
  20. package/dist/mcp-server/routes/canvasSave.d.ts.map +1 -0
  21. package/dist/mcp-server/routes/canvasSave.js +273 -0
  22. package/dist/mcp-server/routes/convertToStory.d.ts +17 -0
  23. package/dist/mcp-server/routes/convertToStory.d.ts.map +1 -0
  24. package/dist/mcp-server/routes/convertToStory.js +730 -0
  25. package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
  26. package/dist/mcp-server/routes/generateStory.js +17 -0
  27. package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
  28. package/dist/mcp-server/routes/generateStoryStream.js +38 -2
  29. package/dist/mcp-server/routes/manifest.d.ts +19 -0
  30. package/dist/mcp-server/routes/manifest.d.ts.map +1 -0
  31. package/dist/mcp-server/routes/manifest.js +100 -0
  32. package/dist/mcp-server/routes/voiceRender.d.ts +19 -0
  33. package/dist/mcp-server/routes/voiceRender.d.ts.map +1 -0
  34. package/dist/mcp-server/routes/voiceRender.js +329 -0
  35. package/dist/story-generator/manifestManager.d.ts +105 -0
  36. package/dist/story-generator/manifestManager.d.ts.map +1 -0
  37. package/dist/story-generator/manifestManager.js +265 -0
  38. package/dist/templates/StoryUI/StoryUIPanel.css +732 -0
  39. package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
  40. package/dist/templates/StoryUI/StoryUIPanel.js +399 -50
  41. package/dist/templates/StoryUI/StoryUIPanel.tsx +2509 -0
  42. package/dist/templates/StoryUI/index.tsx +2 -0
  43. package/dist/templates/StoryUI/voice/VoiceCanvas.d.ts +15 -0
  44. package/dist/templates/StoryUI/voice/VoiceCanvas.d.ts.map +1 -0
  45. package/dist/templates/StoryUI/voice/VoiceCanvas.js +477 -0
  46. package/dist/templates/StoryUI/voice/VoiceCanvas.tsx +702 -0
  47. package/dist/templates/StoryUI/voice/VoiceControls.d.ts +12 -0
  48. package/dist/templates/StoryUI/voice/VoiceControls.d.ts.map +1 -0
  49. package/dist/templates/StoryUI/voice/VoiceControls.js +73 -0
  50. package/dist/templates/StoryUI/voice/VoiceControls.tsx +167 -0
  51. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.d.ts +14 -0
  52. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.d.ts.map +1 -0
  53. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.js +90 -0
  54. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.tsx +168 -0
  55. package/dist/templates/StoryUI/voice/canvas/componentRegistry.d.ts +21 -0
  56. package/dist/templates/StoryUI/voice/canvas/componentRegistry.d.ts.map +1 -0
  57. package/dist/templates/StoryUI/voice/canvas/componentRegistry.js +24 -0
  58. package/dist/templates/StoryUI/voice/canvas/componentRegistry.ts +30 -0
  59. package/dist/templates/StoryUI/voice/canvas/operations.d.ts +8 -0
  60. package/dist/templates/StoryUI/voice/canvas/operations.d.ts.map +1 -0
  61. package/dist/templates/StoryUI/voice/canvas/operations.js +189 -0
  62. package/dist/templates/StoryUI/voice/canvas/operations.ts +233 -0
  63. package/dist/templates/StoryUI/voice/canvas/types.d.ts +89 -0
  64. package/dist/templates/StoryUI/voice/canvas/types.d.ts.map +1 -0
  65. package/dist/templates/StoryUI/voice/canvas/types.js +2 -0
  66. package/dist/templates/StoryUI/voice/canvas/types.ts +106 -0
  67. package/dist/templates/StoryUI/voice/types.d.ts +83 -0
  68. package/dist/templates/StoryUI/voice/types.d.ts.map +1 -0
  69. package/dist/templates/StoryUI/voice/types.js +2 -0
  70. package/dist/templates/StoryUI/voice/types.ts +114 -0
  71. package/dist/templates/StoryUI/voice/useVoiceInput.d.ts +3 -0
  72. package/dist/templates/StoryUI/voice/useVoiceInput.d.ts.map +1 -0
  73. package/dist/templates/StoryUI/voice/useVoiceInput.js +182 -0
  74. package/dist/templates/StoryUI/voice/useVoiceInput.ts +224 -0
  75. package/dist/templates/StoryUI/voice/voiceCommands.d.ts +8 -0
  76. package/dist/templates/StoryUI/voice/voiceCommands.d.ts.map +1 -0
  77. package/dist/templates/StoryUI/voice/voiceCommands.js +46 -0
  78. package/dist/templates/StoryUI/voice/voiceCommands.ts +54 -0
  79. package/package.json +4 -2
  80. package/templates/StoryUI/StoryUIPanel.css +732 -0
  81. package/templates/StoryUI/StoryUIPanel.tsx +423 -42
  82. package/templates/StoryUI/voice/VoiceCanvas.tsx +702 -0
  83. package/templates/StoryUI/voice/VoiceControls.tsx +167 -0
  84. package/templates/StoryUI/voice/canvas/ComponentRenderer.tsx +168 -0
  85. package/templates/StoryUI/voice/canvas/componentRegistry.ts +30 -0
  86. package/templates/StoryUI/voice/canvas/operations.ts +233 -0
  87. package/templates/StoryUI/voice/canvas/types.ts +106 -0
  88. package/templates/StoryUI/voice/types.ts +114 -0
  89. package/templates/StoryUI/voice/useVoiceInput.ts +224 -0
  90. package/templates/StoryUI/voice/voiceCommands.ts +54 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Component Registry Generator
3
+ *
4
+ * Generates a `componentRegistry.ts` file that maps component names to
5
+ * actual React/Vue/etc imports for use by the Voice Canvas ComponentRenderer.
6
+ *
7
+ * Design-system agnostic — uses existing component discovery to find
8
+ * components from npm packages, local directories, and custom elements.
9
+ *
10
+ * Called during `npx story-ui init` and can be re-run with `npx story-ui registry`.
11
+ */
12
+ export interface RegistryGeneratorOptions {
13
+ /** Working directory (defaults to cwd) */
14
+ cwd?: string;
15
+ /** Output path relative to cwd (defaults to src/stories/StoryUI/voice/canvas/) */
16
+ outputDir?: string;
17
+ }
18
+ /**
19
+ * Generate the component registry file using project's discovered components.
20
+ * Returns the path to the generated file.
21
+ */
22
+ export declare function generateComponentRegistry(options?: RegistryGeneratorOptions): Promise<string>;
23
+ //# sourceMappingURL=generateRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateRegistry.d.ts","sourceRoot":"","sources":["../../cli/generateRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,MAAM,WAAW,wBAAwB;IACvC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDvG"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Component Registry Generator
3
+ *
4
+ * Generates a `componentRegistry.ts` file that maps component names to
5
+ * actual React/Vue/etc imports for use by the Voice Canvas ComponentRenderer.
6
+ *
7
+ * Design-system agnostic — uses existing component discovery to find
8
+ * components from npm packages, local directories, and custom elements.
9
+ *
10
+ * Called during `npx story-ui init` and can be re-run with `npx story-ui registry`.
11
+ */
12
+ import path from 'path';
13
+ import fs from 'fs';
14
+ import { loadUserConfig } from '../story-generator/configLoader.js';
15
+ import { EnhancedComponentDiscovery } from '../story-generator/enhancedComponentDiscovery.js';
16
+ /**
17
+ * Generate the component registry file using project's discovered components.
18
+ * Returns the path to the generated file.
19
+ */
20
+ export async function generateComponentRegistry(options = {}) {
21
+ const cwd = options.cwd || process.cwd();
22
+ // loadUserConfig reads from cwd — ensure we're in the right directory
23
+ const originalCwd = process.cwd();
24
+ if (cwd !== originalCwd)
25
+ process.chdir(cwd);
26
+ const config = loadUserConfig();
27
+ if (cwd !== originalCwd)
28
+ process.chdir(originalCwd);
29
+ // Discover components using the existing discovery system
30
+ const discovery = new EnhancedComponentDiscovery(config);
31
+ const components = await discovery.discoverAll();
32
+ const componentNames = components.map(c => c.name).sort();
33
+ if (componentNames.length === 0) {
34
+ console.warn('⚠️ No components discovered — registry will be empty');
35
+ }
36
+ const importPath = config.importPath || '@mantine/core';
37
+ const framework = config.componentFramework || 'react';
38
+ const importStyle = config.importStyle || 'barrel';
39
+ // Determine output directory
40
+ const outputDir = options.outputDir
41
+ ? path.resolve(cwd, options.outputDir)
42
+ : path.resolve(cwd, 'src/stories/StoryUI/voice/canvas');
43
+ if (!fs.existsSync(outputDir)) {
44
+ fs.mkdirSync(outputDir, { recursive: true });
45
+ }
46
+ const outputPath = path.join(outputDir, 'componentRegistry.ts');
47
+ // Generate the file content
48
+ let code;
49
+ if (framework === 'react') {
50
+ code = generateReactRegistry(componentNames, importPath, importStyle);
51
+ }
52
+ else if (framework === 'vue') {
53
+ code = generateVueRegistry(componentNames, importPath, importStyle);
54
+ }
55
+ else {
56
+ // Fallback: generic registry with dynamic imports note
57
+ code = generateGenericRegistry(componentNames, importPath, framework);
58
+ }
59
+ fs.writeFileSync(outputPath, code, 'utf-8');
60
+ console.log(`✅ Component registry generated: ${path.relative(cwd, outputPath)} (${componentNames.length} components)`);
61
+ return outputPath;
62
+ }
63
+ // ── React registry generator ────────────────────────────────
64
+ function generateReactRegistry(names, importPath, importStyle) {
65
+ // Separate parent components from sub-components (e.g., "Card.Section")
66
+ const topLevel = names.filter(n => !n.includes('.'));
67
+ const subComponents = names.filter(n => n.includes('.'));
68
+ // For barrel imports, import all top-level from one path
69
+ // For individual imports, import each from its own path
70
+ let imports;
71
+ if (importStyle === 'individual') {
72
+ imports = topLevel.map(name => `import { ${name} } from '${importPath}/${name}';`).join('\n');
73
+ }
74
+ else {
75
+ // Barrel import — single line
76
+ imports = `import {\n${topLevel.map(n => ` ${n},`).join('\n')}\n} from '${importPath}';`;
77
+ }
78
+ // Build registry entries
79
+ const entries = topLevel.map(name => ` '${name}': ${name},`);
80
+ // Sub-components map automatically via dot-notation in ComponentRenderer
81
+ // e.g., "Card.Section" resolves to Card.Section at runtime
82
+ // But we still add them for explicit lookup
83
+ for (const sub of subComponents) {
84
+ const [parent, child] = sub.split('.');
85
+ entries.push(` '${sub}': (${parent} as any).${child},`);
86
+ }
87
+ return `/**
88
+ * Auto-generated component registry for Voice Canvas
89
+ * Source: ${importPath}
90
+ * Components: ${names.length}
91
+ *
92
+ * DO NOT EDIT — regenerate with: npx story-ui registry
93
+ */
94
+
95
+ ${imports}
96
+ import type { ComponentRegistry } from './ComponentRenderer';
97
+
98
+ export const registry: ComponentRegistry = {
99
+ ${entries.join('\n')}
100
+ };
101
+
102
+ export default registry;
103
+ `;
104
+ }
105
+ // ── Vue registry generator ──────────────────────────────────
106
+ function generateVueRegistry(names, importPath, importStyle) {
107
+ const topLevel = names.filter(n => !n.includes('.'));
108
+ let imports;
109
+ if (importStyle === 'individual') {
110
+ imports = topLevel.map(name => `import { ${name} } from '${importPath}/${name}';`).join('\n');
111
+ }
112
+ else {
113
+ imports = `import {\n${topLevel.map(n => ` ${n},`).join('\n')}\n} from '${importPath}';`;
114
+ }
115
+ const entries = topLevel.map(name => ` '${name}': ${name},`);
116
+ return `/**
117
+ * Auto-generated component registry for Voice Canvas (Vue)
118
+ * Source: ${importPath}
119
+ * Components: ${names.length}
120
+ *
121
+ * DO NOT EDIT — regenerate with: npx story-ui registry
122
+ */
123
+
124
+ ${imports}
125
+
126
+ export const registry: Record<string, any> = {
127
+ ${entries.join('\n')}
128
+ };
129
+
130
+ export default registry;
131
+ `;
132
+ }
133
+ // ── Generic fallback ────────────────────────────────────────
134
+ function generateGenericRegistry(names, importPath, framework) {
135
+ return `/**
136
+ * Auto-generated component registry for Voice Canvas (${framework})
137
+ * Source: ${importPath}
138
+ * Components: ${names.length}
139
+ *
140
+ * NOTE: This is a placeholder registry for ${framework}.
141
+ * You may need to customize imports for your framework.
142
+ *
143
+ * DO NOT EDIT — regenerate with: npx story-ui registry
144
+ */
145
+
146
+ // TODO: Add imports for ${framework} components from '${importPath}'
147
+ // ${names.map(n => `// import { ${n} } from '${importPath}';`).join('\n')}
148
+
149
+ export const registry: Record<string, any> = {
150
+ ${names.map(n => ` // '${n}': ${n},`).join('\n')}
151
+ };
152
+
153
+ export default registry;
154
+ `;
155
+ }
package/dist/cli/index.js CHANGED
@@ -10,9 +10,24 @@ import { updateCommand, statusCommand } from './update.js';
10
10
  import net from 'net';
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = path.dirname(__filename);
13
- // Read version from package.json
14
- const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
15
- const packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).version;
13
+ // Read version from package.json (try both src/cli and dist/cli paths)
14
+ const packageJsonPaths = [
15
+ path.resolve(__dirname, '..', 'package.json'), // From src/cli
16
+ path.resolve(__dirname, '..', '..', 'package.json'), // From dist/cli
17
+ ];
18
+ let packageVersion = 'unknown';
19
+ for (const p of packageJsonPaths) {
20
+ if (fs.existsSync(p)) {
21
+ try {
22
+ const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
23
+ if (pkg.version) {
24
+ packageVersion = pkg.version;
25
+ break;
26
+ }
27
+ }
28
+ catch { /* skip */ }
29
+ }
30
+ }
16
31
  const program = new Command();
17
32
  program
18
33
  .name('story-ui')
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../cli/setup.ts"],"names":[],"mappings":"AAmDA;;GAEG;AACH,wBAAgB,iCAAiC,SA8ChD;AAiWD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,iBA85B5D"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../cli/setup.ts"],"names":[],"mappings":"AAmDA;;GAEG;AACH,wBAAgB,iCAAiC,SA8ChD;AAoXD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,iBA6/B5D"}
package/dist/cli/setup.js CHANGED
@@ -128,10 +128,19 @@ function setupStorybookPreview(designSystem) {
128
128
  const designSystemConfigs = {
129
129
  chakra: {
130
130
  imports: [
131
+ "import React from 'react'",
131
132
  "import type { Preview } from '@storybook/react-vite'",
132
- "import { ChakraProvider, defaultSystem } from '@chakra-ui/react'",
133
- "import React from 'react'"
133
+ "import * as ChakraUI from '@chakra-ui/react'",
134
134
  ],
135
+ globals: `
136
+ const { ChakraProvider, defaultSystem } = ChakraUI;
137
+
138
+ // Expose all Chakra components for Voice Canvas live preview.
139
+ // Voice Canvas uses these globals to render components without import statements.
140
+ (window as any).__STORY_UI_DESIGN_SYSTEM__ = ChakraUI;
141
+ (window as any).__STORY_UI_CANVAS_PROVIDER__ = ({ children }: { children: React.ReactNode }) => (
142
+ <ChakraProvider value={defaultSystem}>{children}</ChakraProvider>
143
+ );`,
135
144
  decorator: `(Story) => (
136
145
  <ChakraProvider value={defaultSystem}>
137
146
  <Story />
@@ -140,11 +149,20 @@ function setupStorybookPreview(designSystem) {
140
149
  },
141
150
  mantine: {
142
151
  imports: [
152
+ "import React from 'react'",
143
153
  "import type { Preview } from '@storybook/react-vite'",
144
- "import { MantineProvider } from '@mantine/core'",
154
+ "import * as MantineCore from '@mantine/core'",
145
155
  "import '@mantine/core/styles.css'",
146
- "import React from 'react'"
147
156
  ],
157
+ globals: `
158
+ const { MantineProvider } = MantineCore;
159
+
160
+ // Expose all Mantine components for Voice Canvas live preview.
161
+ // Voice Canvas uses these globals to render components without import statements.
162
+ (window as any).__STORY_UI_DESIGN_SYSTEM__ = MantineCore;
163
+ (window as any).__STORY_UI_CANVAS_PROVIDER__ = ({ children }: { children: React.ReactNode }) => (
164
+ <MantineProvider>{children}</MantineProvider>
165
+ );`,
148
166
  decorator: `(Story) => (
149
167
  <MantineProvider>
150
168
  <Story />
@@ -157,6 +175,7 @@ function setupStorybookPreview(designSystem) {
157
175
  return;
158
176
  // Create the preview content
159
177
  const previewContent = `${config.imports.join('\n')}
178
+ ${config.globals || ''}
160
179
 
161
180
  const preview: Preview = {
162
181
  parameters: {
@@ -917,6 +936,8 @@ Material UI (MUI) is a React component library implementing Material Design.
917
936
  // Copy component files
918
937
  const templatesDir = path.resolve(__dirname, '../../templates/StoryUI');
919
938
  const componentFiles = ['StoryUIPanel.tsx', 'StoryUIPanel.mdx', 'StoryUIPanel.css'];
939
+ // Voice Canvas files (subdirectory)
940
+ const voiceFiles = ['VoiceCanvas.tsx', 'VoiceControls.tsx', 'useVoiceInput.ts', 'voiceCommands.ts', 'types.ts'];
920
941
  console.log(chalk.blue('\n📦 Installing Story UI component...'));
921
942
  for (const file of componentFiles) {
922
943
  const sourcePath = path.join(templatesDir, file);
@@ -934,6 +955,96 @@ Material UI (MUI) is a React component library implementing Material Design.
934
955
  console.warn(chalk.yellow(`⚠️ Template file not found: ${file}`));
935
956
  }
936
957
  }
958
+ // Copy Voice Canvas files
959
+ const voiceSourceDir = path.join(templatesDir, 'voice');
960
+ const voiceTargetDir = path.join(storyUITargetDir, 'voice');
961
+ if (fs.existsSync(voiceSourceDir)) {
962
+ if (!fs.existsSync(voiceTargetDir)) {
963
+ fs.mkdirSync(voiceTargetDir, { recursive: true });
964
+ }
965
+ for (const file of voiceFiles) {
966
+ const sourcePath = path.join(voiceSourceDir, file);
967
+ const targetPath = path.join(voiceTargetDir, file);
968
+ if (fs.existsSync(sourcePath)) {
969
+ fs.copyFileSync(sourcePath, targetPath);
970
+ console.log(chalk.green(`✅ Copied voice/${file}`));
971
+ }
972
+ }
973
+ }
974
+ // Generate component registry for Voice Canvas with static design system import
975
+ // Vite requires static imports — bare specifiers can't be resolved at runtime
976
+ const canvasTargetDir = path.join(voiceTargetDir, 'canvas');
977
+ if (!fs.existsSync(canvasTargetDir)) {
978
+ fs.mkdirSync(canvasTargetDir, { recursive: true });
979
+ }
980
+ // Copy canvas template files (types.ts, operations.ts, ComponentRenderer.tsx)
981
+ const canvasSourceDir = path.join(voiceSourceDir, 'canvas');
982
+ if (fs.existsSync(canvasSourceDir)) {
983
+ for (const file of fs.readdirSync(canvasSourceDir)) {
984
+ if (file === 'componentRegistry.ts')
985
+ continue; // generated below
986
+ const src = path.join(canvasSourceDir, file);
987
+ const dst = path.join(canvasTargetDir, file);
988
+ if (fs.statSync(src).isFile()) {
989
+ fs.copyFileSync(src, dst);
990
+ console.log(chalk.green(`✅ Copied voice/canvas/${file}`));
991
+ }
992
+ }
993
+ }
994
+ // Generate the registry with a lazy async import of the design system.
995
+ // Uses a literal string in import() so Vite resolves it at build time,
996
+ // but execution is deferred until loadRegistry() is called — avoiding
997
+ // module-level side effects that can crash the docs page.
998
+ if (config.importPath && config.importStyle !== 'individual') {
999
+ const registryContent = `/**
1000
+ * Component registry for Voice Canvas — lazy-loaded from design system.
1001
+ *
1002
+ * Uses dynamic import('${config.importPath}') with a literal string so Vite
1003
+ * resolves it at build time, but the import only executes when loadRegistry()
1004
+ * is called (not at module evaluation time). This prevents crashes from
1005
+ * module-level side effects.
1006
+ *
1007
+ * Generated by story-ui init. Regenerate with: npx story-ui registry
1008
+ */
1009
+
1010
+ export const registry: Record<string, any> = {};
1011
+
1012
+ let _loaded = false;
1013
+
1014
+ export async function loadRegistry(): Promise<Record<string, any>> {
1015
+ if (_loaded) return registry;
1016
+
1017
+ try {
1018
+ const mod = await import('${config.importPath}');
1019
+
1020
+ for (const [key, value] of Object.entries(mod)) {
1021
+ if (/^[A-Z]/.test(key) && (typeof value === 'function' || typeof value === 'object')) {
1022
+ registry[key] = value;
1023
+ }
1024
+ }
1025
+
1026
+ _loaded = true;
1027
+ console.log(\\\`[componentRegistry] Loaded \\\${Object.keys(registry).length} components from ${config.importPath}\\\`);
1028
+ } catch (err) {
1029
+ console.error('[componentRegistry] Failed to load design system:', err);
1030
+ }
1031
+
1032
+ return registry;
1033
+ }
1034
+
1035
+ export default registry;
1036
+ `;
1037
+ fs.writeFileSync(path.join(canvasTargetDir, 'componentRegistry.ts'), registryContent);
1038
+ console.log(chalk.green(`✅ Generated voice/canvas/componentRegistry.ts with ${config.importPath} imports`));
1039
+ }
1040
+ else {
1041
+ // For individual import style or unknown, copy the placeholder
1042
+ const placeholderSrc = path.join(canvasSourceDir, 'componentRegistry.ts');
1043
+ if (fs.existsSync(placeholderSrc)) {
1044
+ fs.copyFileSync(placeholderSrc, path.join(canvasTargetDir, 'componentRegistry.ts'));
1045
+ }
1046
+ console.log(chalk.yellow(`⚠️ Voice Canvas registry is empty — populate voice/canvas/componentRegistry.ts manually`));
1047
+ }
937
1048
  // Configure Storybook bundler for StoryUIPanel requirements
938
1049
  console.log(chalk.blue('\n🔧 Configuring Storybook for Story UI...'));
939
1050
  const mainConfigPath = path.join(process.cwd(), '.storybook', 'main.ts');
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../cli/update.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAgTD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkItF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CA+BpC"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../cli/update.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AA0UD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkItF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CA+BpC"}
@@ -26,6 +26,32 @@ const MANAGED_FILES = [
26
26
  source: 'templates/StoryUI/index.tsx',
27
27
  target: 'src/stories/StoryUI/index.tsx',
28
28
  description: 'Panel registration'
29
+ },
30
+ // Voice Canvas files
31
+ {
32
+ source: 'templates/StoryUI/voice/VoiceCanvas.tsx',
33
+ target: 'src/stories/StoryUI/voice/VoiceCanvas.tsx',
34
+ description: 'Voice Canvas component'
35
+ },
36
+ {
37
+ source: 'templates/StoryUI/voice/VoiceControls.tsx',
38
+ target: 'src/stories/StoryUI/voice/VoiceControls.tsx',
39
+ description: 'Voice control UI components'
40
+ },
41
+ {
42
+ source: 'templates/StoryUI/voice/useVoiceInput.ts',
43
+ target: 'src/stories/StoryUI/voice/useVoiceInput.ts',
44
+ description: 'Voice input hook'
45
+ },
46
+ {
47
+ source: 'templates/StoryUI/voice/voiceCommands.ts',
48
+ target: 'src/stories/StoryUI/voice/voiceCommands.ts',
49
+ description: 'Voice command definitions'
50
+ },
51
+ {
52
+ source: 'templates/StoryUI/voice/types.ts',
53
+ target: 'src/stories/StoryUI/voice/types.ts',
54
+ description: 'Voice module type definitions'
29
55
  }
30
56
  ];
31
57
  // Files that should NEVER be modified by update
@@ -22,14 +22,23 @@ import { UrlRedirectService } from '../story-generator/urlRedirectService.js';
22
22
  import { getProviders, getModels, configureProviderRoute, validateApiKey, setDefaultProvider, setModel, getUISettings, applyUISettings, getSettingsConfig } from './routes/providers.js';
23
23
  import { listFrameworks, detectCurrentFramework, getFrameworkDetails, validateStoryForFramework, postProcessStoryForFramework, } from './routes/frameworks.js';
24
24
  import mcpRemoteRouter from './routes/mcpRemote.js';
25
+ // Voice Canvas endpoints
26
+ import { canvasIntentHandler, warmCanvasComponentCache } from './routes/canvasIntent.js';
27
+ import { canvasSaveHandler } from './routes/canvasSave.js';
28
+ import { canvasGenerateHandler, ensureVoiceCanvasStory } from './routes/canvasGenerate.js';
29
+ import { canvasPreviewHandler } from './routes/canvasPreview.js';
25
30
  import { getAdapterRegistry } from '../story-generator/framework-adapters/index.js';
31
+ // Manifest — story ↔ chat source of truth
32
+ import { manifestGetHandler, manifestPatchHandler, manifestDeleteHandler, manifestReconcileHandler, manifestPollHandler, } from './routes/manifest.js';
33
+ import { getManifestManager } from '../story-generator/manifestManager.js';
26
34
  // Supported story file extensions for all frameworks
27
35
  const STORY_EXTENSIONS = ['.stories.tsx', '.stories.ts', '.stories.svelte', '.stories.js'];
28
36
  /**
29
37
  * Check if a file is a story file (supports all framework extensions)
30
38
  */
31
39
  function isStoryFile(filename) {
32
- return STORY_EXTENSIONS.some(ext => filename.endsWith(ext));
40
+ return STORY_EXTENSIONS.some(ext => filename.endsWith(ext))
41
+ && !filename.startsWith('voice-canvas'); // scratchpad — excluded from story lists
33
42
  }
34
43
  /**
35
44
  * Remove story extension from filename to get base name
@@ -99,6 +108,27 @@ app.get('/mcp/props', getProps);
99
108
  app.post('/mcp/claude', claudeProxy);
100
109
  app.post('/mcp/generate-story', generateStoryFromPrompt);
101
110
  app.post('/mcp/generate-story-stream', generateStoryFromPromptStream);
111
+ // Voice Canvas endpoints
112
+ app.post('/mcp/canvas-generate', canvasGenerateHandler); // generate + write voice-canvas.stories.tsx
113
+ app.post('/mcp/canvas-preview', canvasPreviewHandler); // undo/redo: rewrite voice-canvas.stories.tsx
114
+ app.post('/mcp/canvas-save', canvasSaveHandler); // save canvas to named .stories.tsx
115
+ app.post('/mcp/canvas-intent', canvasIntentHandler); // legacy (kept for compatibility)
116
+ // Manifest — story ↔ chat source of truth
117
+ // NOTE: /reconcile must be registered BEFORE /:fileName to avoid route conflict
118
+ app.get('/story-ui/manifest/poll', manifestPollHandler);
119
+ app.post('/story-ui/manifest/reconcile', manifestReconcileHandler);
120
+ app.get('/story-ui/manifest', manifestGetHandler);
121
+ app.patch('/story-ui/manifest/:fileName', manifestPatchHandler);
122
+ app.delete('/story-ui/manifest/:fileName', manifestDeleteHandler);
123
+ // Expose design-system config for auto-registry loading
124
+ app.get('/mcp/canvas-config', (_req, res) => {
125
+ res.json({
126
+ importPath: config.importPath || '',
127
+ importStyle: config.importStyle || 'barrel',
128
+ componentPrefix: config.componentPrefix || '',
129
+ componentFramework: config.componentFramework || 'react',
130
+ });
131
+ });
102
132
  // LLM Provider management routes
103
133
  app.get('/mcp/providers', getProviders);
104
134
  app.get('/mcp/providers/models', getModels);
@@ -292,6 +322,7 @@ app.delete('/mcp/stories/:storyId', async (req, res) => {
292
322
  // Proxy routes for frontend compatibility (maps /story-ui/ to /mcp/)
293
323
  app.post('/story-ui/generate', generateStoryFromPrompt);
294
324
  app.post('/story-ui/generate-stream', generateStoryFromPromptStream);
325
+ // voice-render and convert-to-story aliases removed
295
326
  app.post('/story-ui/claude', claudeProxy);
296
327
  app.get('/story-ui/components', getComponents);
297
328
  app.get('/story-ui/props', getProps);
@@ -428,6 +459,33 @@ app.post('/story-ui/stories', async (req, res) => {
428
459
  return res.status(500).json({ error: 'Failed to save story' });
429
460
  }
430
461
  });
462
+ // Rename story title in file and manifest
463
+ app.patch('/story-ui/stories/:fileName/rename', async (req, res) => {
464
+ try {
465
+ const { fileName } = req.params;
466
+ const { title } = req.body;
467
+ if (!title || typeof title !== 'string' || !title.trim()) {
468
+ return res.status(400).json({ error: 'title is required' });
469
+ }
470
+ const newTitle = title.trim();
471
+ const storiesPath = config.generatedStoriesPath;
472
+ const filePath = safePath(storiesPath, fileName);
473
+ if (!filePath || !fs.existsSync(filePath)) {
474
+ return res.status(404).json({ error: 'Story not found' });
475
+ }
476
+ // Update the title string inside the file (replaces title: 'Generated/OldTitle')
477
+ let content = fs.readFileSync(filePath, 'utf-8');
478
+ content = content.replace(/(title:\s*['"`]Generated\/)[^'"`]*/, `$1${newTitle.replace(/'/g, "\\'")}`);
479
+ fs.writeFileSync(filePath, content, 'utf-8');
480
+ // Update manifest entry title
481
+ getManifestManager().upsert(fileName, { title: newTitle });
482
+ return res.json({ success: true, title: newTitle });
483
+ }
484
+ catch (error) {
485
+ const message = error instanceof Error ? error.message : String(error);
486
+ return res.status(500).json({ error: message });
487
+ }
488
+ });
431
489
  // Delete story by ID (RESTful endpoint)
432
490
  // Supports both fileName format (Button-a1b2c3d4.stories.tsx) and legacy storyId format (story-a1b2c3d4)
433
491
  app.delete('/story-ui/stories/:id', async (req, res) => {
@@ -451,6 +509,7 @@ app.delete('/story-ui/stories/:id', async (req, res) => {
451
509
  }
452
510
  if (fs.existsSync(filePath)) {
453
511
  fs.unlinkSync(filePath);
512
+ getManifestManager().delete(fileName);
454
513
  console.log(`✅ Deleted story: ${filePath}`);
455
514
  return res.json({ success: true, message: 'Story deleted successfully' });
456
515
  }
@@ -464,6 +523,7 @@ app.delete('/story-ui/stories/:id', async (req, res) => {
464
523
  if (matchingFile) {
465
524
  const matchedFilePath = path.join(storiesPath, matchingFile);
466
525
  fs.unlinkSync(matchedFilePath);
526
+ getManifestManager().delete(matchingFile);
467
527
  console.log(`✅ Deleted story by hash match: ${matchedFilePath}`);
468
528
  return res.json({ success: true, message: 'Story deleted successfully' });
469
529
  }
@@ -602,6 +662,7 @@ app.delete('/story-ui/stories', async (req, res) => {
602
662
  }
603
663
  if (fs.existsSync(filePath)) {
604
664
  fs.unlinkSync(filePath);
665
+ getManifestManager().delete(fileName);
605
666
  console.log(`✅ Deleted story: ${filePath}`);
606
667
  return res.json({ success: true, message: 'Story deleted successfully' });
607
668
  }
@@ -617,6 +678,7 @@ app.delete('/story-ui/stories', async (req, res) => {
617
678
  return res.status(400).json({ success: false, error: 'Invalid file path' });
618
679
  }
619
680
  fs.unlinkSync(filePath);
681
+ getManifestManager().delete(matchingFile);
620
682
  console.log(`✅ Deleted story by hash match: ${filePath}`);
621
683
  return res.json({ success: true, message: 'Story deleted successfully' });
622
684
  }
@@ -632,12 +694,14 @@ app.delete('/story-ui/stories', async (req, res) => {
632
694
  // Support all story file extensions
633
695
  const storyFiles = files.filter(file => isStoryFile(file));
634
696
  let deleted = 0;
697
+ const manifest = getManifestManager();
635
698
  for (const file of storyFiles) {
636
699
  try {
637
700
  const fp = safePath(storiesPath, file);
638
701
  if (!fp)
639
702
  continue;
640
703
  fs.unlinkSync(fp);
704
+ manifest.delete(file);
641
705
  deleted++;
642
706
  }
643
707
  catch (err) {
@@ -819,6 +883,27 @@ if (storybookProxyEnabled) {
819
883
  app.listen(PORT, () => {
820
884
  console.error(`MCP server running on port ${PORT}`);
821
885
  console.error(`Stories will be generated to: ${config.generatedStoriesPath}`);
886
+ // Pre-warm canvas component cache in background so first voice request is fast
887
+ warmCanvasComponentCache().catch(() => { });
888
+ // Ensure voice-canvas scratchpad story file exists before client polling starts.
889
+ // If it's missing, the first canvas generate creates it, triggering a false-positive
890
+ // "externally generated story" detection which reloads the page and kills Voice Canvas.
891
+ try {
892
+ ensureVoiceCanvasStory(config.generatedStoriesPath || './src/stories/generated/');
893
+ }
894
+ catch (err) {
895
+ console.error('[voice-canvas] Could not pre-create story template:', err);
896
+ }
897
+ // Initialize manifest manager (loads file, migrates from StoryTracker, reconciles)
898
+ setTimeout(() => {
899
+ try {
900
+ getManifestManager();
901
+ console.error('[manifest] Initialized and reconciled');
902
+ }
903
+ catch (err) {
904
+ console.error('[manifest] Init error:', err);
905
+ }
906
+ }, 500);
822
907
  }).on('error', (err) => {
823
908
  if (err.code === 'EADDRINUSE') {
824
909
  console.error(`\n❌ Port ${PORT} is already in use!`);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Canvas Generate Endpoint
3
+ *
4
+ * Generates a JSX component for the Voice Canvas preview.
5
+ * Uses the same quality pipeline as standard story generation.
6
+ * Writes a voice-canvas.stories.tsx file so the iframe renders with
7
+ * the full Storybook decorator chain (Provider, themes, etc.).
8
+ *
9
+ * Voice Canvas requires a React-based Storybook framework.
10
+ * Components come from window.__STORY_UI_DESIGN_SYSTEM__ set in .storybook/preview.tsx.
11
+ *
12
+ * POST /mcp/canvas-generate
13
+ * Body: { prompt, canvasCode?, provider, model, conversationHistory? }
14
+ * Returns: { canvasCode: string, storyId: string }
15
+ */
16
+ import { Request, Response } from 'express';
17
+ export declare const VOICE_CANVAS_STORY_ID = "generated-voice-canvas--default";
18
+ export declare function ensureReactLive(): void;
19
+ /**
20
+ * Write the static voice-canvas story template if it doesn't exist yet.
21
+ * Subsequent calls are no-ops — the file never changes after initial creation.
22
+ */
23
+ export declare function ensureVoiceCanvasStory(storiesDir: string): void;
24
+ export declare function canvasGenerateHandler(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
25
+ //# sourceMappingURL=canvasGenerate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAW5C,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAmIvE,wBAAgB,eAAe,IAAI,IAAI,CAuBtC;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU/D;AAuBD,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CAwEtE"}