diff --git a/ios/VibeTunnel/App/VibeTunnelApp.swift b/ios/VibeTunnel/App/VibeTunnelApp.swift index e876ec80..86181be0 100644 --- a/ios/VibeTunnel/App/VibeTunnelApp.swift +++ b/ios/VibeTunnel/App/VibeTunnelApp.swift @@ -33,20 +33,20 @@ struct VibeTunnelApp: App { #endif } } - + private var colorScheme: ColorScheme? { switch colorSchemePreferenceRaw { case "light": return .light case "dark": return .dark - default: return nil // System default + default: return nil } } #if targetEnvironment(macCatalyst) - private func getStoredWindowStyle() -> MacWindowStyle { - let styleRaw = UserDefaults.standard.string(forKey: "macWindowStyle") ?? "standard" - return styleRaw == "inline" ? .inline : .standard - } + private func getStoredWindowStyle() -> MacWindowStyle { + let styleRaw = UserDefaults.standard.string(forKey: "macWindowStyle") ?? "standard" + return styleRaw == "inline" ? .inline : .standard + } #endif private func handleURL(_ url: URL) { diff --git a/ios/VibeTunnel/Models/TerminalData.swift b/ios/VibeTunnel/Models/TerminalData.swift index caf5f8d7..287905ef 100644 --- a/ios/VibeTunnel/Models/TerminalData.swift +++ b/ios/VibeTunnel/Models/TerminalData.swift @@ -131,7 +131,7 @@ struct TerminalInput: Codable { case ctrlE = "\u{0005}" // MARK: - Function Keys - + /// F1 key. case f1 = "\u{001B}OP" /// F2 key. @@ -156,9 +156,9 @@ struct TerminalInput: Codable { case f11 = "\u{001B}[23~" /// F12 key. case f12 = "\u{001B}[24~" - + // MARK: - Additional Special Characters - + /// Backslash character. case backslash = "\\" /// Pipe character. diff --git a/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift b/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift index a9234b57..d0290d65 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift @@ -161,7 +161,7 @@ struct TerminalToolbar: View { } } } - + Spacer() } @@ -181,7 +181,7 @@ struct TerminalToolbar: View { } } } - + Spacer() } @@ -212,7 +212,7 @@ struct TerminalToolbar: View { // Send Ctrl+E for end onSpecialKey(.ctrlE) } - + Spacer() } diff --git a/ios/VibeTunnel/version.xcconfig b/ios/VibeTunnel/version.xcconfig index 1029147b..1fa4734b 100644 --- a/ios/VibeTunnel/version.xcconfig +++ b/ios/VibeTunnel/version.xcconfig @@ -1,8 +1,8 @@ // VibeTunnel Version Configuration // This file contains the version and build number for the app -MARKETING_VERSION = 1.0.0-beta.4 -CURRENT_PROJECT_VERSION = 109 +MARKETING_VERSION = 1.0.0-beta.6 +CURRENT_PROJECT_VERSION = 152 // Domain and GitHub configuration APP_DOMAIN = vibetunnel.sh diff --git a/mac/VibeTunnel/version.xcconfig b/mac/VibeTunnel/version.xcconfig index 0a39b26e..13d1cb41 100644 --- a/mac/VibeTunnel/version.xcconfig +++ b/mac/VibeTunnel/version.xcconfig @@ -2,7 +2,7 @@ // This file contains the version and build number for the app MARKETING_VERSION = 1.0.0-beta.6 -CURRENT_PROJECT_VERSION = 151 +CURRENT_PROJECT_VERSION = 152 // Domain and GitHub configuration APP_DOMAIN = vibetunnel.sh diff --git a/web/src/server/utils/prompt-patterns.ts b/web/src/server/utils/prompt-patterns.ts index 6eb66d14..586d2553 100644 --- a/web/src/server/utils/prompt-patterns.ts +++ b/web/src/server/utils/prompt-patterns.ts @@ -59,18 +59,18 @@ const SHELL_SPECIFIC_PATTERNS = { withEscape: /[$>#%❯➜]\s*\x1b\[/, }; -export class PromptDetector { +export namespace PromptDetector { // Cache for regex test results to avoid repeated tests - private static endPromptCache = new Map(); - private static onlyPromptCache = new Map(); - private static cacheSize = 0; - private static readonly MAX_CACHE_SIZE = 1000; + const endPromptCache = new Map(); + const onlyPromptCache = new Map(); + let cacheSize = 0; + const MAX_CACHE_SIZE = 1000; /** * Check if the entire output is just a prompt (no other content) * Used by activity detector to determine if output is meaningful */ - static isPromptOnly(data: string): boolean { + export function isPromptOnly(data: string): boolean { // Input validation if (data.length > 10000) { logger.warn('Unusually long prompt input detected', { length: data.length }); @@ -80,14 +80,15 @@ export class PromptDetector { const trimmed = data.trim(); // Check cache first - if (PromptDetector.onlyPromptCache.has(trimmed)) { - return PromptDetector.onlyPromptCache.get(trimmed) ?? false; + if (onlyPromptCache.has(trimmed)) { + const cachedResult = onlyPromptCache.get(trimmed); + return cachedResult ?? false; } const result = PROMPT_ONLY_REGEX.test(trimmed); // Cache result - PromptDetector.cacheResult(PromptDetector.onlyPromptCache, trimmed, result); + cacheResult(onlyPromptCache, trimmed, result); return result; } @@ -96,14 +97,15 @@ export class PromptDetector { * Check if output ends with a prompt (for title injection) * This is used to determine when to inject terminal title sequences */ - static endsWithPrompt(data: string): boolean { + export function endsWithPrompt(data: string): boolean { // For title injection, we need to check the last part of the output // Use last 100 chars as cache key to balance cache efficiency and accuracy const cacheKey = data.slice(-100); // Check cache first - if (PromptDetector.endPromptCache.has(cacheKey)) { - return PromptDetector.endPromptCache.get(cacheKey) ?? false; + if (endPromptCache.has(cacheKey)) { + const cachedResult = endPromptCache.get(cacheKey); + return cachedResult ?? false; } // Strip ANSI codes for more reliable detection @@ -111,7 +113,7 @@ export class PromptDetector { const result = UNIFIED_PROMPT_END_REGEX.test(cleanData); // Cache result - PromptDetector.cacheResult(PromptDetector.endPromptCache, cacheKey, result); + cacheResult(endPromptCache, cacheKey, result); if (result) { logger.debug('Detected prompt at end of output'); @@ -124,7 +126,7 @@ export class PromptDetector { * Get specific shell type based on prompt pattern * This can be used for shell-specific optimizations in the future */ - static getShellType(data: string): keyof typeof SHELL_SPECIFIC_PATTERNS | null { + export function getShellType(data: string): keyof typeof SHELL_SPECIFIC_PATTERNS | null { const trimmed = data.trim(); // Check each shell pattern @@ -140,53 +142,53 @@ export class PromptDetector { /** * Helper to cache results with size limit */ - private static cacheResult(cache: Map, key: string, value: boolean): void { - if (PromptDetector.cacheSize >= PromptDetector.MAX_CACHE_SIZE) { + function cacheResult(cache: Map, key: string, value: boolean): void { + if (cacheSize >= MAX_CACHE_SIZE) { // Clear oldest entries when cache is full - const entriesToDelete = Math.floor(PromptDetector.MAX_CACHE_SIZE * 0.2); // Clear 20% + const entriesToDelete = Math.floor(MAX_CACHE_SIZE * 0.2); // Clear 20% const iterator = cache.keys(); for (let i = 0; i < entriesToDelete; i++) { const keyToDelete = iterator.next().value; if (keyToDelete) { cache.delete(keyToDelete); - PromptDetector.cacheSize--; + cacheSize--; } } } cache.set(key, value); - PromptDetector.cacheSize++; + cacheSize++; } /** * Clear all caches (useful for tests or memory management) */ - static clearCache(): void { - PromptDetector.endPromptCache.clear(); - PromptDetector.onlyPromptCache.clear(); - PromptDetector.cacheSize = 0; + export function clearCache(): void { + endPromptCache.clear(); + onlyPromptCache.clear(); + cacheSize = 0; logger.debug('Prompt pattern caches cleared'); } /** * Get cache statistics for monitoring */ - static getCacheStats(): { + export function getCacheStats(): { size: number; maxSize: number; hitRate: { end: number; only: number }; } { return { - size: PromptDetector.cacheSize, - maxSize: PromptDetector.MAX_CACHE_SIZE, + size: cacheSize, + maxSize: MAX_CACHE_SIZE, hitRate: { - end: PromptDetector.endPromptCache.size, - only: PromptDetector.onlyPromptCache.size, + end: endPromptCache.size, + only: onlyPromptCache.size, }, }; } } // Export for backward compatibility -export const isPromptOnly = PromptDetector.isPromptOnly.bind(PromptDetector); -export const endsWithPrompt = PromptDetector.endsWithPrompt.bind(PromptDetector); +export const isPromptOnly = PromptDetector.isPromptOnly; +export const endsWithPrompt = PromptDetector.endsWithPrompt; diff --git a/web/src/server/version.ts b/web/src/server/version.ts index 96da56c0..0802a0db 100644 --- a/web/src/server/version.ts +++ b/web/src/server/version.ts @@ -2,11 +2,12 @@ // This file is updated during the build process import chalk from 'chalk'; +import packageJson from '../../package.json'; import { createLogger } from './utils/logger.js'; const logger = createLogger('version'); -export const VERSION = '1.0.0-beta.3'; +export const VERSION = packageJson.version; // BUILD_DATE will be replaced by build script, fallback to current time in dev export const BUILD_DATE = process.env.BUILD_DATE || new Date().toISOString(); export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP || Date.now(); diff --git a/web/src/test/playwright/helpers/test-data-manager.helper.ts b/web/src/test/playwright/helpers/test-data-manager.helper.ts index e6841e67..c77fd388 100644 --- a/web/src/test/playwright/helpers/test-data-manager.helper.ts +++ b/web/src/test/playwright/helpers/test-data-manager.helper.ts @@ -178,31 +178,31 @@ export class TestSessionManager { /** * Creates test data factory for consistent test data generation */ -export class TestDataFactory { - private static counters: Map = new Map(); +export namespace TestDataFactory { + const counters: Map = new Map(); /** * Generates sequential IDs for a given prefix */ - static sequentialId(prefix: string): string { - const current = TestDataFactory.counters.get(prefix) || 0; - TestDataFactory.counters.set(prefix, current + 1); + export function sequentialId(prefix: string): string { + const current = counters.get(prefix) || 0; + counters.set(prefix, current + 1); return `${prefix}-${current + 1}`; } /** * Generates session name with optional prefix */ - static sessionName(prefix = 'session'): string { + export function sessionName(prefix = 'session'): string { const timestamp = new Date().toISOString().slice(11, 19).replace(/:/g, ''); - const counter = TestDataFactory.sequentialId(prefix); + const counter = sequentialId(prefix); return `${counter}-${timestamp}`; } /** * Generates command for testing */ - static command(type: 'echo' | 'sleep' | 'env' | 'file' = 'echo'): string { + export function command(type: 'echo' | 'sleep' | 'env' | 'file' = 'echo'): string { switch (type) { case 'echo': return `echo "Test output ${Date.now()}"`; @@ -218,8 +218,8 @@ export class TestDataFactory { /** * Reset all counters */ - static reset(): void { - TestDataFactory.counters.clear(); + export function reset(): void { + counters.clear(); } } diff --git a/web/src/test/playwright/specs/debug-session.spec.ts b/web/src/test/playwright/specs/debug-session.spec.ts index 6c327b66..f43fa7f5 100644 --- a/web/src/test/playwright/specs/debug-session.spec.ts +++ b/web/src/test/playwright/specs/debug-session.spec.ts @@ -98,7 +98,9 @@ test.describe('Debug Session Tests', () => { // Check the app component's state const appHideExited = await page.evaluate(() => { - const app = document.querySelector('vibetunnel-app') as any; + const app = document.querySelector('vibetunnel-app') as HTMLElement & { + hideExited?: boolean; + }; return app?.hideExited; }); console.log('App component hideExited:', appHideExited); @@ -140,7 +142,7 @@ test.describe('Debug Session Tests', () => { // Check if all sessions are exited const exitedCount = sessionsResponse.sessions.filter( - (s: any) => s.status === 'exited' + (s: { status: string }) => s.status === 'exited' ).length; console.log(`Exited sessions: ${exitedCount} out of ${sessionsResponse.count}`); } diff --git a/web/src/test/playwright/specs/terminal-interaction.spec.ts b/web/src/test/playwright/specs/terminal-interaction.spec.ts index 48b53331..216c7dea 100644 --- a/web/src/test/playwright/specs/terminal-interaction.spec.ts +++ b/web/src/test/playwright/specs/terminal-interaction.spec.ts @@ -44,10 +44,7 @@ test.describe.skip('Terminal Interaction', () => { test('should execute multiple commands in sequence', async ({ page }) => { // Execute sequence with expected outputs - await executeCommandSequence(page, [ - { command: 'echo "Test 1"', expectedOutput: 'Test 1' }, - { command: 'echo "Test 2"', expectedOutput: 'Test 2', waitBetween: 500 }, - ]); + await executeCommandSequence(page, ['echo "Test 1"', 'echo "Test 2"']); // Both outputs should be visible await assertTerminalContains(page, 'Test 1'); @@ -56,9 +53,7 @@ test.describe.skip('Terminal Interaction', () => { test('should handle long-running commands', async ({ page }) => { // Execute and wait for completion - await executeAndVerifyCommand(page, 'sleep 1 && echo "Done sleeping"', 'Done sleeping', { - timeout: 3000, - }); + await executeAndVerifyCommand(page, 'sleep 1 && echo "Done sleeping"', 'Done sleeping'); }); test('should handle command interruption', async ({ page }) => { @@ -134,7 +129,12 @@ test.describe.skip('Terminal Interaction', () => { test('should handle terminal resize', async ({ page }) => { // Get initial terminal dimensions const initialDimensions = await page.evaluate(() => { - const terminal = document.querySelector('vibe-terminal') as any; + const terminal = document.querySelector('vibe-terminal') as HTMLElement & { + cols?: number; + rows?: number; + actualCols?: number; + actualRows?: number; + }; return { cols: terminal?.cols || 80, rows: terminal?.rows || 24, @@ -159,7 +159,12 @@ test.describe.skip('Terminal Interaction', () => { // Wait for terminal-resize event or dimension change await page.waitForFunction( ({ initial }) => { - const terminal = document.querySelector('vibe-terminal') as any; + const terminal = document.querySelector('vibe-terminal') as HTMLElement & { + cols?: number; + rows?: number; + actualCols?: number; + actualRows?: number; + }; const currentCols = terminal?.cols || 80; const currentRows = terminal?.rows || 24; const currentActualCols = terminal?.actualCols || currentCols; @@ -179,7 +184,12 @@ test.describe.skip('Terminal Interaction', () => { // Verify terminal dimensions changed const newDimensions = await page.evaluate(() => { - const terminal = document.querySelector('vibe-terminal') as any; + const terminal = document.querySelector('vibe-terminal') as HTMLElement & { + cols?: number; + rows?: number; + actualCols?: number; + actualRows?: number; + }; return { cols: terminal?.cols || 80, rows: terminal?.rows || 24, diff --git a/web/src/test/playwright/utils/optimized-wait.utils.ts b/web/src/test/playwright/utils/optimized-wait.utils.ts index c297580b..1b341b08 100644 --- a/web/src/test/playwright/utils/optimized-wait.utils.ts +++ b/web/src/test/playwright/utils/optimized-wait.utils.ts @@ -5,36 +5,34 @@ import { logger } from './logger'; /** * Optimized wait utilities with reduced timeouts and smarter strategies */ -export class OptimizedWaitUtils { +export namespace OptimizedWaitUtils { // Reduced default timeouts for faster test execution - private static readonly QUICK_TIMEOUT = TEST_TIMEOUTS.QUICK; - private static readonly DEFAULT_TIMEOUT = TEST_TIMEOUTS.DEFAULT; - private static readonly LONG_TIMEOUT = TEST_TIMEOUTS.LONG; - private static readonly logger = logger; + const QUICK_TIMEOUT = TEST_TIMEOUTS.QUICK; + const DEFAULT_TIMEOUT = TEST_TIMEOUTS.DEFAULT; /** * Wait for app initialization with optimized checks */ - static async waitForAppReady(page: Page): Promise { + export async function waitForAppReady(page: Page): Promise { // Wait for app element to attach (reduced timeout) await page.waitForSelector('vibetunnel-app', { state: 'attached', - timeout: OptimizedWaitUtils.DEFAULT_TIMEOUT, + timeout: DEFAULT_TIMEOUT, }); // Use Promise.allSettled for better reliability const results = await Promise.allSettled([ page.waitForSelector('button[title="Create New Session"]', { state: 'visible', - timeout: OptimizedWaitUtils.QUICK_TIMEOUT, + timeout: QUICK_TIMEOUT, }), page.waitForSelector('session-card', { state: 'visible', - timeout: OptimizedWaitUtils.QUICK_TIMEOUT, + timeout: QUICK_TIMEOUT, }), page.waitForSelector('auth-login', { state: 'visible', - timeout: OptimizedWaitUtils.QUICK_TIMEOUT, + timeout: QUICK_TIMEOUT, }), ]); @@ -52,7 +50,7 @@ export class OptimizedWaitUtils { /** * Wait for session card with specific name */ - static async waitForSessionCard( + export async function waitForSessionCard( page: Page, sessionName: string, timeout = 3000 @@ -65,7 +63,7 @@ export class OptimizedWaitUtils { /** * Wait for terminal to be ready (optimized) */ - static async waitForTerminalReady(page: Page, timeout = 3000): Promise { + export async function waitForTerminalReady(page: Page, timeout = 3000): Promise { // Wait for xterm element await page.waitForSelector('.xterm', { state: 'visible', timeout }); @@ -84,7 +82,7 @@ export class OptimizedWaitUtils { /** * Wait for session state change */ - static async waitForSessionState( + export async function waitForSessionState( page: Page, sessionName: string, expectedState: 'RUNNING' | 'EXITED', @@ -117,7 +115,7 @@ export class OptimizedWaitUtils { /** * Smart wait for navigation with fallback */ - static async waitForNavigation(page: Page, url: string, timeout = 3000): Promise { + export async function waitForNavigation(page: Page, url: string, timeout = 3000): Promise { // Try to wait for URL change try { await page.waitForURL(url, { timeout }); @@ -133,13 +131,13 @@ export class OptimizedWaitUtils { /** * Wait for element count with early exit */ - static async waitForElementCount( + export async function waitForElementCount( page: Page, selector: string, expectedCount: number, options?: { operator?: 'exact' | 'minimum' | 'maximum'; timeout?: number } ): Promise { - const { operator = 'exact', timeout = OptimizedWaitUtils.DEFAULT_TIMEOUT } = options || {}; + const { operator = 'exact', timeout = DEFAULT_TIMEOUT } = options || {}; const pollInterval = 100; const maxAttempts = timeout / pollInterval; @@ -164,7 +162,7 @@ export class OptimizedWaitUtils { /** * Wait for any text content (useful for terminal output) */ - static async waitForAnyText(locator: Locator, timeout = 2000): Promise { + export async function waitForAnyText(locator: Locator, timeout = 2000): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeout) { @@ -181,7 +179,7 @@ export class OptimizedWaitUtils { /** * Fast visibility check with retry */ - static async isEventuallyVisible(locator: Locator, timeout = 1000): Promise { + export async function isEventuallyVisible(locator: Locator, timeout = 1000): Promise { try { await locator.waitFor({ state: 'visible', timeout }); return true; @@ -194,7 +192,10 @@ export class OptimizedWaitUtils { /** * Wait for network idle with early exit */ - static async waitForNetworkQuiet(page: Page, options?: { timeout?: number }): Promise { + export async function waitForNetworkQuiet( + page: Page, + options?: { timeout?: number } + ): Promise { const { timeout = TEST_TIMEOUTS.NETWORK_QUIET } = options || {}; // Track pending requests