From 15d3789bb496ce8fcfd954ebd8eacb5c88d45fdf Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Wed, 20 Aug 2025 13:48:13 -0500 Subject: [PATCH] Add full-screen layout preview route and fix iframe sandbox issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create LayoutPreviewPage component for full-screen layout previews - Add preview route /themes/:themeId/layouts/:layoutId/preview to App routing - Update theme components with preview links and improved navigation - Fix iframe sandbox error by adding allow-scripts permission - Enhance template renderer with layout metadata support - Replace PostCSS with regex-only CSS parsing for browser compatibility - Add comprehensive standards documentation for code quality - Clean up CSS slot indicators to be always visible with descriptions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 6 +- ERROR_HANDLING_STANDARDS.md | 240 +++++++++++ IMPORT_STANDARDS.md | 26 ++ REACT19.md | 311 --------------- REACT19_IMPLEMENTATION.md | 112 ++++++ VITEBESTPRACTICES.md | 137 ------- VITE_PERFORMANCE.md | 131 ++++++ public/themes-manifest.json | 2 +- .../themes/default/layouts/content-slide.html | 3 + src/App.tsx | 3 +- src/components/themes/LayoutDetailPage.css | 25 ++ src/components/themes/LayoutDetailPage.tsx | 7 + src/components/themes/LayoutPreview.tsx | 2 +- src/components/themes/LayoutPreviewPage.css | 372 ++++++++++++++++++ src/components/themes/LayoutPreviewPage.tsx | 175 ++++++++ src/components/themes/ThemeDetailPage.css | 34 +- src/components/themes/ThemeDetailPage.tsx | 20 +- src/components/themes/index.ts | 1 + src/plugins/themeWatcher.ts | 10 +- src/utils/cssParser.ts | 100 +---- src/utils/templateRenderer.ts | 4 + src/utils/themeLoader.ts | 4 +- vite.config.ts | 11 +- 23 files changed, 1172 insertions(+), 564 deletions(-) create mode 100644 ERROR_HANDLING_STANDARDS.md create mode 100644 IMPORT_STANDARDS.md delete mode 100644 REACT19.md create mode 100644 REACT19_IMPLEMENTATION.md delete mode 100644 VITEBESTPRACTICES.md create mode 100644 VITE_PERFORMANCE.md create mode 100644 src/components/themes/LayoutDetailPage.css create mode 100644 src/components/themes/LayoutPreviewPage.css create mode 100644 src/components/themes/LayoutPreviewPage.tsx diff --git a/CLAUDE.md b/CLAUDE.md index a2b1acc..b9e2b42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,12 +50,10 @@ - Theme structure should not repeat the same information in multiple places - Theme structure should be flexible and allow for custom themes to be created using only html templates and css - Metadata for themes should be stored as comments in the html and css files -- React compoonents and code should be modular and reusable and abide by [REACT19.md](REACT19.md) -- We should refer to [VITEBESTPRACTICES.md](VITEBESTPRACTICES.md) for best practices in Vite -- + # General Claude Guidelines - Don't run npm commands, just tell me what to run and I'll run them myself -- When building componentns, refer to https://www.vite.dev + # Architecture ## Themes - Themes should be stored in a directory structure under public directory that allows for easy access and modification by browser diff --git a/ERROR_HANDLING_STANDARDS.md b/ERROR_HANDLING_STANDARDS.md new file mode 100644 index 0000000..ddeaf84 --- /dev/null +++ b/ERROR_HANDLING_STANDARDS.md @@ -0,0 +1,240 @@ +# Error Handling Standards + +## Current State: INCONSISTENT +Error handling varies across the codebase - some functions have comprehensive error handling while others are missing it entirely. + +## Required Error Handling Patterns + +### 1. Error Boundaries (MISSING) +Implement React error boundaries for component-level error handling: + +```typescript +// ✅ IMPLEMENT - ThemeErrorBoundary.tsx +import React, { Component, ErrorInfo, ReactNode } from 'react'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error?: Error; +} + +export class ThemeErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Theme component error:', error, errorInfo); + // Log to error reporting service in production + } + + render() { + if (this.state.hasError) { + return this.props.fallback || ( +
+

Something went wrong

+

There was an error loading the theme system.

