@tpitre/story-ui 3.10.2 → 3.10.4

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/dist/cli/index.js CHANGED
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgc5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DAwfxE"}
1
+ {"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAua5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DA0hBxE"}
@@ -4,7 +4,8 @@ import * as path from 'path';
4
4
  import { buildClaudePrompt as buildFlexiblePrompt, buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
5
5
  import { getAdapter } from '../../story-generator/framework-adapters/index.js';
6
6
  import { loadUserConfig, validateConfig } from '../../story-generator/configLoader.js';
7
- import { createFallbackStory, validateStoryCode } from '../../story-generator/validateStory.js';
7
+ import { validateStoryCode } from '../../story-generator/validateStory.js';
8
+ import { createFrameworkAwareFallbackStory } from './storyHelpers.js';
8
9
  import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
9
10
  import { StoryTracker } from '../../story-generator/storyTracker.js';
10
11
  import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
@@ -43,54 +44,24 @@ async function buildClaudePrompt(userPrompt) {
43
44
  }
44
45
  // Enhanced function that includes conversation context and previous code
45
46
  // Now supports multi-framework prompt generation and vision-aware prompts
47
+ // NOTE: Framework is now REQUIRED - caller must pass the detected framework
46
48
  async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode, options) {
47
49
  const discovery = new EnhancedComponentDiscovery(config);
48
50
  const components = await discovery.discoverAll();
49
- // Determine if we should use framework-aware prompts
50
- let useFrameworkAware = false;
51
- let frameworkOptions;
52
- if (options?.framework) {
53
- // Explicit framework specified in request
54
- useFrameworkAware = true;
55
- frameworkOptions = { framework: options.framework };
56
- logger.log(`📦 Using specified framework: ${options.framework}`);
57
- }
58
- else if (config.componentFramework || config.framework) {
59
- // Framework configured in story-ui.config.js
60
- const configFramework = (config.componentFramework || config.framework);
61
- useFrameworkAware = true;
62
- frameworkOptions = { framework: configFramework };
63
- logger.log(`📦 Using framework from config: ${configFramework}`);
64
- }
65
- else if (options?.autoDetectFramework) {
66
- // Auto-detect framework from project
67
- try {
68
- const detectedFramework = await detectProjectFramework(process.cwd());
69
- useFrameworkAware = true;
70
- frameworkOptions = { framework: detectedFramework };
71
- // CRITICAL: Also set config.componentFramework so validateStoryCode receives the correct framework
72
- // This ensures React imports are properly removed for non-React frameworks during post-processing
73
- config.componentFramework = detectedFramework;
74
- logger.log(`📦 Auto-detected framework: ${detectedFramework}`);
75
- }
76
- catch (error) {
77
- logger.warn('Failed to auto-detect framework, using React default', { error });
78
- }
51
+ // SIMPLIFIED: Trust the passed framework from early detection
52
+ // The caller (main handler) is responsible for detecting the framework once
53
+ // We no longer re-detect here - this eliminates duplicate detection and inconsistency
54
+ if (!options?.framework) {
55
+ throw new Error('Framework must be passed to buildClaudePromptWithContext - early detection should have determined it');
79
56
  }
57
+ const frameworkOptions = { framework: options.framework };
80
58
  // Always start with component discovery as the authoritative source
81
59
  logger.log(`📦 Discovered ${components.length} components from ${config.importPath}`);
82
60
  const availableComponents = components.map(c => c.name).join(', ');
83
61
  logger.log(`✅ Available components: ${availableComponents}`);
84
- // Build base prompt with discovered components (always required)
85
- // Use framework-aware prompt if configured, otherwise use legacy React prompt
86
- let prompt;
87
- if (useFrameworkAware && frameworkOptions) {
88
- prompt = await buildFrameworkAwarePrompt(userPrompt, config, components, frameworkOptions);
89
- logger.log(`🔧 Built framework-aware prompt for ${frameworkOptions.framework}`);
90
- }
91
- else {
92
- prompt = await buildFlexiblePrompt(userPrompt, config, components);
93
- }
62
+ // Build framework-aware prompt since we now always have a framework
63
+ let prompt = await buildFrameworkAwarePrompt(userPrompt, config, components, frameworkOptions);
64
+ logger.log(`🔧 Built framework-aware prompt for ${options.framework}`);
94
65
  // Enhance prompt with vision-aware context if vision mode is provided
95
66
  if (options?.visionMode) {
96
67
  logger.log(`🔍 Enhancing prompt with vision mode: ${options.visionMode}`);
@@ -98,7 +69,7 @@ async function buildClaudePromptWithContext(userPrompt, config, conversation, pr
98
69
  promptType: options.visionMode,
99
70
  userDescription: userPrompt,
100
71
  availableComponents: components.map(c => c.name),
101
- framework: frameworkOptions?.framework || 'react',
72
+ framework: options.framework, // Use required framework, no fallback
102
73
  designSystem: options.designSystem,
103
74
  });
104
75
  // Combine the vision system prompt with the existing prompt and add the user prompt
@@ -442,7 +413,8 @@ export async function generateStoryFromPrompt(req, res) {
442
413
  let attempts = 0;
443
414
  let selfHealingUsed = false;
444
415
  // Build framework-aware options with vision support
445
- const frameworkOptions = {
416
+ // NOTE: Will be updated with detectedFramework after early detection
417
+ let frameworkOptions = {
446
418
  framework: framework,
447
419
  autoDetectFramework: autoDetectFramework === true,
448
420
  visionMode: visionMode,
@@ -452,17 +424,63 @@ export async function generateStoryFromPrompt(req, res) {
452
424
  const discovery = new EnhancedComponentDiscovery(config);
453
425
  const discoveredComponents = await discovery.discoverAll();
454
426
  const componentNames = discoveredComponents.map(c => c.name);
455
- // Self-healing options for retry prompts
427
+ // EARLY FRAMEWORK DETECTION - detect once and use consistently throughout
428
+ // Priority: request > config.componentFramework > config.framework > auto-detect
429
+ // CRITICAL: Fail explicitly if no framework can be determined rather than silently defaulting to React
430
+ let detectedFramework;
431
+ if (frameworkOptions.framework) {
432
+ detectedFramework = frameworkOptions.framework;
433
+ logger.log(`🎯 Using explicit framework from request: ${detectedFramework}`);
434
+ }
435
+ else if (config.componentFramework) {
436
+ detectedFramework = config.componentFramework;
437
+ logger.log(`🎯 Using framework from config.componentFramework: ${detectedFramework}`);
438
+ }
439
+ else if (config.framework) {
440
+ detectedFramework = config.framework;
441
+ logger.log(`🎯 Using framework from config.framework: ${detectedFramework}`);
442
+ }
443
+ else if (frameworkOptions.autoDetectFramework) {
444
+ try {
445
+ detectedFramework = await detectProjectFramework(process.cwd());
446
+ logger.log(`🎯 Auto-detected framework: ${detectedFramework}`);
447
+ }
448
+ catch (error) {
449
+ // FAIL EXPLICITLY rather than silently defaulting to React
450
+ logger.error('Failed to auto-detect framework and no framework configured', { error });
451
+ return res.status(400).json({
452
+ error: 'Framework detection failed',
453
+ details: 'Could not auto-detect framework. Please set componentFramework in story-ui.config.js or pass framework in the request.',
454
+ suggestion: 'Add componentFramework: "react" (or vue, angular, svelte, web-components) to your story-ui.config.js'
455
+ });
456
+ }
457
+ }
458
+ else {
459
+ // Default to React only with a warning - this is the ONLY place we default
460
+ detectedFramework = 'react';
461
+ logger.warn('⚠️ No framework configured, defaulting to React. Consider setting componentFramework in story-ui.config.js');
462
+ }
463
+ // CREATE a properly-typed options object with the detected framework
464
+ // This ensures TypeScript knows framework is definitely set
465
+ const promptOptions = {
466
+ framework: detectedFramework, // Always set from early detection
467
+ autoDetectFramework: false, // Already detected, don't re-detect
468
+ visionMode: visionMode,
469
+ designSystem: designSystem,
470
+ };
471
+ // Get the framework adapter early for consistent use
472
+ const frameworkAdapter = getAdapter(detectedFramework);
473
+ // Self-healing options for retry prompts - use detected framework
456
474
  const selfHealingOptions = {
457
475
  maxAttempts: maxRetries,
458
476
  availableComponents: componentNames,
459
- framework: frameworkOptions.framework || 'react',
477
+ framework: detectedFramework,
460
478
  importPath: config.importPath || 'your-library',
461
479
  };
462
480
  // Track all attempts for best-attempt selection
463
481
  const allAttempts = [];
464
482
  const errorHistory = [];
465
- const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, frameworkOptions);
483
+ const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, promptOptions);
466
484
  const messages = [{ role: 'user', content: initialPrompt }];
467
485
  while (attempts < maxRetries) {
468
486
  attempts++;
@@ -549,7 +567,7 @@ export async function generateStoryFromPrompt(req, res) {
549
567
  else {
550
568
  // Create fallback story only if we have no usable code
551
569
  logger.log('Creating fallback story - no usable code generated');
552
- fileContents = createFallbackStory(prompt, config);
570
+ fileContents = createFrameworkAwareFallbackStory(prompt, config, detectedFramework);
553
571
  hasValidationWarnings = true;
554
572
  }
555
573
  }
@@ -565,19 +583,9 @@ export async function generateStoryFromPrompt(req, res) {
565
583
  console.error('No valid code could be extracted or generated.');
566
584
  return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
567
585
  }
568
- // Determine the framework being used (priority: request > config.componentFramework > config.framework > auto-detect > default)
569
- // CRITICAL: Check config.componentFramework BEFORE config.framework - many projects use componentFramework (not framework)
570
- const detectedFramework = frameworkOptions.framework ||
571
- config.componentFramework ||
572
- config.framework ||
573
- (frameworkOptions.autoDetectFramework ? await detectProjectFramework(process.cwd()).catch(() => 'react') : 'react');
574
- logger.log(`🎯 Framework detection: request=${frameworkOptions.framework}, config=${config.framework}, detected=${detectedFramework}`);
575
- // Get the framework adapter for post-processing
576
- const frameworkAdapter = getAdapter(detectedFramework);
577
- // Only add React import for React framework
578
- if (detectedFramework === 'react' && !fileContents.includes("import React from 'react';")) {
579
- fileContents = "import React from 'react';\n" + fileContents;
580
- }
586
+ // NOTE: Framework detection and adapter retrieval already done earlier in function
587
+ // detectedFramework and frameworkAdapter are available from lines 555-588
588
+ // React imports are handled by the adapter's postProcess method, not manually added here
581
589
  // Post-processing is now consolidated to run once on the final code
582
590
  let fixedFileContents = postProcessStory(fileContents, config.importPath);
583
591
  // Apply framework-specific post-processing if adapter is available
@@ -619,11 +627,12 @@ export async function generateStoryFromPrompt(req, res) {
619
627
  storyId = providedStoryId;
620
628
  const hashMatch = providedStoryId.match(/^story-([a-f0-9]{8})$/);
621
629
  hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
622
- finalFileName = fileName || `${providedStoryId}.stories.tsx`;
630
+ finalFileName = fileName || `${providedStoryId}${fileExtension}`;
623
631
  logger.log('📝 Using provided storyId:', finalFileName);
624
632
  }
625
633
  else if (fileName) {
626
- const hashMatch = fileName.match(/-([a-f0-9]{8})(?:\.stories\.tsx)?$/);
634
+ // Match hash from filename, supporting both .tsx and .ts extensions
635
+ const hashMatch = fileName.match(/-([a-f0-9]{8})(?:\.stories\.tsx?)?$/);
627
636
  hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
628
637
  storyId = `story-${hash}`;
629
638
  finalFileName = fileName;
@@ -634,8 +643,8 @@ export async function generateStoryFromPrompt(req, res) {
634
643
  storyId = `story-${hash}`;
635
644
  finalFileName = fileNameFromTitle(aiTitle, hash, fileExtension);
636
645
  }
637
- if (!finalFileName.endsWith('.stories.tsx')) {
638
- finalFileName = finalFileName + '.stories.tsx';
646
+ if (!finalFileName.endsWith(fileExtension)) {
647
+ finalFileName = finalFileName + fileExtension;
639
648
  }
640
649
  logger.log('📌 Preserving story identity for update:', { storyId, fileName: finalFileName });
641
650
  }
@@ -646,10 +655,11 @@ export async function generateStoryFromPrompt(req, res) {
646
655
  storyId = `story-${hash}`;
647
656
  logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
648
657
  }
649
- // Escape the title for TypeScript and append hash for uniqueness
658
+ // Create title for the story
650
659
  const prettyPrompt = escapeTitleForTS(aiTitle);
651
- // Append hash to title to prevent Storybook duplicate ID errors
652
- const uniqueTitle = `${prettyPrompt} (${hash})`;
660
+ // Use the title without hash suffix for cleaner sidebar display
661
+ // The filename already contains the hash for uniqueness
662
+ const uniqueTitle = prettyPrompt;
653
663
  // Fix title with storyPrefix and hash - handle both single-line and multi-line formats
654
664
  // Note: (?::\s*\w+(?:<[^>]+>)?)? handles TypeScript type annotations including generics
655
665
  // e.g., "const meta: Meta = {" or "const meta: Meta<typeof Button> = {"
@@ -1 +1 @@
1
- {"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA2c5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA0hB9E"}
1
+ {"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAsc5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAkkB9E"}
@@ -11,10 +11,11 @@ import * as crypto from 'crypto';
11
11
  import * as path from 'path';
12
12
  import { generateStory } from '../../story-generator/generateStory.js';
13
13
  import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
14
- import { buildClaudePrompt as buildFlexiblePrompt, buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
14
+ import { buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
15
15
  import { getAdapter } from '../../story-generator/framework-adapters/index.js';
16
16
  import { loadUserConfig, validateConfig } from '../../story-generator/configLoader.js';
17
- import { extractAndValidateCodeBlock, createFallbackStory } from '../../story-generator/validateStory.js';
17
+ import { extractAndValidateCodeBlock } from '../../story-generator/validateStory.js';
18
+ import { createFrameworkAwareFallbackStory } from './storyHelpers.js';
18
19
  import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
19
20
  import { StoryTracker } from '../../story-generator/storyTracker.js';
20
21
  import { getDocumentation } from '../../story-generator/documentation-sources.js';
@@ -114,24 +115,15 @@ function buildComponentSuggestion(components) {
114
115
  return `Your available components include: ${sampleComponents}${moreCount}. Check story-ui.config.js if expected components are missing.`;
115
116
  }
116
117
  // Analyze prompt to determine intent
118
+ // NOTE: Framework is now REQUIRED - caller must detect it first
117
119
  async function analyzeIntent(prompt, config, conversation, previousCode, options) {
118
- // Determine framework - priority: config > explicit option > auto-detect > default
119
- let framework = 'react';
120
- if (config.componentFramework) {
121
- // First priority: use config.componentFramework if set
122
- framework = config.componentFramework;
123
- }
124
- else if (options.framework) {
125
- framework = options.framework;
126
- }
127
- else if (options.autoDetectFramework) {
128
- try {
129
- framework = await detectProjectFramework(process.cwd());
130
- }
131
- catch {
132
- framework = 'react';
133
- }
120
+ // SIMPLIFIED: Trust the passed framework from early detection
121
+ // The caller (main handler) is responsible for detecting the framework once
122
+ // We no longer re-detect here - this eliminates duplicate detection and inconsistency
123
+ if (!options.framework) {
124
+ throw new Error('Framework must be passed to analyzeIntent - early detection should have determined it');
134
125
  }
126
+ const framework = options.framework;
135
127
  // Analyze prompt for likely components
136
128
  const componentKeywords = {
137
129
  button: ['button', 'click', 'submit', 'action', 'cta'],
@@ -407,6 +399,60 @@ export async function generateStoryFromPromptStream(req, res) {
407
399
  res.end();
408
400
  return;
409
401
  }
402
+ // EARLY FRAMEWORK DETECTION - detect once and use consistently throughout
403
+ // Priority: request > config.componentFramework > config.framework > auto-detect
404
+ // CRITICAL: Fail explicitly if no framework can be determined rather than silently defaulting to React
405
+ let detectedFramework;
406
+ if (framework) {
407
+ detectedFramework = framework;
408
+ logger.log(`🎯 Using explicit framework from request: ${detectedFramework}`);
409
+ }
410
+ else if (config.componentFramework) {
411
+ detectedFramework = config.componentFramework;
412
+ logger.log(`🎯 Using framework from config.componentFramework: ${detectedFramework}`);
413
+ }
414
+ else if (config.framework) {
415
+ detectedFramework = config.framework;
416
+ logger.log(`🎯 Using framework from config.framework: ${detectedFramework}`);
417
+ }
418
+ else if (autoDetectFramework) {
419
+ try {
420
+ detectedFramework = await detectProjectFramework(process.cwd());
421
+ logger.log(`🎯 Auto-detected framework: ${detectedFramework}`);
422
+ }
423
+ catch (error) {
424
+ // FAIL EXPLICITLY rather than silently defaulting to React
425
+ logger.error('Failed to auto-detect framework and no framework configured', { error });
426
+ stream.sendError({
427
+ code: 'FRAMEWORK_DETECTION_FAILED',
428
+ message: 'Could not auto-detect framework',
429
+ details: 'Please set componentFramework in story-ui.config.js or pass framework in the request.',
430
+ recoverable: false,
431
+ suggestion: 'Add componentFramework: "react" (or vue, angular, svelte, web-components) to your story-ui.config.js'
432
+ });
433
+ res.end();
434
+ return;
435
+ }
436
+ }
437
+ else {
438
+ // Default to React only with a warning - this is the ONLY place we default
439
+ detectedFramework = 'react';
440
+ logger.warn('⚠️ No framework configured, defaulting to React. Consider setting componentFramework in story-ui.config.js');
441
+ }
442
+ // Get the framework adapter early for consistent use
443
+ const frameworkAdapter = getAdapter(detectedFramework);
444
+ if (!frameworkAdapter) {
445
+ logger.error(`No adapter found for framework: ${detectedFramework}`);
446
+ stream.sendError({
447
+ code: 'ADAPTER_NOT_FOUND',
448
+ message: `No adapter found for framework: ${detectedFramework}`,
449
+ recoverable: false,
450
+ suggestion: 'Check that the framework name is correct: react, vue, angular, svelte, or web-components'
451
+ });
452
+ res.end();
453
+ return;
454
+ }
455
+ logger.log(`🔧 Using framework adapter: ${frameworkAdapter.name}`);
410
456
  // Process images if provided
411
457
  let processedImages = [];
412
458
  if (images && Array.isArray(images) && images.length > 0) {
@@ -456,9 +502,10 @@ export async function generateStoryFromPromptStream(req, res) {
456
502
  }
457
503
  }
458
504
  // INTENT PREVIEW: Analyze and show what we're going to do
505
+ // Pass the already-detected framework - no need for the function to re-detect
459
506
  const intent = await analyzeIntent(prompt, config, conversation, previousCode, {
460
- framework: framework,
461
- autoDetectFramework: autoDetectFramework === true,
507
+ framework: detectedFramework, // Use early-detected framework, not request's framework
508
+ autoDetectFramework: false, // Already detected, don't re-detect
462
509
  visionMode: visionMode,
463
510
  designSystem,
464
511
  hasImages: processedImages.length > 0
@@ -470,9 +517,10 @@ export async function generateStoryFromPromptStream(req, res) {
470
517
  framework: intent.framework,
471
518
  hasContext: intent.promptAnalysis.hasConversationContext
472
519
  });
520
+ // Pass detected framework to prompt builder - no re-detection needed
473
521
  const frameworkOptions = {
474
- framework: framework,
475
- autoDetectFramework: autoDetectFramework === true,
522
+ framework: detectedFramework, // Use early-detected framework
523
+ autoDetectFramework: false, // Already detected, don't re-detect
476
524
  visionMode: visionMode,
477
525
  designSystem: designSystem,
478
526
  considerations: considerations,
@@ -484,18 +532,14 @@ export async function generateStoryFromPromptStream(req, res) {
484
532
  // Step 4: Call LLM with Self-Healing Loop
485
533
  currentStep++;
486
534
  stream.sendProgress(currentStep, totalSteps, 'llm_thinking', 'AI is generating your story...');
487
- // Determine framework for self-healing options
488
- const detectedFrameworkForHealing = config.componentFramework ||
489
- framework ||
490
- intent.framework ||
491
- 'react';
492
535
  // Get available component names for self-healing prompts
493
536
  const availableComponentNames = components.map(c => c.name);
494
537
  // Self-healing options (design-system agnostic)
538
+ // Uses detectedFramework from early detection - no need to re-detect
495
539
  const selfHealingOptions = {
496
540
  maxAttempts: 3,
497
541
  availableComponents: availableComponentNames,
498
- framework: detectedFrameworkForHealing,
542
+ framework: detectedFramework,
499
543
  importPath: config.importPath,
500
544
  };
501
545
  // Initialize self-healing state
@@ -634,7 +678,8 @@ export async function generateStoryFromPromptStream(req, res) {
634
678
  let fileContents;
635
679
  let hasValidationWarnings = false;
636
680
  if (!validationResult.isValid && !validationResult.fixedCode) {
637
- fileContents = createFallbackStory(prompt, config);
681
+ // Use framework-aware fallback story
682
+ fileContents = createFrameworkAwareFallbackStory(prompt, config, detectedFramework);
638
683
  hasValidationWarnings = true;
639
684
  stream.sendValidation({
640
685
  isValid: false,
@@ -666,22 +711,13 @@ export async function generateStoryFromPromptStream(req, res) {
666
711
  // Step 7: Post-processing
667
712
  currentStep++;
668
713
  stream.sendProgress(currentStep, totalSteps, 'post_processing', 'Applying finishing touches...');
669
- // Detect framework from config or auto-detection
670
- const detectedFramework = config.componentFramework ||
671
- framework ||
672
- intent.framework ||
673
- 'react';
674
- // Only add React import for React framework
675
- if (detectedFramework === 'react' && !fileContents.includes("import React from 'react';")) {
676
- fileContents = "import React from 'react';\n" + fileContents;
677
- }
714
+ // Framework detection already done at start - use detectedFramework and frameworkAdapter
715
+ // The adapter's postProcess() method handles React import injection (or removal for non-React)
678
716
  let fixedFileContents = postProcessStory(fileContents, config.importPath);
679
- // Apply framework-specific post-processing
680
- const frameworkAdapter = getAdapter(detectedFramework);
681
- if (frameworkAdapter) {
682
- logger.log(`🔧 Applying ${detectedFramework} framework post-processing`);
683
- fixedFileContents = frameworkAdapter.postProcess(fixedFileContents);
684
- }
717
+ // Apply framework-specific post-processing via adapter
718
+ const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
719
+ logger.log(`🔧 Applying ${detectedFramework} framework post-processing`);
720
+ fixedFileContents = frameworkAdapter.postProcess(fixedFileContents);
685
721
  // Generate title
686
722
  let aiTitle;
687
723
  if (isActualUpdate && originalTitle) {
@@ -710,23 +746,25 @@ export async function generateStoryFromPromptStream(req, res) {
710
746
  hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
711
747
  }
712
748
  else {
713
- const hashMatch = fileName?.match(/-([a-f0-9]{8})(?:\.stories\.tsx)?$/);
749
+ // Match hash from filename, supporting both .tsx and .ts extensions
750
+ const hashMatch = fileName?.match(/-([a-f0-9]{8})(?:\.stories\.tsx?)?$/);
714
751
  hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
715
752
  storyId = `story-${hash}`;
716
753
  }
717
754
  // Ensure finalFileName is always set
718
- finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
755
+ finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
719
756
  }
720
757
  else {
721
758
  const timestamp = Date.now();
722
759
  hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
723
- finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
760
+ finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
724
761
  storyId = `story-${hash}`;
725
762
  }
726
- // Now create title with hash suffix to ensure uniqueness
763
+ // Create title for the story
727
764
  const prettyPrompt = escapeTitleForTS(aiTitle);
728
- // Append hash to title to prevent Storybook duplicate ID errors
729
- const uniqueTitle = `${prettyPrompt} (${hash})`;
765
+ // Use the title without hash suffix for cleaner sidebar display
766
+ // The filename already contains the hash for uniqueness
767
+ const uniqueTitle = prettyPrompt;
730
768
  // Fix title with storyPrefix and hash
731
769
  // Note: (?::\s*\w+(?:<[^>]+>)?)? handles TypeScript type annotations including generics
732
770
  // e.g., "const meta: Meta = {" or "const meta: Meta<typeof Button> = {"
@@ -744,9 +782,9 @@ export async function generateStoryFromPromptStream(req, res) {
744
782
  return p1 + titleToUse + p3;
745
783
  });
746
784
  }
747
- // Ensure file extension is correct
748
- if (finalFileName && !finalFileName.endsWith('.stories.tsx')) {
749
- finalFileName = finalFileName + '.stories.tsx';
785
+ // Ensure file extension is correct (use framework-specific extension)
786
+ if (finalFileName && !finalFileName.endsWith(fileExtension)) {
787
+ finalFileName = finalFileName + fileExtension;
750
788
  }
751
789
  // Step 8: Save story
752
790
  currentStep++;
@@ -824,44 +862,24 @@ export async function generateStoryFromPromptStream(req, res) {
824
862
  }
825
863
  }
826
864
  // Helper functions (copied from generateStory.ts for consistency)
865
+ // NOTE: Framework is now REQUIRED - caller must pass the detected framework
827
866
  async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode, components, options) {
828
867
  const discovery = new EnhancedComponentDiscovery(config);
829
868
  const discoveredComponents = components || await discovery.discoverAll();
830
- let useFrameworkAware = false;
831
- let frameworkOptions;
832
- // Priority: config.componentFramework > explicit option > auto-detect
833
- if (config.componentFramework) {
834
- // First priority: use config.componentFramework if set
835
- useFrameworkAware = true;
836
- frameworkOptions = { framework: config.componentFramework };
837
- }
838
- else if (options?.framework) {
839
- useFrameworkAware = true;
840
- frameworkOptions = { framework: options.framework };
841
- }
842
- else if (options?.autoDetectFramework) {
843
- try {
844
- const detectedFramework = await detectProjectFramework(process.cwd());
845
- useFrameworkAware = true;
846
- frameworkOptions = { framework: detectedFramework };
847
- }
848
- catch {
849
- // Default to React
850
- }
851
- }
852
- let prompt;
853
- if (useFrameworkAware && frameworkOptions) {
854
- prompt = await buildFrameworkAwarePrompt(userPrompt, config, discoveredComponents, frameworkOptions);
855
- }
856
- else {
857
- prompt = await buildFlexiblePrompt(userPrompt, config, discoveredComponents);
869
+ // SIMPLIFIED: Trust the passed framework from early detection
870
+ // No more duplicate detection logic here
871
+ if (!options?.framework) {
872
+ throw new Error('Framework must be passed to buildClaudePromptWithContext - early detection should have determined it');
858
873
  }
874
+ const frameworkOptions = { framework: options.framework };
875
+ // Always use framework-aware prompt since we now always have a framework
876
+ let prompt = await buildFrameworkAwarePrompt(userPrompt, config, discoveredComponents, frameworkOptions);
859
877
  if (options?.visionMode) {
860
878
  const visionPrompts = buildVisionAwarePrompt({
861
879
  promptType: options.visionMode,
862
880
  userDescription: userPrompt,
863
881
  availableComponents: discoveredComponents.map((c) => c.name),
864
- framework: frameworkOptions?.framework || 'react',
882
+ framework: options.framework, // Use the required framework, no fallback
865
883
  designSystem: options.designSystem,
866
884
  });
867
885
  prompt = `${visionPrompts.systemPrompt}\n\n---\n\n${prompt}\n\n---\n\n${visionPrompts.userPrompt}`;
@@ -962,7 +980,7 @@ function escapeTitleForTS(title) {
962
980
  .replace(/\r/g, '\\r')
963
981
  .replace(/\t/g, '\\t');
964
982
  }
965
- function fileNameFromTitle(title, hash) {
983
+ function fileNameFromTitle(title, hash, extension = '.stories.tsx') {
966
984
  if (!title || typeof title !== 'string') {
967
985
  title = 'untitled';
968
986
  }
@@ -975,7 +993,7 @@ function fileNameFromTitle(title, hash) {
975
993
  .replace(/^-+|-+$/g, '')
976
994
  .replace(/"|'/g, '')
977
995
  .slice(0, 60);
978
- return `${base}-${hash}.stories.tsx`;
996
+ return `${base}-${hash}${extension}`;
979
997
  }
980
998
  function extractImportsFromCode(code, importPath) {
981
999
  const imports = [];
@@ -41,11 +41,19 @@ export declare function escapeTitleForTS(title: string): string;
41
41
  export declare function extractImportsFromCode(code: string, importPath: string): string[];
42
42
  /**
43
43
  * Generate a filename from a story title and hash.
44
+ * @param title - The story title
45
+ * @param hash - The unique hash for the story
46
+ * @param extension - The file extension (e.g., '.stories.tsx' or '.stories.ts')
44
47
  */
45
- export declare function fileNameFromTitle(title: string, hash: string): string;
48
+ export declare function fileNameFromTitle(title: string, hash: string, extension?: string): string;
46
49
  /**
47
50
  * Find a similar icon name from the allowed icons set.
48
51
  * Used for providing suggestions when an invalid icon is used.
49
52
  */
50
53
  export declare function findSimilarIcon(iconName: string, allowedIcons: Set<string>): string | null;
54
+ /**
55
+ * Creates a framework-aware fallback story when generation fails.
56
+ * Uses the adapter to generate framework-appropriate code.
57
+ */
58
+ export declare function createFrameworkAwareFallbackStory(prompt: string, config: any, framework: string): string;
51
59
  //# sourceMappingURL=storyHelpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storyHelpers.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/storyHelpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAC;AAE5E;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ3C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC3D,MAAM,CAAC,EAAE,YAAY,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAkC1D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAajF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBrE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAwB1F"}
1
+ {"version":3,"file":"storyHelpers.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/storyHelpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAC;AAE5E;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ3C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC3D,MAAM,CAAC,EAAE,YAAY,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAkC1D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAajF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAuB,GAAG,MAAM,CAgBzG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAwB1F;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,MAAM,GAChB,MAAM,CA0JR"}