@tpitre/story-ui 1.7.1 → 2.0.1

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 (35) hide show
  1. package/.env.sample +3 -1
  2. package/README.md +160 -606
  3. package/dist/cli/index.js +23 -24
  4. package/dist/cli/setup.js +295 -36
  5. package/dist/mcp-server/index.js +67 -0
  6. package/dist/mcp-server/routes/generateStory.js +323 -56
  7. package/dist/story-generator/componentBlacklist.js +181 -0
  8. package/dist/story-generator/componentDiscovery.js +9 -2
  9. package/dist/story-generator/configLoader.js +109 -39
  10. package/dist/story-generator/considerationsLoader.js +204 -0
  11. package/dist/story-generator/documentation-sources.js +36 -0
  12. package/dist/story-generator/documentationLoader.js +214 -0
  13. package/dist/story-generator/dynamicPackageDiscovery.js +527 -0
  14. package/dist/story-generator/enhancedComponentDiscovery.js +369 -118
  15. package/dist/story-generator/generateStory.js +7 -3
  16. package/dist/story-generator/postProcessStory.js +71 -0
  17. package/dist/story-generator/promptGenerator.js +286 -37
  18. package/dist/story-generator/storyHistory.js +118 -0
  19. package/dist/story-generator/storyTracker.js +33 -18
  20. package/dist/story-generator/storyValidator.js +39 -0
  21. package/dist/story-generator/universalDesignSystemAdapter.js +209 -0
  22. package/dist/story-generator/validateStory.js +82 -7
  23. package/dist/story-ui.config.js +12 -5
  24. package/package.json +11 -6
  25. package/templates/StoryUI/StoryUIPanel.stories.tsx +29 -13
  26. package/templates/StoryUI/StoryUIPanel.tsx +489 -359
  27. package/templates/react-import-rule.json +36 -0
  28. package/templates/story-generation-rules.json +29 -0
  29. package/templates/story-ui-considerations.json +156 -0
  30. package/templates/story-ui-considerations.md +109 -0
  31. package/templates/story-ui-docs-README.md +55 -0
  32. package/dist/scripts/test-validation.js +0 -81
  33. package/dist/test-storybooks/chakra-test/src/components/index.js +0 -3
  34. package/dist/test-storybooks/custom-design-test/src/components/index.js +0 -3
  35. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -14,7 +14,9 @@ export class StoryTracker {
14
14
  if (fs.existsSync(this.mappingFile)) {
15
15
  const data = JSON.parse(fs.readFileSync(this.mappingFile, 'utf-8'));
16
16
  for (const mapping of data) {
17
- this.mappings.set(mapping.title.toLowerCase(), mapping);
17
+ if (mapping && mapping.title && typeof mapping.title === 'string') {
18
+ this.mappings.set(mapping.title.toLowerCase(), mapping);
19
+ }
18
20
  }
19
21
  }
20
22
  }