+ +
+ ); + } + + return this.props.children; + } +} +``` + +### 2. Async Error Handling (STANDARDIZE) +Consistent pattern for all async operations: + +```typescript +// ✅ STANDARD PATTERN +const performAsyncOperation = async (id: string) => { + try { + setLoading(true); + setError(null); + + const result = await riskyOperation(id); + + if (!result) { + throw new Error(`Operation failed for ${id}`); + } + + return result; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error occurred'; + setError(message); + console.error(`Async operation failed:`, error); + throw error; // Re-throw if caller needs to handle + } finally { + setLoading(false); + } +}; +``` + +### 3. Hook Error Handling (IMPLEMENT) +Custom hook for consistent error states: + +```typescript +// ✅ IMPLEMENT - useAsyncOperation.ts +import { useState, useCallback } from 'react'; + +interface AsyncState { + data: T | null; + loading: boolean; + error: string | null; +} + +export function useAsyncOperation() { + const [state, setState] = useState>({ + data: null, + loading: false, + error: null + }); + + const execute = useCallback(async (operation: () => Promise) => { + setState(prev => ({ ...prev, loading: true, error: null })); + + try { + const data = await operation(); + setState({ data, loading: false, error: null }); + return data; + } catch (error) { + const message = error instanceof Error ? error.message : 'Operation failed'; + setState(prev => ({ ...prev, loading: false, error: message })); + throw error; + } + }, []); + + return { ...state, execute }; +} +``` + +### 4. Network Error Handling (ENHANCE) +Standardized fetch error handling: + +```typescript +// ✅ STANDARD PATTERN +const fetchWithErrorHandling = async (url: string, options?: RequestInit) => { + try { + const response = await fetch(url, options); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + return response; + } catch (error) { + if (error instanceof TypeError) { + throw new Error('Network error - check your connection'); + } + throw error; + } +}; +``` + +## Error Types & Classifications + +### 1. User Errors (Recoverable) +- Invalid theme selection +- Missing form data +- File upload issues +**Handling**: Show user-friendly message, allow retry + +### 2. System Errors (Technical) +- Network failures +- Parse errors +- Configuration issues +**Handling**: Log details, show generic message, provide fallback + +### 3. Critical Errors (Unrecoverable) +- Dependency failures +- Memory issues +- Corrupt data +**Handling**: Error boundary, full page reload option + +## Logging Standards + +### Development +```typescript +// ✅ PATTERN +console.group(`🔥 ${operation} Error`); +console.error('Details:', error); +console.error('Context:', context); +console.groupEnd(); +``` + +### Production +```typescript +// ✅ PATTERN (when error service added) +errorReportingService.captureException(error, { + context, + user: getCurrentUser(), + timestamp: new Date().toISOString() +}); +``` + +## Error Message Standards + +### User-Facing Messages +- **Clear & Actionable**: "Could not load theme. Please try again." +- **No technical jargon**: Avoid error codes, stack traces +- **Suggest solutions**: "Check your connection and retry" + +### Developer Messages +- **Detailed context**: Include operation, parameters, state +- **Stack traces**: Preserve for debugging +- **Structured data**: Consistent error object format + +## Implementation Checklist + +### HIGH PRIORITY +- [ ] Add ThemeErrorBoundary to App.tsx +- [ ] Implement useAsyncOperation hook +- [ ] Standardize theme loading error handling +- [ ] Add network error handling to fetch operations + +### MEDIUM PRIORITY +- [ ] Create error fallback components +- [ ] Add error recovery mechanisms (retry buttons) +- [ ] Implement error state consistency across components +- [ ] Add loading state management standards + +### LOW PRIORITY +- [ ] Add error reporting service integration +- [ ] Implement error analytics tracking +- [ ] Add error rate monitoring +- [ ] Create error handling documentation + +## Testing Requirements +- **Error boundary testing**: Verify fallback rendering +- **Async error testing**: Test network failures, timeouts +- **Recovery testing**: Ensure retry mechanisms work +- **Error state testing**: Verify error messages display correctly + +## Current Problem Areas + +### Missing Error Boundaries +No React error boundaries implemented - component errors crash the app + +### Inconsistent Async Patterns +Some async operations handle errors, others don't: +- `themeLoader.ts` ✅ Has good error handling +- Some components ❌ Missing error handling + +### No Error Recovery +Users cannot recover from errors - no retry buttons or reload options + +### Logging Inconsistency +Mix of `console.warn`, `console.error`, and no logging \ No newline at end of file diff --git a/IMPORT_STANDARDS.md b/IMPORT_STANDARDS.md new file mode 100644 index 0000000..9a97cdd --- /dev/null +++ b/IMPORT_STANDARDS.md @@ -0,0 +1,26 @@ +# Import Standards + +## Direct Imports Required +- **NEVER use barrel files** for component imports (violates Vite best practices) +- **ALWAYS use direct file imports** with explicit extensions for better Vite performance +- **AVOID** `import { Component } from './components'` +- **USE** `import { Component } from './components/Component.tsx'` + +## Examples +```typescript +// ❌ BAD - Barrel file usage +import { ThemeBrowser, ThemeDetailPage } from './components/themes' + +// ✅ GOOD - Direct imports +import { ThemeBrowser } from './components/themes/ThemeBrowser.tsx' +import { ThemeDetailPage } from './components/themes/ThemeDetailPage.tsx' +``` + +## Performance Impact +- Barrel files prevent Vite's tree shaking optimization +- Direct imports enable faster module resolution +- Explicit extensions reduce lookup overhead + +## Exceptions +- CSS imports don't require extensions: `import './Component.css'` +- Type-only imports: `import type { Theme } from './types/theme.ts'` \ No newline at end of file diff --git a/REACT19.md b/REACT19.md deleted file mode 100644 index c995ef9..0000000 --- a/REACT19.md +++ /dev/null @@ -1,311 +0,0 @@ -# React 19 Guidelines & Best Practices - -*Based on [React 19 Release Blog](https://react.dev/blog/2024/12/05/react-19)* - -## Core Philosophy -**"React 19 provides powerful tools to create more responsive, efficient web applications with simplified state management and rendering strategies."** - -## Key New Features - -### 1. Actions -Actions simplify data mutations and state updates by automatically managing: -- **Pending states** - know when operations are in progress -- **Optimistic updates** - update UI before server confirms -- **Error handling** - graceful error recovery -- **Form submissions** - streamlined form processing - -```jsx -// Example: Using Actions for form submission -function UpdateName({ name, updateName }) { - const [isPending, startTransition] = useTransition(); - - return ( -
- - -
- ); -} -``` - -### 2. New Hooks - -#### `useActionState` -Simplifies action handling with built-in state management: -```jsx -function MyComponent() { - const [state, formAction] = useActionState(actionFunction, initialState); - - return ( -
- {/* Form elements */} -
- ); -} -``` - -#### `useOptimistic` -Enables optimistic UI updates for responsive user experience: -```jsx -function TodoList({ todos, addTodo }) { - const [optimisticTodos, addOptimisticTodo] = useOptimistic( - todos, - (state, newTodo) => [...state, { ...newTodo, sending: true }] - ); - - async function formAction(formData) { - addOptimisticTodo({ name: formData.get("name") }); - await addTodo(formData); - } - - return ( -
- {optimisticTodos.map(todo => ( -
- {todo.name} -
- ))} -
- ); -} -``` - -#### `useFormStatus` -Provides form submission status: -```jsx -function SubmitButton() { - const { pending, data, method, action } = useFormStatus(); - - return ( - - ); -} -``` - -#### `use` -Reads resources during rendering: -```jsx -function Profile({ userPromise }) { - const user = use(userPromise); - - return

{user.name}

; -} -``` - -### 3. Improved Component Rendering - -#### `ref` as a Prop -Function components can now receive `ref` as a regular prop: -```jsx -function MyInput({ placeholder, ref }) { - return ; -} - -// Usage - -``` - -#### Direct Context Rendering -Context can be rendered directly without `.Provider`: -```jsx -// Before React 19 - - - - -// React 19 - - - -``` - -#### Cleanup Functions for Refs -```jsx -function MyComponent() { - const ref = useCallback((node) => { - if (node) { - // Setup - node.focus(); - - // Cleanup function - return () => { - node.blur(); - }; - } - }, []); - - return ; -} -``` - -#### Metadata Support -Components can render metadata tags directly: -```jsx -function BlogPost({ post }) { - return ( -
- {post.title} - -

