@tpitre/story-ui 3.6.2 → 3.7.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 (44) hide show
  1. package/README.md +36 -32
  2. package/dist/cli/index.js +0 -5
  3. package/dist/cli/setup.js +1 -1
  4. package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
  5. package/dist/mcp-server/routes/generateStory.js +142 -87
  6. package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
  7. package/dist/mcp-server/routes/generateStoryStream.js +149 -31
  8. package/dist/story-generator/dynamicPackageDiscovery.d.ts +35 -2
  9. package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -1
  10. package/dist/story-generator/dynamicPackageDiscovery.js +332 -6
  11. package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -1
  12. package/dist/story-generator/enhancedComponentDiscovery.js +149 -2
  13. package/dist/story-generator/framework-adapters/base-adapter.d.ts +1 -0
  14. package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -1
  15. package/dist/story-generator/framework-adapters/base-adapter.js +12 -2
  16. package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -1
  17. package/dist/story-generator/framework-adapters/react-adapter.js +2 -0
  18. package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -1
  19. package/dist/story-generator/framework-adapters/svelte-adapter.js +53 -7
  20. package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -1
  21. package/dist/story-generator/framework-adapters/vue-adapter.js +21 -1
  22. package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -1
  23. package/dist/story-generator/framework-adapters/web-components-adapter.js +4 -0
  24. package/dist/story-generator/llm-providers/openai-provider.js +2 -2
  25. package/dist/story-generator/promptGenerator.d.ts.map +1 -1
  26. package/dist/story-generator/promptGenerator.js +179 -26
  27. package/dist/story-generator/selfHealingLoop.d.ts +112 -0
  28. package/dist/story-generator/selfHealingLoop.d.ts.map +1 -0
  29. package/dist/story-generator/selfHealingLoop.js +202 -0
  30. package/dist/story-generator/validateStory.d.ts.map +1 -1
  31. package/dist/story-generator/validateStory.js +81 -12
  32. package/dist/story-ui.config.d.ts +2 -0
  33. package/dist/story-ui.config.d.ts.map +1 -1
  34. package/dist/templates/StoryUI/StoryUIPanel.d.ts +0 -5
  35. package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
  36. package/dist/templates/StoryUI/StoryUIPanel.js +411 -223
  37. package/package.json +4 -4
  38. package/templates/StoryUI/StoryUIPanel.mdx +84 -0
  39. package/templates/StoryUI/StoryUIPanel.tsx +493 -265
  40. package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +0 -18
  41. package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +0 -1
  42. package/dist/templates/StoryUI/StoryUIPanel.stories.js +0 -37
  43. package/templates/StoryUI/StoryUIPanel.stories.tsx +0 -44
  44. package/templates/StoryUI/manager.tsx +0 -859
package/README.md CHANGED
@@ -66,7 +66,7 @@ Story UI will guide you through:
66
66
  | **Gemini** (Google) | Gemini 3 Pro, Gemini 2.0 Flash, Gemini 1.5 Pro | Fast generation, cost efficiency |
67
67
 
68
68
  ### Production Deployment
69
- - **Railway**: Node.js backend with PostgreSQL for story persistence
69
+ - **Railway**: Node.js backend with file-based story persistence
70
70
  - **MCP Integration**: Connect AI clients directly to production
71
71
 
72
72
  ---
@@ -248,27 +248,40 @@ export class DataTableComponent { }
248
248
 
249
249
  Story UI includes a Model Context Protocol (MCP) server, allowing direct integration with AI clients like Claude Desktop and Claude Code.
250
250
 
251
- ### Claude Code Integration (Recommended)
251
+ ### Claude Desktop Integration (Recommended)
252
252
 
253
- The easiest way to connect is via Claude Code's built-in MCP support:
253
+ The easiest way to connect is via Claude Desktop's built-in connector UI:
254
+
255
+ 1. Open **Claude Desktop**
256
+ 2. Go to **Settings** → **Connectors**
257
+ 3. Click **"Add custom connector"**
258
+ 4. Enter:
259
+ - **Name**: `Story UI` (or any name you prefer)
260
+ - **URL**: `https://story-ui-demo.up.railway.app/mcp-remote/mcp` (production)
261
+ 5. Click **Add**
262
+ 6. **Restart Claude Desktop**
263
+
264
+ Once connected, you'll have access to all Story UI tools directly in your Claude conversations:
265
+ - `generate-story` - Generate Storybook stories from natural language
266
+ - `list-components` - Discover available components
267
+ - `list-stories` - View existing stories
268
+ - `get-story` / `update-story` / `delete-story` - Manage stories
269
+ - `get-component-props` - Get component property information
270
+ - `test-connection` - Verify MCP connection
271
+
272
+ ### Claude Code Integration
273
+
274
+ Connect via Claude Code's built-in MCP support:
254
275
 
