diff --git a/servers/nextjs/app/layout-preview/[slug]/page.tsx b/servers/nextjs/app/layout-preview/[slug]/page.tsx
index f6ee9e27..00dee152 100644
--- a/servers/nextjs/app/layout-preview/[slug]/page.tsx
+++ b/servers/nextjs/app/layout-preview/[slug]/page.tsx
@@ -12,7 +12,7 @@ const GroupLayoutPreview = () => {
const router = useRouter()
const slug = params.slug as string
- const { layoutGroup, loading, error, retry, isWatcherConnected } = useGroupLayoutLoader(slug)
+ const { layoutGroup, loading, error, retry } = useGroupLayoutLoader(slug)
// Handle loading state
if (loading) {
diff --git a/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts b/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts
index 42596224..00a14e2a 100644
--- a/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts
+++ b/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts
@@ -1,6 +1,6 @@
'use client'
-import { useState, useEffect, useRef, useCallback } from 'react'
-import { useLayoutWatcher } from './useLayoutWatcher'
+import { useState, useEffect, useRef } from 'react'
+
import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types'
import { toast } from 'sonner'
@@ -9,33 +9,44 @@ interface UseGroupLayoutLoaderReturn {
loading: boolean
error: string | null
retry: () => void
- isWatcherConnected: boolean
}
+// Global cache to store layout groups and avoid re-fetching
+const layoutGroupCache = new Map()
+const loadingGroupsCache = new Set()
+
export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderReturn => {
const [layoutGroup, setLayoutGroup] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
-
const hasMountedRef = useRef(false)
- const loadGroupLayouts = useCallback(async () => {
+ const loadGroupLayouts = async () => {
+ // Check cache first
+ if (layoutGroupCache.has(groupSlug)) {
+ setLayoutGroup(layoutGroupCache.get(groupSlug)!)
+ setLoading(false)
+ setError(null)
+ return
+ }
+
+ // Prevent multiple simultaneous requests for the same group
+ if (loadingGroupsCache.has(groupSlug)) {
+ return
+ }
+
try {
setLoading(true)
setError(null)
+ loadingGroupsCache.add(groupSlug)
- const response = await fetch('/api/layouts', {
- cache: 'no-cache',
- headers: { 'Cache-Control': 'no-cache' }
- })
-
+ const response = await fetch('/api/layouts')
if (!response.ok) {
toast.error('Error loading layouts', {
description: response.statusText,
})
return
}
-
const groupedLayoutsData: GroupedLayoutsResponse[] = await response.json()
// Find the specific group by slug
@@ -60,8 +71,6 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
for (const fileName of targetGroupData.files) {
try {
const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
-
- // Import the module to get exports
const module = await import(`@/presentation-layouts/${targetGroupData.groupName}/${layoutName}`)
if (!module.default) {
@@ -83,9 +92,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
// Use empty object to let schema apply its default values
const sampleData = module.Schema.parse({})
const layoutId = module.layoutId || layoutName.toLowerCase().replace(/layout$/, '')
-
-
-
+
const layoutInfo: LayoutInfo = {
name: layoutName,
component: module.default,
@@ -100,6 +107,31 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
} catch (importError) {
console.error(`Failed to import ${fileName} from ${targetGroupData.groupName}:`, importError)
+
+ // Try alternative import path
+ try {
+ const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
+ const module = await import(`@/presentation-layouts/${targetGroupData.groupName}/${layoutName}`)
+
+ if (module.default && module.Schema) {
+ const sampleData = module.Schema.parse({})
+ const layoutId = module.layoutId || layoutName.toLowerCase().replace(/layout$/, '')
+ const layoutInfo: LayoutInfo = {
+ name: layoutName,
+ component: module.default,
+ schema: module.Schema,
+ sampleData,
+ fileName,
+ groupName: targetGroupData.groupName,
+ layoutId
+ }
+ groupLayouts.push(layoutInfo)
+ } else {
+ console.error(`${layoutName} is missing required exports (default component or Schema)`)
+ }
+ } catch (altError) {
+ console.error(`Alternative import also failed for ${fileName} from ${targetGroupData.groupName}:`, altError)
+ }
}
}
@@ -115,6 +147,8 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
settings: groupSettings
}
+ // Cache the result
+ layoutGroupCache.set(groupSlug, group)
setLayoutGroup(group)
setError(null)
}
@@ -124,39 +158,27 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
setError(error instanceof Error ? error.message : 'Failed to load group layouts')
} finally {
setLoading(false)
+ loadingGroupsCache.delete(groupSlug)
}
- }, [groupSlug])
+ }
- const handleLayoutChange = useCallback(() => {
- console.log(`π Layout change detected for group: ${groupSlug}, reloading...`)
-
- setTimeout(() => {
- loadGroupLayouts()
- }, 150)
- }, [loadGroupLayouts])
-
- // Setup file watcher for development
- const { isConnected: isWatcherConnected } = useLayoutWatcher({
- onLayoutChange: handleLayoutChange,
- enabled: process.env.NODE_ENV === 'development'
- })
-
- const retry = useCallback(() => {
+ const retry = () => {
+ // Clear cache for this group to force reload
+ layoutGroupCache.delete(groupSlug)
loadGroupLayouts()
- }, [loadGroupLayouts])
+ }
useEffect(() => {
if (groupSlug && !hasMountedRef.current) {
hasMountedRef.current = true
loadGroupLayouts()
}
- }, [groupSlug, loadGroupLayouts])
+ }, [groupSlug])
return {
layoutGroup,
loading,
error,
- retry,
- isWatcherConnected
+ retry
}
}
\ No newline at end of file
diff --git a/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts b/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts
index 004fc1f0..3d4e3a2d 100644
--- a/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts
+++ b/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts
@@ -1,6 +1,6 @@
'use client'
-import { useState, useEffect, useCallback } from 'react'
-import { useLayoutWatcher } from './useLayoutWatcher'
+import { useState, useEffect } from 'react'
+
import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types'
import { toast } from 'sonner'
@@ -10,7 +10,6 @@ interface UseLayoutLoaderReturn {
loading: boolean
error: string | null
retry: () => void
- isWatcherConnected: boolean
}
export const useLayoutLoader = (): UseLayoutLoaderReturn => {
@@ -19,17 +18,12 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
- const loadAllLayouts = useCallback(async () => {
+ const loadAllLayouts = async () => {
try {
setLoading(true)
setError(null)
- // Always fetch fresh data for layout preview
- const response = await fetch('/api/layouts', {
- cache: 'no-cache',
- headers: { 'Cache-Control': 'no-cache' }
- })
-
+ const response = await fetch('/api/layouts')
if (!response.ok) {
toast.error('Error loading layouts', {
description: response.statusText,
@@ -52,8 +46,6 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
for (const fileName of groupData.files) {
try {
const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
-
- // Always fresh import for hot reloading - no caching
const module = await import(`@/presentation-layouts/${groupData.groupName}/${layoutName}`)
if (!module.default) {
@@ -73,8 +65,8 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
}
// Use empty object to let schema apply its default values
+ // User will need to provide actual data when using the layouts
const sampleData = module.Schema.parse({})
- console.log('π Sample data:', sampleData)
const layoutId = module.layoutId || layoutName.toLowerCase().replace(/layout$/, '')
const layoutInfo: LayoutInfo = {
@@ -92,6 +84,33 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
} catch (importError) {
console.error(`Failed to import ${fileName} from ${groupData.groupName}:`, importError)
+
+ // Try alternative import path
+ try {
+ const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
+ const module = await import(`@/presentation-layouts/${groupData.groupName}/${layoutName}`)
+
+ if (module.default && module.Schema) {
+ // Use empty object to let schema apply its default values
+ const sampleData = module.Schema.parse({})
+ const layoutId = module.layoutId || layoutName.toLowerCase().replace(/layout$/, '')
+ const layoutInfo: LayoutInfo = {
+ name: layoutName,
+ component: module.default,
+ schema: module.Schema,
+ sampleData,
+ fileName,
+ groupName: groupData.groupName,
+ layoutId
+ }
+ groupLayouts.push(layoutInfo)
+ allLayouts.push(layoutInfo)
+ } else {
+ console.error(`${layoutName} is missing required exports (default component or Schema)`)
+ }
+ } catch (altError) {
+ console.error(`Alternative import also failed for ${fileName} from ${groupData.groupName}:`, altError)
+ }
}
}
@@ -121,35 +140,21 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
} finally {
setLoading(false)
}
- }, [])
+ }
- const handleLayoutChange = () => {
- console.log('π Layout change detected, reloading...')
- setTimeout(() => {
- loadAllLayouts()
- }, 150)
- };
-
- // Setup file watcher for development
- const { isConnected: isWatcherConnected } = useLayoutWatcher({
- onLayoutChange: handleLayoutChange,
- enabled: process.env.NODE_ENV === 'development'
- })
-
- const retry = useCallback(() => {
+ const retry = () => {
loadAllLayouts()
- }, [loadAllLayouts])
+ }
useEffect(() => {
loadAllLayouts()
- }, [loadAllLayouts])
+ }, [])
return {
layoutGroups,
layouts,
loading,
error,
- retry,
- isWatcherConnected
+ retry
}
}
\ No newline at end of file
diff --git a/servers/nextjs/app/layout-preview/hooks/useLayoutWatcher.ts b/servers/nextjs/app/layout-preview/hooks/useLayoutWatcher.ts
deleted file mode 100644
index 4e3b57a8..00000000
--- a/servers/nextjs/app/layout-preview/hooks/useLayoutWatcher.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-'use client'
-import { useEffect, useRef, useState } from 'react'
-import { toast } from 'sonner'
-
-interface UseLayoutWatcherProps {
- onLayoutChange: () => void
- enabled?: boolean
-}
-
-export const useLayoutWatcher = ({ onLayoutChange, enabled = true }: UseLayoutWatcherProps) => {
- const wsRef = useRef(null)
- const reconnectTimeoutRef = useRef(null)
- const [isConnected, setIsConnected] = useState(false)
-
- const connectWebSocket = () => {
- if (!enabled || typeof window === 'undefined') return
-
- try {
- // Get the current host (works in Docker and local development)
- const host = window.location.hostname
- const wsUrl = `ws://${host}:3001`
-
- console.log('π Connecting to layout watcher:', wsUrl)
-
- // Create WebSocket connection to our layout watcher endpoint
- const ws = new WebSocket(wsUrl)
- wsRef.current = ws
-
- ws.onopen = () => {
- console.log('β
Layout watcher connected successfully')
- setIsConnected(true)
-
- // Clear any existing reconnect timeout
- if (reconnectTimeoutRef.current) {
- clearTimeout(reconnectTimeoutRef.current)
- reconnectTimeoutRef.current = null
- }
- }
-
- ws.onmessage = (event) => {
- try {
- const data = JSON.parse(event.data)
- console.log('π¨ WebSocket message received:', data)
-
- if (data.type === 'layout-change') {
- console.log('π₯ Layout file changed:', data.file)
- console.log('π Group:', data.groupName, 'File:', data.fileName)
-
- // Show toast notification
- toast.success('Layout updated!', {
- description: `${data.file} has been reloaded`,
- duration: 2000,
- })
-
- // Trigger reload
- console.log('π Triggering layout reload...')
- onLayoutChange()
- } else if (data.type === 'connected') {
- console.log('β
Layout watcher handshake complete')
- }
- } catch (error) {
- console.error('β Error parsing WebSocket message:', error)
- }
- }
-
- ws.onclose = (event) => {
- console.log('π Layout watcher disconnected. Code:', event.code, 'Reason:', event.reason)
- setIsConnected(false)
- wsRef.current = null
-
- // Attempt to reconnect after 3 seconds if enabled
- if (enabled) {
- console.log('β° Will attempt to reconnect in 3 seconds...')
- reconnectTimeoutRef.current = setTimeout(() => {
- console.log('π Attempting to reconnect layout watcher...')
- connectWebSocket()
- }, 3000)
- }
- }
-
- ws.onerror = (error) => {
- console.error('β Layout watcher WebSocket error:', error)
- setIsConnected(false)
- ws.close()
- }
-
- } catch (error) {
- console.error('β Failed to create WebSocket connection:', error)
- setIsConnected(false)
- }
- }
-
- useEffect(() => {
- if (enabled) {
- console.log('π― Layout watcher hook enabled, connecting...')
- connectWebSocket()
- } else {
- console.log('βΈοΈ Layout watcher hook disabled')
- }
-
- return () => {
- if (wsRef.current) {
- console.log('π§Ή Cleaning up WebSocket connection')
- wsRef.current.close()
- wsRef.current = null
- }
- if (reconnectTimeoutRef.current) {
- clearTimeout(reconnectTimeoutRef.current)
- reconnectTimeoutRef.current = null
- }
- setIsConnected(false)
- }
- }, [enabled])
-
- // Cleanup on component unmount
- useEffect(() => {
- return () => {
- console.log('π§Ή Cleaning up WebSocket connection')
- if (wsRef.current) {
- wsRef.current.close()
- }
- if (reconnectTimeoutRef.current) {
- clearTimeout(reconnectTimeoutRef.current)
- }
- setIsConnected(false)
- }
- }, [])
-
- return {
- isConnected,
- reconnect: connectWebSocket
- }
-}
\ No newline at end of file
diff --git a/servers/nextjs/app/layout-preview/page.tsx b/servers/nextjs/app/layout-preview/page.tsx
index 904a94ef..a7186109 100644
--- a/servers/nextjs/app/layout-preview/page.tsx
+++ b/servers/nextjs/app/layout-preview/page.tsx
@@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button'
import { ExternalLink, Wifi, WifiOff, RefreshCw } from 'lucide-react'
const LayoutPreview = () => {
- const { layoutGroups, layouts, loading, error, retry, isWatcherConnected } = useLayoutLoader()
+ const { layoutGroups, layouts, loading, error, retry } = useLayoutLoader()
const router = useRouter()
// Handle loading state
@@ -36,39 +36,6 @@ const LayoutPreview = () => {
{layoutGroups.length} groups β’ {layouts.length} layouts
- {/* Development Mode Status */}
- {process.env.NODE_ENV === 'development' && (
-
-
- {isWatcherConnected ? (
- <>
-
- Hot Reload Active
- >
- ) : (
- <>
-
- Hot Reload Disconnected
- >
- )}
-
-
-
-
- )}
-
-
diff --git a/servers/nextjs/next.config.mjs b/servers/nextjs/next.config.mjs
index ec366315..e19c31de 100644
--- a/servers/nextjs/next.config.mjs
+++ b/servers/nextjs/next.config.mjs
@@ -8,44 +8,7 @@ const __filename = fileURLToPath(import.meta.url);
const nextConfig = {
reactStrictMode: false,
- // Enable experimental features for better HMR
- experimental: {
- optimizePackageImports: ['presentation-layouts'],
- },
- // Webpack configuration for better hot reloading
- webpack: (config, { dev, isServer }) => {
- if (dev && !isServer) {
- // Enable hot reloading for presentation layouts
- config.watchOptions = {
- ...config.watchOptions,
- ignored: /node_modules/,
- poll: 1000,
- aggregateTimeout: 300,
- };
-
- // Add alias for presentation layouts
- config.resolve.alias = {
- ...config.resolve.alias,
- '@/presentation-layouts': path.join(process.cwd(), 'presentation-layouts'),
- };
-
- // Configure module resolution for better hot reloading
- config.resolve.symlinks = false;
- config.resolve.cache = false;
-
- // Optimize for faster rebuilds
- config.cache = {
- type: 'filesystem',
- buildDependencies: {
- config: [__filename],
- },
- cacheDirectory: path.join(process.cwd(), '.next/cache/webpack'),
- };
- }
-
- return config;
- },
images: {
remotePatterns: [
diff --git a/servers/nextjs/package.json b/servers/nextjs/package.json
index 632b9ae1..9e80adf7 100644
--- a/servers/nextjs/package.json
+++ b/servers/nextjs/package.json
@@ -4,14 +4,11 @@
"private": true,
"type": "module",
"scripts": {
- "dev": "WATCHPACK_POLLING=true next dev",
- "dev:with-watcher": "concurrently \"npm run dev\" \"npm run layout-watcher\"",
- "layout-watcher": "node scripts/layout-watcher.cjs",
+ "dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint",
- "get:version": "next --version"
- },
+ "lint": "next lint"
+ },
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
@@ -40,11 +37,11 @@
"@tiptap/extension-underline": "^2.0.0",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
- "chokidar": "^4.0.1",
+
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
- "concurrently": "^9.1.0",
+
"jsonrepair": "^3.12.0",
"lucide-react": "^0.447.0",
"marked": "^15.0.11",
diff --git a/servers/nextjs/presentation-layouts/default/Type1SlideLayout.tsx b/servers/nextjs/presentation-layouts/default/Type1SlideLayout.tsx
index 6694a02e..79fd6e66 100644
--- a/servers/nextjs/presentation-layouts/default/Type1SlideLayout.tsx
+++ b/servers/nextjs/presentation-layouts/default/Type1SlideLayout.tsx
@@ -39,7 +39,7 @@ const Type1SlideLayout: React.FC = ({ data: slideData })
{/* Title */}
- {slideData?.title || ' This is the title of slide'}
+ nice {slideData?.title || ' This is the title of slide'}
{/* Description */}
diff --git a/servers/nextjs/scripts/layout-watcher.cjs b/servers/nextjs/scripts/layout-watcher.cjs
deleted file mode 100644
index 49acdebf..00000000
--- a/servers/nextjs/scripts/layout-watcher.cjs
+++ /dev/null
@@ -1,174 +0,0 @@
-const WebSocket = require('ws');
-const chokidar = require('chokidar');
-const path = require('path');
-
-const PORT = 3001;
-const LAYOUTS_DIR = path.join(process.cwd(), 'presentation-layouts');
-
-console.log('π Starting Layout Watcher...');
-console.log('π Watching directory:', LAYOUTS_DIR);
-
-// Create WebSocket server (without path since ws doesn't handle paths like HTTP)
-const wss = new WebSocket.Server({
- port: PORT
-});
-
-console.log(`π WebSocket server running on ws://localhost:${PORT}`);
-
-// Track connected clients
-let connectedClients = new Set();
-
-wss.on('connection', (ws) => {
- console.log('π Client connected to layout watcher');
- connectedClients.add(ws);
-
- ws.on('close', () => {
- console.log('π Client disconnected from layout watcher');
- connectedClients.delete(ws);
- });
-
- ws.on('error', (error) => {
- console.error('β WebSocket error:', error);
- connectedClients.delete(ws);
- });
-
- // Send initial connection confirmation
- ws.send(JSON.stringify({
- type: 'connected',
- message: 'Layout watcher connected',
- timestamp: Date.now()
- }));
-});
-
-// File watcher setup
-const watcher = chokidar.watch(LAYOUTS_DIR, {
- ignored: [
- /(^|[\/\\])\../, // ignore dotfiles
- /node_modules/,
- /\.git/,
- /\.next/,
- /\.DS_Store/,
- /thumbs\.db/i
- ],
- persistent: true,
- ignoreInitial: true, // Don't fire events for initial scan
- followSymlinks: false,
- depth: 3, // Limit depth to avoid deep nested directories
- awaitWriteFinish: {
- stabilityThreshold: 100, // Wait 100ms after last write
- pollInterval: 50
- }
-});
-
-// Debounce function to prevent rapid fire events
-const debounce = (func, wait) => {
- let timeout;
- return function executedFunction(...args) {
- const later = () => {
- clearTimeout(timeout);
- func(...args);
- };
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- };
-};
-
-// Broadcast changes to all connected clients
-const broadcastChange = debounce((eventType, filePath) => {
- if (connectedClients.size === 0) return;
-
- const relativePath = path.relative(LAYOUTS_DIR, filePath);
- const pathParts = relativePath.split(path.sep);
-
- // Only process .tsx and .ts files in group directories
- if (pathParts.length >= 2 && (filePath.endsWith('.tsx') || filePath.endsWith('.ts'))) {
- const groupName = pathParts[0];
- const fileName = pathParts[pathParts.length - 1];
-
- // Skip non-layout files
- if (fileName.startsWith('.') || fileName.includes('.test.') || fileName.includes('.spec.')) {
- return;
- }
-
- const changeData = {
- type: 'layout-change',
- eventType,
- file: relativePath,
- groupName,
- fileName,
- fullPath: filePath,
- timestamp: Date.now()
- };
-
- console.log(`π₯ Broadcasting ${eventType}:`, relativePath);
-
- // Send to all connected clients
- connectedClients.forEach(ws => {
- if (ws.readyState === WebSocket.OPEN) {
- try {
- ws.send(JSON.stringify(changeData));
- } catch (error) {
- console.error('Error sending WebSocket message:', error);
- connectedClients.delete(ws);
- }
- } else {
- // Remove disconnected clients
- connectedClients.delete(ws);
- }
- });
- }
-}, 200); // Debounce for 200ms
-
-// Watch for file changes
-watcher
- .on('change', (filePath) => {
- console.log('π File changed:', path.relative(LAYOUTS_DIR, filePath));
- broadcastChange('change', filePath);
- })
- .on('add', (filePath) => {
- console.log('β File added:', path.relative(LAYOUTS_DIR, filePath));
- broadcastChange('add', filePath);
- })
- .on('unlink', (filePath) => {
- console.log('ποΈ File removed:', path.relative(LAYOUTS_DIR, filePath));
- broadcastChange('unlink', filePath);
- })
- .on('addDir', (dirPath) => {
- console.log('π Directory added:', path.relative(LAYOUTS_DIR, dirPath));
- broadcastChange('addDir', dirPath);
- })
- .on('unlinkDir', (dirPath) => {
- console.log('π Directory removed:', path.relative(LAYOUTS_DIR, dirPath));
- broadcastChange('unlinkDir', dirPath);
- })
- .on('error', (error) => {
- console.error('β Watcher error:', error);
- })
- .on('ready', () => {
- console.log('β
Initial scan complete. Ready for changes.');
- console.log(`π Watching ${Object.keys(watcher.getWatched()).length} directories`);
- });
-
-// Graceful shutdown
-process.on('SIGINT', () => {
- console.log('\nπ Shutting down layout watcher...');
- watcher.close().then(() => {
- console.log('π File watcher closed');
- wss.close(() => {
- console.log('π WebSocket server closed');
- process.exit(0);
- });
- });
-});
-
-process.on('SIGTERM', () => {
- console.log('\nπ Received SIGTERM, shutting down...');
- watcher.close().then(() => {
- wss.close(() => {
- process.exit(0);
- });
- });
-});
-
-// Keep the process alive
-console.log('π Layout watcher is running. Press Ctrl+C to stop.');
\ No newline at end of file