{post.title}

-

{post.content}

-
- ); -} -``` - -### 4. Performance Enhancements - -#### Resource Preloading -```jsx -import { preload, preinit } from 'react-dom'; - -// Preload resources -preload('/api/user', { as: 'fetch' }); -preinit('/styles.css', { as: 'style' }); -``` - -#### Better Hydration Error Reporting -- More detailed error messages -- Better debugging information -- Improved compatibility with third-party scripts - -### 5. Server Components - -#### Server-Side Rendering -```jsx -// Server Component -async function BlogPost({ slug }) { - const post = await fetchPost(slug); - - return ( -
-

{post.title}

-

{post.content}

-
- ); -} -``` - -#### Server Actions -```jsx -// actions.js -'use server' - -export async function updateUser(formData) { - const name = formData.get('name'); - // Update user on server - await database.updateUser({ name }); -} - -// Component -import { updateUser } from './actions.js'; - -function UserForm() { - return ( -
- - -
- ); -} -``` - -## Best Practices for Our Project - -### 1. Forms and Data Management -- **Use Actions** for theme selection and configuration -- **Implement useOptimistic** for responsive theme preview -- **Leverage useFormStatus** for loading states - -### 2. Component Design -- **Use ref as prop** for theme slot components -- **Implement cleanup functions** for theme CSS loading -- **Render metadata** for theme preview information - -### 3. Performance -- **Preload theme assets** using new preloading APIs -- **Use Server Components** for theme discovery if server-side rendering is added -- **Implement error boundaries** with improved error handling - -### 4. State Management -- **Use useActionState** for complex theme operations -- **Implement optimistic updates** for theme switching -- **Leverage new Context syntax** for theme providers - -## Implementation Guidelines - -### Theme Selection with Actions -```jsx -function ThemeBrowser({ themes }) { - const [selectedTheme, selectTheme] = useActionState( - async (currentState, formData) => { - const themeId = formData.get('themeId'); - const theme = await loadTheme(themeId); - return theme; - }, - null - ); - - return ( -
- {themes.map(theme => ( - - ))} -
- ); -} -``` - -### Optimistic Theme Switching -```jsx -function ThemePreview({ currentTheme, onThemeChange }) { - const [optimisticTheme, setOptimisticTheme] = useOptimistic( - currentTheme, - (state, newTheme) => newTheme - ); - - async function switchTheme(newTheme) { - setOptimisticTheme(newTheme); - await onThemeChange(newTheme); - } - - return ( -
-

{optimisticTheme.name}

- {/* Theme preview content */} -
- ); -} -``` - -### Theme Context (New Syntax) -```jsx -function App() { - const [theme, setTheme] = useState(defaultTheme); - - return ( - - - - - ); -} -``` - -## Key Takeaways - -1. **Embrace Actions** - Simplify form handling and state management -2. **Use Optimistic Updates** - Create responsive user interfaces -3. **Leverage New Hooks** - Reduce boilerplate code -4. **Improve Performance** - Utilize preloading and better error handling -5. **Simplify Context** - Use direct rendering without `.Provider` -6. **Handle Refs Better** - Pass refs as regular props to function components \ No newline at end of file diff --git a/REACT19_IMPLEMENTATION.md b/REACT19_IMPLEMENTATION.md new file mode 100644 index 0000000..299fb46 --- /dev/null +++ b/REACT19_IMPLEMENTATION.md @@ -0,0 +1,112 @@ +# React 19 Implementation Standards + +## Current Status: NOT IMPLEMENTED +The codebase uses React 19.1.1 but implements ZERO React 19 features. This is a significant gap. +This might be OK if the features aren't needed, are complex or costly, or there is a better way that +doesn't violate react standard approaches. + +## Required React 19 Features to Implement + +### 1. Actions for Form Handling +Replace manual form state management with React 19 Actions: + +```typescript +// ❌ CURRENT - Manual state management +const [loading, setLoading] = useState(false); +const [error, setError] = useState(null); +const handleSubmit = async (e) => { + setLoading(true); + try { + await submitForm(e); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } +}; + +// ✅ REACT 19 - Actions +const [state, submitAction] = useActionState(async (prevState, formData) => { + // Action automatically handles loading/error states + return await submitForm(formData); +}, initialState); +``` + +### 2. useOptimistic for Theme Previews +Implement optimistic updates for theme switching: + +```typescript +// ✅ IMPLEMENT +function ThemePreview({ currentTheme }) { + const [optimisticTheme, setOptimisticTheme] = useOptimistic( + currentTheme, + (currentTheme, optimisticValue) => ({ ...currentTheme, ...optimisticValue }) + ); + + const switchTheme = async (newTheme) => { + setOptimisticTheme(newTheme); // Immediate UI update + await saveTheme(newTheme); // Actual save + }; +} +``` + +### 3. Modern Context Syntax +Replace Provider wrapper with direct Context rendering: + +```typescript +// ❌ OLD + + + + +// ✅ REACT 19 + + + +``` + +### 4. Error Boundaries with Actions +Implement error boundaries that work with Actions: + +```typescript +// ✅ IMPLEMENT +class ThemeErrorBoundary extends React.Component { + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + // Log to error reporting service + console.error('Theme loading error:', error, errorInfo); + } +} +``` + +## Implementation Priority + +### HIGH PRIORITY +1. **Theme selection Actions** - Replace manual form handling +2. **Error boundaries** - Add proper error handling +3. **useOptimistic for theme previews** - Better UX + +### MEDIUM PRIORITY +1. **Context syntax modernization** - Update provider patterns +2. **Form Actions for theme creation** - When that feature is added +3. **Server Actions integration** - If backend is added + +### LOW PRIORITY +1. **Preloading optimization** - Use React 19 preloading APIs +2. **Streaming SSR** - If SSR is implemented +3. **Advanced concurrent features** - As needed + +## Migration Strategy +1. Start with theme selection Actions (highest impact) +2. Add error boundaries for robustness +3. Implement optimistic updates for better UX +4. Gradually modernize Context usage +5. Add advanced features as needed + +## Resources +- [React 19 Actions Documentation](https://react.dev/reference/react/useActionState) +- [useOptimistic Hook](https://react.dev/reference/react/useOptimistic) +- [Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) \ No newline at end of file diff --git a/VITEBESTPRACTICES.md b/VITEBESTPRACTICES.md deleted file mode 100644 index 707dc85..0000000 --- a/VITEBESTPRACTICES.md +++ /dev/null @@ -1,137 +0,0 @@ -# Vite Best Practices & Performance Guide - -*Based on [Vite Performance Documentation](https://vite.dev/guide/performance)* - -## Core Performance Principle -**"Reduce the amount of work for source files (JS/TS/CSS)"** to maintain performance as projects grow. - -## Browser Setup - -### Development Environment -- **Create a dev-only browser profile** without extensions -- **Use incognito mode** for faster performance -- **Don't disable browser cache** while using dev tools (it significantly slows down requests and HMR) - -## Plugin Management - -### Best Practices -- **Be cautious with community plugins** - they may impact performance -- **Dynamically import large dependencies** instead of importing them statically -- **Avoid long-running operations** in plugin hooks -- **Minimize file transformation time** - -## Import and Resolve Optimization - -### File Extensions -- **Be explicit with import paths** - use `import './Component.jsx'` instead of `import './Component'` -- **Narrow down `resolve.extensions` list** - don't include unnecessary file types -- Use common extensions first: `['.js', '.ts', '.jsx', '.tsx']` - -### Module Imports -- **Avoid barrel files** that re-export multiple modules from index files -- **Import individual APIs directly** instead of from index files -- Example: Use `import { debounce } from 'lodash-es/debounce'` instead of `import { debounce } from 'lodash-es'` - -## File Transformation - -### Warmup Strategy -- **Use `server.warmup`** for frequently used files -- Pre-transform commonly accessed modules during dev server startup - -### Native Tooling -- **Use native tooling when possible** for better performance -- Consider these alternatives: - - **Rolldown** instead of Rollup - - **LightningCSS** for CSS processing - - **`@vitejs/plugin-react-swc`** for React projects - -## CSS and Styling - -### CSS Optimization -- **Use CSS instead of preprocessors** when possible (native CSS is faster) -- **Import SVGs as strings/URLs** rather than components when appropriate -- **Minimize unnecessary transformations** - -## Performance Profiling - -### Diagnostic Tools -- **Use `vite --profile`** to generate performance profiles -- **Use `vite --debug plugin-transform`** to inspect transformation times -- **Use tools like speedscope** to analyze bottlenecks - -### Monitoring -- Track build times and identify slow transformations -- Monitor bundle sizes and chunk splitting effectiveness -- Profile HMR performance during development - -## Import Strategies - -### Dynamic Imports -```typescript -// Good: Dynamic import for large dependencies -const heavyLibrary = await import('heavy-library'); - -// Bad: Static import of large library -import heavyLibrary from 'heavy-library'; -``` - -### Explicit Extensions -```typescript -// Good: Explicit file extension -import Component from './Component.jsx'; - -// Bad: Missing extension (requires resolution) -import Component from './Component'; -``` - -### Direct API Imports -```typescript -// Good: Direct import -import { debounce } from 'lodash-es/debounce'; - -// Bad: Barrel import (imports entire library) -import { debounce } from 'lodash-es'; -``` - -## Configuration Recommendations - -### Resolve Extensions (in order of frequency) -```typescript -export default { - resolve: { - extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'] - } -} -``` - -### Server Warmup -```typescript -export default { - server: { - warmup: { - clientFiles: ['./src/components/**/*.tsx', './src/utils/**/*.ts'] - } - } -} -``` - -## Development vs Production - -### Development Focus -- Fast HMR and dev server startup -- Minimal transformations -- Browser-friendly module resolution - -### Production Focus -- Optimized bundle sizes -- Tree shaking effectiveness -- Code splitting strategies - -## Key Takeaways - -1. **Minimize transformations** - every transformation adds overhead -2. **Be explicit** - help Vite resolve files faster with explicit paths -3. **Use native tools** - they're typically faster than JavaScript alternatives -4. **Profile regularly** - identify and fix performance bottlenecks early -5. **Optimize imports** - direct imports are faster than barrel imports -6. **Consider browser setup** - development environment affects performance significantly \ No newline at end of file diff --git a/VITE_PERFORMANCE.md b/VITE_PERFORMANCE.md new file mode 100644 index 0000000..9cdbdb9 --- /dev/null +++ b/VITE_PERFORMANCE.md @@ -0,0 +1,131 @@ +# Vite Performance Standards + +## Missing Configuration +The current `vite.config.ts` is missing critical performance optimizations. + +## Required Vite Configuration Updates + +### 1. Resolve Extensions (CRITICAL) +```typescript +// ADD to vite.config.ts +export default defineConfig({ + resolve: { + extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'] + } +}); +``` +**Impact**: Faster module resolution, explicit extension requirements + +### 2. Server Warmup (HIGH IMPACT) +```typescript +// ADD to vite.config.ts +export default defineConfig({ + server: { + warmup: { + clientFiles: [ + './src/components/**/*.tsx', + './src/utils/**/*.ts', + './src/types/**/*.ts', + './src/themes/**/*.ts' + ] + } + } +}); +``` +**Impact**: Faster initial load times, better dev experience + +### 3. Build Optimizations +```typescript +// ADD to vite.config.ts +export default defineConfig({ + build: { + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + router: ['react-router-dom'], + themes: ['./src/utils/themeLoader', './src/utils/cssParser'] + } + } + } + } +}); +``` +**Impact**: Better code splitting, improved cache utilization + +### 4. Dependency Pre-bundling +```typescript +// ADD to vite.config.ts +export default defineConfig({ + optimizeDeps: { + include: ['react', 'react-dom', 'react-router-dom'], + exclude: ['@vite/client', '@vite/env'] + } +}); +``` +**Impact**: Faster cold starts, consistent dependency handling + +## Current Performance Issues + +### 1. Barrel File Usage +**Problem**: Two barrel files present +- `src/components/themes/index.ts` +- `src/themes/index.ts` + +**Impact**: Prevents tree-shaking, slower builds +**Solution**: Remove barrel files, use direct imports + +### 2. Missing File Extensions +**Problem**: Import statements lack explicit extensions +```typescript +// ❌ CURRENT +import App from './App.tsx' +import { getThemes } from '../../themes' + +// ✅ SHOULD BE +import App from './App.tsx' +import { getThemes } from '../../themes/index.ts' +``` + +### 3. No Warmup Configuration +**Problem**: Cold start performance is suboptimal +**Solution**: Configure server warmup for frequently accessed files + +## Implementation Priority + +### IMMEDIATE (Critical Performance Impact) +1. **Remove barrel file imports** - Replace with direct imports +2. **Add resolve extensions** - Configure explicit extension handling +3. **Configure server warmup** - Improve dev server startup + +### SHORT TERM (Build Performance) +1. **Add manual chunk configuration** - Optimize bundle splitting +2. **Configure dependency pre-bundling** - Improve cold start times +3. **Add explicit file extensions** - All import statements + +### LONG TERM (Advanced Optimizations) +1. **Dynamic imports for themes** - Code splitting for theme system +2. **Service worker integration** - Cache theme assets +3. **Module federation** - If theme system becomes shared + +## Monitoring Standards +- **Bundle size analysis**: Use `npm run build -- --analyze` +- **Dev server startup time**: Should be < 1 second after warmup +- **Hot reload performance**: Changes should reflect in < 200ms +- **Build time targets**: Production build < 30 seconds + +## File Size Targets +- **Main bundle**: < 200KB gzipped +- **Theme chunks**: < 50KB each +- **CSS bundles**: < 100KB total +- **Asset optimization**: Images < 500KB each + +## Implementation Checklist +- [ ] Remove barrel file exports +- [ ] Add resolve extensions configuration +- [ ] Configure server warmup paths +- [ ] Add manual chunk configuration +- [ ] Configure dependency pre-bundling +- [ ] Add explicit import extensions +- [ ] Set up bundle analysis +- [ ] Monitor performance metrics \ No newline at end of file diff --git a/public/themes-manifest.json b/public/themes-manifest.json index f879293..4d94958 100644 --- a/public/themes-manifest.json +++ b/public/themes-manifest.json @@ -11,5 +11,5 @@ "hasMasterSlide": true } }, - "generated": "2025-08-20T15:25:28.729Z" + "generated": "2025-08-20T18:06:52.256Z" } \ No newline at end of file diff --git a/public/themes/default/layouts/content-slide.html b/public/themes/default/layouts/content-slide.html index 613cf6f..f3d443e 100644 --- a/public/themes/default/layouts/content-slide.html +++ b/public/themes/default/layouts/content-slide.html @@ -2,6 +2,9 @@