255
276
  ```bash
256
- # Add remote HTTP MCP server
257
- claude mcp add --transport http story-ui https://your-worker.workers.dev/mcp
277
+ # Add remote HTTP MCP server (production)
278
+ claude mcp add --transport http story-ui https://story-ui-demo.up.railway.app/mcp-remote/mcp
258
279
 
259
280
  # Or for local development
260
- claude mcp add --transport http story-ui-local http://localhost:4005/mcp
281
+ claude mcp add --transport http story-ui-local http://localhost:4005/mcp-remote/mcp
261
282
  ```
262
283
 
263
- ### Claude Desktop Integration
264
-
265
- Claude Desktop now supports a connector UI for adding MCP servers. Simply:
266
-
267
- 1. Open Claude Desktop Settings
268
- 2. Navigate to the MCP Servers section
269
- 3. Add a new server with:
270
- - **Name**: Story UI
271
- - **URL**: `https://your-worker.workers.dev/mcp` (production) or `http://localhost:4005/mcp` (local)
284
+ ### Manual Configuration (Advanced)
272
285
 
273
286
  For advanced users who prefer manual configuration, add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
274
287
 
@@ -292,7 +305,7 @@ For advanced users who prefer manual configuration, add to your Claude Desktop c
292
305
  npx story-ui start
293
306
  ```
294
307
 
295
- This starts the Story UI HTTP server with MCP endpoint at `http://localhost:4005/mcp`.
308
+ This starts the Story UI HTTP server with MCP endpoint at `http://localhost:4005/mcp-remote/mcp`.
296
309
 
297
310
  ### Available MCP Commands
298
311
 
@@ -315,22 +328,17 @@ Story UI v3 can be deployed as a standalone web application accessible from anyw
315
328
  │ Railway Deployment │
316
329
  │ ┌─────────────────────────────────────────────────────────┐│
317
330
  │ │ Express MCP Server (Node.js) ││
318
- │ │ - Serves React frontend ││
331
+ │ │ - Serves Storybook with Story UI addon ││
319
332
  │ │ - API routes for story generation ││
320
- │ │ - Multi-provider LLM support ││
321
- └─────────────────────────────────────────────────────────┘│
322
- │ │ │
323
- │ ▼ │
324
- │ ┌─────────────────────────────────────────────────────────┐│
325
- │ │ PostgreSQL Database ││
326
- │ │ - Story persistence across deployments ││
333
+ │ │ - Multi-provider LLM support (Claude, OpenAI, Gemini) ││
334
+ │ - File-based story persistence ││
327
335
  │ └─────────────────────────────────────────────────────────┘│
328
336
  └──────────────────────────────────────────────────────────────┘
329
337
  ```
330
338
 
331
339
  ### Deploy to Railway
332
340
 
333
- Railway provides a complete deployment experience with PostgreSQL for story persistence.
341
+ Railway provides a straightforward deployment experience with file-based story persistence.
334
342
 
335
343
  **Quick Start:**
336
344
 
@@ -339,11 +347,8 @@ Railway provides a complete deployment experience with PostgreSQL for story pers
339
347
  npm install -g @railway/cli
340
348
  railway login
341
349
 
342
- # Initialize project with PostgreSQL
350
+ # Initialize and deploy
343
351
  railway init
344
- railway add --plugin postgresql
345
-
346
- # Deploy
347
352
  railway up
348
353
  ```
349
354
 
@@ -351,16 +356,15 @@ railway up
351
356
  - `ANTHROPIC_API_KEY` - Required for Claude models
352
357
  - `OPENAI_API_KEY` - Optional for OpenAI models
353
358
  - `GEMINI_API_KEY` - Optional for Gemini models
