From 5595157bb84fe733f5aaa85717f1d63922030599 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Thu, 27 Nov 2025 03:30:46 -0600 Subject: [PATCH] MAESTRO: Create separate Vite config for web interface build - Add vite.config.web.mts with dedicated web interface configuration - Configure build output to dist/web/ directory - Set up code splitting with React in separate chunk - Enable source maps for development debugging - Add dev server proxy for API and WebSocket connections - Create index.html entry point with mobile-optimized meta tags - Create main.tsx with device detection for mobile/desktop routing - Add index.css with Tailwind, CSS custom properties, and animations - Create placeholder mobile/desktop App components for Phase 1/2 - Update tailwind.config.mjs to include src/web files --- src/web/desktop/App.tsx | 151 ++++++++++++++++++++++ src/web/index.css | 279 ++++++++++++++++++++++++++++++++++++++++ src/web/index.html | 62 +++++++++ src/web/main.tsx | 140 ++++++++++++++++++++ src/web/mobile/App.tsx | 142 ++++++++++++++++++++ tailwind.config.mjs | 1 + vite.config.web.mts | 111 ++++++++++++++++ 7 files changed, 886 insertions(+) create mode 100644 src/web/desktop/App.tsx create mode 100644 src/web/index.css create mode 100644 src/web/index.html create mode 100644 src/web/main.tsx create mode 100644 src/web/mobile/App.tsx create mode 100644 vite.config.web.mts diff --git a/src/web/desktop/App.tsx b/src/web/desktop/App.tsx new file mode 100644 index 00000000..eaeffefe --- /dev/null +++ b/src/web/desktop/App.tsx @@ -0,0 +1,151 @@ +/** + * Maestro Desktop Web App + * + * Full-featured collaborative interface for hackathons and team coding. + * Provides real-time collaboration, full visibility, and shared editing. + * + * Phase 2 implementation will expand this component. + */ + +import React from 'react'; +import { useThemeColors } from '../components/ThemeProvider'; + +export default function DesktopApp() { + const colors = useThemeColors(); + + return ( +
+ {/* Header */} +
+

+ Maestro Web +

+
+
+ + Connecting... +
+
+
+ + {/* Main content */} +
+
+
+

+ Desktop Collaborative Interface +

+

+ Full-featured web interface for team collaboration during + hackathons and pair programming sessions. Share your AI coding + sessions with teammates in real-time. +

+
+ +
+ {[ + { title: 'Real-time Sync', desc: 'See changes instantly' }, + { title: 'Multi-user', desc: 'Collaborate together' }, + { title: 'Full History', desc: 'Complete session logs' }, + ].map(({ title, desc }) => ( +
+

+ {title} +

+

+ {desc} +

+
+ ))} +
+ +

+ This interface will be implemented in Phase 2. +
+ Make sure Maestro desktop app is running to connect. +

+
+
+
+ ); +} diff --git a/src/web/index.css b/src/web/index.css new file mode 100644 index 00000000..4898c23c --- /dev/null +++ b/src/web/index.css @@ -0,0 +1,279 @@ +/** + * Maestro Web Interface Styles + * + * Base styles and CSS custom properties for the web interface. + * Theme colors are injected via JavaScript from ThemeProvider. + */ + +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* CSS Custom Properties - Default values (overridden by theme) */ +:root { + /* Colors */ + --color-background: #1a1a2e; + --color-surface: #16213e; + --color-surface-elevated: #1f2e54; + --color-border: #2d3a5a; + --color-text-main: #eaeaea; + --color-text-muted: #8892b0; + --color-accent: #4a9eff; + --color-accent-hover: #3a8eef; + --color-success: #22c55e; + --color-warning: #f59e0b; + --color-error: #ef4444; + + /* Typography */ + --font-mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace; + + /* Spacing */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + + /* Border Radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-full: 9999px; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 200ms ease; + --transition-slow: 300ms ease; + + /* Z-Index layers */ + --z-dropdown: 100; + --z-sticky: 200; + --z-modal-backdrop: 300; + --z-modal: 400; + --z-toast: 500; +} + +/* Base styles */ +html { + font-size: 16px; + -webkit-text-size-adjust: 100%; +} + +body { + font-family: var(--font-mono); + background-color: var(--color-background); + color: var(--color-text-main); + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Selection styling */ +::selection { + background-color: var(--color-accent); + color: white; +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--color-surface); +} + +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} + +/* Focus styles */ +:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +/* Remove default focus outline (we use focus-visible) */ +:focus:not(:focus-visible) { + outline: none; +} + +/* Link styles */ +a { + color: var(--color-accent); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--color-accent-hover); +} + +/* Button reset */ +button { + font-family: inherit; + font-size: inherit; + cursor: pointer; + background: none; + border: none; + padding: 0; +} + +/* Input reset */ +input, +textarea, +select { + font-family: inherit; + font-size: inherit; + background: none; + border: none; +} + +/* Mobile-specific styles */ +@media (max-width: 767px) { + /* Prevent text size adjustment */ + html { + -webkit-text-size-adjust: none; + text-size-adjust: none; + } + + /* Larger tap targets */ + button, + a, + input, + select, + textarea { + min-height: 44px; + } + + /* Disable pull-to-refresh on body (we'll handle it ourselves) */ + body { + overscroll-behavior-y: contain; + } +} + +/* Safe area insets for iOS */ +@supports (padding-top: env(safe-area-inset-top)) { + .safe-area-top { + padding-top: env(safe-area-inset-top); + } + + .safe-area-bottom { + padding-bottom: env(safe-area-inset-bottom); + } + + .safe-area-left { + padding-left: env(safe-area-inset-left); + } + + .safe-area-right { + padding-right: env(safe-area-inset-right); + } +} + +/* Animation keyframes */ +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideDown { + from { + transform: translateY(-100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Utility classes */ +.animate-spin { + animation: spin 1s linear infinite; +} + +.animate-pulse { + animation: pulse 2s ease-in-out infinite; +} + +.animate-fadeIn { + animation: fadeIn var(--transition-normal); +} + +.animate-slideUp { + animation: slideUp var(--transition-normal); +} + +.animate-slideDown { + animation: slideDown var(--transition-normal); +} + +/* Hide scrollbar but keep functionality */ +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +/* Truncate text */ +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Line clamp */ +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/src/web/index.html b/src/web/index.html new file mode 100644 index 00000000..d0814a58 --- /dev/null +++ b/src/web/index.html @@ -0,0 +1,62 @@ + + + + + + + + + + Maestro Web + + + + + + +
+
+
+
+
+ + + diff --git a/src/web/main.tsx b/src/web/main.tsx new file mode 100644 index 00000000..4b8dbd1e --- /dev/null +++ b/src/web/main.tsx @@ -0,0 +1,140 @@ +/** + * Maestro Web Interface Entry Point + * + * This is the main entry point for the web interface. + * It detects the device type and renders the appropriate interface. + */ + +import React, { StrictMode, lazy, Suspense } from 'react'; +import { createRoot } from 'react-dom/client'; +import { ThemeProvider } from './components/ThemeProvider'; +import './index.css'; + +// Lazy load mobile and desktop apps for code splitting +// These will be created in Phase 1 and Phase 2 +const MobileApp = lazy(() => + import('./mobile/App').catch(() => ({ + default: () => , + })) +); + +const DesktopApp = lazy(() => + import('./desktop/App').catch(() => ({ + default: () => , + })) +); + +/** + * Detect if the device is mobile based on screen size and touch capability + */ +function isMobileDevice(): boolean { + // Check for touch capability + const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + + // Check screen width (768px is a common breakpoint) + const isSmallScreen = window.innerWidth < 768; + + // Check user agent for mobile indicators + const mobileUserAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ); + + // Consider mobile if small screen OR (has touch AND mobile user agent) + return isSmallScreen || (hasTouch && mobileUserAgent); +} + +/** + * Placeholder component shown while the actual app loads + * or if the app module hasn't been created yet + */ +function PlaceholderApp({ type }: { type: 'mobile' | 'desktop' }) { + return ( +
+

Maestro Web

+

+ {type === 'mobile' ? 'Mobile' : 'Desktop'} interface coming soon +

+

+ Connect to your Maestro desktop app to get started +

+
+ ); +} + +/** + * Loading fallback component + */ +function LoadingFallback() { + return ( +
+
+
+ ); +} + +/** + * Main App component that routes to mobile or desktop + */ +function App() { + const [isMobile, setIsMobile] = React.useState(isMobileDevice); + + // Re-check on resize (for responsive design testing) + React.useEffect(() => { + const handleResize = () => { + setIsMobile(isMobileDevice()); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return ( + + }> + {isMobile ? : } + + + ); +} + +// Mount the application +const container = document.getElementById('root'); +if (container) { + const root = createRoot(container); + root.render( + + + + ); +} else { + console.error('Root element not found'); +} diff --git a/src/web/mobile/App.tsx b/src/web/mobile/App.tsx new file mode 100644 index 00000000..17445d2c --- /dev/null +++ b/src/web/mobile/App.tsx @@ -0,0 +1,142 @@ +/** + * Maestro Mobile Web App + * + * Lightweight remote control interface for mobile devices. + * Focused on quick command input and session monitoring. + * + * Phase 1 implementation will expand this component. + */ + +import React from 'react'; +import { useThemeColors } from '../components/ThemeProvider'; + +export default function MobileApp() { + const colors = useThemeColors(); + + return ( +
+ {/* Header */} +
+

Maestro

+
+ + Connecting... +
+
+ + {/* Main content area */} +
+
+

+ Mobile Remote Control +

+

+ Send commands to your AI assistants from anywhere. This interface + will be implemented in Phase 1. +

+
+

+ Make sure Maestro desktop app is running +

+
+ + {/* Bottom input bar placeholder */} + +
+ ); +} diff --git a/tailwind.config.mjs b/tailwind.config.mjs index 2c0d72ef..d8415d58 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -2,6 +2,7 @@ export default { content: [ "./src/renderer/**/*.{js,ts,jsx,tsx}", + "./src/web/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { diff --git a/vite.config.web.mts b/vite.config.web.mts new file mode 100644 index 00000000..bbf76b92 --- /dev/null +++ b/vite.config.web.mts @@ -0,0 +1,111 @@ +/** + * Vite configuration for Maestro Web Interface + * + * This config builds the web interface (both mobile and desktop) + * as a standalone bundle that can be served by the Fastify server. + * + * Output: dist/web/ + */ + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; +import { readFileSync } from 'fs'; + +// Read version from package.json +const packageJson = JSON.parse( + readFileSync(path.join(__dirname, 'package.json'), 'utf-8') +); +const appVersion = process.env.VITE_APP_VERSION || packageJson.version; + +export default defineConfig({ + plugins: [react()], + + // Entry point for web interface + root: path.join(__dirname, 'src/web'), + + // Use relative paths for assets (served from Fastify) + base: './', + + define: { + __APP_VERSION__: JSON.stringify(appVersion), + }, + + resolve: { + alias: { + // Allow importing from renderer types/constants + '@renderer': path.join(__dirname, 'src/renderer'), + '@web': path.join(__dirname, 'src/web'), + '@shared': path.join(__dirname, 'src/shared'), + }, + }, + + build: { + outDir: path.join(__dirname, 'dist/web'), + emptyOutDir: true, + + // Generate source maps for debugging + sourcemap: true, + + rollupOptions: { + input: { + // Single entry point that handles routing to mobile/desktop + main: path.join(__dirname, 'src/web/index.html'), + }, + output: { + // Organize output by type + entryFileNames: 'assets/[name]-[hash].js', + chunkFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash].[ext]', + + // Manual chunking for better caching + manualChunks: { + // React core in its own chunk + react: ['react', 'react-dom'], + }, + }, + }, + + // Target modern browsers (web interface doesn't need legacy support) + target: 'es2020', + + // Minimize bundle size + minify: 'esbuild', + + // Report chunk sizes + reportCompressedSize: true, + }, + + // Development server (for testing web interface standalone) + server: { + port: 5174, // Different from renderer dev server (5173) + strictPort: true, + // Proxy API calls to the running Maestro app during development + proxy: { + '/api': { + target: 'http://localhost:45678', + changeOrigin: true, + }, + '/ws': { + target: 'ws://localhost:45678', + ws: true, + }, + }, + }, + + // Preview server for testing production build + preview: { + port: 5175, + strictPort: true, + }, + + // Enable CSS code splitting + css: { + devSourcemap: true, + }, + + // Optimize dependencies + optimizeDeps: { + include: ['react', 'react-dom'], + }, +});