{{title}}

+

+ {{subtitle}} +

{{content}}
diff --git a/src/App.tsx b/src/App.tsx index 6a795bc..5ac715b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' -import { ThemeBrowser, ThemeDetailPage, LayoutDetailPage } from './components/themes' +import { ThemeBrowser, ThemeDetailPage, LayoutDetailPage, LayoutPreviewPage } from './components/themes' import { AppHeader } from './components/AppHeader' import { Welcome } from './components/Welcome' import './App.css' @@ -16,6 +16,7 @@ function App() { } /> } /> } /> + } /> diff --git a/src/components/themes/LayoutDetailPage.css b/src/components/themes/LayoutDetailPage.css new file mode 100644 index 0000000..d02a550 --- /dev/null +++ b/src/components/themes/LayoutDetailPage.css @@ -0,0 +1,25 @@ +/* Additional styles for LayoutDetailPage preview link */ +.layout-stats { + display: flex; + align-items: center; + gap: 1rem; + margin-top: 0.5rem; + flex-wrap: wrap; +} + +.preview-link { + color: #3b82f6; + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + padding: 0.375rem 0.75rem; + background: #dbeafe; + border-radius: 0.375rem; + transition: all 0.2s ease; +} + +.preview-link:hover { + background: #bfdbfe; + text-decoration: none; + transform: translateY(-1px); +} \ No newline at end of file diff --git a/src/components/themes/LayoutDetailPage.tsx b/src/components/themes/LayoutDetailPage.tsx index 72a4adf..344f399 100644 --- a/src/components/themes/LayoutDetailPage.tsx +++ b/src/components/themes/LayoutDetailPage.tsx @@ -3,6 +3,7 @@ import { useParams, Link } from 'react-router-dom'; import type { Theme, SlideLayout } from '../../types/theme'; import { getTheme } from '../../themes'; import { LayoutPreview } from './LayoutPreview'; +import './LayoutDetailPage.css'; export const LayoutDetailPage: React.FC = () => { const { themeId, layoutId } = useParams<{ themeId: string; layoutId: string }>(); @@ -95,6 +96,12 @@ export const LayoutDetailPage: React.FC = () => {
{layout.slots.length} slots from {theme.name} + + View Full Preview → +
diff --git a/src/components/themes/LayoutPreview.tsx b/src/components/themes/LayoutPreview.tsx index 717d1a6..4a80e98 100644 --- a/src/components/themes/LayoutPreview.tsx +++ b/src/components/themes/LayoutPreview.tsx @@ -50,7 +50,7 @@ export const LayoutPreview: React.FC = ({ ref={iframeRef} className="layout-preview-frame" title={`Preview of ${layout.name}`} - sandbox="allow-same-origin" + sandbox="allow-same-origin allow-scripts" /> diff --git a/src/components/themes/LayoutPreviewPage.css b/src/components/themes/LayoutPreviewPage.css new file mode 100644 index 0000000..8620ef7 --- /dev/null +++ b/src/components/themes/LayoutPreviewPage.css @@ -0,0 +1,372 @@ +.layout-preview-page { + min-height: 100vh; + display: flex; + flex-direction: column; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + position: relative; +} + +/* Header */ +.preview-header { + background: white; + border-bottom: 1px solid #e5e7eb; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; +} + +.preview-nav { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; +} + +.nav-link { + color: #3b82f6; + text-decoration: none; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + transition: background-color 0.2s ease; +} + +.nav-link:hover { + background-color: #eff6ff; + text-decoration: underline; +} + +.nav-separator { + color: #9ca3af; + font-weight: 500; +} + +.nav-current { + color: #6b7280; + font-weight: 500; +} + +.preview-actions { + display: flex; + gap: 0.75rem; +} + +.action-button { + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + text-decoration: none; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.action-button.primary { + background: #3b82f6; + color: white; +} + +.action-button.primary:hover { + background: #2563eb; +} + +.action-button.secondary { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} + +.action-button.secondary:hover { + background: #e5e7eb; +} + +/* Main Content */ +.preview-content { + flex: 1; + background: #f8fafc; + position: relative; + overflow: hidden; +} + +.layout-description-banner { + position: fixed; + top: 10%; + left: calc(50% - 600px - 15rem); + width: 14rem; + background: white; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + padding: 1rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + z-index: 15; +} + +.layout-description-banner h3 { + margin: 0 0 0.5rem 0; + font-size: 1rem; + font-weight: 600; + color: #1f2937; +} + +.layout-description-banner p { + margin: 0 0 1rem 0; + font-size: 0.875rem; + color: #6b7280; + line-height: 1.4; +} + +.slot-count-badge { + background: #dbeafe; + color: #1e40af; + font-size: 0.75rem; + font-weight: 500; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + display: inline-block; +} + +.layout-rendered-content { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + border-radius: 0.5rem; + min-height: 600px; + min-width: 800px; + max-width: 1200px; + padding: 2rem; + z-index: 10; + max-height: 80vh; + overflow-y: auto; +} + +/* Make slot indicators always visible with blue dotted borders */ +.layout-rendered-content [data-slot] { + border: 2px dotted #3b82f6 !important; + position: relative; + min-height: 1.5rem; + padding: 0.5rem; +} + +/* Add slot labels */ +.layout-rendered-content [data-slot]::before { + content: "Slot: " attr(data-slot) " (" attr(data-type) ")"; + position: absolute; + top: -1.5rem; + left: 0; + background: #3b82f6; + color: white; + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + z-index: 1; + font-weight: 500; +} + +/* Ensure slot content is visible */ +.layout-rendered-content [data-slot]:empty::after { + content: "[Empty slot - " attr(data-slot) "]"; + color: #9ca3af; + font-style: italic; + font-size: 0.875rem; +} + +.close-preview-button { + position: fixed; + top: 10%; + right: calc(50% - 600px - 3rem); + width: 3rem; + height: 3rem; + background: #ef4444; + color: white; + border: none; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.25rem; + font-weight: bold; + cursor: pointer; + z-index: 20; + text-decoration: none; + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); + transition: all 0.2s ease; +} + +.close-preview-button:hover { + background: #dc2626; + transform: scale(1.05); + box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4); +} + +.close-preview-button:active { + transform: scale(0.95); +} + +/* Footer */ +.preview-footer { + background: white; + border-top: 1px solid #e5e7eb; + padding: 1rem 2rem; + flex-shrink: 0; +} + +.preview-info { + display: flex; + gap: 2rem; + justify-content: center; + font-size: 0.75rem; + color: #6b7280; +} + +.preview-info span { + display: flex; + align-items: center; +} + +.theme-info::before { + content: "🎨"; + margin-right: 0.5rem; +} + +.layout-info::before { + content: "📐"; + margin-right: 0.5rem; +} + +.slots-info::before { + content: "🔧"; + margin-right: 0.5rem; +} + +/* Loading and Error States */ +.layout-preview-page.loading, +.layout-preview-page.error, +.layout-preview-page.not-found { + justify-content: center; + align-items: center; + min-height: 100vh; + text-align: center; +} + +.loading-content, +.error-content, +.not-found-content { + max-width: 500px; + padding: 2rem; +} + +.loading-spinner { + font-size: 1.125rem; + color: #6b7280; + margin-bottom: 1.5rem; +} + +.error-content h2, +.not-found-content h2 { + color: #ef4444; + margin-bottom: 1rem; +} + +.back-link { + display: inline-block; + margin-top: 1rem; + color: #3b82f6; + text-decoration: none; + font-size: 0.875rem; +} + +.back-link:hover { + text-decoration: underline; +} + +/* Print Styles */ +@media print { + .preview-header, + .preview-footer { + display: none; + } + + .preview-content { + padding: 0; + background: white; + } + + .layout-rendered-content { + box-shadow: none; + max-width: none; + width: 100%; + } +} + +/* Responsive */ +@media (max-width: 1024px) { + .layout-rendered-content { + min-width: 90vw; + max-width: 90vw; + min-height: 500px; + padding: 1.5rem; + } + + .close-preview-button { + right: 5%; + top: 12%; + } + + .layout-description-banner { + left: 5%; + width: 12rem; + } +} + +@media (max-width: 768px) { + .preview-header { + padding: 1rem; + flex-direction: column; + gap: 1rem; + align-items: stretch; + } + + .preview-nav { + justify-content: center; + } + + .preview-actions { + justify-content: center; + } + + .layout-rendered-content { + min-width: 95vw; + max-width: 95vw; + min-height: 400px; + padding: 1rem; + border-radius: 0.25rem; + } + + .close-preview-button { + right: 2.5%; + top: 15%; + width: 2.5rem; + height: 2.5rem; + font-size: 1rem; + } + + .layout-description-banner { + position: relative; + left: auto; + top: auto; + width: 100%; + margin: 1rem; + transform: none; + } + + .preview-info { + flex-direction: column; + gap: 0.5rem; + align-items: center; + } +} \ No newline at end of file diff --git a/src/components/themes/LayoutPreviewPage.tsx b/src/components/themes/LayoutPreviewPage.tsx new file mode 100644 index 0000000..1a21036 --- /dev/null +++ b/src/components/themes/LayoutPreviewPage.tsx @@ -0,0 +1,175 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import type { Theme, SlideLayout } from '../../types/theme'; +import { getTheme } from '../../themes'; +import { renderTemplateWithSampleData } from '../../utils/templateRenderer'; +import './LayoutPreviewPage.css'; + +export const LayoutPreviewPage: React.FC = () => { + const { themeId, layoutId } = useParams<{ themeId: string; layoutId: string }>(); + const [theme, setTheme] = useState(null); + const [layout, setLayout] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [renderedContent, setRenderedContent] = useState(''); + + useEffect(() => { + const loadThemeAndLayout = async () => { + if (!themeId || !layoutId) { + setError('Missing theme ID or layout ID'); + setLoading(false); + return; + } + + try { + setLoading(true); + const themeData = await getTheme(themeId); + if (!themeData) { + setError(`Theme "${themeId}" not found`); + return; + } + + const layoutData = themeData.layouts.find((l: SlideLayout) => l.id === layoutId); + if (!layoutData) { + setError(`Layout "${layoutId}" not found in theme "${themeId}"`); + return; + } + + setTheme(themeData); + setLayout(layoutData); + + // Render the template with sample data + const rendered = renderTemplateWithSampleData(layoutData.htmlTemplate, layoutData); + setRenderedContent(rendered); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load theme and layout'); + } finally { + setLoading(false); + } + }; + + loadThemeAndLayout(); + }, [themeId, layoutId]); + + useEffect(() => { + if (theme) { + // Dynamically load theme CSS + const themeStyleId = 'theme-preview-style'; + let existingStyle = document.getElementById(themeStyleId); + + if (existingStyle) { + existingStyle.remove(); + } + + const link = document.createElement('link'); + link.id = themeStyleId; + link.rel = 'stylesheet'; + link.href = `${theme.basePath}/${theme.cssFile}`; + document.head.appendChild(link); + + // Cleanup function + return () => { + const styleToRemove = document.getElementById(themeStyleId); + if (styleToRemove) { + styleToRemove.remove(); + } + }; + } + }, [theme]); + + if (loading) { + return ( +
+
+
Loading preview...
+ ← Back to Themes +
+
+ ); + } + + if (error) { + return ( +
+
+

Error

+

{error}

+ ← Back to Themes +
+
+ ); + } + + if (!theme || !layout) { + return ( +
+
+

Preview Not Found

+

The requested layout preview could not be found.

+ ← Back to Themes +
+
+ ); + } + + return ( +
+
+ + +
+ + Layout Details + + +
+
+ +
+
+

{layout.name} Layout

+

{layout.description}

+
+ {layout.slots.length} slot{layout.slots.length !== 1 ? 's' : ''} +
+
+
+ + ✕ + +
+ +
+
+ Theme: {theme.name} + Layout: {layout.name} + {layout.slots.length} slots +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/themes/ThemeDetailPage.css b/src/components/themes/ThemeDetailPage.css index 17660fb..474395d 100644 --- a/src/components/themes/ThemeDetailPage.css +++ b/src/components/themes/ThemeDetailPage.css @@ -147,6 +147,8 @@ display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; + gap: 0.5rem; } .layout-name { @@ -164,17 +166,37 @@ border-radius: 0.25rem; } -.layout-detail-link { - color: #3b82f6; - text-decoration: none; - font-size: 0.875rem; - font-weight: 500; +.layout-actions { + display: flex; + gap: 0.5rem; } -.layout-detail-link:hover { +.layout-detail-link, +.layout-preview-link { + color: #3b82f6; + text-decoration: none; + font-size: 0.75rem; + font-weight: 500; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + transition: background-color 0.2s ease; +} + +.layout-detail-link:hover, +.layout-preview-link:hover { + background-color: #eff6ff; text-decoration: underline; } +.layout-preview-link { + background-color: #dbeafe; + color: #1e40af; +} + +.layout-preview-link:hover { + background-color: #bfdbfe; +} + .layout-preview-container { height: 200px; } diff --git a/src/components/themes/ThemeDetailPage.tsx b/src/components/themes/ThemeDetailPage.tsx index 217e976..3c3d30f 100644 --- a/src/components/themes/ThemeDetailPage.tsx +++ b/src/components/themes/ThemeDetailPage.tsx @@ -114,12 +114,20 @@ export const ThemeDetailPage: React.FC = () => {

{layout.name}

{layout.slots.length} slots - - View Details → - +
+ + Details + + + Preview + +
diff --git a/src/components/themes/index.ts b/src/components/themes/index.ts index 988c60e..d4a699e 100644 --- a/src/components/themes/index.ts +++ b/src/components/themes/index.ts @@ -2,4 +2,5 @@ export { ThemeBrowser } from './ThemeBrowser'; export { LayoutPreview } from './LayoutPreview'; export { ThemeDetailPage } from './ThemeDetailPage'; export { LayoutDetailPage } from './LayoutDetailPage'; +export { LayoutPreviewPage } from './LayoutPreviewPage'; export type { Theme } from '../../types/theme'; \ No newline at end of file diff --git a/src/plugins/themeWatcher.ts b/src/plugins/themeWatcher.ts index d17b005..4396d4e 100644 --- a/src/plugins/themeWatcher.ts +++ b/src/plugins/themeWatcher.ts @@ -1,6 +1,4 @@ import type { Plugin } from 'vite'; -import { watch } from 'fs'; -import path from 'path'; /** * Vite plugin to watch theme files in public directory and trigger HMR @@ -8,11 +6,15 @@ import path from 'path'; export function themeWatcherPlugin(): Plugin { return { name: 'theme-watcher', - configureServer(server) { + async configureServer(server) { + // Dynamic imports for Node.js modules in server context + const fs = await import('fs'); + const path = await import('path'); + const publicThemesPath = path.resolve(process.cwd(), 'public/themes'); // Watch for changes in the themes directory - const watcher = watch( + const watcher = fs.watch( publicThemesPath, { recursive: true }, (eventType: string, filename: string | null) => { diff --git a/src/utils/cssParser.ts b/src/utils/cssParser.ts index 4188846..706f731 100644 --- a/src/utils/cssParser.ts +++ b/src/utils/cssParser.ts @@ -1,5 +1,3 @@ -import postcss from 'postcss'; - export interface CSSVariables { [key: string]: string; } @@ -13,44 +11,18 @@ export interface ThemeMetadata { } /** - * Parses CSS variables from CSS text content using PostCSS + * Parses CSS variables from CSS text content using regex */ export const parseCSSVariables = (cssContent: string): CSSVariables => { const variables: CSSVariables = {}; - try { - const ast = postcss.parse(cssContent); - - // Find :root rule and extract custom properties - ast.walkRules(':root', (rule) => { - rule.walkDecls((decl) => { - if (decl.prop.startsWith('--')) { - const name = decl.prop.substring(2); // Remove '--' prefix - variables[name] = decl.value.trim(); - } - }); - }); - } catch (error) { - console.warn('Error parsing CSS with PostCSS:', error); - // Fallback to regex parsing if PostCSS fails - return parseCSSVariablesRegex(cssContent); - } + // Parse :root rules and extract CSS variables + const rootRegex = /:root\s*\{([^}]+)\}/gs; + let rootMatch; - return variables; -}; - -/** - * Fallback regex-based CSS variable parsing - */ -const parseCSSVariablesRegex = (cssContent: string): CSSVariables => { - const variables: CSSVariables = {}; - - const rootRegex = /:root\s*\{([^}]+)\}/g; - const variableRegex = /--([^:]+):\s*([^;]+);/g; - - const rootMatch = rootRegex.exec(cssContent); - if (rootMatch) { + while ((rootMatch = rootRegex.exec(cssContent)) !== null) { const rootContent = rootMatch[1]; + const variableRegex = /--([^:]+):\s*([^;]+);/g; let variableMatch; while ((variableMatch = variableRegex.exec(rootContent)) !== null) { @@ -62,6 +34,7 @@ const parseCSSVariablesRegex = (cssContent: string): CSSVariables => { return variables; }; + /** * Loads CSS file and extracts variables */ @@ -96,65 +69,9 @@ export const setCSSVariable = (variableName: string, value: string, element?: HT }; /** - * Parses theme metadata from CSS comment block using PostCSS + * Parses theme metadata from CSS comment block using regex */ export const parseThemeMetadata = (cssContent: string): ThemeMetadata | null => { - try { - const ast = postcss.parse(cssContent); - const metadata: Partial = {}; - - // Find the first comment that contains theme metadata - ast.walkComments((comment) => { - if (!metadata.id) { // Only process the first metadata comment - const commentContent = comment.text; - - // Parse each line in the comment - const lines = commentContent.split('\n'); - for (const line of lines) { - const cleanLine = line.replace(/^\s*\*\s?/, '').trim(); - if (cleanLine.includes(':')) { - const [key, ...valueParts] = cleanLine.split(':'); - const value = valueParts.join(':').trim(); - - switch (key.toLowerCase().trim()) { - case 'theme': - metadata.id = value.toLowerCase().replace(/\s+/g, '-'); - break; - case 'name': - metadata.name = value; - break; - case 'description': - metadata.description = value; - break; - case 'author': - metadata.author = value; - break; - case 'version': - metadata.version = value; - break; - } - } - } - } - }); - - // Ensure required fields - if (!metadata.id || !metadata.name || !metadata.description) { - return null; - } - - return metadata as ThemeMetadata; - } catch (error) { - console.warn('Error parsing CSS metadata with PostCSS:', error); - // Fallback to regex parsing - return parseThemeMetadataRegex(cssContent); - } -}; - -/** - * Fallback regex-based metadata parsing - */ -const parseThemeMetadataRegex = (cssContent: string): ThemeMetadata | null => { const commentRegex = /\/\*\s*([\s\S]*?)\s*\*\//; const match = commentRegex.exec(cssContent); @@ -199,6 +116,7 @@ const parseThemeMetadataRegex = (cssContent: string): ThemeMetadata | null => { return metadata as ThemeMetadata; }; + /** * Gets all CSS variables from computed styles */ diff --git a/src/utils/templateRenderer.ts b/src/utils/templateRenderer.ts index 8f1b57e..370c7c8 100644 --- a/src/utils/templateRenderer.ts +++ b/src/utils/templateRenderer.ts @@ -79,6 +79,10 @@ export const generateSampleDataForLayout = (layout: SlideLayout): Record { }); // Clean up empty attributes - if (Object.keys(slotConfig.attributes!).length === 0) { + /*if (Object.keys(slotConfig.attributes!).length === 0) { delete (slotConfig as any).attributes; } + + */ slots.push(slotConfig); }); diff --git a/vite.config.ts b/vite.config.ts index 8a54697..7f32f48 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,10 +8,19 @@ export default defineConfig({ react(), themeWatcherPlugin() ], + define: { + global: 'globalThis', + process: { env: {} } + }, + optimizeDeps: { + exclude: ['fs', 'path'] + }, server: { watch: { // Watch public directory for theme changes ignored: ['!**/public/themes/**'] } - } + }, + // Ensure static assets are served correctly + publicDir: 'public' })