354
- - `DATABASE_URL` - Auto-set by Railway PostgreSQL plugin
355
359
 
356
360
  **Connect MCP Clients:**
357
361
 
358
362
  ```bash
359
363
  # Add your Railway deployment as an MCP server
360
- claude mcp add --transport http story-ui https://your-app.up.railway.app/story-ui
364
+ claude mcp add --transport http story-ui https://your-app.up.railway.app/mcp-remote/mcp
361
365
  ```
362
366
 
363
- See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed deployment instructions, PostgreSQL setup, and troubleshooting.
367
+ See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed deployment instructions and troubleshooting.
364
368
 
365
369
  ---
366
370
 
package/dist/cli/index.js CHANGED
@@ -247,11 +247,6 @@ program
247
247
  .option('--backend-url <url>', 'Use existing backend URL for app/frontend')
248
248
  .option('--storybook-dir <dir>', 'Path to Storybook project')
249
249
  .option('--project-name <name>', 'Project name prefix', 'story-ui')
250
- // Legacy flags (deprecated)
251
- .option('--init', '[DEPRECATED] Use --live instead')
252
- .option('--edge', '[DEPRECATED] Use --live instead')
253
- .option('--pages', '[DEPRECATED] Use --live instead')
254
- .option('--all', '[DEPRECATED] Use --live instead')
255
250
  .action(async (options) => {
256
251
  await deployCommand(options);
257
252
  });
package/dist/cli/setup.js CHANGED
@@ -851,7 +851,7 @@ Material UI (MUI) is a React component library implementing Material Design.
851
851
  }
852
852
  // Copy component files
853
853
  const templatesDir = path.resolve(__dirname, '../../templates/StoryUI');
854
- const componentFiles = ['StoryUIPanel.tsx', 'StoryUIPanel.stories.tsx'];
854
+ const componentFiles = ['StoryUIPanel.tsx', 'StoryUIPanel.mdx'];
855
855
  console.log(chalk.blue('\n📦 Installing Story UI component...'));