@@ -39,47 +41,56 @@ export class StoryTracker {
39
41
  * Find an existing story by title
40
42
  */
41
43
  findByTitle(title) {
44
+ if (!title || typeof title !== 'string') {
45
+ return undefined;
46
+ }
42
47
  // Normalize the title for comparison
43
48
  const normalizedTitle = title.toLowerCase().trim();
44
49
  // Try exact match first
45
50
  let mapping = this.mappings.get(normalizedTitle);
46
51
  if (mapping)
47
52
  return mapping;
48
- // Try fuzzy matching for similar titles
49
- for (const [key, value] of this.mappings) {
50
- // Check if the key contains the normalized title or vice versa
51
- if (key.includes(normalizedTitle) || normalizedTitle.includes(key)) {
52
- return value;
53
- }
54
- // Check for very similar titles (e.g., "dashboard" vs "inventory dashboard")
55
- const keywords = normalizedTitle.split(/\s+/);
56
- const keyKeywords = key.split(/\s+/);
57
- // If all keywords from the shorter title are in the longer one
58
- const shortKeywords = keywords.length < keyKeywords.length ? keywords : keyKeywords;
59
- const longKeywords = keywords.length < keyKeywords.length ? keyKeywords : keywords;
60
- if (shortKeywords.every(word => longKeywords.includes(word))) {
61
- return value;
62
- }
63
- }
53
+ // Don't do fuzzy matching for titles - only exact matches
54
+ // This prevents "Card" from matching "Card Layouts" or "Profile Card"
55
+ // Users expect new stories when they request variations
64
56
  return undefined;
65
57
  }
66
58
  /**
67
59
  * Find an existing story by prompt similarity
68
60
  */
69
61
  findByPrompt(prompt) {
62
+ if (!prompt || typeof prompt !== 'string') {
63
+ return undefined;
64
+ }
70
65
  const normalizedPrompt = prompt.toLowerCase().trim();
71
66
  // Remove common prefixes like "generate a", "create a", etc.
72
67
  const cleanPrompt = normalizedPrompt
73
68
  .replace(/^(generate|create|build|make|design|show|write|produce|construct|draft|compose|implement|add|render|display)\s+(a|an|the)?\s*/i, '')
74
69
  .trim();
70
+ // Extract key terms from the prompt
71
+ const promptKeywords = cleanPrompt.split(/\s+/).filter(word => word.length > 2);
75
72
  // Try to find by similar prompts
76
73
  for (const mapping of this.mappings.values()) {
74
+ if (!mapping || !mapping.prompt || typeof mapping.prompt !== 'string') {
75
+ continue;
76
+ }
77
77
  const mappingPrompt = mapping.prompt.toLowerCase()
78
78
  .replace(/^(generate|create|build|make|design|show|write|produce|construct|draft|compose|implement|add|render|display)\s+(a|an|the)?\s*/i, '')
79
79
  .trim();
80
+ // Exact match
80
81
  if (mappingPrompt === cleanPrompt) {
81
82
  return mapping;
82
83
  }
84
+ // Fuzzy matching based on shared keywords
85
+ const mappingKeywords = mappingPrompt.split(/\s+/).filter(word => word.length > 2);
86
+ const sharedKeywords = promptKeywords.filter(word => mappingKeywords.includes(word));
87
+ // Only consider it similar if 90% or more keywords match AND at least 4 keywords
88
+ // This prevents false positives like "card" matching "card layouts" vs "card animations"
89
+ const similarityThreshold = Math.max(4, Math.floor(promptKeywords.length * 0.9));
90
+ if (sharedKeywords.length >= similarityThreshold && promptKeywords.length >= 4) {
91
+ console.log(`🔄 Found similar story: "${mapping.title}" (${sharedKeywords.length}/${promptKeywords.length} keywords match)`);
92
+ return mapping;
93
+ }
83
94
  }
84
95
  return undefined;
85
96
  }
@@ -87,6 +98,10 @@ export class StoryTracker {
87
98
  * Register a new or updated story
88
99
  */
89
100
  registerStory(mapping) {
101
+ if (!mapping || !mapping.title || typeof mapping.title !== 'string') {
102
+ console.warn('Invalid mapping provided to registerStory:', mapping);
103
+ return;
104
+ }
90
105
  const normalizedTitle = mapping.title.toLowerCase();
91
106
  // Check if we're updating an existing story
92
107
  const existing = this.findByTitle(mapping.title);
@@ -109,7 +124,7 @@ export class StoryTracker {
109
124
  removeStory(titleOrFileName) {
110
125
  // Try to find by title first
111
126
  const byTitle = this.findByTitle(titleOrFileName);
112
- if (byTitle) {
127
+ if (byTitle && byTitle.title && typeof byTitle.title === 'string') {
113
128
  this.mappings.delete(byTitle.title.toLowerCase());
114
129
  this.saveMappings();
115
130
  return true;
@@ -0,0 +1,39 @@
1
+ export function validateStory(storyContent) {
2
+ const errors = [];
3
+ const lines = storyContent.split('\n');
4
+ // These are warnings/suggestions rather than strict forbidden patterns
5
+ // Only flag truly problematic patterns that would break the story
6
+ const forbiddenPatterns = [
7
+ { pattern: /UNSAFE_style\s*=\s*\{/i, message: 'The `UNSAFE_style` prop is strictly forbidden. Do not use it for any reason.' },
8
+ { pattern: /UNSAFE_className\s*=\s*['"]/i, message: 'The `UNSAFE_className` prop is forbidden.' },
9
+ { pattern: /<Text\s+as\s*=\s*["']h[1-6]["']/i, message: 'Text component does not support heading elements (h1-h6) in the "as" prop. Use Heading component instead.' },
10
+ // Remove overly strict rules - divs, imgs, and inline styles are fine in moderation
11
+ // Only check for actual syntax errors or patterns that would break Storybook
12
+ ];
13
+ lines.forEach((line, index) => {
14
+ for (const { pattern, message } of forbiddenPatterns) {
15
+ if (pattern.test(line)) {
16
+ errors.push({
17
+ message,
18
+ line: index + 1,
19
+ });
20
+ }
21
+ }
22
+ });
23
+ // Check for truncated story (multiple closing tags on a single line followed by };)
24
+ const lastFewLines = lines.slice(-5).join('\n');
25
+ if (lastFewLines.match(/<\/\w+><\/\w+><\/\w+><\/\w+>.*\n\s*\};/)) {
26
+ errors.push({
27
+ message: 'Story appears to be truncated. Multiple closing tags found on a single line followed by abrupt ending.',
28
+ line: lines.length - 1,
29
+ });
30
+ }
31
+ // Check for proper story structure
32
+ if (!storyContent.includes('export default meta')) {
33
+ errors.push({
34
+ message: 'Story is missing required "export default meta" statement.',
35
+ line: 1,
36
+ });
37
+ }
38
+ return errors;
39
+ }
@@ -0,0 +1,209 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Universal adapter to make Story UI work with any React design system
5
+ */
6
+ export class UniversalDesignSystemAdapter {
7
+ constructor(projectRoot = process.cwd()) {
8
+ this.detectedSystems = [];
9
+ this.projectRoot = projectRoot;
10
+ }
11
+ /**
12
+ * Auto-detect which design systems are available in the project
13
+ */
14
+ async detectDesignSystems() {
15
+ const packageJsonPath = path.join(this.projectRoot, 'package.json');
16
+ if (!fs.existsSync(packageJsonPath)) {
17
+ console.log('📦 No package.json found for design system detection');
18
+ return [];
19
+ }
20
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
21
+ const allDeps = {
22
+ ...packageJson.dependencies,
23
+ ...packageJson.devDependencies,
24
+ ...packageJson.peerDependencies
25
+ };
26
+ this.detectedSystems = [];
27
+ // Check for known design systems
28
+ this.checkForChakraUI(allDeps);
29
+ this.checkForAntDesign(allDeps);
30
+ this.checkForMantine(allDeps);
31
+ this.checkForGenericReactComponents(allDeps);
32
+ console.log(`🎨 Detected ${this.detectedSystems.length} design systems:`, this.detectedSystems.map(ds => ds.name));
33
+ return this.detectedSystems;
34
+ }
35
+ /**
36
+ * Generate optimal Story UI config for detected design systems
37
+ */
38
+ generateOptimalConfig() {
39
+ const primarySystem = this.getPrimaryDesignSystem();
40
+ if (!primarySystem) {
41
+ return this.getGenericReactConfig();
42
+ }
43
+ switch (primarySystem.type) {
44
+ case 'chakra-ui':
45
+ return this.getChakraUIConfig();
46
+ case 'antd':
47
+ return this.getAntDesignConfig();
48
+ case 'mantine':
49
+ return this.getMantineConfig();
50
+ default:
51
+ return this.getGenericReactConfig();
52
+ }
53
+ }
54
+ // Design system detection methods
55
+ checkForChakraUI(deps) {
56
+ if (deps['@chakra-ui/react']) {
57
+ this.detectedSystems.push({
58
+ name: 'Chakra UI',
59
+ type: 'chakra-ui',
60
+ scope: '@chakra-ui',
61
+ primaryPackage: '@chakra-ui/react',
62
+ commonComponents: ['Box', 'Flex', 'Grid', 'Stack', 'Text', 'Heading', 'Button', 'Input'],
63
+ layoutComponents: ['Box', 'Flex', 'Grid', 'Stack', 'HStack', 'VStack'],
64
+ formComponents: ['Input', 'Button', 'Select', 'Checkbox', 'Radio'],
65
+ importPatterns: {
66
+ default: [],
67
+ named: ['Box', 'Flex', 'Grid', 'Stack', 'Text', 'Heading', 'Button', 'Input']
68
+ },
69
+ designTokens: {
70
+ spacing: 'spacing.*',
71
+ colors: 'colors.*'
72
+ }
73
+ });
74
+ }
75
+ }
76
+ checkForAntDesign(deps) {
77
+ if (deps['antd']) {
78
+ this.detectedSystems.push({
79
+ name: 'Ant Design',
80
+ type: 'antd',
81
+ primaryPackage: 'antd',
82
+ commonComponents: ['Layout', 'Row', 'Col', 'Space', 'Typography', 'Button', 'Input'],
83
+ layoutComponents: ['Layout', 'Row', 'Col', 'Space'],
84
+ formComponents: ['Input', 'Button', 'Select', 'Checkbox', 'Radio'],
85
+ importPatterns: {
86
+ default: [],
87
+ named: ['Layout', 'Row', 'Col', 'Space', 'Typography', 'Button', 'Input']
88
+ }
89
+ });
90
+ }
91
+ }
92
+ checkForMantine(deps) {
93
+ if (deps['@mantine/core']) {
94
+ this.detectedSystems.push({
95
+ name: 'Mantine',
96
+ type: 'mantine',
97
+ scope: '@mantine',
98
+ primaryPackage: '@mantine/core',
99
+ commonComponents: ['Box', 'Flex', 'Grid', 'Stack', 'Text', 'Title', 'Button', 'TextInput'],
100
+ layoutComponents: ['Box', 'Flex', 'Grid', 'Stack'],
101
+ formComponents: ['TextInput', 'Button', 'Select', 'Checkbox', 'Radio'],
102
+ importPatterns: {
103
+ default: [],
104
+ named: ['Box', 'Flex', 'Grid', 'Stack', 'Text', 'Title', 'Button', 'TextInput']
105
+ }
106
+ });
107
+ }
108
+ }
109
+ checkForGenericReactComponents(deps) {
110
+ // Check for generic React component libraries
111
+ const genericLibraries = [
112
+ 'react-bootstrap',
113
+ 'semantic-ui-react',
114
+ 'rebass',
115
+ 'theme-ui',
116
+ 'styled-components',
117
+ '@emotion/react'
118
+ ];
119
+ const foundGeneric = genericLibraries.filter(lib => deps[lib]);
120
+ if (foundGeneric.length > 0) {
121
+ this.detectedSystems.push({
122
+ name: 'Generic React Components',
123
+ type: 'generic',
124
+ primaryPackage: foundGeneric[0],
125
+ commonComponents: ['div', 'span', 'button', 'input', 'form'],
126
+ layoutComponents: ['div', 'section', 'main'],
127
+ formComponents: ['input', 'button', 'select', 'textarea'],
128
+ importPatterns: {
129
+ default: [],
130
+ named: []
131
+ }
132
+ });
133
+ }
134
+ }
135
+ // Configuration generators
136
+ getPrimaryDesignSystem() {
137
+ // Prioritize based on completeness and popularity
138
+ const priorities = ['chakra-ui', 'antd', 'mantine', 'generic'];
139
+ for (const priority of priorities) {
140
+ const system = this.detectedSystems.find(ds => ds.type === priority);
141
+ if (system)
142
+ return system;
143
+ }
144
+ return this.detectedSystems[0] || null;
145
+ }
146
+ getChakraUIConfig() {
147
+ return {
148
+ designSystemGuidelines: {
149
+ name: "Chakra UI",
150
+ preferredComponents: {
151
+ layout: "@chakra-ui/react",
152
+ buttons: "@chakra-ui/react",
153
+ forms: "@chakra-ui/react"
154
+ }
155
+ },
156
+ layoutRules: {
157
+ multiColumnWrapper: "Grid",
158
+ columnComponent: "Box",
159
+ containerComponent: "Container"
160
+ }
161
+ };
162
+ }
163
+ getAntDesignConfig() {
164
+ return {
165
+ designSystemGuidelines: {
166
+ name: "Ant Design",
167
+ preferredComponents: {
168
+ layout: "antd",
169
+ buttons: "antd",
170
+ forms: "antd"
171
+ }
172
+ },
173
+ layoutRules: {
174
+ multiColumnWrapper: "Row",
175
+ columnComponent: "Col",
176
+ containerComponent: "Layout"
177
+ }
178
+ };
179
+ }
180
+ getMantineConfig() {
181
+ return {
182
+ designSystemGuidelines: {
183
+ name: "Mantine",
184
+ preferredComponents: {
185
+ layout: "@mantine/core",
186
+ buttons: "@mantine/core",
187
+ forms: "@mantine/core"
188
+ }
189
+ }
190
+ };
191
+ }
192
+ getGenericReactConfig() {
193
+ return {
194
+ designSystemGuidelines: {
195
+ name: "Generic React",
196
+ preferredComponents: {
197
+ layout: "react",
198
+ buttons: "react",
199
+ forms: "react"
200
+ }
201
+ },
202
+ layoutRules: {
203
+ multiColumnWrapper: "div",
204
+ columnComponent: "div",
205
+ containerComponent: "div"
206
+ }
207
+ };
208
+ }
209
+ }
@@ -1,8 +1,9 @@
1
1
  import * as ts from 'typescript';
2
+ import { isBlacklistedComponent, validateImports } from './componentBlacklist.js';
2
3
  /**
3
4
  * Validates TypeScript code syntax and attempts to fix common issues
4
5
  */
5
- export function validateStoryCode(code, fileName = 'story.tsx') {
6
+ export function validateStoryCode(code, fileName = 'story.tsx', config) {
6
7
  const result = {
7
8
  isValid: true,
8
9
  errors: [],
@@ -47,8 +48,15 @@ export function validateStoryCode(code, fileName = 'story.tsx') {
47
48
  }
48
49
  }
49
50
  // Additional semantic checks
50
- const semanticErrors = performSemanticChecks(sourceFile);
51
+ const semanticErrors = performSemanticChecks(sourceFile, config);
51
52
  result.errors.push(...semanticErrors);
53
+ // Check for React import
54
+ const hasJSX = code.includes('<') || code.includes('/>');
55
+ const hasReactImport = code.includes('import React from \'react\';');
56
+ if (hasJSX && !hasReactImport) {
57
+ result.errors.push('Missing React import - add "import React from \'react\';" at the top of the file');
58
+ result.isValid = false;
59
+ }
52
60
  if (result.errors.length > 0) {
53
61
  result.isValid = false;
54
62
  // Attempt to fix common issues
@@ -56,7 +64,7 @@ export function validateStoryCode(code, fileName = 'story.tsx') {
56
64
  if (fixedCode && fixedCode !== code) {
57
65
  result.fixedCode = fixedCode;
58
66
  // Re-validate the fixed code
59
- const fixedValidation = validateStoryCode(fixedCode, fileName);
67
+ const fixedValidation = validateStoryCode(fixedCode, fileName, config);
60
68
  if (fixedValidation.isValid) {
61
69
  result.isValid = true;
62
70
  result.warnings.push('Code was automatically fixed for syntax errors');
@@ -73,9 +81,50 @@ export function validateStoryCode(code, fileName = 'story.tsx') {
73
81
  /**
74
82
  * Performs additional semantic checks on the AST
75
83
  */
76
- function performSemanticChecks(sourceFile) {
84
+ function performSemanticChecks(sourceFile, config) {
77
85
  const errors = [];
86
+ const availableComponents = new Set();
87
+ const importedComponents = new Set();
88
+ // If config is provided, collect available components
89
+ if (config && config.componentsToImport) {
90
+ config.componentsToImport.forEach((comp) => availableComponents.add(comp));
91
+ }
78
92
  function visit(node) {
93
+ // Check import statements for invalid components
94
+ if (ts.isImportDeclaration(node) && node.importClause && node.importClause.namedBindings) {
95
+ const moduleSpecifier = node.moduleSpecifier;
96
+ if (ts.isStringLiteral(moduleSpecifier)) {
97
+ const importPath = moduleSpecifier.text;
98
+ // Check if this is the main component library import
99
+ if (config && config.importPath && importPath === config.importPath) {
100
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
101
+ node.importClause.namedBindings.elements.forEach(element => {
102
+ const componentName = element.name.text;
103
+ importedComponents.add(componentName);
104
+ // Check if component exists in available components
105
+ if (availableComponents.size > 0) {
106
+ if (isBlacklistedComponent(componentName, availableComponents)) {
107
+ // This is a blacklisted component
108
+ const validation = validateImports([componentName], availableComponents);
109
+ const suggestions = validation.suggestions.get(componentName);
110
+ let errorMsg = `Import error: "${componentName}" is not a valid component from ${importPath}. This appears to be a story export name or made-up component.`;
111
+ if (suggestions && suggestions.length > 0) {
112
+ errorMsg += ` Use these components instead: ${suggestions.join(', ')}.`;
113
+ }
114
+ else {
115
+ errorMsg += ` Use basic components like Box, Stack, Text, Button instead.`;
116
+ }
117
+ errors.push(errorMsg);
118
+ }
119
+ else if (!availableComponents.has(componentName)) {
120
+ errors.push(`Import error: "${componentName}" is not available from ${importPath}. Available components include: ${Array.from(availableComponents).slice(0, 10).join(', ')}...`);
121
+ }
122
+ }
123
+ });
124
+ }
125
+ }
126
+ }
127
+ }
79
128
  // Check for unclosed JSX elements
80
129
  if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
81
130
  // Additional JSX-specific checks could go here
@@ -94,6 +143,10 @@ function performSemanticChecks(sourceFile) {
94
143
  */
95
144
  function attemptAutoFix(code, errors) {
96
145
  let fixedCode = code;
146
+ // CRITICAL: Fix missing React import first (most important for JSX)
147
+ if (errors.some(e => e.includes('Missing React import'))) {
148
+ fixedCode = fixMissingReactImport(fixedCode);
149
+ }
97
150
  // First, check if the code appears to be truncated
98
151
  if (isCodeTruncated(fixedCode)) {
99
152
  fixedCode = fixTruncatedCode(fixedCode);
@@ -229,6 +282,27 @@ function fixUnterminatedStrings(code) {
229
282
  }
230
283
  return lines.join('\n');
231
284
  }
285
+ /**
286
+ * Fixes missing React import for JSX
287
+ */
288
+ function fixMissingReactImport(code) {
289
+ // Check if code has JSX but no React import
290
+ const hasJSX = code.includes('<') || code.includes('/>');
291
+ const hasReactImport = code.includes('import React') || code.includes('* as React');
292
+ if (hasJSX && !hasReactImport) {
293
+ // Find the first import statement or the beginning of the file
294
+ const firstImportIndex = code.indexOf('import');
295
+ if (firstImportIndex !== -1) {
296
+ // Insert React import before the first import
297
+ return code.slice(0, firstImportIndex) + "import React from 'react';\n" + code.slice(firstImportIndex);
298
+ }
299
+ else {
300
+ // No imports, add at the beginning
301
+ return "import React from 'react';\n" + code;
302
+ }
303
+ }
304
+ return code;
305
+ }
232
306
  /**
233
307
  * Fixes unclosed JSX elements
234
308
  */
@@ -333,7 +407,7 @@ function findInsertPosition(code) {
333
407
  /**
334
408
  * Extracts and validates code blocks from AI responses
335
409
  */
336
- export function extractAndValidateCodeBlock(aiResponse) {
410
+ export function extractAndValidateCodeBlock(aiResponse, config) {
337
411
  // Try multiple extraction methods
338
412
  const extractionMethods = [
339
413
  // Standard code blocks
@@ -366,7 +440,7 @@ export function extractAndValidateCodeBlock(aiResponse) {
366
440
  };
367
441
  }
368
442
  // Validate the extracted code
369
- return validateStoryCode(extractedCode);
443
+ return validateStoryCode(extractedCode, 'story.tsx', config);
370
444
  }
371
445
  /**
372
446
  * Creates a fallback story template when generation fails
@@ -374,8 +448,9 @@ export function extractAndValidateCodeBlock(aiResponse) {
374
448
  export function createFallbackStory(prompt, config) {
375
449
  const title = prompt.length > 50 ? prompt.substring(0, 50) + '...' : prompt;
376
450
  const escapedTitle = title.replace(/"/g, '\\"');
451
+ const storybookFramework = config.storybookFramework || '@storybook/react';
377
452
  return `import React from 'react';
378
- import type { StoryObj } from '@storybook/react';
453
+ import type { StoryObj } from '${storybookFramework}';
379
454
 
380
455
  // Fallback story generated due to AI generation error
381
456
  export default {
@@ -54,15 +54,22 @@ export const DEFAULT_CONFIG = {
54
54
  },
55
55
  prohibitedElements: []
56
56
  },
57
- sampleStory: `import type { StoryObj } from '@storybook/react-webpack5';
57
+ sampleStory: `import type { Meta, StoryObj } from '@storybook/react';
58
+ import React from 'react';
58
59
  import { Card } from 'your-component-library';
59
60
 
60
- export default {
61
- title: 'Layouts/Sample Layout',
61
+ const meta = {
62
+ title: 'Generated/Sample Layout',
62
63
  component: Card,
63
- };
64
+ parameters: {
65
+ layout: 'centered',
66
+ },
67
+ } satisfies Meta<typeof Card>;
68
+
69
+ export default meta;
70
+ type Story = StoryObj<typeof meta>;
64
71
 
65
- export const Default: StoryObj<typeof Card> = {
72
+ export const Default: Story = {
66
73
  args: {
67
74
  children: (
68
75
  <Card>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tpitre/story-ui",
3
- "version": "1.7.1",
4
- "description": "AI-powered Storybook story generator for any React component library",
3
+ "version": "2.0.1",
4
+ "description": "AI-powered Storybook story generator with dynamic component discovery",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -24,8 +24,7 @@
24
24
  "release": "semantic-release",
25
25
  "release:dry-run": "semantic-release --dry-run",
26
26
  "commit": "cz",
27
- "prepare": "husky",
28
- "push": "./scripts/push.sh"
27
+ "prepare": "husky"
29
28
  },
30
29
  "keywords": [
31
30
  "storybook",
@@ -36,7 +35,12 @@
36
35
  "design-system",
37
36
  "claude",
38
37
  "mcp",
39
- "story-generation"
38
+ "story-generation",
39
+ "documentation",
40
+ "real-time",
41
+ "component-library",
42
+ "iteration",
43
+ "version-history"
40
44
  ],
41
45
  "author": "Story UI Contributors",
42
46
  "license": "MIT",
@@ -58,10 +62,10 @@
58
62
  "dependencies": {
59
63
  "chalk": "^5.3.0",
60
64
  "commander": "^11.0.0",
61
- "concurrently": "^8.2.0",
62
65
  "cors": "^2.8.5",
63
66
  "dotenv": "^16.3.1",
64
67
  "express": "^4.18.2",
68
+ "glob": "^11.0.3",
65
69
  "inquirer": "^9.2.0",
66
70
  "node-fetch": "^2.6.7",
67
71
  "typescript": "^5.8.3"
@@ -77,6 +81,7 @@
77
81
  "@semantic-release/release-notes-generator": "^14.0.1",
78
82
  "@types/cors": "^2.8.17",
79
83
  "@types/express": "^4.17.21",
84
+ "@types/glob": "^8.1.0",
80
85
  "@types/inquirer": "^9.0.0",
81
86
  "@types/node": "^20.4.2",
82
87
  "@types/node-fetch": "^2.6.12",
@@ -1,28 +1,44 @@
1
1
  import React from 'react';
2
- import type { StoryFn, Meta } from '@storybook/react';
3
- import StoryUIPanel from './StoryUIPanel';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { StoryUIPanel } from './StoryUIPanel';
4
4
 
5
- export default {
5
+ const meta = {
6
6
  title: 'Story UI/Story Generator',
7
7
  component: StoryUIPanel,
8
8
  parameters: {
9
9
  layout: 'fullscreen',
10
10
  docs: {
11
11
  description: {
12
- component: 'AI-powered story generator for creating complex UI layouts using your component library.'
12
+ component: `
13
+ Story UI Panel connects to the MCP server running on your configured port.
14
+ The port is determined by:
15
+ 1. VITE_STORY_UI_PORT environment variable (recommended)
16
+ 2. URL parameter: ?mcp-port=XXXX
17
+ 3. Default port: 4001
18
+
19
+ This design system agnostic approach works with any component library.
20
+ `
13
21
  }
14
22
  }
15
- }
16
- } as Meta<typeof StoryUIPanel>;
23
+ },
24
+ } satisfies Meta<typeof StoryUIPanel>;
17
25
 
18
- const Template: StoryFn<typeof StoryUIPanel> = (args) => <StoryUIPanel {...args} />;
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
19
28
 
20
- export const Default = Template.bind({});
21
- Default.args = {};
22
- Default.parameters = {
23
- docs: {
24
- description: {
25
- story: 'Use natural language prompts to generate Storybook stories with your components.'
29
+ export const Default: Story = {
30
+ render: () => {
31
+ // Check for URL parameter override first
32
+ if (typeof window !== 'undefined') {
33
+ const urlParams = new URLSearchParams(window.location.search);
34
+ const mcpPortParam = urlParams.get('mcp-port');
35
+
36
+ if (mcpPortParam) {
37
+ // Set the global variable that the panel will use
38
+ (window as any).STORY_UI_MCP_PORT = mcpPortParam;
39
+ }
26
40
  }
41
+
42
+ return <StoryUIPanel />;
27
43
  }
28
44
  };