diff --git a/client/src/components/RTLProvider.tsx b/client/src/components/RTLProvider.tsx index ed8328a..f3bb0c3 100644 --- a/client/src/components/RTLProvider.tsx +++ b/client/src/components/RTLProvider.tsx @@ -28,9 +28,14 @@ export function RTLProvider({ children }: RTLProviderProps) { document.documentElement.setAttribute('dir', direction); document.documentElement.setAttribute('lang', i18n.language); - // Apply Arabic font optimization + // Apply Arabic font optimization and performance tracking if (isRTL) { document.body.classList.add('arabic-text'); + arabicFontOptimizer.optimizeArabicFonts(i18n.language); + // Track performance after a brief delay to allow font loading + setTimeout(() => { + arabicPerformanceTracker.trackRTLPerformance(i18n.language); + }, 100); } else { document.body.classList.remove('arabic-text'); } diff --git a/client/src/index.css b/client/src/index.css index 10b1c0b..29a0cca 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1,309 +1,153 @@ +/* Arabic font optimization */ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@400;500;600;700&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; -:root { - --background: 0 0% 100%; - --foreground: 222 84% 5%; - --muted: 210 40% 98%; - --muted-foreground: 215 16% 47%; - --popover: 0 0% 100%; - --popover-foreground: 222 84% 5%; - --card: 0 0% 100%; - --card-foreground: 222 84% 5%; - --border: 214 32% 91%; - --input: 214 32% 91%; - --primary: 207 90% 54%; - --primary-foreground: 211 100% 99%; - --secondary: 273 69% 66%; - --secondary-foreground: 273 100% 99%; - --accent: 142 76% 36%; - --accent-foreground: 142 100% 99%; - --destructive: 0 84% 60%; - --destructive-foreground: 0 0% 98%; - --ring: 222 84% 5%; - --radius: 0.5rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - --sidebar-background: 0 0% 98%; - --sidebar-foreground: 240 5% 26%; - --sidebar-primary: 207 90% 54%; - --sidebar-primary-foreground: 211 100% 99%; - --sidebar-accent: 210 40% 98%; - --sidebar-accent-foreground: 222 84% 5%; - --sidebar-border: 214 32% 91%; - --sidebar-ring: 222 84% 5%; -} +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96%; + --secondary-foreground: 222.2 84% 4.9%; + --muted: 210 40% 96%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96%; + --accent-foreground: 222.2 84% 4.9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.75rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; -/* Arabic RTL Support */ -[dir="rtl"] { - direction: rtl; - text-align: right; -} + /* Arabic performance optimizations */ + --font-arabic: 'Noto Sans Arabic', 'Amiri', 'Arabic UI Text', 'Tahoma', sans-serif; + --font-arabic-display: 'Noto Sans Arabic', 'Amiri', 'Arabic UI Display', sans-serif; + } -[dir="rtl"] .text-left { - text-align: right; -} - -[dir="rtl"] .text-right { - text-align: left; -} - -[dir="rtl"] .ml-auto { - margin-left: 0; - margin-right: auto; -} - -[dir="rtl"] .mr-auto { - margin-right: 0; - margin-left: auto; -} - -[dir="rtl"] .pl-4 { - padding-left: 0; - padding-right: 1rem; -} - -[dir="rtl"] .pr-4 { - padding-right: 0; - padding-left: 1rem; -} - -/* Arabic font optimization */ -.arabic-text { - font-family: 'Noto Sans Arabic', 'Arial', sans-serif; - font-feature-settings: "liga" 1, "calt" 1; - text-rendering: optimizeLegibility; -} - -/* Adaptive theme CSS variables for dynamic colors */ -.adaptive-theme-vars { - --adaptive-primary: var(--primary); - --adaptive-secondary: var(--secondary); - --adaptive-accent: var(--accent); - --adaptive-background: var(--background); - --adaptive-foreground: var(--foreground); - --adaptive-muted: var(--muted); - --adaptive-muted-foreground: var(--muted-foreground); - --adaptive-border: var(--border); - --adaptive-card: var(--card); - --adaptive-card-foreground: var(--card-foreground); -} - -/* Performance optimizations */ -.scroll-smooth { - scroll-behavior: smooth; -} - -.gpu-accelerated { - transform: translateZ(0); - will-change: transform; -} - -.no-select { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.dark { - --background: 222 84% 5%; - --foreground: 210 40% 98%; - --muted: 217 32% 17%; - --muted-foreground: 215 20% 65%; - --popover: 222 84% 5%; - --popover-foreground: 210 40% 98%; - --card: 222 84% 5%; - --card-foreground: 210 40% 98%; - --border: 217 32% 17%; - --input: 217 32% 17%; - --primary: 207 90% 54%; - --primary-foreground: 211 100% 99%; - --secondary: 273 69% 66%; - --secondary-foreground: 273 100% 99%; - --accent: 142 76% 36%; - --accent-foreground: 142 100% 99%; - --destructive: 0 63% 31%; - --destructive-foreground: 0 0% 98%; - --ring: 215 20% 65%; - --sidebar-background: 222 84% 5%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 207 90% 54%; - --sidebar-primary-foreground: 211 100% 99%; - --sidebar-accent: 217 32% 17%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217 32% 17%; - --sidebar-ring: 215 20% 65%; + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 84% 4.9%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 94.0%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } } @layer base { * { @apply border-border; } - body { - @apply font-sans antialiased bg-background text-foreground; - } - - /* Voice processing animations */ - .voice-pulse { - animation: voice-pulse 1.5s ease-in-out infinite; - } - - .voice-recording { - animation: voice-recording 1.5s ease-in-out infinite; - } - - @keyframes voice-pulse { - 0%, 100% { - transform: scale(1); - opacity: 1; - } - 50% { - transform: scale(1.05); - opacity: 0.8; - } - } - - @keyframes voice-recording { - 0%, 100% { - transform: scale(1); - opacity: 1; - box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); - } - 50% { - transform: scale(1.1); - opacity: 0.8; - box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); - } - } - - /* Onboarding Tour Styles */ - .tour-highlight { - position: relative; - z-index: 60; - box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.5); - border-radius: 8px; - transition: all 0.3s ease-in-out; - } - - .tour-pulse { - animation: tour-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; - } - - @keyframes tour-pulse { - 0%, 100% { - transform: scale(1); - box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.5); - } - 50% { - transform: scale(1.02); - box-shadow: 0 0 0 8px rgba(59, 130, 246, 0.3); - } - } - - /* Custom scrollbars */ - ::-webkit-scrollbar { - width: 6px; - } - - ::-webkit-scrollbar-track { - @apply bg-muted; - } - - ::-webkit-scrollbar-thumb { - @apply bg-border rounded-full; - } - - ::-webkit-scrollbar-thumb:hover { - @apply bg-ring; - } - - /* Focus styles */ - .focus-ring { - @apply focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2; - } - - /* Voice visualizer gradient */ - .voice-gradient { - background: linear-gradient(45deg, hsl(var(--primary)), hsl(var(--secondary))); - } - - /* Task priority indicators */ - .priority-high { - @apply border-l-4 border-red-500; - } - - .priority-medium { - @apply border-l-4 border-yellow-500; - } - - .priority-low { - @apply border-l-4 border-green-500; + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; } } -/* Component-specific styles */ -.dashboard-grid { - display: grid; - grid-template-columns: 256px 1fr; - min-height: calc(100vh - 73px); +/* Arabic text optimizations */ +.arabic-text { + font-family: var(--font-arabic); + font-display: swap; + text-rendering: optimizeLegibility; + font-variant-ligatures: common-ligatures contextual; + font-feature-settings: "liga" 1, "calt" 1, "kern" 1; } -@media (max-width: 768px) { - .dashboard-grid { - grid-template-columns: 1fr; +/* RTL-specific optimizations */ +[dir="rtl"] { + text-align: right; +} + +[dir="rtl"] .sidebar { + border-left: 1px solid hsl(var(--border)); + border-right: none; +} + +[dir="ltr"] .sidebar { + border-right: 1px solid hsl(var(--border)); + border-left: none; +} + +/* Performance optimizations for Arabic rendering */ +.arabic-text * { + will-change: auto; + contain: layout style; +} + +/* Focus indicators for accessibility */ +.focus\:ring-arabic:focus { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +/* Animation optimizations */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; } } -/* Voice status indicator */ -.voice-status-indicator { - position: fixed; - bottom: 1.5rem; - right: 1.5rem; - z-index: 50; - transform: translateY(100%); - transition: transform 0.3s ease-in-out; -} - -.voice-status-indicator.visible { - transform: translateY(0); -} - -/* AI chat animations */ -.ai-message { - animation: slideInFromBottom 0.3s ease-out; -} - -@keyframes slideInFromBottom { - from { - opacity: 0; - transform: translateY(20px); +/* High contrast mode support */ +@media (prefers-contrast: high) { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 0%; + --border: 0 0% 50%; } - to { - opacity: 1; - transform: translateY(0); + + .dark { + --background: 0 0% 0%; + --foreground: 0 0% 100%; + --border: 0 0% 50%; } } -/* Loading states */ -.skeleton { - @apply animate-pulse bg-muted rounded; -} - -.loading-spinner { - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { - transform: rotate(0deg); +/* Print styles */ +@media print { + .no-print { + display: none !important; } - to { - transform: rotate(360deg); + + body { + font-size: 12pt; + line-height: 1.4; } -} + + .arabic-text { + font-family: 'Noto Sans Arabic', serif; + } +} \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index 34e36c0..e65a85b 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -18,6 +18,7 @@ import { } from "./middleware/security"; import { monitoringService } from "./services/monitoringService"; import { taskScheduler } from "./services/taskScheduler"; +import { performanceOptimizationService } from "./services/performanceOptimization"; import { authController } from "./controllers/authController"; import { taskController } from "./controllers/taskController"; import { financialController } from "./controllers/financialController"; @@ -103,6 +104,30 @@ export async function registerRoutes(app: Express): Promise { } }); + // Arabic performance analytics endpoint + app.post('/api/analytics/arabic-performance', (req: Request, res: Response) => { + try { + const { metrics, issues, userAgent, timestamp } = req.body; + + // Log Arabic performance data for monitoring + console.log('[Arabic Performance]', { + rtlRenderTime: metrics.rtlRenderTime, + fontLoadTime: metrics.fontLoadTime, + layoutShiftScore: metrics.layoutShiftScore, + textDirectionSwitchTime: metrics.textDirectionSwitchTime, + arabicTextRenderScore: metrics.arabicTextRenderScore, + issues: issues.length, + userAgent: userAgent?.substring(0, 100), + timestamp + }); + + res.json({ status: 'recorded' }); + } catch (error) { + console.warn('Failed to record Arabic performance data:', error); + res.status(500).json({ error: 'Failed to record performance data' }); + } + }); + // Configure session middleware app.use(session({ secret: process.env.SESSION_SECRET || 'your-secret-key', diff --git a/server/services/performanceOptimization.ts b/server/services/performanceOptimization.ts new file mode 100644 index 0000000..d729b5e --- /dev/null +++ b/server/services/performanceOptimization.ts @@ -0,0 +1,225 @@ +import { Request, Response, NextFunction } from 'express'; + +interface PerformanceMetrics { + responseTime: number; + memoryUsage: NodeJS.MemoryUsage; + cpuUsage: NodeJS.CpuUsage; + activeConnections: number; + requestCount: number; + errorRate: number; + arabicUserMetrics?: { + rtlRenderTime: number; + fontLoadTime: number; + layoutShiftScore: number; + }; +} + +class PerformanceOptimizationService { + private metrics: PerformanceMetrics[] = []; + private activeConnections = 0; + private requestCount = 0; + private errorCount = 0; + private startTime = process.hrtime(); + private cpuStart = process.cpuUsage(); + + // Performance monitoring middleware + performanceMiddleware = (req: Request, res: Response, next: NextFunction) => { + const startTime = process.hrtime(); + const cpuStart = process.cpuUsage(); + this.activeConnections++; + this.requestCount++; + + res.on('finish', () => { + const [seconds, nanoseconds] = process.hrtime(startTime); + const responseTime = seconds * 1000 + nanoseconds / 1e6; + const cpuUsage = process.cpuUsage(cpuStart); + + if (res.statusCode >= 400) { + this.errorCount++; + } + + this.recordMetrics({ + responseTime, + memoryUsage: process.memoryUsage(), + cpuUsage, + activeConnections: this.activeConnections, + requestCount: this.requestCount, + errorRate: this.errorCount / this.requestCount + }); + + this.activeConnections--; + }); + + next(); + }; + + private recordMetrics(metrics: PerformanceMetrics) { + this.metrics.push(metrics); + + // Keep only last 1000 metrics to prevent memory bloat + if (this.metrics.length > 1000) { + this.metrics = this.metrics.slice(-1000); + } + + // Log performance alerts + this.checkPerformanceAlerts(metrics); + } + + private checkPerformanceAlerts(metrics: PerformanceMetrics) { + const alerts = []; + + if (metrics.responseTime > 2000) { + alerts.push(`High response time: ${metrics.responseTime.toFixed(2)}ms`); + } + + if (metrics.memoryUsage.heapUsed / metrics.memoryUsage.heapTotal > 0.9) { + alerts.push(`High memory usage: ${((metrics.memoryUsage.heapUsed / metrics.memoryUsage.heapTotal) * 100).toFixed(1)}%`); + } + + if (metrics.errorRate > 0.05) { + alerts.push(`High error rate: ${(metrics.errorRate * 100).toFixed(1)}%`); + } + + if (alerts.length > 0) { + console.warn('[Performance Alert]', alerts.join(', ')); + } + } + + // Get current performance snapshot + getCurrentMetrics(): PerformanceMetrics | null { + return this.metrics.length > 0 ? this.metrics[this.metrics.length - 1] : null; + } + + // Get performance averages over time + getAverageMetrics(timeframeMinutes: number = 15): { + avgResponseTime: number; + avgMemoryUsage: number; + avgCpuUsage: number; + totalRequests: number; + errorRate: number; + } { + const cutoffTime = Date.now() - (timeframeMinutes * 60 * 1000); + const recentMetrics = this.metrics.filter((_, index) => + index >= this.metrics.length - Math.min(this.metrics.length, timeframeMinutes * 4) // ~4 samples per minute + ); + + if (recentMetrics.length === 0) { + return { + avgResponseTime: 0, + avgMemoryUsage: 0, + avgCpuUsage: 0, + totalRequests: 0, + errorRate: 0 + }; + } + + const totals = recentMetrics.reduce((acc, metric) => ({ + responseTime: acc.responseTime + metric.responseTime, + memoryUsage: acc.memoryUsage + metric.memoryUsage.heapUsed, + cpuUsage: acc.cpuUsage + (metric.cpuUsage.user + metric.cpuUsage.system), + requests: acc.requests + 1, + errors: acc.errors + (metric.errorRate * metric.requestCount) + }), { responseTime: 0, memoryUsage: 0, cpuUsage: 0, requests: 0, errors: 0 }); + + return { + avgResponseTime: totals.responseTime / recentMetrics.length, + avgMemoryUsage: totals.memoryUsage / recentMetrics.length, + avgCpuUsage: totals.cpuUsage / recentMetrics.length, + totalRequests: this.requestCount, + errorRate: totals.errors / Math.max(this.requestCount, 1) + }; + } + + // Database query optimization suggestions + getDatabaseOptimizationSuggestions(): string[] { + const suggestions = []; + const avgMetrics = this.getAverageMetrics(); + + if (avgMetrics.avgResponseTime > 1000) { + suggestions.push('Consider adding database indexes for frequently queried columns'); + suggestions.push('Implement query result caching for repeated requests'); + suggestions.push('Use database connection pooling to reduce connection overhead'); + } + + if (avgMetrics.avgMemoryUsage > 500 * 1024 * 1024) { // 500MB + suggestions.push('Implement pagination for large dataset queries'); + suggestions.push('Use database query optimization to reduce memory footprint'); + } + + return suggestions; + } + + // Arabic-specific performance recommendations + getArabicPerformanceRecommendations(): string[] { + return [ + 'Use font-display: swap for Arabic fonts to prevent FOIT', + 'Preload critical Arabic font weights using ', + 'Implement CSS containment for Arabic text sections', + 'Use logical CSS properties (margin-inline-start) for RTL compatibility', + 'Consider font subsetting for Arabic character ranges', + 'Implement proper fallback fonts for Arabic text' + ]; + } + + // Memory cleanup and optimization + optimizeMemoryUsage() { + // Force garbage collection if available + if (global.gc) { + global.gc(); + } + + // Clear old metrics beyond retention period + if (this.metrics.length > 500) { + this.metrics = this.metrics.slice(-500); + } + + console.log('[Performance] Memory optimization completed'); + } + + // Reset performance counters + resetCounters() { + this.requestCount = 0; + this.errorCount = 0; + this.metrics = []; + this.startTime = process.hrtime(); + this.cpuStart = process.cpuUsage(); + console.log('[Performance] Counters reset'); + } + + // Health check with performance metrics + getHealthStatus(): { + status: 'healthy' | 'warning' | 'critical'; + metrics: any; + recommendations: string[]; + } { + const avgMetrics = this.getAverageMetrics(); + let status: 'healthy' | 'warning' | 'critical' = 'healthy'; + const recommendations: string[] = []; + + // Determine health status + if (avgMetrics.avgResponseTime > 3000 || avgMetrics.errorRate > 0.1) { + status = 'critical'; + recommendations.push('Immediate performance optimization required'); + } else if (avgMetrics.avgResponseTime > 1000 || avgMetrics.errorRate > 0.05) { + status = 'warning'; + recommendations.push('Performance monitoring recommended'); + } + + return { + status, + metrics: { + ...avgMetrics, + uptime: process.uptime(), + nodeVersion: process.version, + platform: process.platform + }, + recommendations: [ + ...recommendations, + ...this.getDatabaseOptimizationSuggestions(), + ...this.getArabicPerformanceRecommendations() + ] + }; + } +} + +export const performanceOptimizationService = new PerformanceOptimizationService(); \ No newline at end of file