856
856
  for (const file of componentFiles) {
857
857
  const sourcePath = path.join(templatesDir, file);
@@ -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;AAqZ5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DAkbxE"}
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;AA2b5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DA6dxE"}
@@ -4,7 +4,7 @@ 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 { extractAndValidateCodeBlock, createFallbackStory, validateStoryCode } from '../../story-generator/validateStory.js';
7
+ import { createFallbackStory, validateStoryCode } from '../../story-generator/validateStory.js';
8
8
  import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
9
9
  import { StoryTracker } from '../../story-generator/storyTracker.js';
10
10
  import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
@@ -17,6 +17,22 @@ import { UrlRedirectService } from '../../story-generator/urlRedirectService.js'
17
17
  import { chatCompletion, generateTitle as llmGenerateTitle, isProviderConfigured, getProviderInfo, chatCompletionWithImages, buildMessageWithImages } from '../../story-generator/llm-providers/story-llm-service.js';
18
18
  import { processImageInputs } from '../../story-generator/imageProcessor.js';
19
19
  import { buildVisionAwarePrompt } from '../../story-generator/visionPrompts.js';
20
+ import { aggregateValidationErrors, shouldContinueRetrying, buildSelfHealingPrompt, hasNoErrors, getTotalErrorCount, createEmptyErrors, formatErrorsForLog, selectBestAttempt, } from '../../story-generator/selfHealingLoop.js';
21
+ // Build suggestion using the user's actual discovered components (design-system agnostic)
22
+ function buildComponentSuggestion(components) {
23
+ if (!components?.length) {
24
+ return 'Check your story-ui.config.js to ensure components are properly configured.';
25
+ }
26
+ // Show a sample of the user's actual available components (up to 5)
27
+ const sampleComponents = components
28
+ .slice(0, 5)
29
+ .map(c => c.name)
30
+ .join(', ');
31
+ const moreCount = components.length > 5
32
+ ? ` and ${components.length - 5} more`
33
+ : '';
34
+ return `Your available components include: ${sampleComponents}${moreCount}. Check story-ui.config.js if expected components are missing.`;
35
+ }
20
36
  // Legacy function - now uses flexible system with enhanced discovery
21
37
  async function buildClaudePrompt(userPrompt) {
22
38
  const config = loadUserConfig();
@@ -33,17 +49,27 @@ async function buildClaudePromptWithContext(userPrompt, config, conversation, pr
33
49
  let useFrameworkAware = false;
34
50
  let frameworkOptions;
35
51
  if (options?.framework) {
36
- // Explicit framework specified
52
+ // Explicit framework specified in request
37
53
  useFrameworkAware = true;
38
54
  frameworkOptions = { framework: options.framework };
39
55
  logger.log(`📦 Using specified framework: ${options.framework}`);
40
56
  }
57
+ else if (config.componentFramework || config.framework) {
58
+ // Framework configured in story-ui.config.js
59
+ const configFramework = (config.componentFramework || config.framework);
60
+ useFrameworkAware = true;
61
+ frameworkOptions = { framework: configFramework };
62
+ logger.log(`📦 Using framework from config: ${configFramework}`);
63
+ }
41
64
  else if (options?.autoDetectFramework) {
42
65
  // Auto-detect framework from project
43
66
  try {
44
67
  const detectedFramework = await detectProjectFramework(process.cwd());
45
68
  useFrameworkAware = true;
46
69
  frameworkOptions = { framework: detectedFramework };
70
+ // CRITICAL: Also set config.componentFramework so validateStoryCode receives the correct framework
71
+ // This ensures React imports are properly removed for non-React frameworks during post-processing
72
+ config.componentFramework = detectedFramework;
47
73
  logger.log(`📦 Auto-detected framework: ${detectedFramework}`);
48
74
  }
49
75
  catch (error) {
@@ -323,7 +349,7 @@ function findSimilarIcon(iconName, allowedIcons) {
323
349
  }
324
350
  return null;
325
351
  }
326
- function fileNameFromTitle(title, hash) {
352
+ function fileNameFromTitle(title, hash, extension = '.stories.tsx') {
327
353
  if (!title || typeof title !== 'string') {
328
354
  title = 'untitled';
329
355
  }
@@ -337,7 +363,7 @@ function fileNameFromTitle(title, hash) {
337
363
  .replace(/^-+|-+$/g, '')
338
364
  .replace(/"|'/g, '')
339
365
  .slice(0, 60);
340
- return `${base}-${hash}.stories.tsx`;
366
+ return `${base}-${hash}${extension}`;
341
367
  }
342
368
  export async function generateStoryFromPrompt(req, res) {
343
369
  const { prompt, fileName, conversation, isUpdate, originalTitle, storyId: providedStoryId, framework, // Explicit framework (react, vue, angular, svelte, web-components)
@@ -408,11 +434,12 @@ export async function generateStoryFromPrompt(req, res) {
408
434
  }
409
435
  }
410
436
  }
411
- // --- Start of Validation and Retry Loop ---
437
+ // --- Start of Self-Healing Validation and Retry Loop ---
412
438
  let aiText = '';
413
439
  let validationErrors = [];
414
440
  const maxRetries = 3;
415
441
  let attempts = 0;
442
+ let selfHealingUsed = false;
416
443
  // Build framework-aware options with vision support
417
444
  const frameworkOptions = {
418
445
  framework: framework,
@@ -420,6 +447,20 @@ export async function generateStoryFromPrompt(req, res) {
420
447
  visionMode: visionMode,
421
448
  designSystem: designSystem,
422
449
  };
450
+ // Create enhanced component discovery BEFORE the loop for use in self-healing
451
+ const discovery = new EnhancedComponentDiscovery(config);
452
+ const discoveredComponents = await discovery.discoverAll();
453
+ const componentNames = discoveredComponents.map(c => c.name);
454
+ // Self-healing options for retry prompts
455
+ const selfHealingOptions = {
456
+ maxAttempts: maxRetries,
457
+ availableComponents: componentNames,
458
+ framework: frameworkOptions.framework || 'react',
459
+ importPath: config.importPath || 'your-library',
460
+ };
461
+ // Track all attempts for best-attempt selection
462
+ const allAttempts = [];
463
+ const errorHistory = [];
423
464
  const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, frameworkOptions);
424
465
  const messages = [{ role: 'user', content: initialPrompt }];
425
466
  while (attempts < maxRetries) {
@@ -443,95 +484,90 @@ export async function generateStoryFromPrompt(req, res) {
443
484
  else {
444
485
  aiText = extractedCode;
445
486
  }
487
+ // --- COMPREHENSIVE VALIDATION (Pattern + AST + Import) ---
488
+ // 1. Pattern validation (storyValidator)
446
489
  validationErrors = validateStory(aiText);
447
- if (validationErrors.length === 0) {
448
- logger.log(' Validation successful!');
490
+ // 2. TypeScript AST validation (validateStory.ts)
491
+ const astValidation = validateStoryCode(aiText, 'story.tsx', config);
492
+ // 3. Import validation (check against discovered components)
493
+ const importValidation = await preValidateImports(aiText, config, discovery);
494
+ // Aggregate all errors
495
+ const aggregatedErrors = aggregateValidationErrors(astValidation, validationErrors, importValidation.isValid ? [] : importValidation.errors);
496
+ // Track this attempt
497
+ const autoFixApplied = !!astValidation.fixedCode;
498
+ allAttempts.push({
499
+ code: astValidation.fixedCode || aiText,
500
+ errors: aggregatedErrors,
501
+ autoFixed: autoFixApplied,
502
+ });
503
+ errorHistory.push(aggregatedErrors);
504
+ // Log validation results
505
+ logger.log(`Validation: ${formatErrorsForLog(aggregatedErrors)}`);
506
+ if (autoFixApplied) {
507
+ logger.log('Auto-fix applied to code');
508
+ aiText = astValidation.fixedCode;
509
+ }
510
+ // Check if all validations pass
511
+ if (hasNoErrors(aggregatedErrors)) {
512
+ logger.log('✅ All validations passed!');
449
513
  break; // Exit loop on success
450
514
  }
451
- logger.log(`❌ Validation failed with ${validationErrors.length} errors:`);
452
- validationErrors.forEach(err => logger.log(` - Line ${err.line}: ${err.message}`));
453
- if (attempts < maxRetries) {
454
- const errorFeedback = validationErrors
455
- .map(err => `- Line ${err.line}: ${err.message}`)
456
- .join('\n');
457
- const retryPrompt = `Your previous attempt failed validation with the following errors:\n${errorFeedback}\n\nPlease correct these issues and provide the full, valid story code. Do not use the forbidden patterns.`;
458
- messages.push({ role: 'assistant', content: claudeResponse });
459
- messages.push({ role: 'user', content: retryPrompt });
515
+ // Check if we should continue retrying
516
+ const retryDecision = shouldContinueRetrying(attempts, maxRetries, errorHistory);
517
+ if (!retryDecision.shouldRetry) {
518
+ logger.log(`🛑 Stopping retries: ${retryDecision.reason}`);
519
+ break;
460
520
  }
521
+ // --- SELF-HEALING: Send errors back to LLM for correction ---
522
+ selfHealingUsed = true;
523
+ logger.log(`🔄 Self-healing attempt ${attempts} of ${maxRetries}`);
524
+ logger.log(` Errors: Syntax(${aggregatedErrors.syntaxErrors.length}), Pattern(${aggregatedErrors.patternErrors.length}), Import(${aggregatedErrors.importErrors.length})`);
525
+ // Build the self-healing prompt with all errors
526
+ const selfHealingPrompt = buildSelfHealingPrompt(aiText, aggregatedErrors, attempts, selfHealingOptions);
527
+ messages.push({ role: 'assistant', content: claudeResponse });
528
+ messages.push({ role: 'user', content: selfHealingPrompt });
461
529
  }
462
- if (validationErrors.length > 0) {
463
- console.error(`Story generation failed after ${maxRetries} attempts.`);
464
- // Optional: decide if you want to return an error or proceed with the last attempt
465
- // For now, we'll proceed with the last attempt and let the user see the result
466
- }
467
- // --- End of Validation and Retry Loop ---
468
- logger.log('Claude final response:', aiText);
469
- // Create enhanced component discovery for validation
470
- const discovery = new EnhancedComponentDiscovery(config);
471
- await discovery.discoverAll();
472
- // Pre-validate imports in the raw AI text to catch blacklisted components early
473
- const preValidation = await preValidateImports(aiText, config, discovery);
474
- if (!preValidation.isValid) {
475
- console.error('Pre-validation failed - blacklisted components detected:', preValidation.errors);
476
- // Return error immediately without creating file
477
- return res.status(400).json({
478
- error: 'Generated code contains invalid imports',
479
- details: preValidation.errors,
480
- suggestion: 'The AI tried to use components that do not exist. Please try rephrasing your request using basic components like Box, Stack, Header, Button, etc.'
481
- });
482
- }
483
- // Use the new robust validation system
484
- const validationResult = extractAndValidateCodeBlock(aiText, config);
530
+ // --- End of Self-Healing Validation and Retry Loop ---
531
+ // Determine the best code to use
485
532
  let fileContents;
486
533
  let hasValidationWarnings = false;
487
- logger.log('Validation result:', {
488
- isValid: validationResult.isValid,
489
- errors: validationResult.errors,
490
- warnings: validationResult.warnings,
491
- hasFixedCode: !!validationResult.fixedCode
492
- });
493
- if (!validationResult.isValid && !validationResult.fixedCode) {
494
- console.error('Generated code validation failed:', validationResult.errors);
495
- // Create fallback story only if we can't fix the code
496
- logger.log('Creating fallback story due to validation failure');
497
- fileContents = createFallbackStory(prompt, config);
498
- hasValidationWarnings = true;
499
- }
500
- else {
501
- // Use fixed code if available, otherwise use the extracted code
502
- if (validationResult.fixedCode) {
503
- fileContents = validationResult.fixedCode;
534
+ // Select the best attempt (fewest errors)
535
+ const bestAttempt = selectBestAttempt(allAttempts);
536
+ const finalErrors = bestAttempt ? bestAttempt.errors : createEmptyErrors();
537
+ const finalErrorCount = getTotalErrorCount(finalErrors);
538
+ logger.log(`Generation complete: ${attempts} attempts, self-healing=${selfHealingUsed}, final errors=${finalErrorCount}`);
539
+ if (finalErrorCount > 0 && bestAttempt) {
540
+ logger.log(`⚠️ Using best attempt with ${finalErrorCount} remaining errors`);
541
+ logger.log(` Remaining: ${formatErrorsForLog(finalErrors)}`);
542
+ // If there are still errors but we have an attempt, use the best one
543
+ // Only create fallback if we have no valid code at all
544
+ if (bestAttempt.code && bestAttempt.code.includes('export')) {
545
+ fileContents = bestAttempt.code;
504
546
  hasValidationWarnings = true;
505
- logger.log('Using auto-fixed code');
506
547
  }
507
548
  else {
508
- // Extract the validated code
509
- const codeMatch = aiText.match(/```(?:tsx|jsx|typescript|ts|js|javascript)?\s*([\s\S]*?)\s*```/i);
510
- if (codeMatch) {
511
- fileContents = codeMatch[1].trim();
512
- }
513
- else {
514
- // Fallback: extract from import to end of valid TypeScript
515
- const importIdx = aiText.indexOf('import');
516
- if (importIdx !== -1) {
517
- fileContents = aiText;
518
- }
519
- else {
520
- fileContents = aiText.trim();
521
- }
522
- }
523
- }
524
- if (validationResult.warnings && validationResult.warnings.length > 0) {
549
+ // Create fallback story only if we have no usable code
550
+ logger.log('Creating fallback story - no usable code generated');
551
+ fileContents = createFallbackStory(prompt, config);
525
552
  hasValidationWarnings = true;
526
- logger.log('Validation warnings:', validationResult.warnings);
527
553
  }
528
554
  }
555
+ else if (bestAttempt) {
556
+ // Success - use the best attempt (which should have no errors)
557
+ fileContents = bestAttempt.code;
558
+ }
559
+ else {
560
+ // No attempts at all (shouldn't happen)
561
+ fileContents = aiText;
562
+ }
529
563
  if (!fileContents) {
530
564
  console.error('No valid code could be extracted or generated.');
531
565
  return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
532
566
  }
533
- // Determine the framework being used (priority: request > config > auto-detect > default)
567
+ // Determine the framework being used (priority: request > config.componentFramework > config.framework > auto-detect > default)
568
+ // CRITICAL: Check config.componentFramework BEFORE config.framework - many projects use componentFramework (not framework)
534
569
  const detectedFramework = frameworkOptions.framework ||
570
+ config.componentFramework ||
535
571
  config.framework ||
536
572
  (frameworkOptions.autoDetectFramework ? await detectProjectFramework(process.cwd()).catch(() => 'react') : 'react');
537
573
  logger.log(`🎯 Framework detection: request=${frameworkOptions.framework}, config=${config.framework}, detected=${detectedFramework}`);
@@ -591,14 +627,17 @@ export async function generateStoryFromPrompt(req, res) {
591
627
  // FIX #5: Final validation after ALL post-processing
592
628
  // This catches any syntax errors introduced by post-processing (e.g., buggy regex replacements)
593
629
  const finalValidation = validateStoryCode(fixedFileContents, 'story.tsx', config);
630
+ // ALWAYS apply fixedCode if it exists - handles both:
631
+ // 1. Syntax error fixes (where isValid = false)
632
+ // 2. React import removal for non-React frameworks (where isValid = true but fixedCode exists)
633
+ if (finalValidation.fixedCode) {
634
+ logger.log('✅ Applied validation fixes (React import removal or syntax fixes)');
635
+ fixedFileContents = finalValidation.fixedCode;
636
+ }
594
637
  if (!finalValidation.isValid) {
595
638
  logger.log('⚠️ Post-processing introduced syntax errors:', finalValidation.errors);
596
- // If we have fixed code from validation, use it
597
- if (finalValidation.fixedCode) {
598
- logger.log('✅ Auto-fixed post-processing errors');
599
- fixedFileContents = finalValidation.fixedCode;
600
- }
601
- else {
639
+ // If we don't have fixed code at this point, we can't recover
640
+ if (!finalValidation.fixedCode) {
602
641
  // Post-processing broke the code and we can't fix it
603
642
  // Return an error rather than serving broken code
604
643
  console.error('Post-processing introduced unfixable syntax errors:', finalValidation.errors);
@@ -633,7 +672,19 @@ export async function generateStoryFromPrompt(req, res) {
633
672
  let hash, finalFileName, storyId;
634
673
  if (isActualUpdate && (fileName || providedStoryId)) {
635
674
  // For updates, preserve the existing fileName and ID
636
- finalFileName = fileName;
675
+ // Ensure the filename has the proper .stories.tsx extension
676
+ // FIX: Handle case where fileName is undefined but providedStoryId exists
677
+ if (fileName) {
678
+ finalFileName = fileName;
679
+ }
680
+ else if (providedStoryId) {
681
+ // Generate filename from storyId when fileName not provided
682
+ finalFileName = `${providedStoryId}.stories.tsx`;
683
+ logger.log('📝 Generated filename from storyId:', finalFileName);
684
+ }
685
+ if (finalFileName && !finalFileName.endsWith('.stories.tsx')) {
686
+ finalFileName = finalFileName + '.stories.tsx';
687
+ }
637
688
  // Use provided storyId or extract from fileName
638
689
  if (providedStoryId) {
639
690
  storyId = providedStoryId;
@@ -653,9 +704,11 @@ export async function generateStoryFromPrompt(req, res) {
653
704
  // For new stories, ALWAYS generate new IDs with timestamp to ensure uniqueness
654
705
  const timestamp = Date.now();
655
706
  hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
656
- finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
707
+ // Use the framework adapter's defaultExtension for the correct file extension
708
+ const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
709
+ finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
657
710
  storyId = `story-${hash}`;
658
- logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
711
+ logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName, extension: fileExtension });
659
712
  }
660
713
  // Write story to file system
661
714
  const outPath = generateStory({
@@ -702,8 +755,10 @@ export async function generateStoryFromPrompt(req, res) {
702
755
  isUpdate: isActualUpdate,
703
756
  validation: {
704
757
  hasWarnings: hasValidationWarnings,
705
- errors: validationResult?.errors || [],
706
- warnings: validationResult?.warnings || []
758
+ errors: [...finalErrors.syntaxErrors, ...finalErrors.patternErrors, ...finalErrors.importErrors],
759
+ warnings: [],
760
+ selfHealingUsed,
761
+ attempts
707
762
  }
708
763
  });
709
764
  }
@@ -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;AAwa5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAob9E"}
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,iBAqhB9E"}