From 160eb5770c5f161a5cdfbd9d11ade7d1e263d266 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:34:49 -0600 Subject: [PATCH 01/15] feat: cleanly port windows onboarding flow onto main --- CODEBASE_DOCUMENTATION.md | 11 + client/app.js | 2277 +++++++++++--- client/index.html | 106 +- client/notifications.js | 6 +- client/styles.css | 5772 +++++++++++++++++++++------------- client/styles/tabs.css | 19 +- server/diagnosticsService.js | 201 +- server/index.js | 124 + server/setupActionService.js | 542 ++++ src-tauri/src/main.rs | 15 + 10 files changed, 6318 insertions(+), 2755 deletions(-) create mode 100644 server/setupActionService.js diff --git a/CODEBASE_DOCUMENTATION.md b/CODEBASE_DOCUMENTATION.md index eadc9144..41397f7b 100644 --- a/CODEBASE_DOCUMENTATION.md +++ b/CODEBASE_DOCUMENTATION.md @@ -538,5 +538,16 @@ LOGGING: Winston-based structured logging with rotation 9. **Mixed-repo workspaces**: Terminal naming must avoid conflicts between repos 10. **Template validation**: Always validate workspace templates against schemas + +## First-Run Dependency Onboarding (Windows) + +``` +server/setupActionService.js - Defines setup actions and launches PowerShell installers +server/index.js - Routes: GET /api/setup-actions, POST /api/setup-actions/run +client/app.js - Guided dependency onboarding steps + diagnostics integration +client/index.html - Dependency onboarding modal markup + launch button +client/styles.css - Dependency onboarding progress/step styling +``` + --- ๐Ÿšจ **END OF FILE - ENSURE YOU READ EVERYTHING ABOVE** ๐Ÿšจ diff --git a/client/app.js b/client/app.js index c9936ba9..e4f8dec4 100644 --- a/client/app.js +++ b/client/app.js @@ -845,7 +845,7 @@ class ClaudeOrchestrator { if (!expiresAt || expiresAt <= now) this.pendingWorktreeReservations.delete(key); } } - + async init() { try { // Initialize managers @@ -984,21 +984,21 @@ class ClaudeOrchestrator { if (this.settings.notifications) { this.notificationManager.requestPermission(); } - + // Set up UI this.setupEventListeners(); this.applyTheme(); this.syncSettingsUI(); this.applySimpleModeConfig(); this.installAuthFetchShim(); - + // Connect to server await this.connectToServer(); await this.ensureProjectTypeTaxonomy(); // Hook panels that depend on socket events this.activityFeedPanel?.onSocketConnected?.(this.socket); - + // Load user settings from server await this.loadUserSettings(); this.applySidebarDesktopCollapsedFromPrefs(); @@ -1016,22 +1016,22 @@ class ClaudeOrchestrator { // WIP / Queue banner (process status) this.startProcessStatusBanner(); - + // Check for updates on startup this.checkForSettingsUpdates(); - + // Hide loading message if it exists const loadingMessage = document.getElementById('loading-message'); if (loadingMessage) { loadingMessage.classList.add('hidden'); } - + } catch (error) { console.error('Failed to initialize:', error); this.showError('Failed to initialize application'); } } - + async connectToServer() { return new Promise((resolve, reject) => { console.log('Attempting to connect to server...'); @@ -1044,29 +1044,29 @@ class ClaudeOrchestrator { const serverUrl = window.location.origin; this.socket = io(serverUrl, socketOptions); console.log(`Socket connecting to ${serverUrl}...`); - + // Connection events this.socket.on('connect', () => { console.log('Connected to server'); this.updateConnectionStatus(true); resolve(); }); - + this.socket.on('connect_error', (error) => { console.error('Connection error:', error); this.updateConnectionStatus(false); - + if (error.message === 'Authentication failed') { this.showError('Authentication failed. Please check your token.'); } reject(error); }); - + this.socket.on('disconnect', () => { console.log('Disconnected from server'); this.updateConnectionStatus(false); }); - + // Session events this.socket.on('sessions', async (sessionStates) => { console.log('Received sessions event:', sessionStates); @@ -1085,7 +1085,7 @@ class ClaudeOrchestrator { this.worktreeTags.set(worktreePath, tag || {}); this.buildSidebar(); }); - + this.socket.on('terminal-output', ({ sessionId, data }) => { this.terminalManager.handleOutput(sessionId, data); @@ -1133,7 +1133,7 @@ class ClaudeOrchestrator { this.sessionActivity.set(sessionId, 'active'); } }); - + this.socket.on('autosuggest-response', ({ sessionId, suggestion, prefix }) => { this.terminalManager.handleAutosuggestResponse(sessionId, suggestion, prefix); }); @@ -1142,15 +1142,15 @@ class ClaudeOrchestrator { this.updateSessionStatus(sessionId, status); this.maybeAutoSendPrompt(sessionId, status); }); - + this.socket.on('branch-update', ({ sessionId, branch, remoteUrl, defaultBranch, existingPR }) => { this.updateSessionBranch(sessionId, branch, remoteUrl, defaultBranch, existingPR); }); - + this.socket.on('notification-trigger', (notification) => { this.notificationManager.handleNotification(notification); }); - + this.socket.on('session-exited', ({ sessionId, exitCode }) => { this.handleSessionExit(sessionId, exitCode); }); @@ -1343,7 +1343,7 @@ class ClaudeOrchestrator { // Hide + persist dismissal so it doesn't resurrect on refresh/worktree-add this.hideStartupUI(sessionId); this.scheduleAutoPromptFallback(sessionId, 'claude'); - + // Enable the start button now that Claude has started const startBtn = document.getElementById(`claude-start-btn-${sessionId}`); if (startBtn) { @@ -1361,7 +1361,7 @@ class ClaudeOrchestrator { this.socket.on('claude-update-required', (updateInfo) => { this.showClaudeUpdateRequired(updateInfo); }); - + this.socket.on('user-settings-updated', (settings) => { console.log('User settings updated:', settings); this.userSettings = settings; @@ -1589,7 +1589,7 @@ class ClaudeOrchestrator { this.socket.on('git-updated', (result) => { console.log('Git updated:', result); this.showTemporaryMessage(`Repository updated successfully! ${result.wasUpToDate ? 'Already up to date.' : 'Changes pulled.'}`, 'success'); - + // Refresh the page after successful update if (!result.wasUpToDate) { setTimeout(() => { @@ -1600,45 +1600,45 @@ class ClaudeOrchestrator { }, 3000); } }); - + // Build production events this.socket.on('build-started', ({ sessionId, worktreeNum }) => { console.log(`Build started for worktree ${worktreeNum}`); }); - + this.socket.on('build-completed', ({ sessionId, worktreeNum, zipPath }) => { console.log(`Build completed for worktree ${worktreeNum}: ${zipPath}`); - + // Restore the build button (use work{num} pattern to find buttons) this.restoreBuildButton(`work${worktreeNum}`); - + // Request to reveal the file in explorer this.socket.emit('reveal-in-explorer', { path: zipPath }); }); - + this.socket.on('build-failed', ({ sessionId, worktreeNum, error }) => { console.error(`Build failed for worktree ${worktreeNum}:`, error); this.showError(`โŒ Build failed for Worktree ${worktreeNum}: ${error}`); - + // Restore the build button (use work{num} pattern to find buttons) this.restoreBuildButton(`work${worktreeNum}`); }); - + // Periodic heartbeat to keep sessions alive while UI is open this.startHeartbeats(); - + this.socket.on('server-started', ({ sessionId, port }) => { console.log(`[SERVER-STARTED EVENT] Session: ${sessionId}, Port: ${port}`); this.serverPorts.set(sessionId, port); console.log(`Server ${sessionId} started on port ${port}`); console.log('Current serverPorts:', Array.from(this.serverPorts.entries())); - + // Only open localhost automatically - Hytopia needs manual click due to popup blockers setTimeout(() => { const localhostUrl = `https://localhost:${port}`; console.log(`Opening localhost for initialization: ${localhostUrl}`); window.open(localhostUrl, '_blank'); - + // Show notification that server is ready if (this.settings.notifications) { this.showNotification('Server Ready', `Server ${sessionId.replace('-server', '')} is running on port ${port}. Click ๐ŸŽฎ to play!`); @@ -1657,14 +1657,14 @@ class ClaudeOrchestrator { reject(new Error('Connection timeout')); } }, 10000); - + // Clear timeout on successful connection this.socket.on('connect', () => { clearTimeout(timeoutId); }); }); } - + startHeartbeats() { if (this._heartbeatInterval) { clearInterval(this._heartbeatInterval); @@ -1676,7 +1676,7 @@ class ClaudeOrchestrator { } }, 30000); } - + setupEventListeners() { // Check if elements exist before adding listeners const elements = { @@ -1739,7 +1739,7 @@ class ClaudeOrchestrator { 'start-claude', 'cancel-claude-startup' ]); - + // Check all elements exist for (const id in elements) { elements[id] = document.getElementById(id); @@ -1747,7 +1747,7 @@ class ClaudeOrchestrator { console.warn(`Element not found: ${id}`); } } - + // Sidebar worktree clicks - use toggle instead of show if (elements['worktree-list']) { elements['worktree-list'].addEventListener('click', (e) => { @@ -1856,7 +1856,7 @@ class ClaudeOrchestrator { if (!document.body.classList.contains('sidebar-open')) return; this.closeSidebar(); }); - + // View buttons const dashboardBtn = document.getElementById('dashboard-btn'); if (dashboardBtn) { @@ -1878,26 +1878,26 @@ class ClaudeOrchestrator { this.setViewMode('all'); if (this.isMobileLayout()) this.closeSidebar(); }); - + document.getElementById('view-claude-only').addEventListener('click', () => { this.setViewMode('claude'); if (this.isMobileLayout()) this.closeSidebar(); }); - + document.getElementById('view-servers-only').addEventListener('click', () => { this.setViewMode('server'); if (this.isMobileLayout()) this.closeSidebar(); }); - + // Presets document.getElementById('view-presets').addEventListener('click', () => { document.getElementById('presets-modal').classList.remove('hidden'); }); - + document.getElementById('close-presets').addEventListener('click', () => { document.getElementById('presets-modal').classList.add('hidden'); }); - + // Preset buttons document.querySelectorAll('.preset-btn').forEach(btn => { btn.addEventListener('click', () => { @@ -1906,9 +1906,9 @@ class ClaudeOrchestrator { document.getElementById('presets-modal').classList.add('hidden'); }); }); - + // Grid layout dropdown removed - using dynamic layout now - + // Settings const settingsToggle = document.getElementById('settings-toggle'); const closeSettingsPanel = () => { @@ -1936,7 +1936,7 @@ class ClaudeOrchestrator { } else { console.error('Settings toggle button not found!'); } - + document.getElementById('close-settings').addEventListener('click', () => { closeSettingsPanel(); }); @@ -1962,7 +1962,7 @@ class ClaudeOrchestrator { if (panel.contains(e.target)) return; closeSettingsPanel(); }); - + // Settings inputs document.getElementById('enable-notifications').addEventListener('change', (e) => { this.settings.notifications = e.target.checked; @@ -1971,12 +1971,12 @@ class ClaudeOrchestrator { this.notificationManager.requestPermission(); } }); - + document.getElementById('enable-sounds').addEventListener('change', (e) => { this.settings.sounds = e.target.checked; this.saveSettings(); }); - + document.getElementById('auto-scroll').addEventListener('change', (e) => { this.settings.autoScroll = e.target.checked; this.saveSettings(); @@ -1994,7 +1994,7 @@ class ClaudeOrchestrator { } } }); - + document.getElementById('theme-select').addEventListener('change', (e) => { this.settings.theme = e.target.value; this.saveSettings(); @@ -2048,6 +2048,7 @@ class ClaudeOrchestrator { // Settings UI helpers: search + section jump so the panel doesnโ€™t feel like an endless scroll. this.setupSettingsPanelNavigation(); this.setupDiagnosticsPanel(); + this.setupDependencySetupWizard(); const tasksThemeSelect = document.getElementById('tasks-theme-select'); if (tasksThemeSelect) { @@ -2306,7 +2307,7 @@ class ClaudeOrchestrator { document.getElementById('dismiss-git-notification').addEventListener('click', () => { document.getElementById('git-update-notification').classList.add('hidden'); }); - + // Workflow notification settings (server-persisted) const workflowNotifyMode = document.getElementById('workflow-notify-mode'); if (workflowNotifyMode) { @@ -2727,33 +2728,33 @@ class ClaudeOrchestrator { if (markReadBtn) { markReadBtn.addEventListener('click', () => this.notificationManager?.markAllAsRead?.()); } - + // Claude startup modal handlers (simplified) const cancelClaudeBtn = document.getElementById('cancel-claude-startup'); - + if (cancelClaudeBtn) { cancelClaudeBtn.addEventListener('click', () => { this.hideClaudeStartupModal(); }); } - + // Handle startup option button clicks document.addEventListener('click', (e) => { if (e.target.closest('.startup-option-btn')) { const btn = e.target.closest('.startup-option-btn'); const mode = btn.dataset.mode; - + // Check if modal YOLO is checked const modalYolo = document.getElementById('modal-yolo'); const skipPermissions = modalYolo ? modalYolo.checked : false; - + if (this.pendingClaudeSession) { this.startClaudeWithOptions(this.pendingClaudeSession, mode, skipPermissions); this.hideClaudeStartupModal(); } } }); - + // Handle window resize to fix blank terminals let resizeTimeout; window.addEventListener('resize', () => { @@ -2899,7 +2900,7 @@ class ClaudeOrchestrator { }); }); } - + setViewMode(mode, { persist = true } = {}) { const normalized = String(mode || '').toLowerCase(); if (!['all', 'claude', 'server'].includes(normalized)) return; @@ -2914,7 +2915,7 @@ class ClaudeOrchestrator { this.updateGlobalUserSetting('ui.terminals.viewMode', normalized); } } - + updateViewModeButtons() { const allBtn = document.getElementById('view-all'); const claudeBtn = document.getElementById('view-claude-only'); @@ -3544,21 +3545,21 @@ class ClaudeOrchestrator { return null; } - + matchesViewMode(sessionId) { if (this.viewMode === 'all') return true; - + const session = this.sessions.get(sessionId); const type = session?.type; - + if (this.viewMode === 'claude') { return type === 'claude' || type === 'codex' || /-(claude|codex)$/.test(String(sessionId || '')); } - + if (this.viewMode === 'server') { return type === 'server' || sessionId.includes('-server'); } - + return true; } @@ -3571,7 +3572,7 @@ class ClaudeOrchestrator { && this.matchesTierFilter(sessionId) && this.matchesWorkflowMode(sessionId); } - + handleInitialSessions(sessionStates) { console.log('Received initial sessions:', sessionStates); @@ -3588,7 +3589,7 @@ class ClaudeOrchestrator { this.sessions.clear(); this.sessionActivity.clear(); this.visibleTerminals.clear(); - + // Process sessions for (const [sessionId, state] of Object.entries(sessionStates)) { const sessionData = { @@ -3634,13 +3635,13 @@ class ClaudeOrchestrator { this.pruneIntentHaikuState(new Set(Object.keys(sessionStates))); this.lastSessionsWorkspaceId = currentWorkspaceId; - + // Hide loading message FIRST const loadingMessage = document.getElementById('loading-message'); if (loadingMessage) { loadingMessage.style.display = 'none'; } - + // Build sidebar this.buildSidebar(); @@ -4237,10 +4238,10 @@ class ClaudeOrchestrator { // Always ensure filter toggle exists and is updated FIRST this.ensureFilterToggleExists(); - + // Clear and rebuild the worktree list worktreeList.innerHTML = ''; - + // Group sessions by worktree and repository for mixed-repo support const worktrees = new Map(); @@ -4274,22 +4275,22 @@ class ClaudeOrchestrator { worktree.server = session; } } - + // Create sidebar items for (const [worktreeId, worktree] of worktrees) { // Check if worktree is active (has any session marked as active) const isActive = this.isWorktreeActive(worktreeId); - + // Skip inactive worktrees if filter is enabled if (this.showActiveOnly && !isActive) { continue; } - + // Check if any session in this worktree is visible. const claudeVisible = !!(worktree.claude && this.isSessionVisibleByWorktreeSelection(worktree.claude.sessionId, worktree.claude)); const serverVisible = !!(worktree.server && this.isSessionVisibleByWorktreeSelection(worktree.server.sessionId, worktree.server)); const isVisible = claudeVisible || serverVisible; - + const item = document.createElement('div'); // Only show visibility state, not activity state (activity filtering is handled separately) item.className = `worktree-item ${!isVisible ? 'hidden-terminal' : ''}`; @@ -4408,9 +4409,9 @@ class ClaudeOrchestrator { `; - + // Click handler is already attached via event delegation in setupEventListeners - + worktreeList.appendChild(item); } @@ -4791,21 +4792,21 @@ class ClaudeOrchestrator { const current = this.worktreeTags.get(worktreePath)?.readyForReview; return this.setWorktreeReadyForReview(worktreePath, !current); } - + ensureFilterToggleExists() { let filterToggle = document.getElementById('filter-toggle'); - + if (!filterToggle) { // Create the filter toggle element filterToggle = document.createElement('div'); filterToggle.className = 'filter-toggle'; filterToggle.id = 'filter-toggle'; - + // Insert it right before the worktree list const worktreeList = document.getElementById('worktree-list'); worktreeList.parentNode.insertBefore(filterToggle, worktreeList); } - + // Always update the button content const visibility = this.getUiVisibilityConfig().sidebar || {}; const showActiveFilter = visibility.activeFilter !== false; @@ -4838,7 +4839,7 @@ class ClaudeOrchestrator { if (status === 'error') return 'error'; return 'idle'; } - + isWorktreeActive(worktreeIdOrKey) { // Check if any session for this worktree has been marked as active. // For mixed-repo workspaces we may receive: @@ -4867,11 +4868,11 @@ class ClaudeOrchestrator { return false; } - + toggleActivityFilter() { this.showActiveOnly = !this.showActiveOnly; this.buildSidebar(); - + // Also update the main grid view to match the filter if (this.showActiveOnly) { this.showActiveWorktreesOnly(); @@ -4879,11 +4880,11 @@ class ClaudeOrchestrator { this.showAllTerminals(); } } - + showActiveWorktreesOnly() { // Clear visible terminals first this.visibleTerminals.clear(); - + // Add only active worktree sessions to visible set for (const [sessionId, session] of this.sessions) { const sessionWorktreeId = session.worktreeId || sessionId.split('-')[0]; @@ -4894,7 +4895,7 @@ class ClaudeOrchestrator { this.visibleTerminals.add(sessionId); } } - + // If no active sessions, show all if (this.visibleTerminals.size === 0) { this.showAllTerminals(); @@ -4903,7 +4904,7 @@ class ClaudeOrchestrator { this.buildSidebar(); } } - + resizeAllVisibleTerminals() { // Force resize all visible terminals to fit their containers this.activeView.forEach(sessionId => { @@ -5037,7 +5038,7 @@ class ClaudeOrchestrator { this.updateTerminalGrid(); this.buildSidebar(); } - + showWorktree(worktreeIdOrKey) { // Show terminals for this EXACT worktree key const claudeId = `${worktreeIdOrKey}-claude`; @@ -5049,17 +5050,17 @@ class ClaudeOrchestrator { this.updateTerminalGrid(); this.buildSidebar(); } - + showAllTerminals() { // Add all sessions to visible set for (const sessionId of this.sessions.keys()) { this.visibleTerminals.add(sessionId); } - + this.updateTerminalGrid(); this.buildSidebar(); } - + /** * Get the terminal grid container for the current tab */ @@ -5088,7 +5089,7 @@ class ClaudeOrchestrator { const allSessions = Array.from(this.sessions.keys()); this.renderTerminalsWithVisibility(allSessions); } - + renderTerminalsWithVisibility(sessionIds) { // Render all terminals but apply visibility using CSS (don't destroy DOM) this.activeView = sessionIds.filter(id => this.isSessionVisibleInCurrentView(id)); @@ -5232,18 +5233,18 @@ class ClaudeOrchestrator { this.resizeAllVisibleTerminals(); }, 200); } - + showClaudeOnly() { this.setViewMode('claude'); } - + showServersOnly() { this.setViewMode('server'); } - + applyPreset(preset) { this.visibleTerminals.clear(); - + switch (preset) { case 'all': this.showAllTerminals(); @@ -5277,9 +5278,9 @@ class ClaudeOrchestrator { break; } } - + // changeLayout method removed - using dynamic layout based on visible terminal count - + showTerminals(sessionIds) { // Legacy function - update visible set and refresh everything this.visibleTerminals.clear(); @@ -5291,31 +5292,31 @@ class ClaudeOrchestrator { this.updateTerminalGrid(); this.buildSidebar(); } - + renderTerminals(sessionIds) { // Core rendering function - just displays terminals without updating state this.activeView = sessionIds; const grid = this.getTerminalGrid(); - + // Sort sessionIds to ensure proper ordering: work1-claude, work1-server, work2-claude, work2-server, etc. const sortedSessionIds = sessionIds.slice().sort((a, b) => { // Extract worktree number const getWorkNum = (id) => parseInt(id.match(/work(\d+)/)?.[1] || 0); const numA = getWorkNum(a); const numB = getWorkNum(b); - + // First sort by worktree number if (numA !== numB) return numA - numB; - + // Then claude before server if (a.includes('claude') && b.includes('server')) return -1; if (a.includes('server') && b.includes('claude')) return 1; return 0; }); - + // Clear grid but don't destroy terminals grid.innerHTML = ''; - + // Create terminal elements for active view sortedSessionIds.forEach((sessionId) => { const session = this.sessions.get(sessionId); @@ -5324,7 +5325,7 @@ class ClaudeOrchestrator { grid.appendChild(wrapper); } }); - + // Now handle terminal instances sortedSessionIds.forEach((sessionId, index) => { const session = this.sessions.get(sessionId); @@ -5332,25 +5333,25 @@ class ClaudeOrchestrator { setTimeout(() => { const terminalEl = document.getElementById(this.getSessionDomId('terminal', sessionId)); if (!terminalEl) return; - + if (this.terminalManager.terminals.has(sessionId)) { // Re-attach existing terminal to the new element const term = this.terminalManager.terminals.get(sessionId); - + // Clear and re-open the terminal in the new element terminalEl.innerHTML = ''; term.open(terminalEl); - + // Force a resize and refresh this.terminalManager.fitTerminal(sessionId); - + // Force a screen refresh to show content term.refresh(0, term.rows - 1); } else { // Create new terminal only if it doesn't exist this.terminalManager.createTerminal(sessionId, session); } - + // Don't auto-start Claude - let user choose via modal or button }, 50 + (index * 25)); // Reduced stagger time } @@ -5576,41 +5577,41 @@ class ClaudeOrchestrator { getTicketMetaForSession(sessionId, sessionOverride = null) { const sid = String(sessionId || '').trim(); if (!sid) return null; - + const session = sessionOverride || this.sessions.get(sid); const recordIds = []; recordIds.push(`session:${sid}`); - + const worktreePath = session?.config?.cwd || session?.cwd || session?.worktreePath || null; if (worktreePath) recordIds.push(`worktree:${worktreePath}`); - + const prUrl = this.githubLinks.get(sid)?.pr || null; const prTaskId = prUrl ? this.getPRTaskIdFromUrl(prUrl) : null; if (prTaskId) recordIds.push(prTaskId); - + for (const id of recordIds) { const rec = this.taskRecords.get(id); if (!rec) continue; - + const ticketProvider = String(rec.ticketProvider || '').trim().toLowerCase(); const ticketCardId = String(rec.ticketCardId || '').trim(); const ticketCardUrl = String(rec.ticketCardUrl || '').trim(); const ticketTitle = String(rec.ticketTitle || '').trim(); - + if (!ticketTitle && !ticketCardId && !ticketCardUrl) continue; - + const rawUrl = ticketCardUrl || ((ticketProvider === 'trello' || !ticketProvider) && ticketCardId ? `https://trello.com/c/${ticketCardId}` : ''); const url = (rawUrl && /^https?:\/\//i.test(rawUrl)) ? rawUrl : ''; - + const label = ticketTitle || (ticketProvider && ticketCardId ? `${ticketProvider}:${ticketCardId}` : (ticketCardId ? ticketCardId : url)); const tooltipParts = [ ticketTitle || null, ticketProvider && ticketCardId ? `${ticketProvider}:${ticketCardId}` : null, url || null ].filter(Boolean); - + return { provider: ticketProvider || null, cardId: ticketCardId || null, @@ -5620,36 +5621,36 @@ class ClaudeOrchestrator { tooltip: tooltipParts.join(' โ€ข ') }; } - + return null; } - + updateTerminalTicketLabel(sessionId) { const sid = String(sessionId || '').trim(); if (!sid) return; - + const wrapper = this.getSessionWrapperElement(sid); if (!wrapper) return; - + const titleRow = wrapper.querySelector('.terminal-title'); if (!titleRow) return; - + const existing = titleRow.querySelector('.terminal-ticket'); const meta = this.getTicketMetaForSession(sid); - + if (!meta || !meta.label) { existing?.remove(); return; } - + const label = `๐Ÿงพ ${meta.label}`; const tooltip = meta.tooltip || meta.title || meta.label; const url = meta.url || ''; - + const wantsLink = !!url; const isLink = existing && existing.tagName && existing.tagName.toLowerCase() === 'a'; const isSpan = existing && existing.tagName && existing.tagName.toLowerCase() === 'span'; - + if (wantsLink) { let el = existing; if (!isLink) { @@ -5658,7 +5659,7 @@ class ClaudeOrchestrator { el.className = 'terminal-ticket'; el.target = '_blank'; el.rel = 'noopener noreferrer'; - + const branchEl = titleRow.querySelector('.terminal-branch'); if (branchEl && branchEl.parentElement === titleRow) { branchEl.insertAdjacentElement('afterend', el); @@ -5671,7 +5672,7 @@ class ClaudeOrchestrator { el.title = tooltip; return; } - + let el = existing; if (!isSpan) { existing?.remove(); @@ -5687,7 +5688,7 @@ class ClaudeOrchestrator { el.textContent = label; el.title = tooltip; } - + refreshBranchLabels() { try { for (const [sessionId, session] of this.sessions) { @@ -5699,7 +5700,7 @@ class ClaudeOrchestrator { } this.buildSidebar(); } - + createTerminalElement(sessionId, session) { const wrapper = document.createElement('div'); wrapper.className = 'terminal-wrapper'; @@ -5709,7 +5710,7 @@ class ClaudeOrchestrator { wrapper.addEventListener('mousedown', () => { this.lastInteractedSessionId = sessionId; }); - + const sessionType = String(session?.type || '').trim().toLowerCase(); const isAgentSession = sessionType === 'claude' || sessionType === 'codex'; const isServerSession = sessionType === 'server'; @@ -5819,7 +5820,7 @@ class ClaudeOrchestrator { return wrapper; } - + updateSessionStatus(sessionId, status) { const statusElement = document.getElementById(this.getSessionDomId('status', sessionId)); // Update session data @@ -5903,7 +5904,7 @@ class ClaudeOrchestrator { apply(status); } } - + // Update quick actions for agent sessions if (/-claude$|-codex$/.test(sessionId)) { // Clear any pending notification timer if agent goes busy again @@ -5935,11 +5936,11 @@ class ClaudeOrchestrator { }, 3000); } } - + // Update sidebar this.updateSidebarStatus(sessionId, status); } - + updateSidebarStatus(sessionId, status) { const session = this.sessions.get(sessionId); const key = this.getSessionWorktreeKey(sessionId, session); @@ -5996,7 +5997,7 @@ class ClaudeOrchestrator { } } } - + updateSessionBranch(sessionId, branch, remoteUrl, defaultBranch, existingPR) { const session = this.sessions.get(sessionId); if (session) { @@ -6007,9 +6008,9 @@ class ClaudeOrchestrator { if (defaultBranch) { session.defaultBranch = defaultBranch; } - + console.log(`Branch updated for ${sessionId}: ${branch}`, existingPR ? `(existing PR: ${existingPR})` : ''); - + // If there's an existing PR, add it to GitHub links automatically if (existingPR) { const links = this.githubLinks.get(sessionId) || {}; @@ -6019,17 +6020,17 @@ class ClaudeOrchestrator { this.maybeSchedulePrIntentRefresh(sessionId, existingPR); } } - + // Update terminal branch display this.updateTerminalBranchLabel(sessionId, branch || ''); - + // Update sidebar this.buildSidebar(); - + // Update GitHub buttons with new remote URL this.updateTerminalControls(sessionId); } - + // Server control methods toggleServer(sessionId, environment = 'development') { const status = this.serverStatuses.get(sessionId); @@ -6056,22 +6057,22 @@ class ClaudeOrchestrator { }); } } - + killServer(sessionId) { // Send force kill this.socket.emit('server-control', { sessionId, action: 'kill' }); this.serverStatuses.set(sessionId, 'idle'); - + // Update UI const button = document.getElementById(`server-toggle-${sessionId}`); if (button) { button.textContent = 'โ–ถ'; } - + this.updateSidebarStatus(sessionId, 'idle'); this.updateServerControls(sessionId); } - + playInHytopia(sessionId) { console.log(`[PLAY IN HYTOPIA] Session: ${sessionId}`); console.log('Available ports:', Array.from(this.serverPorts.entries())); @@ -6089,21 +6090,21 @@ class ClaudeOrchestrator { } return; } - + const serverUrl = `localhost:${port}`; const hytopiaUrl = `https://hytopia.com/play/?${serverUrl}`; - + console.log(`Opening Hytopia for ${sessionId} at ${hytopiaUrl}`); window.open(hytopiaUrl, '_blank'); } - + restoreBuildButton(sessionId) { // Find any button that might be building for this worktree const worktreeMatch = sessionId.match(/work(\d+)/); if (!worktreeMatch) return; - + const worktreeNum = worktreeMatch[1]; - + // Check both claude and server buttons for this worktree [`work${worktreeNum}-claude`, `work${worktreeNum}-server`].forEach(id => { const btn = this.buildingButtons?.get(id); @@ -6115,7 +6116,7 @@ class ClaudeOrchestrator { } }); } - + buildProduction(sessionId) { // Extract worktree number from sessionId (e.g., 'work1-claude' -> 1) const worktreeMatch = sessionId.match(/work(\d+)/); @@ -6124,10 +6125,10 @@ class ClaudeOrchestrator { this.showError('Failed to identify worktree for build'); return; } - + const worktreeNum = worktreeMatch[1]; console.log(`Building production ZIP for worktree ${worktreeNum}`); - + // Disable the build button and show loading state const wrapper = document.getElementById(this.getSessionDomId('wrapper', sessionId)); const buildBtn = wrapper ? wrapper.querySelector('button[onclick*="buildProduction"]') : null; @@ -6136,26 +6137,26 @@ class ClaudeOrchestrator { buildBtn.innerHTML = ''; buildBtn.classList.add('building'); } - + // Store the button reference for later this.buildingButtons = this.buildingButtons || new Map(); this.buildingButtons.set(sessionId, buildBtn); - + // Emit socket event to trigger build on backend - this.socket.emit('build-production', { + this.socket.emit('build-production', { sessionId, - worktreeNum + worktreeNum }); } - + detectGitHubLinks(sessionId, data) { // Look for GitHub URLs with improved pattern matching const githubUrlPattern = /https:\/\/github\.com\/[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+(?:\/(?!https:\/\/github\.com\/)[^\s\)\]\}\>\"\'\`]*)?/g; const matches = data.match(githubUrlPattern); - + if (matches) { const links = this.githubLinks.get(sessionId) || {}; - + matches.forEach(originalUrl => { // Clean up ANSI escape codes and other terminal artifacts let url = originalUrl @@ -6164,7 +6165,7 @@ class ClaudeOrchestrator { .replace(/\u001b\[[0-9;]*m/g, '') // Remove Unicode ANSI codes .replace(/[\x00-\x1F\x7F-\x9F]/g, '') // Remove other control characters .trim(); - + // Remove common trailing punctuation that might be captured url = url.replace(/[,;.!?)\]}>'"`]*$/, ''); @@ -6173,7 +6174,7 @@ class ClaudeOrchestrator { if (secondUrlIndex > 0) { url = url.slice(0, secondUrlIndex); } - + // Validate URL format try { new URL(url); @@ -6181,7 +6182,7 @@ class ClaudeOrchestrator { console.warn('Invalid GitHub URL detected:', url); return; } - + // Categorize the URL if (url.includes('/pull/') && url.match(/\/pull\/\d+\/?$/)) { if (links.pr !== url) { @@ -6201,36 +6202,36 @@ class ClaudeOrchestrator { } } }); - + this.githubLinks.set(sessionId, links); this.updateTerminalControls(sessionId); } } - + clearGitHubLinks(sessionId) { this.githubLinks.delete(sessionId); this.githubLinkLogs.delete(sessionId); this.updateTerminalControls(sessionId); } - + copyLocalhostUrl(sessionId) { const port = this.serverPorts.get(sessionId); if (!port) { console.error('No port found for server', sessionId); return; } - + const url = `https://localhost:${port}`; navigator.clipboard.writeText(url).then(() => { console.log(`Copied ${url} to clipboard`); this.showNotification('Copied!', `${url} copied to clipboard`); }); } - + openHytopiaWebsite() { window.open('https://hytopia.com', '_blank'); } - + openPRLink(url) { try { // Validate the URL @@ -6242,17 +6243,17 @@ class ClaudeOrchestrator { this.showToast('Invalid PR URL', 'error'); } } - + getGitHubButtons(sessionId) { const links = this.githubLinks.get(sessionId) || {}; let buttons = ''; const visibility = this.getTerminalVisibilityConfig(); - + // Always show branch button (uses current session's git info) const session = this.sessions.get(sessionId); if (session && session.branch && session.branch !== 'master' && session.branch !== 'main') { const worktreeId = sessionId.split('-')[0]; - + // Use dynamic remote URL if available if (session.remoteUrl) { const encodeRef = (ref) => encodeURIComponent(String(ref || '').trim()); @@ -6261,7 +6262,7 @@ class ClaudeOrchestrator { // Use the actual default branch from git, fallback to 'main' if not available const defaultBranch = session.defaultBranch || 'main'; const compareUrl = `${session.remoteUrl}/compare/${encodeRef(defaultBranch)}...${branchRef}`; - + if (visibility.viewBranchOnGithub !== false) { buttons += ``; } @@ -6273,7 +6274,7 @@ class ClaudeOrchestrator { } } } - + // Show PR button if PR link detected if (links.pr) { const lastLogged = this.githubLinkLogs.get(sessionId); @@ -6288,17 +6289,17 @@ class ClaudeOrchestrator { buttons += ``; } } - + // Check for commit URLs if (links.commit) { if (visibility.advancedDiff !== false) { buttons += ``; } } - + return buttons; } - + updateTerminalControls(sessionId) { const wrapper = document.getElementById(this.getSessionDomId('wrapper', sessionId)); if (!wrapper) return; @@ -6378,43 +6379,43 @@ class ClaudeOrchestrator { : ''; return [stopBtn, removeBtn].filter(Boolean).join('\n'); } - + updateServerStatus(sessionId, output) { // Check if server started - look for various startup messages - if (output.includes('Server started') || - output.includes('Listening on') || + if (output.includes('Server started') || + output.includes('Listening on') || output.includes('Server running') || output.includes('Started server') || output.includes('๐Ÿš€')) { this.serverStatuses.set(sessionId, 'running'); this.updateSidebarStatus(sessionId, 'running'); - + const button = document.getElementById(`server-toggle-${sessionId}`); if (button) { button.textContent = 'โน'; } - + this.updateServerControls(sessionId); const linkedClaude = this.getLinkedClaudeSessionIdForServer(sessionId); if (linkedClaude) this.updateTerminalControls(linkedClaude); } - + // Check if server stopped if (output.includes('Server stopped') || output.includes('exit')) { this.serverStatuses.set(sessionId, 'idle'); this.updateSidebarStatus(sessionId, 'idle'); - + const button = document.getElementById(`server-toggle-${sessionId}`); if (button) { button.textContent = 'โ–ถ'; } - + this.updateServerControls(sessionId); const linkedClaude = this.getLinkedClaudeSessionIdForServer(sessionId); if (linkedClaude) this.updateTerminalControls(linkedClaude); } } - + /** * Get dynamic launch options based on current workspace */ @@ -6485,14 +6486,14 @@ class ClaudeOrchestrator { // Use dynamic button system controlsDiv.innerHTML = this.getServerControlsHTML(sessionId); } - + handleServerError(sessionId, output) { const worktreeId = sessionId.split('-')[0]; - + // Update status this.serverStatuses.set(sessionId, 'error'); this.updateSidebarStatus(sessionId, 'error'); - + // Show notification this.notificationManager.handleNotification({ sessionId, @@ -6518,13 +6519,13 @@ class ClaudeOrchestrator { session.hasUserInput = false; this.updateSidebarStatus(sid, String(session.status || 'idle').trim().toLowerCase() || 'idle'); } - + sendTerminalInput(sessionId, data) { if (!this.socket || !this.socket.connected) { console.error('Not connected to server'); return; } - + // Mark session as active when user first provides input // But only for meaningful input (not just arrow keys, etc.) if (data.length > 0 && !data.match(/^[\x1b\x7f\r\n]/) && data.trim().length > 0) { @@ -6547,7 +6548,7 @@ class ClaudeOrchestrator { console.error('Failed to interrupt session', e); } } - + resizeTerminal(sessionId, cols, rows) { if (this.socket && this.socket.connected) { this.socket.emit('terminal-resize', { sessionId, cols, rows }); @@ -7522,7 +7523,7 @@ class ClaudeOrchestrator { } } } - + handleSessionRestart(sessionId) { console.log(`Session ${sessionId} restarted`); // Terminal will automatically reconnect and show new content @@ -7551,20 +7552,20 @@ class ClaudeOrchestrator { } } } - + restartClaudeSession(sessionId) { console.log(`Restarting Claude session: ${sessionId}`); - + if (this.socket && this.socket.connected) { this.socket.emit('restart-session', { sessionId }); - + // Update UI to show restarting this.updateSessionStatus(sessionId, 'restarting'); } else { this.showError('Not connected to server'); } } - + refreshTerminal(sessionId) { console.log('Refreshing terminal:', sessionId); const term = this.terminalManager.terminals.get(sessionId); @@ -7572,10 +7573,10 @@ class ClaudeOrchestrator { // Force fit and refresh this.terminalManager.fitTerminal(sessionId); term.refresh(0, term.rows - 1); - + // Also try scrolling to bottom to trigger redraw term.scrollToBottom(); - + // If still blank, re-attach to DOM const terminalEl = document.getElementById(this.getSessionDomId('terminal', sessionId)); if (terminalEl && terminalEl.children.length === 0) { @@ -7583,13 +7584,13 @@ class ClaudeOrchestrator { } } } - + updateConnectionStatus(connected) { const statusElement = document.getElementById('connection-status'); if (statusElement) { const dot = statusElement.querySelector('.status-dot'); const text = statusElement.querySelector('span:last-child'); - + if (connected) { dot.classList.remove('disconnected'); dot.classList.add('connected'); @@ -7601,12 +7602,12 @@ class ClaudeOrchestrator { } } } - + showError(message) { // For now, use alert. Could be improved with a toast notification alert(`Error: ${message}`); } - + showClaudeUpdateRequired(updateInfo) { // Create update banner const banner = document.createElement('div'); @@ -7621,10 +7622,10 @@ class ClaudeOrchestrator { `; - + // Add to top of page document.body.insertBefore(banner, document.body.firstChild); - + // Also show in console console.warn('Claude Update Required:', updateInfo); } @@ -7661,36 +7662,18 @@ class ClaudeOrchestrator { return; } this.lastNotificationTime[sessionId] = now; - + const worktreeId = sessionId.replace('-claude', ''); const session = this.sessions.get(sessionId); const branch = session ? session.branch : ''; - - // Create small toast notification - const toast = document.createElement('div'); - toast.className = 'ready-toast'; - toast.innerHTML = ` -
- โœ… - Claude ${worktreeId} ready ${branch ? `(${branch})` : ''} -
- `; - - // Add to page - document.body.appendChild(toast); - - // Remove after 3 seconds - setTimeout(() => { - if (toast.parentNode) { - toast.remove(); - } - }, 3000); - + + this.showToast(`Claude ${worktreeId} ready ${branch ? `(${branch})` : ''}`, 'success', { durationMs: 3000 }); + // Play notification sound if enabled if (this.settings.sounds) { this.playNotificationSound(); } - + // Browser notification if enabled if (this.settings.notifications && 'Notification' in window && Notification.permission === 'granted') { new Notification(`Claude ${worktreeId} Ready`, { @@ -7709,26 +7692,26 @@ class ClaudeOrchestrator { }); } } - + playNotificationSound() { // Create a simple notification sound const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); - + oscillator.connect(gainNode); gainNode.connect(audioContext.destination); - + oscillator.frequency.setValueAtTime(800, audioContext.currentTime); oscillator.frequency.setValueAtTime(600, audioContext.currentTime + 0.1); - + gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); - + oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.3); } - + loadSettings() { const stored = localStorage.getItem('claude-orchestrator-settings'); const defaults = { @@ -8501,11 +8484,11 @@ class ClaudeOrchestrator { globalNodeInput.value = nodeOptions.join(' '); globalArgsInput.value = gameArgs.join(' '); } - + saveSettings() { localStorage.setItem('claude-orchestrator-settings', JSON.stringify(this.settings)); } - + applyTheme() { const mode = this.settings.theme === 'light' ? 'light' : 'dark'; document.body.classList.toggle('light-theme', mode === 'light'); @@ -8683,7 +8666,7 @@ class ClaudeOrchestrator { } return normalized; } - + syncSettingsUI() { // Sync checkbox states with settings document.getElementById('enable-notifications').checked = this.settings.notifications; @@ -8702,29 +8685,29 @@ class ClaudeOrchestrator { const label = document.getElementById('skin-intensity-value'); if (label) label.textContent = `${v}%`; } - + // Sync user settings UI if loaded if (this.userSettings) { this.syncUserSettingsUI(); } } - + showCodeReviewDropdown(sessionId) { // Close any existing dropdowns document.querySelectorAll('.review-dropdown').forEach(dropdown => dropdown.remove()); - + // Get the terminal controls container const terminalWrapper = document.getElementById(this.getSessionDomId('wrapper', sessionId)); const controlsContainer = terminalWrapper.querySelector('.terminal-controls'); - + // Create dropdown const dropdown = document.createElement('div'); dropdown.className = 'review-dropdown'; dropdown.innerHTML = this.buildReviewerDropdownHTML(sessionId); - + // Position and add to DOM controlsContainer.appendChild(dropdown); - + // Close dropdown when clicking outside const closeDropdown = (e) => { if (!dropdown.contains(e.target)) { @@ -8732,22 +8715,22 @@ class ClaudeOrchestrator { document.removeEventListener('click', closeDropdown); } }; - + // Add close listener after a short delay to prevent immediate closure setTimeout(() => { document.addEventListener('click', closeDropdown); }, 100); } - + buildReviewerDropdownHTML(requestingSessionId) { const availableReviewers = this.getAvailableReviewers(requestingSessionId); - + let html = `
Assign Code Review
`; - + if (availableReviewers.length === 0) { html += `
@@ -8767,19 +8750,19 @@ class ClaudeOrchestrator { `; }); } - + return html; } - + getAvailableReviewers(requestingSessionId) { const reviewers = []; - + for (const [sessionId, session] of this.sessions) { // Only include Claude sessions that are not the requesting session if (sessionId.includes('-claude') && sessionId !== requestingSessionId) { const worktreeNumber = sessionId.replace('-claude', '').replace('work', ''); const isActive = this.sessionActivity.get(sessionId) === 'active'; - + // Prefer active sessions, but include inactive ones as backup if (isActive || session.status === 'waiting') { reviewers.push({ @@ -8792,7 +8775,7 @@ class ClaudeOrchestrator { } } } - + // Sort by preference: active + ready first, then active + busy, then inactive reviewers.sort((a, b) => { if (a.isActive && !b.isActive) return -1; @@ -8801,48 +8784,48 @@ class ClaudeOrchestrator { if (a.status !== 'waiting' && b.status === 'waiting') return 1; return 0; }); - + return reviewers; } - + async assignCodeReview(requestingSessionId, reviewerSessionId) { // Close dropdown document.querySelectorAll('.review-dropdown').forEach(dropdown => dropdown.remove()); - + try { // Extract code/PR information from the requesting session const codeInfo = await this.extractCodeForReview(requestingSessionId); - + if (!codeInfo.hasContent) { this.showToast(`No code changes detected in Claude ${requestingSessionId.replace('work', '').replace('-claude', '')}`, 'warning'); return; } - + // Format review request const reviewRequest = this.formatReviewRequest(codeInfo, requestingSessionId); - + // Send to reviewer Claude this.sendTerminalInput(reviewerSessionId, reviewRequest); - + // Mark both sessions as active this.sessionActivity.set(reviewerSessionId, 'active'); this.buildSidebar(); - + // Show success message const requestingWorktree = requestingSessionId.replace('work', '').replace('-claude', ''); const reviewerWorktree = reviewerSessionId.replace('work', '').replace('-claude', ''); this.showToast(`Code review assigned: Claude ${requestingWorktree} โ†’ Claude ${reviewerWorktree}`, 'success'); - + } catch (error) { console.error('Error assigning code review:', error); this.showToast('Failed to assign code review', 'error'); } } - + async extractCodeForReview(sessionId) { // Get terminal content const terminalContent = this.terminalManager.getTerminalContent(sessionId); - + // Look for various types of code content const codePatterns = { prUrl: /https:\/\/github\.com\/[^\s]+\/pull\/\d+/g, @@ -8851,7 +8834,7 @@ class ClaudeOrchestrator { codeBlocks: /```[\s\S]*?```/g, bashCommands: /(?:git\s+(?:diff|log|show)|gh\s+pr)/g }; - + const extracted = { prUrls: [...(terminalContent.match(codePatterns.prUrl) || [])], gitDiffs: [...(terminalContent.match(codePatterns.gitDiff) || [])], @@ -8859,20 +8842,20 @@ class ClaudeOrchestrator { recentCommands: this.extractRecentCommands(terminalContent), hasContent: false }; - + // Determine if there's reviewable content - extracted.hasContent = extracted.prUrls.length > 0 || - extracted.gitDiffs.length > 0 || + extracted.hasContent = extracted.prUrls.length > 0 || + extracted.gitDiffs.length > 0 || extracted.codeBlocks.length > 0 || extracted.recentCommands.some(cmd => cmd.includes('git') || cmd.includes('gh pr')); - + return extracted; } - + extractRecentCommands(terminalContent) { const lines = terminalContent.split('\n'); const commands = []; - + // Look for command patterns (simple approach) for (let i = lines.length - 1; i >= 0 && commands.length < 10; i--) { const line = lines[i].trim(); @@ -8880,15 +8863,15 @@ class ClaudeOrchestrator { commands.unshift(line); } } - + return commands; } - + formatReviewRequest(codeInfo, requestingSessionId) { const requestingWorktree = requestingSessionId.replace('work', '').replace('-claude', ''); - + let request = `Please review the code from Claude ${requestingWorktree}:\n\n`; - + if (codeInfo.prUrls.length > 0) { request += `**Pull Request(s):**\n`; codeInfo.prUrls.forEach(url => { @@ -8900,19 +8883,19 @@ class ClaudeOrchestrator { request += `- Suggestions for improvement\n`; request += `- Architecture and design patterns\n\n`; } - + if (codeInfo.gitDiffs.length > 0) { request += `**Git Diff:**\n\`\`\`diff\n`; request += codeInfo.gitDiffs.slice(0, 2).join('\n'); // Limit to first 2 diffs request += `\n\`\`\`\n\n`; } - + if (codeInfo.codeBlocks.length > 0) { request += `**Code Changes:**\n`; request += codeInfo.codeBlocks.slice(0, 3).join('\n\n'); // Limit to first 3 blocks request += `\n\n`; } - + if (codeInfo.recentCommands.length > 0) { request += `**Recent Commands:**\n`; codeInfo.recentCommands.forEach(cmd => { @@ -8920,9 +8903,9 @@ class ClaudeOrchestrator { }); request += `\n`; } - + request += `Please provide a thorough code review with specific feedback and suggestions.\n`; - + return request; } @@ -9250,75 +9233,1426 @@ class ClaudeOrchestrator { throw new Error(String(data?.error || data?.message || `HTTP ${res.status}`)); } - const diagnostics = data?.diagnostics; - if (diagnostics && typeof diagnostics === 'object') { - state.firstRun = diagnostics; - renderRepairActions(state.firstRun); - } else { - await refreshFirstRun(); + const diagnostics = data?.diagnostics; + if (diagnostics && typeof diagnostics === 'object') { + state.firstRun = diagnostics; + renderRepairActions(state.firstRun); + } else { + await refreshFirstRun(); + } + if (!state.base) await refreshBase(); + await refreshInstallWizard().catch(() => {}); + render(state.base, state.firstRun, state.wizard); + + const appliedCount = Number(data?.appliedCount || 0); + const failedCount = Number(data?.failedCount || 0); + const skippedManualCount = Number(data?.skippedManualCount || 0); + if (failedCount > 0) { + this.showToast?.(`Auto-fix applied ${appliedCount}, failed ${failedCount}`, 'warning'); + } else { + const tail = skippedManualCount > 0 ? `, ${skippedManualCount} manual step(s) left` : ''; + this.showToast?.(`Auto-fix applied ${appliedCount}${tail}`, 'success'); + } + if (statusEl) statusEl.textContent = 'Safe auto-fix completed'; + } catch (error) { + this.showToast?.(`Safe auto-fix failed: ${String(error?.message || error)}`, 'error'); + if (statusEl) statusEl.textContent = ''; + } finally { + btnRepairSafe.disabled = false; + } + }); + repairEl?.addEventListener('click', async (event) => { + const target = event.target.closest('[data-diagnostics-repair]'); + if (!target) return; + const action = String(target.getAttribute('data-diagnostics-repair') || '').trim(); + if (!action) return; + target.disabled = true; + if (statusEl) statusEl.textContent = `Running repair: ${action}โ€ฆ`; + try { + const res = await fetch('/api/diagnostics/first-run/repair', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action }) + }); + const data = await res.json().catch(() => ({})); + if (!res.ok || data?.ok === false) { + throw new Error(String(data?.error || data?.message || `HTTP ${res.status}`)); + } + const repair = data?.repair || {}; + if (repair.manual) { + this.showToast?.(String(repair?.message || 'Manual action required'), 'warning'); + } else { + this.showToast?.(String(repair?.message || 'Repair completed'), 'success'); + } + if (data?.diagnostics) { + state.firstRun = data.diagnostics; + renderRepairActions(state.firstRun); + } else { + await refreshFirstRun(); + } + if (!state.base) await refreshBase(); + await refreshInstallWizard().catch(() => {}); + render(state.base, state.firstRun, state.wizard); + if (statusEl) statusEl.textContent = `Repair completed: ${action}`; + } catch (error) { + this.showToast?.(`Repair failed: ${String(error?.message || error)}`, 'error'); + if (statusEl) statusEl.textContent = ''; + } finally { + target.disabled = false; + } + }); + } + + openDiagnosticsPanel({ refresh = true } = {}) { + try { + document.getElementById('settings-panel')?.classList?.remove?.('hidden'); + setTimeout(() => { + try { + document.getElementById('diagnostics-output')?.scrollIntoView?.({ behavior: 'smooth', block: 'start' }); + } catch { + // ignore + } + if (!refresh) return; + if (typeof this.refreshDiagnosticsPanel === 'function') { + this.refreshDiagnosticsPanel(); + return; + } + try { + document.getElementById('diagnostics-refresh')?.click?.(); + } catch { + // ignore + } + }, 50); + } catch { + // ignore + } + } + + setupDependencySetupWizard() { + const modal = document.getElementById('dependency-setup-modal'); + const openBtn = document.getElementById('dependency-setup-open'); + const summaryEl = document.getElementById('dependency-setup-summary'); + const listEl = document.getElementById('dependency-setup-list'); + const closeBtn = document.getElementById('dependency-setup-close'); + if (!modal || !summaryEl || !listEl) return; + const body = document.body; + const isWindowsHost = (() => { + try { + const platform = String(navigator?.platform || '').toLowerCase(); + const userAgent = String(navigator?.userAgent || '').toLowerCase(); + return platform.includes('win') || userAgent.includes('windows'); + } catch { + return false; + } + })(); + + const setBootstrapPending = (pending) => { + if (!isWindowsHost) return; + if (pending) { + body?.classList?.add?.('dependency-onboarding-booting'); + body?.classList?.remove?.('dependency-onboarding-active'); + return; + } + body?.classList?.remove?.('dependency-onboarding-booting'); + }; + setBootstrapPending(true); + + const dismissKey = 'orchestrator-dependency-setup-dismissed-v3'; + const completedKey = 'orchestrator-dependency-onboarding-completed-v2'; + const progressKey = 'orchestrator-dependency-onboarding-progress-v2'; + const skippedStepsKey = 'orchestrator-dependency-onboarding-skipped-v1'; + const state = { + loading: false, + diagnostics: null, + actions: [], + currentStep: 0, + showWelcome: true, + skippedActionIds: new Set(), + actionRuns: new Map(), + actionRunPollers: new Map(), + gitIdentity: { + name: '', + email: '' + }, + gitIdentityHelpVisible: false + }; + + const readDismissed = () => { + try { + return localStorage.getItem(dismissKey) === 'true'; + } catch { + return false; + } + }; + + const writeDismissed = (value) => { + try { + if (value) localStorage.setItem(dismissKey, 'true'); + else localStorage.removeItem(dismissKey); + } catch { + // ignore + } + }; + + const readCompleted = () => { + try { + return localStorage.getItem(completedKey) === 'true'; + } catch { + return false; + } + }; + + const writeCompleted = (value) => { + try { + if (value) localStorage.setItem(completedKey, 'true'); + else localStorage.removeItem(completedKey); + } catch { + // ignore + } + }; + + const readSavedStep = () => { + try { + const raw = Number.parseInt(String(localStorage.getItem(progressKey) || ''), 10); + if (Number.isFinite(raw) && raw >= 0) return raw; + return 0; + } catch { + return 0; + } + }; + + const writeSavedStep = (step) => { + try { + localStorage.setItem(progressKey, String(Math.max(0, Number(step) || 0))); + } catch { + // ignore + } + }; + + const readSkippedStepIds = () => { + try { + const raw = localStorage.getItem(skippedStepsKey); + if (!raw) return new Set(); + const parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return new Set(); + const ids = parsed + .map((value) => String(value || '').trim()) + .filter(Boolean); + return new Set(ids); + } catch { + return new Set(); + } + }; + + const writeSkippedStepIds = () => { + try { + if (!(state.skippedActionIds instanceof Set) || state.skippedActionIds.size === 0) { + localStorage.removeItem(skippedStepsKey); + return; + } + localStorage.setItem(skippedStepsKey, JSON.stringify(Array.from(state.skippedActionIds))); + } catch { + // ignore + } + }; + + const setStepSkipped = (actionId, skipped) => { + const id = String(actionId || '').trim(); + if (!id) return; + if (skipped) state.skippedActionIds.add(id); + else state.skippedActionIds.delete(id); + writeSkippedStepIds(); + }; + + const toToolMap = (diagnostics) => { + const map = new Map(); + const tools = Array.isArray(diagnostics?.tools) ? diagnostics.tools : []; + tools.forEach((tool) => { + const id = String(tool?.id || '').trim(); + if (!id) return; + map.set(id, !!tool?.ok); + }); + return map; + }; + + const getToolResult = (diagnostics, toolId) => { + const id = String(toolId || '').trim(); + if (!id) return null; + const tools = Array.isArray(diagnostics?.tools) ? diagnostics.tools : []; + return tools.find((tool) => String(tool?.id || '').trim() === id) || null; + }; + + const parseGitIdentityVersion = (value) => { + const raw = String(value || '').trim(); + if (!raw) return { name: '', email: '' }; + const pair = raw.match(/^(.*)\s<([^<>]+)>$/); + if (pair?.[1] && pair?.[2]) { + return { + name: String(pair[1] || '').trim(), + email: String(pair[2] || '').trim() + }; + } + if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw)) { + return { name: '', email: raw }; + } + return { name: raw, email: '' }; + }; + + const stripAnsiText = (value) => String(value || '').replace(/\u001b\[[0-9;]*m/g, ''); + + const collectRunOutputLines = (runInfo, { limit = 25 } = {}) => { + const lines = Array.isArray(runInfo?.output) + ? runInfo.output + .map((entry) => stripAnsiText(String(entry?.line || '')).trim()) + .filter(Boolean) + : []; + if (!Number.isFinite(limit) || limit <= 0) return lines; + return lines.slice(-Math.max(1, Number(limit) || 1)); + }; + + const extractGithubLoginInfo = (lines = []) => { + const fallbackUrl = 'https://github.com/login/device'; + let link = fallbackUrl; + let code = ''; + let sawDeviceHint = false; + + (Array.isArray(lines) ? lines : []).forEach((lineRaw) => { + const line = String(lineRaw || '').trim(); + if (!line) return; + + if (/one[-\s]?time code|login\/device|authenticate in your web browser|copied to your clipboard|open this url/i.test(line)) { + sawDeviceHint = true; + } + + const linkMatch = line.match(/https:\/\/github\.com\/login\/device(?:\S*)?/i); + if (linkMatch?.[0]) link = linkMatch[0].trim(); + + const codeMatch = line.match(/\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/i); + if (codeMatch?.[1]) code = String(codeMatch[1]).toUpperCase(); + }); + + return { + link, + code, + sawDeviceHint + }; + }; + + const hydrateGitIdentityDraft = (diagnostics) => { + const gitIdentityTool = getToolResult(diagnostics, 'gitIdentity'); + const parsed = parseGitIdentityVersion(String(gitIdentityTool?.version || '')); + if (!state.gitIdentity.name && parsed.name) { + state.gitIdentity.name = parsed.name; + } + if (!state.gitIdentity.email && parsed.email) { + state.gitIdentity.email = parsed.email; + } + }; + + const getRequirementState = (toolsMap) => { + const gitOk = !!toolsMap.get('git'); + const claudeOk = !!toolsMap.get('claude'); + const codexOk = !!toolsMap.get('codex'); + const hasAgentCli = claudeOk || codexOk; + const coreReady = gitOk && hasAgentCli; + const missingCore = []; + if (!gitOk) missingCore.push('git'); + if (!hasAgentCli) missingCore.push('agent-cli'); + return { + gitOk, + claudeOk, + codexOk, + hasAgentCli, + coreReady, + missingCore + }; + }; + + const isActionComplete = (actionId, toolsMap) => { + switch (String(actionId || '').trim()) { + case 'install-git': + return !!toolsMap.get('git'); + case 'configure-git-identity': + return !!toolsMap.get('gitIdentity'); + case 'install-node': + return !!toolsMap.get('node') && !!toolsMap.get('npm'); + case 'install-gh': + return !!toolsMap.get('gh'); + case 'gh-login': + return !!toolsMap.get('ghAuth'); + case 'install-claude': + return !!toolsMap.get('claude'); + case 'install-codex': + return !!toolsMap.get('codex'); + default: + return false; + } + }; + + const getActionLevelText = (level) => { + if (level === 'required') return 'Required'; + if (level === 'optional') return 'Optional'; + if (level === 'core-option') return 'Core option'; + return 'Recommended'; + }; + + const getActionLevelClass = (level) => { + if (level === 'optional') return 'level-optional'; + return level === 'recommended' ? 'level-recommended' : 'level-required'; + }; + + const getActionStatusText = (actionId, done) => { + const id = String(actionId || '').trim(); + if (id === 'gh-login') return done ? 'Logged in' : 'Not logged in'; + if (id === 'configure-git-identity') return done ? 'Configured' : 'Not configured'; + return done ? 'Installed' : 'Missing'; + }; + + const getResolvedSteps = () => { + const toolsMap = toToolMap(state.diagnostics); + const actions = Array.isArray(state.actions) ? state.actions : []; + return actions.map((action) => { + const id = String(action?.id || '').trim(); + const level = getActionLevel(id); + const done = isActionComplete(id, toolsMap); + return { + ...action, + id, + level, + optional: action?.optional === true || level === 'optional', + done, + levelText: getActionLevelText(level), + levelClass: getActionLevelClass(level), + statusText: getActionStatusText(id, done), + statusClass: done ? 'status-ok' : 'status-missing', + runSupported: action?.runSupported !== false + }; + }); + }; + + const syncSkippedSteps = (steps) => { + if (!(state.skippedActionIds instanceof Set)) { + state.skippedActionIds = new Set(); + } + const validSkippedIds = new Set( + (Array.isArray(steps) ? steps : []) + .filter((step) => { + const id = String(step?.id || '').trim(); + return !!id && step?.optional && !step?.done; + }) + .map((step) => String(step?.id || '').trim()) + ); + let changed = false; + for (const id of Array.from(state.skippedActionIds)) { + if (!validSkippedIds.has(id)) { + state.skippedActionIds.delete(id); + changed = true; + } + } + if (changed) writeSkippedStepIds(); + }; + + const isOnboardingLocked = () => { + const toolsMap = toToolMap(state.diagnostics); + const req = getRequirementState(toolsMap); + if (!req?.coreReady) return true; + return !readCompleted(); + }; + + const applyOnboardingLockUI = () => { + const locked = isOnboardingLocked(); + if (closeBtn) { + closeBtn.disabled = locked; + closeBtn.style.visibility = locked ? 'hidden' : ''; + } + modal.setAttribute('data-onboarding-locked', locked ? 'true' : 'false'); + return locked; + }; + + const setCurrentStep = (nextStep, { persist = true } = {}) => { + const previousStep = state.currentStep; + const maxStep = Math.max(0, (Array.isArray(state.actions) ? state.actions.length : 0) - 1); + const parsed = Number.parseInt(String(nextStep), 10); + const safe = Number.isFinite(parsed) ? parsed : 0; + state.currentStep = Math.max(0, Math.min(safe, maxStep)); + if (state.currentStep !== previousStep) { + state.gitIdentityHelpVisible = false; + } + if (persist) writeSavedStep(state.currentStep); + return state.currentStep; + }; + + const getActionLevel = (actionId) => { + const id = String(actionId || '').trim(); + if (id === 'install-git') return 'required'; + if (id === 'configure-git-identity') return 'optional'; + if (id === 'install-gh' || id === 'gh-login') return 'optional'; + if (id === 'install-claude') return 'optional'; + if (id === 'install-codex') return 'optional'; + return 'recommended'; + }; + + const buildStepIconSvg = (iconMarkup) => ( + `` + ); + + const stepIconSvgByActionId = Object.freeze({ + 'install-git': buildStepIconSvg(''), + 'configure-git-identity': buildStepIconSvg(''), + 'install-node': buildStepIconSvg(''), + 'install-gh': buildStepIconSvg(''), + 'gh-login': buildStepIconSvg(''), + 'install-claude': buildStepIconSvg(''), + 'install-codex': buildStepIconSvg('') + }); + + const getStepIconSvg = (actionId) => { + const id = String(actionId || '').trim(); + return stepIconSvgByActionId[id] + || buildStepIconSvg(''); + }; + + const render = () => { + const toolsMap = toToolMap(state.diagnostics); + const req = getRequirementState(toolsMap); + const steps = getResolvedSteps(); + syncSkippedSteps(steps); + if (!steps.length) { + summaryEl.textContent = 'No setup actions are available for this platform.'; + listEl.innerHTML = '
No setup actions are available for this platform.
'; + return { req, steps, current: null }; + } + + setCurrentStep(state.currentStep, { persist: false }); + const current = steps[state.currentStep]; + const stepNo = state.currentStep + 1; + const totalSteps = steps.length; + const detectedCount = steps.filter((step) => step.done).length; + const doneRatio = totalSteps > 0 ? Math.round((detectedCount / totalSteps) * 100) : 0; + const missingCore = []; + if (!req.gitOk) missingCore.push('Git'); + if (!req.hasAgentCli) missingCore.push('Claude Code or Codex CLI'); + + if (state.showWelcome) { + summaryEl.textContent = ''; + listEl.innerHTML = ` +
+

Letโ€™s get you ready in a minute.

+

+ Weโ€™ll check your system and install whatโ€™s needed. + Optional tools can be skipped. +

+
+ +
+
`; + return { req, steps, current }; + } + + summaryEl.textContent = ''; + + const currentId = String(current?.id || '').trim(); + const currentStepIconSvg = getStepIconSvg(currentId); + const currentTitle = this.escapeHtml(String(current?.title || currentId || 'Setup action')); + const currentDesc = this.escapeHtml(String(current?.description || '')); + const commandRaw = String(current?.command || ''); + const runInfo = state.actionRuns.get(currentId) || null; + const runStatus = String(runInfo?.status || '').trim().toLowerCase(); + const isRunning = runStatus === 'running'; + const isVerifying = runStatus === 'verifying'; + const isFinalizing = runStatus === 'success' || runStatus === 'completed'; + const isRunBusy = isRunning || isVerifying || isFinalizing; + const isGitIdentityStep = currentId === 'configure-git-identity'; + const runOutputAll = collectRunOutputLines(runInfo, { limit: 160 }); + const runOutput = runOutputAll.slice(-8); + const runOutputText = this.escapeHtml(runOutput.join('\n')); + const shouldShowInstallerOutput = currentId !== 'gh-login' && !isGitIdentityStep && ( + runOutput.length > 0 || + isRunBusy || + runStatus === 'failed' || + runStatus === 'needs-attention' + ); + const installerOutputText = runOutput.length + ? runOutputText + : this.escapeHtml( + isRunning + ? 'Installer started. Waiting for output...' + : (isVerifying + ? 'Installer finished. Verifying dependency...' + : 'No installer output captured yet.') + ); + const githubDeviceUrl = 'https://github.com/login/device'; + const ghInstalled = !!toolsMap.get('gh'); + const ghLoggedIn = !!toolsMap.get('ghAuth'); + const ghLoginRunInfo = state.actionRuns.get('gh-login') || null; + const ghLoginRunStatus = String(ghLoginRunInfo?.status || '').trim().toLowerCase(); + const ghLoginIsRunning = ghLoginRunStatus === 'running'; + const ghLoginIsVerifying = ghLoginRunStatus === 'verifying'; + const ghLoginIsFinalizing = ghLoginRunStatus === 'success' || ghLoginRunStatus === 'completed'; + const ghLoginIsBusy = ghLoginIsRunning || ghLoginIsVerifying || ghLoginIsFinalizing; + const ghLoginOutputAll = collectRunOutputLines(ghLoginRunInfo, { limit: 160 }); + const ghLoginInfo = extractGithubLoginInfo(ghLoginOutputAll); + const ghLoginLink = String(ghLoginRunInfo?.ghDeviceUrl || ghLoginInfo.link || githubDeviceUrl).trim() || githubDeviceUrl; + const ghLoginCode = String(ghLoginRunInfo?.ghDeviceCode || ghLoginInfo.code || '').trim().toUpperCase(); + const ghLoginHasSignal = !!( + ghLoginRunInfo?.ghHasDeviceHint + || ghLoginInfo.sawDeviceHint + || ghLoginCode + || ghLoginLink !== githubDeviceUrl + ); + const ghLoginUiPhase = (() => { + if (!ghInstalled || ghLoggedIn) return 'none'; + if (!ghLoginRunInfo) return 'start'; + if (ghLoginCode) return 'code'; + if (ghLoginIsBusy) return 'wait-code'; + return 'retry'; + })(); + const ghLoginInlineStatusText = (() => { + if (!ghInstalled) return 'Install GitHub CLI first'; + if (ghLoggedIn) return 'Logged in'; + if (ghLoginIsFinalizing) return 'Finalizing login'; + if (ghLoginIsRunning) return 'Signing in'; + if (ghLoginIsVerifying) return 'Checking login'; + return 'Not logged in'; + })(); + const ghLoginInlineStatusClass = ghLoggedIn + ? 'status-ok' + : ((ghLoginIsBusy || ghLoginRunStatus === 'needs-attention') ? 'status-pending' : 'status-missing'); + const ghLoginInlineRunLabel = (() => { + if (ghLoggedIn) return 'Logged in'; + if (ghLoginIsFinalizing) return 'Finalizing...'; + if (ghLoginIsBusy) return 'Waiting...'; + return 'Start login'; + })(); + const ghLoginInlineRunDisabled = !ghInstalled || ghLoggedIn || ghLoginIsBusy; + const showInlineGhLogin = currentId === 'install-gh' && ghInstalled; + const isGhLoginStep = currentId === 'gh-login'; + const codexNeedsNode = currentId === 'install-codex' && !(toolsMap.get('node') && toolsMap.get('npm')); + const gitIdentityName = this.escapeHtml(String(state.gitIdentity?.name || '')); + const gitIdentityEmail = this.escapeHtml(String(state.gitIdentity?.email || '')); + const showRunButton = current?.runSupported !== false && !isGitIdentityStep && !(isGhLoginStep && current?.done); + const runDisabled = !!current?.done || runStatus === 'verified' || isRunBusy || codexNeedsNode; + const runLabel = (() => { + if (current?.done || runStatus === 'verified') { + if (currentId === 'gh-login') return 'Logged in'; + if (currentId === 'configure-git-identity') return 'Configured'; + return 'Installed'; + } + if (isFinalizing) return 'Finalizing...'; + if (isRunBusy) return isGhLoginStep ? 'Waiting...' : 'Running...'; + if (currentId === 'gh-login') return 'Start login'; + return 'Run step'; + })(); + const baseStatusText = String(current?.statusText || (current?.done ? 'Installed' : 'Missing')); + const statusText = (() => { + if (runStatus === 'verified') return baseStatusText; + if (isFinalizing) return isGhLoginStep ? 'Finalizing login' : 'Finalizing'; + if (isRunning) return isGhLoginStep ? 'Signing in' : (isGitIdentityStep ? 'Saving' : 'Installing'); + if (isVerifying) return isGhLoginStep ? 'Checking login' : (isGitIdentityStep ? 'Checking' : 'Verifying'); + if (runStatus === 'failed') return isGhLoginStep ? 'Login failed' : (isGitIdentityStep ? 'Save failed' : 'Failed'); + return baseStatusText; + })(); + const statusClass = current?.done || runStatus === 'verified' + ? 'status-ok' + : ((isRunning || isVerifying || isFinalizing) ? 'status-pending' : (runStatus === 'failed' ? 'status-missing' : (current?.statusClass || 'status-missing'))); + let guidance = 'Run this step. We will detect completion automatically.'; + if (current?.done || runStatus === 'verified') { + guidance = isGhLoginStep + ? 'GitHub CLI is authenticated. Continue to the next step.' + : (currentId === 'install-gh' + ? 'GitHub CLI is installed. Optional next step: sign in with GitHub to enable PR and repo actions.' + : (isGitIdentityStep + ? 'Git identity is configured. Continue to the next step.' + : 'Already installed on this machine. Continue to the next step.')); + } else if (isRunning) { + guidance = isGhLoginStep + ? 'GitHub login started. Use the browser flow below and keep this window open; we recheck automatically.' + : (isGitIdentityStep + ? 'Saving Git identity now. Keep this window open and we will recheck automatically.' + : 'Installing now via PowerShell. Keep this window open and we will recheck automatically.'); + } else if (isVerifying) { + guidance = isGhLoginStep + ? 'Checking GitHub login automatically...' + : (isGitIdentityStep + ? 'Git identity saved. Checking your system automatically...' + : 'Install command finished. Checking your system automatically...'); + } else if (isFinalizing) { + guidance = isGhLoginStep + ? 'Login command finished. Finalizing and checking status automatically...' + : 'Install command finished. Finalizing and checking your system automatically...'; + } else if (runStatus === 'failed') { + const errorText = String(runInfo?.error || '').trim(); + guidance = errorText + ? `${isGhLoginStep ? 'Login failed' : (isGitIdentityStep ? 'Save failed' : 'Install failed')}: ${errorText}` + : `${isGhLoginStep ? 'Login failed' : (isGitIdentityStep ? 'Save failed' : 'Install failed')}. Review and run the step again.`; + } else if (runStatus === 'needs-attention') { + guidance = isGhLoginStep + ? (ghLoginHasSignal + ? 'GitHub login is not detected yet. If browser sign-in is complete, click Start login again to request a new code.' + : 'GitHub CLI did not return a one-time code. Click Start login again; if needed, reinstall GitHub CLI first.') + : (isGitIdentityStep + ? 'Git identity was saved, but it is still not detected. Check your values and save again.' + : 'Install command finished, but this dependency is still not detected. Review output below and run again.'); + } else if (isGitIdentityStep) { + guidance = 'Enter your Git name and email, then click Save identity. We will detect it automatically.'; + } else if (isGhLoginStep) { + guidance = 'Optional after GitHub CLI install. Start login when you are ready.'; + } else if (codexNeedsNode) { + guidance = 'Install Node.js LTS first. Codex uses npm and cannot be installed until Node is detected.'; + } else if (!current?.runSupported && current?.optional) { + guidance = 'Optional but strongly recommended: set Git user.name and user.email so commits and PR authorship are correct.'; + } else if (!current?.runSupported) { + guidance = 'Manual step: run the command below in your terminal. We will detect it automatically afterward.'; + } + if (currentId === 'install-gh' && (current?.done || runStatus === 'verified')) { + if (ghLoggedIn) { + guidance = 'GitHub CLI is installed and authenticated. You can continue.'; + } else if (ghLoginIsRunning) { + guidance = 'GitHub login started. Use the browser flow in this step and keep this window open.'; + } else if (ghLoginIsVerifying) { + guidance = 'Checking GitHub login automatically...'; + } else if (ghLoginIsFinalizing) { + guidance = 'Login command finished. Finalizing and checking status automatically...'; + } else if (ghLoginRunStatus === 'needs-attention') { + guidance = ghLoginHasSignal + ? 'GitHub login is not detected yet. If browser sign-in is complete, click Start login again to request a new code.' + : 'GitHub CLI did not return a one-time code. Click Start login again.'; + } + } + const canAdvance = !!current?.done || !!current?.optional; + const nextLabel = !canAdvance + ? 'Complete this step first' + : (!current?.done && current?.optional + ? 'Skip' + : (stepNo >= totalSteps ? 'Finish onboarding' : 'Next step')); + + listEl.innerHTML = ` +
+ ${steps.map((step, idx) => { + const isActive = idx === state.currentStep; + const actionId = String(step?.id || '').trim(); + const isSkipped = state.skippedActionIds.has(actionId); + const isDone = step.done || isSkipped; + const isPast = idx < state.currentStep; + const isFuture = idx > state.currentStep; + let statusClass = 'stepper-upcoming'; + if (isActive) { + statusClass = 'stepper-active'; + } else if (isPast && isDone) { + statusClass = 'stepper-done'; + } else { + statusClass = 'stepper-upcoming'; + } + const stepStateLabel = isActive + ? 'Current step' + : (isPast && isDone ? 'Completed' : (isFuture ? 'Upcoming' : 'Pending')); + return ` +
+
+ ${isActive ? `Step ${stepNo}` : ''} +
+
+
+ `; + }).join('')} +
+ +
+
+ ${currentStepIconSvg} +
+
+

${currentTitle}

+ +
+ ${current?.done ? '' : ''} +

${currentDesc} ${statusText ? `(${statusText})` : ''}

+
+ + ${isGitIdentityStep ? ` +
+
+ + + +
+
+ ` : ''} + + ${showInlineGhLogin ? ` + + ` : ''} + + ${shouldShowInstallerOutput ? ` +
+
${installerOutputText}
+
+ ` : ''} + +
+ ${showRunButton ? `` : ''} + ${!isGhLoginStep && !isGitIdentityStep ? `` : ''} +
+
+
+ +
+ + +
`; + + return { req, steps, current }; + }; + + const closeModal = ({ force = false } = {}) => { + const locked = applyOnboardingLockUI(); + if (!force && locked) { + openModal(); + return false; + } + modal.classList.add('hidden'); + body?.classList?.remove?.('dependency-onboarding-active'); + setBootstrapPending(false); + return true; + }; + const openModal = ({ showWelcome = null } = {}) => { + const wasHidden = modal.classList.contains('hidden'); + modal.classList.remove('hidden'); + setBootstrapPending(false); + body?.classList?.add?.('dependency-onboarding-active'); + if (typeof showWelcome === 'boolean') { + state.showWelcome = showWelcome; + } else if (wasHidden) { + state.showWelcome = true; + } + if (state.diagnostics && Array.isArray(state.actions) && state.actions.length > 0) { + render(); + } + applyOnboardingLockUI(); + }; + + const setLoading = (loading) => { + state.loading = !!loading; + if (openBtn) openBtn.disabled = state.loading; + if (state.loading) { + summaryEl.textContent = ''; + } + }; + + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0))); + + const loadAndRender = async ({ open = false, forceAutoShow = false, bootstrap = false } = {}) => { + if (state.loading) return false; + setLoading(true); + try { + const [diagRes, actionsRes] = await Promise.all([ + fetch('/api/diagnostics'), + fetch('/api/setup-actions') + ]); + const diagData = await diagRes.json().catch(() => ({})); + const actionsData = await actionsRes.json().catch(() => ({})); + + if (!diagRes.ok || diagData?.ok === false) { + throw new Error(String(diagData?.error || `Diagnostics HTTP ${diagRes.status}`)); + } + if (!actionsRes.ok || actionsData?.ok === false) { + throw new Error(String(actionsData?.error || `Setup actions HTTP ${actionsRes.status}`)); + } + + state.diagnostics = diagData; + hydrateGitIdentityDraft(diagData); + const allActions = Array.isArray(actionsData?.actions) ? actionsData.actions : []; + const toolsMap = toToolMap(diagData); + state.actions = allActions.filter((action) => String(action?.id || '').trim() !== 'gh-login'); + const allowedActionIds = new Set( + state.actions + .map((action) => String(action?.id || '').trim()) + .filter(Boolean) + ); + const persistedSkippedIds = readSkippedStepIds(); + state.skippedActionIds = new Set( + Array.from(persistedSkippedIds).filter((id) => allowedActionIds.has(id)) + ); + if (state.actions.length > 0) { + const savedStep = readSavedStep(); + setCurrentStep(savedStep, { persist: false }); + } + const view = render(); + applyOnboardingLockUI(); + if (view.req?.coreReady) writeDismissed(false); + + const shouldAutoShow = forceAutoShow || (!readDismissed() && (!readCompleted() || !(view.req?.coreReady))); + if (open || shouldAutoShow) { + openModal(); + } else { + setBootstrapPending(false); + } + return true; + } catch (err) { + summaryEl.textContent = `Dependency check failed: ${String(err?.message || err)}`; + listEl.innerHTML = '
Unable to load setup actions right now.
'; + if (open) openModal(); + else if (!bootstrap) setBootstrapPending(false); + return false; + } finally { + setLoading(false); + } + }; + + const stopRunPolling = (actionId) => { + const id = String(actionId || '').trim(); + if (!id) return; + const poller = state.actionRunPollers.get(id); + if (poller?.timer) clearTimeout(poller.timer); + state.actionRunPollers.delete(id); + }; + + const updateActionRunState = (actionId, patch = {}, { rerender = true } = {}) => { + const id = String(actionId || '').trim(); + if (!id) return null; + const prev = state.actionRuns.get(id) || { actionId: id }; + const next = { + ...prev, + ...patch, + actionId: id + }; + state.actionRuns.set(id, next); + if (rerender) render(); + return next; + }; + + const fetchSetupActionRunStatus = async ({ runId = '', actionId = '' } = {}) => { + const params = new URLSearchParams(); + if (runId) params.set('runId', String(runId)); + if (actionId) params.set('actionId', String(actionId)); + const res = await fetch(`/api/setup-actions/run-status?${params.toString()}`); + const data = await res.json().catch(() => ({})); + if (!res.ok || data?.ok === false || !data?.run) { + throw new Error(String(data?.error || `HTTP ${res.status}`)); + } + return data.run; + }; + + const getVerifyPolicyForAction = (actionId) => { + const id = String(actionId || '').trim(); + if (id === 'gh-login') { + return { attempts: 14, delayMs: 900 }; + } + if (id === 'install-git' || id === 'install-node' || id === 'install-gh') { + return { attempts: 10, delayMs: 650 }; + } + return { attempts: 8, delayMs: 650 }; + }; + + const verifyActionInstalled = async (actionId, runId, options = {}) => { + const id = String(actionId || '').trim(); + if (!id) return false; + const policy = { + ...getVerifyPolicyForAction(id), + ...(options && typeof options === 'object' ? options : {}) + }; + const attempts = Math.max(1, Number(policy.attempts) || 1); + const delayMs = Math.max(250, Number(policy.delayMs) || 650); + + for (let attempt = 1; attempt <= attempts; attempt += 1) { + const runState = state.actionRuns.get(id); + if (!runState || String(runState?.runId || '') !== String(runId || '')) return false; + + updateActionRunState(id, { + status: 'verifying', + verifyAttempt: attempt, + verifyMax: attempts, + updatedAt: new Date().toISOString() + }); + + await loadAndRender({ open: true, forceAutoShow: true }); + const toolsMap = toToolMap(state.diagnostics); + if (isActionComplete(id, toolsMap)) { + updateActionRunState(id, { + status: 'verified', + verifyAttempt: attempts, + verifyMax: attempts, + updatedAt: new Date().toISOString() + }); + this.showToast('Dependency detected automatically.', 'success'); + return true; + } + await sleep(delayMs); + } + + updateActionRunState(id, { + status: 'needs-attention', + updatedAt: new Date().toISOString() + }); + this.showToast( + id === 'gh-login' + ? 'GitHub login is not detected yet. Complete sign-in in browser and try again.' + : 'Install finished but dependency is still missing. Review output and run again if needed.', + 'warning' + ); + return false; + }; + + const verifyActionWithoutRun = async (actionId, options = {}) => { + const id = String(actionId || '').trim(); + if (!id) return false; + const policy = { + ...getVerifyPolicyForAction(id), + ...(options && typeof options === 'object' ? options : {}) + }; + const attempts = Math.max(1, Number(policy.attempts) || 1); + const delayMs = Math.max(250, Number(policy.delayMs) || 650); + + for (let attempt = 1; attempt <= attempts; attempt += 1) { + updateActionRunState(id, { + status: 'verifying', + verifyAttempt: attempt, + verifyMax: attempts, + updatedAt: new Date().toISOString() + }); + + await loadAndRender({ open: true, forceAutoShow: true }); + const toolsMap = toToolMap(state.diagnostics); + if (isActionComplete(id, toolsMap)) { + updateActionRunState(id, { + status: 'verified', + verifyAttempt: attempts, + verifyMax: attempts, + updatedAt: new Date().toISOString() + }); + return true; + } + if (attempt < attempts) { + await sleep(delayMs); + } + } + + updateActionRunState(id, { + status: 'needs-attention', + updatedAt: new Date().toISOString() + }); + return false; + }; + + const pollRunUntilDone = async (actionId, runId) => { + const id = String(actionId || '').trim(); + const rid = String(runId || '').trim(); + if (!id || !rid) return; + + stopRunPolling(id); + const pollLoop = async () => { + try { + const run = await fetchSetupActionRunStatus({ runId: rid, actionId: id }); + updateActionRunState(id, run); + + if (String(run?.status || '').toLowerCase() === 'running') { + const timer = setTimeout(pollLoop, 850); + state.actionRunPollers.set(id, { runId: rid, timer }); + return; + } + + stopRunPolling(id); + await loadAndRender({ open: true, forceAutoShow: true }); + + if (String(run?.status || '').toLowerCase() === 'success') { + await verifyActionInstalled(id, rid); + return; + } + + if (String(run?.status || '').toLowerCase() === 'failed') { + if (id === 'gh-login') { + const ghRunLines = collectRunOutputLines(run, { limit: 160 }); + const ghRunInfo = extractGithubLoginInfo(ghRunLines); + const hasDeviceSignal = ghRunInfo.sawDeviceHint || !!ghRunInfo.code || /login\/device/i.test(ghRunLines.join('\n')); + const verifyOptions = hasDeviceSignal + ? { attempts: 14, delayMs: 900 } + : { attempts: 5, delayMs: 700 }; + const detected = await verifyActionWithoutRun(id, verifyOptions); + if (detected) { + this.showToast('GitHub login detected automatically.', 'success'); + return; + } + + const runError = String(run?.error || '').trim(); + const exitCode = Number(run?.exitCode); + const interrupted = exitCode === 1 || /code\s*1/i.test(runError) || /cancel|not completed/i.test(runError); + const missingDeviceCode = !hasDeviceSignal; + updateActionRunState(id, { + status: 'needs-attention', + error: interrupted + ? 'Login was not completed in browser.' + : (missingDeviceCode + ? 'GitHub CLI did not return a one-time code.' + : (runError || 'Login was not completed.')), + updatedAt: new Date().toISOString() + }); + this.showToast( + missingDeviceCode + ? 'GitHub CLI did not return a one-time code. Click Start login again.' + : (interrupted + ? 'GitHub login is still not detected. If browser sign-in just finished, click Start login again.' + : `GitHub login failed: ${runError || 'Unknown error'}`), + (interrupted || missingDeviceCode) ? 'warning' : 'error' + ); + return; + } + this.showToast(`Install failed: ${String(run?.error || 'Unknown error')}`, 'error'); + } + } catch (err) { + stopRunPolling(id); + updateActionRunState(id, { + status: 'failed', + error: String(err?.message || err || 'Failed to fetch setup status'), + updatedAt: new Date().toISOString() + }); + this.showToast(`Install monitoring failed: ${String(err?.message || err)}`, 'error'); + } + }; + + const timer = setTimeout(pollLoop, 250); + state.actionRunPollers.set(id, { runId: rid, timer }); + }; + + const runBootstrapLoad = async () => { + const delaysMs = [0, 240, 420, 700, 1050, 1450, 1900]; + for (let attempt = 0; attempt < delaysMs.length; attempt += 1) { + if (attempt > 0) { + await sleep(delaysMs[attempt]); + } + const ok = await loadAndRender({ open: false, forceAutoShow: false, bootstrap: true }); + if (ok) return; + } + setBootstrapPending(false); + }; + + const runSetupAction = async (actionId, btnEl) => { + const id = String(actionId || '').trim(); + if (!id) return; + const button = btnEl || null; + if (button) button.disabled = true; + try { + const existingRunStatus = String(state.actionRuns.get(id)?.status || '').trim().toLowerCase(); + if (existingRunStatus === 'running' || existingRunStatus === 'verifying' || existingRunStatus === 'success' || existingRunStatus === 'completed') { + this.showToast( + id === 'gh-login' + ? 'Login is still in progress. Please wait while we finish checking.' + : 'Install is still in progress. Please wait while we finish checking.', + 'info' + ); + return; + } + + const existingStep = getResolvedSteps().find((step) => String(step?.id || '').trim() === id); + const toolsMap = toToolMap(state.diagnostics); + if (id === 'gh-login' && !toToolMap(state.diagnostics).get('gh')) { + updateActionRunState(id, { + status: 'needs-attention', + error: 'Install GitHub CLI before starting login.', + updatedAt: new Date().toISOString() + }); + this.showToast('Install GitHub CLI first. Login is optional and only available after installation.', 'warning'); + await loadAndRender({ open: true, forceAutoShow: true }); + return; + } + if (id === 'install-codex' && !(toolsMap.get('node') && toolsMap.get('npm'))) { + updateActionRunState(id, { + status: 'needs-attention', + error: 'Install Node.js LTS first. Codex requires npm.', + updatedAt: new Date().toISOString() + }); + this.showToast('Install Node.js LTS first. Codex depends on npm.', 'warning'); + await loadAndRender({ open: true, forceAutoShow: true }); + return; + } + if (existingStep?.done) { + updateActionRunState(id, { + status: 'verified', + updatedAt: new Date().toISOString() + }); + this.showToast('Dependency already detected.', 'success'); + await loadAndRender({ open: true, forceAutoShow: true }); + return; + } + + updateActionRunState(id, { + runId: null, + status: 'running', + error: null, + output: [], + verifyAttempt: 0, + verifyMax: 0, + updatedAt: new Date().toISOString() + }); + const res = await fetch('/api/setup-actions/run', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ actionId: id }) + }); + const data = await res.json().catch(() => ({})); + if (!res.ok || data?.ok === false) { + throw new Error(String(data?.error || `HTTP ${res.status}`)); } - if (!state.base) await refreshBase(); - await refreshInstallWizard().catch(() => {}); - render(state.base, state.firstRun, state.wizard); - - const appliedCount = Number(data?.appliedCount || 0); - const failedCount = Number(data?.failedCount || 0); - const skippedManualCount = Number(data?.skippedManualCount || 0); - if (failedCount > 0) { - this.showToast?.(`Auto-fix applied ${appliedCount}, failed ${failedCount}`, 'warning'); + const run = (data?.run && typeof data.run === 'object') ? data.run : null; + if (run) { + updateActionRunState(id, { + ...run, + verifyAttempt: 0, + verifyMax: 0 + }); + if (run?.runId) { + await pollRunUntilDone(id, run.runId); + } else { + await loadAndRender({ open: true, forceAutoShow: true }); + } } else { - const tail = skippedManualCount > 0 ? `, ${skippedManualCount} manual step(s) left` : ''; - this.showToast?.(`Auto-fix applied ${appliedCount}${tail}`, 'success'); + await loadAndRender({ open: true, forceAutoShow: true }); } - if (statusEl) statusEl.textContent = 'Safe auto-fix completed'; - } catch (error) { - this.showToast?.(`Safe auto-fix failed: ${String(error?.message || error)}`, 'error'); - if (statusEl) statusEl.textContent = ''; + const defaultMessage = data?.alreadyRunning + ? (id === 'gh-login' + ? 'GitHub login is already running. Complete it in your browser.' + : 'Install is already running. Watching for completion...') + : (id === 'gh-login' + ? 'GitHub login started. Complete sign-in in your browser.' + : 'Install started. We will check this step automatically.'); + this.showToast(String(data?.message || defaultMessage), 'info'); + } catch (err) { + updateActionRunState(id, { + status: 'failed', + error: String(err?.message || err), + updatedAt: new Date().toISOString() + }); + this.showToast(`Failed to start action: ${String(err?.message || err)}`, 'error'); } finally { - btnRepairSafe.disabled = false; + if (button) button.disabled = false; } - }); - repairEl?.addEventListener('click', async (event) => { - const target = event.target.closest('[data-diagnostics-repair]'); - if (!target) return; - const action = String(target.getAttribute('data-diagnostics-repair') || '').trim(); - if (!action) return; - target.disabled = true; - if (statusEl) statusEl.textContent = `Running repair: ${action}โ€ฆ`; + }; + + const saveGitIdentity = async (btnEl) => { + const button = btnEl || null; + const id = 'configure-git-identity'; + const nameInput = listEl.querySelector('[data-setup-git-name]'); + const emailInput = listEl.querySelector('[data-setup-git-email]'); + const name = String(nameInput?.value || state.gitIdentity?.name || '').trim(); + const email = String(emailInput?.value || state.gitIdentity?.email || '').trim(); + + state.gitIdentity.name = name; + state.gitIdentity.email = email; + + if (!name || !email) { + this.showToast('Enter both Git name and email.', 'warning'); + return; + } + + if (button) button.disabled = true; try { - const res = await fetch('/api/diagnostics/first-run/repair', { + updateActionRunState(id, { + runId: 'manual-git-identity', + status: 'running', + error: null, + output: [], + verifyAttempt: 0, + verifyMax: 0, + updatedAt: new Date().toISOString() + }); + + const res = await fetch('/api/setup-actions/configure-git-identity', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action }) + body: JSON.stringify({ name, email }) }); const data = await res.json().catch(() => ({})); if (!res.ok || data?.ok === false) { - throw new Error(String(data?.error || data?.message || `HTTP ${res.status}`)); + throw new Error(String(data?.error || `HTTP ${res.status}`)); } - const repair = data?.repair || {}; - if (repair.manual) { - this.showToast?.(String(repair?.message || 'Manual action required'), 'warning'); + + state.gitIdentity.name = String(data?.name || name).trim(); + state.gitIdentity.email = String(data?.email || email).trim(); + + const detected = await verifyActionWithoutRun(id, { attempts: 6, delayMs: 350 }); + if (detected) { + this.showToast('Git identity saved and detected automatically.', 'success'); } else { - this.showToast?.(String(repair?.message || 'Repair completed'), 'success'); + this.showToast('Git identity saved, but detection is delayed. Try saving again in a few seconds.', 'warning'); } - if (data?.diagnostics) { - state.firstRun = data.diagnostics; - renderRepairActions(state.firstRun); - } else { - await refreshFirstRun(); - } - if (!state.base) await refreshBase(); - await refreshInstallWizard().catch(() => {}); - render(state.base, state.firstRun, state.wizard); - if (statusEl) statusEl.textContent = `Repair completed: ${action}`; - } catch (error) { - this.showToast?.(`Repair failed: ${String(error?.message || error)}`, 'error'); - if (statusEl) statusEl.textContent = ''; + } catch (err) { + updateActionRunState(id, { + status: 'failed', + error: String(err?.message || err), + updatedAt: new Date().toISOString() + }); + this.showToast(`Failed to save Git identity: ${String(err?.message || err)}`, 'error'); } finally { - target.disabled = false; + if (button) button.disabled = false; + await loadAndRender({ open: true, forceAutoShow: true }); + } + }; + + listEl.addEventListener('click', async (event) => { + const runBtn = event.target.closest('[data-setup-run]'); + if (runBtn) { + await runSetupAction(runBtn.getAttribute('data-setup-run'), runBtn); + return; + } + + const saveGitBtn = event.target.closest('[data-setup-git-save]'); + if (saveGitBtn) { + await saveGitIdentity(saveGitBtn); + return; + } + + const prevBtn = event.target.closest('[data-setup-prev]'); + if (prevBtn) { + setCurrentStep(state.currentStep - 1); + render(); + return; + } + + const nextBtn = event.target.closest('[data-setup-next]'); + if (nextBtn) { + const total = Array.isArray(state.actions) ? state.actions.length : 0; + const steps = getResolvedSteps(); + syncSkippedSteps(steps); + const currentStep = steps[state.currentStep]; + if (!currentStep?.done) { + if (!currentStep?.optional) { + this.showToast('Install this dependency before continuing.', 'warning'); + return; + } + setStepSkipped(currentStep?.id, true); + this.showToast('Skipping optional setup for now. You can configure it later.', 'warning'); + } else { + setStepSkipped(currentStep?.id, false); + } + if (state.currentStep >= (total - 1)) { + writeCompleted(true); + writeDismissed(false); + closeModal({ force: true }); + this.showToast('Dependency onboarding complete.', 'success'); + return; + } + setCurrentStep(state.currentStep + 1); + render(); + return; + } + const beginBtn = event.target.closest('[data-setup-begin]'); + if (beginBtn) { + state.showWelcome = false; + render(); + return; + } + + const jumpBtn = event.target.closest('[data-setup-jump]'); + if (jumpBtn) { + const idx = Number.parseInt(String(jumpBtn.getAttribute('data-setup-jump') || ''), 10); + if (Number.isFinite(idx)) { + setCurrentStep(idx); + render(); + } + return; + } + + const copyBtn = event.target.closest('[data-setup-copy-id]'); + if (copyBtn) { + const actionId = String(copyBtn.getAttribute('data-setup-copy-id') || '').trim(); + const action = (Array.isArray(state.actions) ? state.actions : []).find((item) => String(item?.id || '').trim() === actionId); + const command = String(action?.command || '').trim(); + if (!command) return; + try { + await navigator.clipboard.writeText(command); + this.showToast('Command copied to clipboard.', 'success'); + } catch (err) { + this.showToast(`Copy failed: ${String(err?.message || err)}`, 'error'); + } + return; + } + + const copyGhCodeBtn = event.target.closest('[data-setup-copy-gh-code]'); + if (copyGhCodeBtn) { + event.preventDefault(); + event.stopPropagation(); + const code = String(copyGhCodeBtn.getAttribute('data-setup-copy-gh-code') || '').trim(); + if (!code) return; + try { + await navigator.clipboard.writeText(code); + this.showToast('GitHub one-time code copied.', 'success'); + } catch (err) { + this.showToast(`Copy failed: ${String(err?.message || err)}`, 'error'); + } + return; + } + + const openGhLoginBtn = event.target.closest('[data-setup-open-gh-login]'); + if (openGhLoginBtn) { + event.preventDefault(); + event.stopPropagation(); + const link = String(openGhLoginBtn.getAttribute('data-setup-open-gh-login') || '').trim(); + if (!link) return; + try { + const res = await fetch('/api/setup-actions/open-url', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: link }) + }); + const data = await res.json().catch(() => ({})); + if (!res.ok || data?.ok === false) { + throw new Error(String(data?.error || `HTTP ${res.status}`)); + } + this.showToast('Opened GitHub login in your browser.', 'info'); + } catch (err) { + this.showToast(`Could not open login link: ${String(err?.message || err)}`, 'error'); + } + return; + } + + const toggleGitHelpBtn = event.target.closest('[data-setup-toggle-git-help]'); + if (toggleGitHelpBtn) { + event.preventDefault(); + event.stopPropagation(); + state.gitIdentityHelpVisible = !state.gitIdentityHelpVisible; + render(); + return; } }); - } + + if (openBtn) { + openBtn.addEventListener('click', () => { + writeDismissed(false); + setCurrentStep(0); + loadAndRender({ open: true, forceAutoShow: true }); + }); + } + if (closeBtn) { + closeBtn.addEventListener('click', () => closeModal()); + } + + modal.addEventListener('click', (event) => { + if (event.target === modal) closeModal(); + }); + + document.addEventListener('keydown', (event) => { + if (event.key !== 'Escape') return; + if (modal.classList.contains('hidden')) return; + closeModal(); + }); + + runBootstrapLoad(); + } notifyWorkflow({ type = 'info', message = '', sessionId = null, metadata = null } = {}) { const msg = String(message || '').trim(); @@ -9353,46 +10687,59 @@ class ClaudeOrchestrator { } } } - - showToast(message, type = 'info') { + + showToast(message, type = 'info', options = {}) { + const rawMessage = String(message || '').trim(); + if (!rawMessage) return; + + const normalizedType = (['info', 'success', 'warning', 'error'].includes(type)) ? type : 'info'; + const durationMsRaw = Number(options?.durationMs); + const durationMs = Number.isFinite(durationMsRaw) ? Math.max(1200, durationMsRaw) : 5000; + + let stack = document.getElementById('toast-stack'); + if (!stack) { + stack = document.createElement('div'); + stack.id = 'toast-stack'; + stack.className = 'toast-stack'; + document.body.appendChild(stack); + } + + const iconByType = { + info: '', + success: '', + warning: '', + error: '' + }; + const toast = document.createElement('div'); - toast.className = `toast toast-${type}`; + toast.className = `toast toast-${normalizedType}`; + toast.setAttribute('role', 'status'); toast.innerHTML = `
- ${type === 'success' ? 'โœ…' : type === 'warning' ? 'โš ๏ธ' : type === 'error' ? 'โŒ' : 'โ„น๏ธ'} - ${message} + ${iconByType[normalizedType] || iconByType.info} + ${this.escapeHtml(rawMessage)}
+ `; - - // Add styles for different toast types - const styles = { - info: 'var(--accent-primary)', - success: 'var(--accent-success)', - warning: 'var(--accent-warning)', - error: 'var(--accent-danger)' - }; - - toast.style.cssText = ` - position: fixed; - top: calc(var(--header-height) + 20px); - right: 20px; - background: ${styles[type]}; - color: white; - padding: var(--space-sm) var(--space-md); - border-radius: var(--radius-md); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - z-index: 1000; - animation: slideInRight 0.3s ease-out, fadeOutRight 0.3s ease-in 4.7s forwards; - `; - - document.body.appendChild(toast); - - // Remove after 5 seconds - setTimeout(() => { - if (toast.parentNode) { + + const removeToast = () => { + if (!toast.parentNode) return; + if (toast.classList.contains('is-leaving')) return; + toast.classList.add('is-leaving'); + setTimeout(() => { toast.remove(); - } - }, 5000); + if (stack && !stack.children.length) { + stack.remove(); + } + }, 240); + }; + + const closeBtn = toast.querySelector('.toast-close'); + closeBtn?.addEventListener('click', removeToast); + + stack.appendChild(toast); + requestAnimationFrame(() => toast.classList.add('is-visible')); + setTimeout(removeToast, durationMs); } async launchDiffViewer(githubUrl) { @@ -9400,9 +10747,9 @@ class ClaudeOrchestrator { const prMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)/); const commitMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/commit\/([a-f0-9]{40})/); const compareMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/compare\/([^?#]+)/); - + let diffViewerPath = ''; - + if (prMatch) { const [, owner, repo, pr] = prMatch; diffViewerPath = `/pr/${owner}/${repo}/${pr}`; @@ -9430,7 +10777,7 @@ class ClaudeOrchestrator { this.showToast('Unable to parse GitHub URL', 'error'); return; } - + // Open a placeholder tab immediately (avoids popup blockers), then redirect once ready. const popup = window.open('', '_blank'); if (!popup) { @@ -12743,19 +14090,7 @@ class ClaudeOrchestrator { }); bodyEl.querySelectorAll?.('[data-open-diagnostics="true"]')?.forEach?.((btn) => { btn.addEventListener('click', () => { - try { - document.getElementById('settings-panel')?.classList?.remove?.('hidden'); - setTimeout(() => { - try { - document.getElementById('diagnostics-output')?.scrollIntoView?.({ behavior: 'smooth', block: 'start' }); - } catch {} - try { - document.getElementById('diagnostics-refresh')?.click?.(); - } catch {} - }, 50); - } catch { - // ignore - } + this.openDiagnosticsPanel({ refresh: true }); }); }); bodyEl.querySelectorAll('[data-pr-refresh]').forEach((btn0) => { @@ -12899,7 +14234,7 @@ class ClaudeOrchestrator { // Check URL params first const urlParams = new URLSearchParams(window.location.search); const tokenFromUrl = urlParams.get('token'); - + if (tokenFromUrl) { // Save to localStorage for future use localStorage.setItem('claude-orchestrator-token', tokenFromUrl); @@ -12907,7 +14242,7 @@ class ClaudeOrchestrator { window.history.replaceState({}, document.title, window.location.pathname); return tokenFromUrl; } - + // Check localStorage return localStorage.getItem('claude-orchestrator-token'); } @@ -13733,7 +15068,7 @@ class ClaudeOrchestrator { this.showToast?.(`Pruned ${prunedCount} old recoverable session(s)`, 'success'); return true; } - + installAuthFetchShim() { if (window.__claudeOrchestratorFetchAuthInstalled) return; if (typeof window.fetch !== 'function') return; @@ -13777,7 +15112,7 @@ class ClaudeOrchestrator { window.__claudeOrchestratorFetchAuthInstalled = true; } - + // Terminal Focus Feature - Now shows only that worktree focusTerminal(sessionId) { // Extract worktree ID from session ID @@ -13815,14 +15150,14 @@ class ClaudeOrchestrator { console.error(`Terminal instance not found for ${sessionId}`); return; } - + // Store original parent for unfocus const terminalElement = terminalWrapper.querySelector('.terminal'); if (!terminalElement) { console.error(`Terminal element not found in wrapper for ${sessionId}`); return; } - + this.focusedTerminalInfo = { sessionId: sessionId, originalParent: terminalElement.parentElement, @@ -13834,41 +15169,41 @@ class ClaudeOrchestrator { rows: xtermInstance.rows || 24 } }; - + // Add focusing animation to original terminal terminalWrapper.classList.add('focusing'); - + // Update overlay header const focusedTitle = document.getElementById('focused-title'); const focusedBranch = document.getElementById('focused-branch'); const focusedStatus = document.getElementById('focused-status'); - + const isAgentSession = /-(claude|codex)$/.test(String(sessionId || '')); const worktreeNumber = sessionId.split('-')[0].replace('work', ''); - + if (focusedTitle) focusedTitle.textContent = `${isAgentSession ? '๐Ÿค– Agent' : '๐Ÿ’ป Server'} ${worktreeNumber}`; if (focusedBranch) focusedBranch.textContent = this.formatBranchLabel(session.branch || '', { context: 'terminal' }).text || ''; if (focusedStatus) focusedStatus.className = `status-indicator ${session.status || 'idle'}`; - + // Move the actual terminal element to focused container const focusedTerminalBody = document.getElementById('focused-terminal-body'); if (!focusedTerminalBody) { console.error('Focused terminal body container not found'); return; } - + focusedTerminalBody.innerHTML = ''; focusedTerminalBody.appendChild(terminalElement); - + // Hide original wrapper terminalWrapper.style.visibility = 'hidden'; - + // Activate focus overlay with animation const focusOverlay = document.getElementById('focus-overlay'); if (focusOverlay) { focusOverlay.classList.add('active'); } - + // Bind ESC key for unfocus this.handleEscKey = (e) => { if (e.key === 'Escape') { @@ -13876,41 +15211,41 @@ class ClaudeOrchestrator { } }; document.addEventListener('keydown', this.handleEscKey); - + // Resize terminal to fit the focused container after animation setTimeout(() => { try { // Store original font size this.focusedTerminalInfo.originalFontSize = xtermInstance.options.fontSize || 12; - + // Increase font size for better readability in focused mode const originalSize = this.focusedTerminalInfo.originalFontSize; const newFontSize = Math.round(originalSize * 1.8); // 1.8x larger (reduced from 3x by ~60%) xtermInstance.options.fontSize = newFontSize; - + const rect = focusedTerminalBody.getBoundingClientRect(); // Calculate new dimensions based on container size with larger font const charWidth = newFontSize * 0.6; // Approximate character width const lineHeight = newFontSize * 1.4; // Approximate line height - + const cols = Math.floor((rect.width - 30) / charWidth); const rows = Math.floor((rect.height - 30) / lineHeight); - + // Apply reasonable limits const finalCols = Math.min(200, Math.max(80, cols)); const finalRows = Math.min(80, Math.max(24, rows)); - + console.log(`Resizing focused terminal from ${xtermInstance.cols}x${xtermInstance.rows} to ${finalCols}x${finalRows} with font size ${newFontSize}px`); - + // Resize xterm xtermInstance.resize(finalCols, finalRows); - + // Use fit addon if available const fitAddon = this.terminalManager?.fitAddons?.get(sessionId); if (fitAddon) { fitAddon.fit(); } - + // Send resize command to backend if (this.socket) { this.socket.emit('resize', { @@ -13919,46 +15254,46 @@ class ClaudeOrchestrator { rows: finalRows }); } - + // Focus the terminal for input xtermInstance.focus(); } catch (resizeError) { console.error('Error resizing focused terminal:', resizeError); } }, 200); - + // Remove focusing animation after transition setTimeout(() => { terminalWrapper.classList.remove('focusing'); }, 300); - + } catch (error) { console.error('Error focusing terminal:', error); } } - + unfocusTerminal() { try { if (!this.focusedTerminalInfo) return; - + const { sessionId, originalParent, originalNextSibling, terminalElement, terminalWrapper, originalDimensions } = this.focusedTerminalInfo; - + // Move terminal element back to original location if (originalNextSibling) { originalParent.insertBefore(terminalElement, originalNextSibling); } else { originalParent.appendChild(terminalElement); } - + // Show original wrapper terminalWrapper.style.visibility = 'visible'; - + // Deactivate focus overlay const focusOverlay = document.getElementById('focus-overlay'); if (focusOverlay) { focusOverlay.classList.remove('active'); } - + // Restore original terminal size and font const xtermInstance = this.terminalManager?.terminals?.get(sessionId); if (xtermInstance) { @@ -13966,21 +15301,21 @@ class ClaudeOrchestrator { const originalFontSize = this.focusedTerminalInfo.originalFontSize || 12; console.log(`Restoring font size from ${xtermInstance.options.fontSize}px to ${originalFontSize}px`); xtermInstance.options.fontSize = originalFontSize; - + // Force a refresh of the terminal to apply font change xtermInstance.refresh(0, xtermInstance.rows - 1); - + if (originalDimensions) { setTimeout(() => { console.log(`Restoring terminal dimensions to ${originalDimensions.cols}x${originalDimensions.rows}`); xtermInstance.resize(originalDimensions.cols, originalDimensions.rows); - + // Use fit addon if available const fitAddon = this.terminalManager?.fitAddons?.get(sessionId); if (fitAddon) { setTimeout(() => fitAddon.fit(), 50); } - + // Send resize command to backend if (this.socket) { this.socket.emit('resize', { @@ -13992,10 +15327,10 @@ class ClaudeOrchestrator { }, 100); } } - + // Clean up this.focusedTerminalInfo = null; - + // Remove ESC key listener if (this.handleEscKey) { document.removeEventListener('keydown', this.handleEscKey); @@ -14005,14 +15340,14 @@ class ClaudeOrchestrator { console.error('Error unfocusing terminal:', error); } } - + calculateTerminalDimensions(container) { if (!container) return null; - + const rect = container.getBoundingClientRect(); const cols = Math.floor(rect.width / 9); // Approximate character width const rows = Math.floor(rect.height / 20); // Approximate line height - + return { cols: Math.max(80, cols), rows: Math.max(24, rows) }; } @@ -14149,7 +15484,7 @@ class ClaudeOrchestrator { if (startupUI) { startupUI.style.display = 'none'; } - + } catch (error) { console.error('Error auto-starting Claude:', error); this.showError('Failed to start Claude with settings'); @@ -14178,7 +15513,7 @@ class ClaudeOrchestrator { } } } - + hideClaudeStartupModal() { const modal = document.getElementById('claude-startup-modal'); if (modal) { @@ -14186,7 +15521,7 @@ class ClaudeOrchestrator { this.pendingClaudeSession = null; } } - + async startClaudeWithOptions(sessionId, mode, skipPermissions) { if (!this.socket || !this.socket.connected) { this.showError('Not connected to server'); @@ -14316,7 +15651,7 @@ class ClaudeOrchestrator { if (startupUI) startupUI.style.display = 'none'; this.dismissedStartupUI.set(sid, true); } - + quickStartClaude(sessionId, mode) { // Check if YOLO mode is enabled const yoloCheckbox = document.getElementById(`yolo-${sessionId}`); @@ -14817,7 +16152,7 @@ class ClaudeOrchestrator { document.addEventListener('keydown', handleEsc); }); } - + updateYoloState(sessionId, checked) { // Update button styles to show YOLO is active const buttons = [ @@ -14825,7 +16160,7 @@ class ClaudeOrchestrator { document.getElementById(`btn-continue-${sessionId}`), document.getElementById(`btn-resume-${sessionId}`) ]; - + buttons.forEach(btn => { if (btn) { if (checked) { @@ -14836,25 +16171,25 @@ class ClaudeOrchestrator { } }); } - + async startClaudeFromTerminal(sessionId) { if (!this.socket || !this.socket.connected) { return; } - + try { // Get effective settings for this session const response = await fetch(`/api/user-settings/effective/${sessionId}`); let effectiveSettings = { claudeFlags: { skipPermissions: false } }; - + if (response.ok) { effectiveSettings = await response.json(); } - + // Get selected options from the inline UI, but use effective settings as fallback const mode = document.querySelector(`input[name="claude-mode-${sessionId}"]:checked`)?.value || 'fresh'; const skipPermissions = document.getElementById(`skip-permissions-${sessionId}`)?.checked ?? effectiveSettings.claudeFlags.skipPermissions; - + // Send command to server this.socket.emit('start-claude', { sessionId: sessionId, @@ -14863,19 +16198,19 @@ class ClaudeOrchestrator { skipPermissions: skipPermissions } }); - + // Hide the startup UI const startupUI = document.getElementById(this.getSessionDomId('startup-ui', sessionId)); if (startupUI) { startupUI.style.display = 'none'; } - + // Enable the start button for future use const startBtn = document.getElementById(`claude-start-btn-${sessionId}`); if (startBtn) { startBtn.disabled = false; } - + } catch (error) { console.error('Error starting Claude from terminal:', error); } @@ -14883,10 +16218,10 @@ class ClaudeOrchestrator { restartClaudeSession(sessionId) { console.log(`Restarting Claude session: ${sessionId}`); - + if (this.socket && this.socket.connected) { this.socket.emit('restart-session', { sessionId }); - + // Update UI to show restarting this.updateSessionStatus(sessionId, 'restarting'); } else { @@ -14922,16 +16257,16 @@ class ClaudeOrchestrator { if (!this.userSettings) { console.warn('User settings not loaded, attempting to load...'); await this.loadUserSettings(); - + if (!this.userSettings) { console.error('Failed to load user settings'); return; } } - + const pathParts = path.split('.'); const newGlobal = JSON.parse(JSON.stringify(this.userSettings.global)); - + // Navigate to the correct nested property let current = newGlobal; for (let i = 0; i < pathParts.length - 1; i++) { @@ -15556,7 +16891,7 @@ class ClaudeOrchestrator { this.userSettings = updatedSettings; this.syncUserSettingsUI(); console.log('Reset to defaults successfully'); - + // Show user feedback this.showTemporaryMessage('Settings reset to defaults'); } else { @@ -15582,7 +16917,7 @@ class ClaudeOrchestrator { if (response.ok) { console.log('Saved as default template successfully'); - + // Show user feedback with commit reminder this.showTemporaryMessage('Settings saved as default template. Remember to commit and push the changes to user-settings.default.json!', 'success'); } else { @@ -15600,7 +16935,7 @@ class ClaudeOrchestrator { const messageEl = document.createElement('div'); messageEl.className = `temporary-message ${type}`; messageEl.textContent = message; - + // Style the message messageEl.style.cssText = ` position: fixed; @@ -15616,14 +16951,14 @@ class ClaudeOrchestrator { transform: translateX(100%); transition: transform 0.3s ease; `; - + document.body.appendChild(messageEl); - + // Animate in setTimeout(() => { messageEl.style.transform = 'translateX(0)'; }, 100); - + // Remove after delay setTimeout(() => { messageEl.style.transform = 'translateX(100%)'; @@ -15642,9 +16977,9 @@ class ClaudeOrchestrator { this.showTemporaryMessage('Invalid session ID for replay viewer', 'error'); return; } - + const worktreeNum = worktreeMatch[1]; - + // Get worktree configuration from server for accurate path let worktreeConfig = null; try { @@ -15655,19 +16990,19 @@ class ClaudeOrchestrator { } catch (error) { console.warn('Could not get worktree config, using defaults:', error); } - + // Use server-hosted replay viewer (avoids browser file:// restrictions) const replayViewerUrl = `${window.location.origin}/replay-viewer/work${worktreeNum}/`; - + console.log(`Opening replay viewer for ${sessionId} at ${replayViewerUrl}`); - + // Open in new tab (simpler approach) window.open(replayViewerUrl, '_blank'); - + // Show success message with URL for reference this.showTemporaryMessage(`Opening replay viewer for work${worktreeNum}`, 'success'); console.log(`Replay viewer URL: ${replayViewerUrl}`); - + } catch (error) { console.error('Error opening replay viewer:', error); this.showTemporaryMessage('Failed to open replay viewer', 'error'); @@ -15687,7 +17022,7 @@ class ClaudeOrchestrator { setTimeout(checkAndStart, 500); // Check again in 500ms } }; - + setTimeout(checkAndStart, 1000); // Initial delay for terminal setup } @@ -15696,7 +17031,7 @@ class ClaudeOrchestrator { const response = await fetch('/api/user-settings/check-updates'); if (response.ok) { const result = await response.json(); - + if (result && result.hasUpdates) { const notification = document.getElementById('settings-update-notification'); notification.classList.remove('hidden'); @@ -15802,17 +17137,17 @@ class ClaudeOrchestrator { try { this.showTemporaryMessage('Checking for updates...', 'info'); - + const response = await fetch('/api/git/check-updates'); if (response.ok) { const result = await response.json(); - + if (result.hasUpdates) { const notification = document.getElementById('git-update-notification'); const textElement = document.getElementById('git-notification-text'); textElement.textContent = `${result.commitsBehind} update${result.commitsBehind > 1 ? 's' : ''} available on ${result.currentBranch}`; notification.classList.remove('hidden'); - + this.showTemporaryMessage(`Found ${result.commitsBehind} update${result.commitsBehind > 1 ? 's' : ''} available`, 'success'); } else if (result.hasUpdates === false) { this.showTemporaryMessage('Repository is up to date', 'success'); @@ -15835,7 +17170,7 @@ class ClaudeOrchestrator { } this.showTemporaryMessage('Pulling latest changes...', 'info'); - + const response = await fetch('/api/git/pull', { method: 'POST', headers: { 'Content-Type': 'application/json' } @@ -15843,14 +17178,14 @@ class ClaudeOrchestrator { if (response.ok) { const result = await response.json(); - + if (result.success) { // Success message will be handled by socket event const notification = document.getElementById('git-update-notification'); notification.classList.add('hidden'); } else { this.showTemporaryMessage(result.error || 'Failed to pull changes', 'error'); - + // Show specific error details if available if (result.changes && result.changes.length > 0) { console.log('Uncommitted changes:', result.changes); @@ -19679,7 +21014,7 @@ class ClaudeOrchestrator { applyView(); return; } - + state.selectedCardId = card.id || null; applyView(); @@ -21029,7 +22364,7 @@ class ClaudeOrchestrator { col.style.setProperty('--tasks-card-rows', '1'); return; } - + const containerHeight = cardsContainer.clientHeight; if (!containerHeight || containerHeight < 40) { col.style.setProperty('--tasks-card-columns', '1'); @@ -21047,7 +22382,7 @@ class ClaudeOrchestrator { return; } delete col.dataset.tasksWrapExpandRetry; - + const styles = window.getComputedStyle(cardsContainer); const rowGap = Number.parseFloat(styles.rowGap || styles.gap || '0') || 0; const columnGap = Number.parseFloat(styles.columnGap || styles.gap || '0') || 0; @@ -21092,10 +22427,10 @@ class ClaudeOrchestrator { col.style.minWidth = `${Math.round(target)}px`; } }; - + apply(rowsFit); const fits = () => (cardsContainer.scrollHeight <= cardsContainer.clientHeight + 1); - + // If we still overflow vertically, reduce rows (creating more columns) until we fit. for (let attempt = 0; attempt < 24; attempt++) { // Force reflow and then check overflow. @@ -30324,7 +31659,7 @@ class ClaudeOrchestrator { // While worktree sessions are spinning up, we reserve the worktree so it isn't recommended again. this.cleanupExpiredWorktreeReservations(); if (this.isWorktreeReserved(repoPathNorm, worktreeId)) return true; - + // Extract repo name from path for session matching const repoName = (repoNameOverride || repoPathNorm.split('/').pop() || '').toLowerCase(); diff --git a/client/index.html b/client/index.html index 0605696e..c27ae39b 100644 --- a/client/index.html +++ b/client/index.html @@ -1,5 +1,6 @@ + @@ -7,15 +8,17 @@ - + - + +
- +
@@ -131,7 +134,7 @@

Claude Orchestrator

- + - + - + +

Pager / Pollcat

@@ -785,7 +799,8 @@

PR Merge Automation

Enable PR-merge automation (Trello) -

When a PR merges, auto-move/comment on the linked Trello card (when a Trello card is linked to the PR task).

+

When a PR merges, auto-move/comment on the linked Trello card (when a + Trello card is linked to the PR task).

-

Configure board-specific Done/For Test lists in Tasks โ†’ Board Settings โ†’ Conventions.

+

Configure board-specific Done/For Test lists in Tasks โ†’ Board + Settings โ†’ Conventions.

- +

Terminal Settings

- +
- +
@@ -939,10 +959,11 @@

Terminal Settings

Per-Terminal Overrides
-

Override global settings for specific terminals (requires terminal restart)

+

Override global settings for specific terminals (requires terminal + restart)

- +
Default Template Management

Manage the default settings template committed to the repository

@@ -962,10 +983,11 @@
Default Template Management
- +
Repository Updates
-

Web/dev mode: Git pull updates. Tauri desktop mode: app updater check/install.

+

Web/dev mode: Git pull updates. Tauri desktop mode: app updater + check/install.

+ + + - + - + @@ -1044,10 +1085,12 @@

Notifications

- +
- +
@@ -1062,4 +1105,5 @@

Notifications

+ diff --git a/client/notifications.js b/client/notifications.js index 6fa218c3..7ec3ea9d 100644 --- a/client/notifications.js +++ b/client/notifications.js @@ -389,21 +389,21 @@ class NotificationManager { // Add notification styles const notificationStyles = document.createElement('style'); notificationStyles.textContent = ` - .empty-message { + .notifications-panel .empty-message { padding: var(--space-xl); text-align: center; color: var(--text-secondary); font-size: 0.875rem; } - .notification-header { + .notifications-panel .notification-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-xs); } - .notification-meta { + .notifications-panel .notification-meta { font-size: 0.75rem; color: var(--text-secondary); margin-top: var(--space-xs); diff --git a/client/styles.css b/client/styles.css index eab6a28d..62b1bf22 100644 --- a/client/styles.css +++ b/client/styles.css @@ -79,6 +79,15 @@ --shadow-float: 0 18px 40px rgba(0, 0, 0, 0.45); --shadow-soft: 0 4px 12px rgba(0, 0, 0, 0.15); --focus-terminal-bg: color-mix(in srgb, var(--bg-primary) 72%, #000000 28%); + + /* Scrollbars */ + --scrollbar-size: 10px; + --scrollbar-radius: 999px; + --scrollbar-track: #1b2230; + --scrollbar-thumb: #3f5f8f; + --scrollbar-thumb-hover: #4f76ae; + --scrollbar-thumb-active: #5a84bf; + --scrollbar-thumb-border: rgba(13, 17, 23, 0.72); } /* Light Theme */ @@ -107,6 +116,30 @@ body.light-theme { --shadow-float: 0 18px 40px rgba(0, 0, 0, 0.22); --shadow-soft: 0 4px 12px rgba(0, 0, 0, 0.12); --focus-terminal-bg: color-mix(in srgb, var(--bg-primary) 94%, #000000 6%); + + --scrollbar-track: #dbe1e8; + --scrollbar-thumb: #7f91aa; + --scrollbar-thumb-hover: #6f86a8; + --scrollbar-thumb-active: #5d789d; + --scrollbar-thumb-border: rgba(255, 255, 255, 0.76); +} + +@supports (color: color-mix(in srgb, #000000 50%, #ffffff 50%)) { + :root { + --scrollbar-track: color-mix(in srgb, var(--bg-secondary) 78%, #000000 22%); + --scrollbar-thumb: color-mix(in srgb, var(--accent-primary) 46%, var(--bg-tertiary) 54%); + --scrollbar-thumb-hover: color-mix(in srgb, var(--accent-primary-hover) 60%, var(--bg-tertiary) 40%); + --scrollbar-thumb-active: color-mix(in srgb, var(--accent-primary) 72%, var(--bg-tertiary) 28%); + --scrollbar-thumb-border: color-mix(in srgb, var(--bg-primary) 76%, transparent 24%); + } + + body.light-theme { + --scrollbar-track: color-mix(in srgb, var(--bg-tertiary) 70%, #ffffff 30%); + --scrollbar-thumb: color-mix(in srgb, var(--accent-primary) 44%, #7f8a98 56%); + --scrollbar-thumb-hover: color-mix(in srgb, var(--accent-primary) 58%, #6f7c8f 42%); + --scrollbar-thumb-active: color-mix(in srgb, var(--accent-primary) 68%, #617187 32%); + --scrollbar-thumb-border: color-mix(in srgb, #ffffff 68%, transparent 32%); + } } /* Skins */ @@ -178,6 +211,133 @@ body.light-theme.skin-high-contrast { margin: 0; padding: 0; box-sizing: border-box; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +*::-webkit-scrollbar { + width: var(--scrollbar-size); + height: var(--scrollbar-size); +} + +*::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: var(--scrollbar-radius); +} + +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: var(--scrollbar-radius); + border: 2px solid var(--scrollbar-thumb-border); + background-clip: padding-box; +} + +*::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +*::-webkit-scrollbar-thumb:active { + background: var(--scrollbar-thumb-active); +} + +*::-webkit-scrollbar-corner { + background: var(--scrollbar-track); +} + +:where( + .onboarding-overlay, + .onboarding-container, + .dependency-setup-body, + .dependency-setup-item-output, + .diagnostics-output, + .worktree-list, + .workspace-tabs-container, + .header-actions, + .terminal .xterm-viewport, + .focused-terminal-body .xterm-viewport +) { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +:where( + .onboarding-overlay, + .onboarding-container, + .dependency-setup-body, + .dependency-setup-item-output, + .diagnostics-output, + .worktree-list, + .workspace-tabs-container, + .header-actions, + .terminal .xterm-viewport, + .focused-terminal-body .xterm-viewport +)::-webkit-scrollbar { + width: var(--scrollbar-size); + height: var(--scrollbar-size); +} + +:where( + .onboarding-overlay, + .onboarding-container, + .dependency-setup-body, + .dependency-setup-item-output, + .diagnostics-output, + .worktree-list, + .workspace-tabs-container, + .header-actions, + .terminal .xterm-viewport, + .focused-terminal-body .xterm-viewport +)::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: var(--scrollbar-radius); +} + +:where( + .onboarding-overlay, + .onboarding-container, + .dependency-setup-body, + .dependency-setup-item-output, + .diagnostics-output, + .worktree-list, + .workspace-tabs-container, + .header-actions, + .terminal .xterm-viewport, + .focused-terminal-body .xterm-viewport +)::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: var(--scrollbar-radius); + border: 2px solid var(--scrollbar-thumb-border); + background-clip: padding-box; +} + +:where( + .onboarding-overlay, + .onboarding-container, + .dependency-setup-body, + .dependency-setup-item-output, + .diagnostics-output, + .worktree-list, + .workspace-tabs-container, + .header-actions, + .terminal .xterm-viewport, + .focused-terminal-body .xterm-viewport +)::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +:where( + .onboarding-overlay, + .onboarding-container, + .dependency-setup-body, + .dependency-setup-item-output, + .diagnostics-output, + .worktree-list, + .workspace-tabs-container, + .header-actions, + .terminal .xterm-viewport, + .focused-terminal-body .xterm-viewport +)::-webkit-scrollbar-thumb:active { + background: var(--scrollbar-thumb-active); } body { @@ -2143,12 +2303,19 @@ header h1 { } .worktree-inspector-header.review-console-header::-webkit-scrollbar { - height: 6px; + height: 8px; +} + +.worktree-inspector-header.review-console-header::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: var(--scrollbar-radius); } .worktree-inspector-header.review-console-header::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.16); - border-radius: 999px; + background: var(--scrollbar-thumb); + border-radius: var(--scrollbar-radius); + border: 2px solid var(--scrollbar-thumb-border); + background-clip: padding-box; } .review-console-route-bar { @@ -4354,19 +4521,23 @@ header h1 { line-height: 1.35; } -.settings-toolbar input { - flex: 1 1 auto; - min-width: 0; - padding: 0 10px; +body.dependency-onboarding-active { + overflow: hidden; + background: + radial-gradient(circle at 12% 18%, rgba(56, 139, 253, 0.16), transparent 48%), + radial-gradient(circle at 88% 8%, rgba(34, 197, 94, 0.14), transparent 44%), + var(--bg-primary); } -.settings-toolbar select { - flex: 0 0 auto; - min-width: 120px; - padding: 0 8px; +body.dependency-onboarding-booting { + overflow: hidden; + background: + radial-gradient(circle at 14% 22%, rgba(56, 139, 253, 0.14), transparent 46%), + radial-gradient(circle at 82% 10%, rgba(34, 197, 94, 0.12), transparent 42%), + var(--bg-primary); } -.settings-filter-hidden { +body.dependency-onboarding-booting > :not(#dependency-setup-modal):not(#toast-stack):not(.toast):not(.ready-toast):not(#notifications-panel):not(.cross-workspace-notifications-area) { display: none !important; } @@ -4416,57 +4587,45 @@ header h1 { border-radius: 10px; } -.setting-group { - margin-bottom: var(--space-md); +body.dependency-onboarding-active > :not(#dependency-setup-modal):not(#toast-stack):not(.toast):not(.ready-toast):not(#notifications-panel):not(.cross-workspace-notifications-area) { + display: none !important; } -.setting-group label { +body.dependency-onboarding-active #dependency-setup-modal { display: flex; align-items: center; - gap: var(--space-sm); - cursor: pointer; -} - -.setting-group select { - background: var(--bg-tertiary); - color: var(--text-primary); - border: 1px solid var(--border-color); - padding: var(--space-xs) var(--space-sm); - border-radius: var(--radius-sm); - margin-left: var(--space-sm); + justify-content: center; + padding: 24px; + z-index: 4500; } -.setting-section { - margin: var(--space-lg) 0; - padding-top: var(--space-lg); - border-top: 1px solid var(--border-color); +.dependency-setup-content { + max-width: min(760px, 94vw); + width: min(760px, 94vw); + border: 1px solid rgba(56, 139, 253, 0.2); + box-shadow: 0 24px 54px rgba(0, 0, 0, 0.35); } -.setting-section h4 { - color: var(--text-primary); - margin-bottom: var(--space-md); - font-size: 1.1em; +.dependency-setup-content .modal-header { + margin-bottom: 10px; } -.setting-section h5 { - color: var(--text-secondary); - margin-bottom: var(--space-sm); - font-size: 0.9em; - font-weight: 600; +.dependency-setup-content .modal-header h3 { + font-size: 1.2rem; + letter-spacing: 0.01em; } -.setting-group label small { - display: block; - color: var(--text-tertiary); - font-size: 0.8em; - margin-top: var(--space-xs); - margin-left: var(--space-lg); +.dependency-setup-body { + max-height: 68vh; + overflow: auto; + padding-right: 4px; } -.setting-description { - color: var(--text-tertiary); - font-size: 0.85em; - margin-bottom: var(--space-md); +.dependency-setup-list { + display: flex; + flex-direction: column; + gap: 14px; + margin-top: 12px; } .skin-gallery { @@ -4539,612 +4698,1216 @@ header h1 { .settings-glossary details { border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - padding: 8px 10px; + border-radius: var(--radius-lg); background: var(--bg-tertiary); - margin: 10px 0; -} - -.settings-glossary details[open] { - background: rgba(255, 255, 255, 0.02); + padding: 14px; } -.settings-glossary summary { - cursor: pointer; +.dependency-setup-item-header { display: flex; align-items: center; - gap: 8px; - list-style: none; -} - -.settings-glossary summary::-webkit-details-marker { - display: none; + justify-content: space-between; + gap: 10px; + margin-bottom: 6px; + flex-wrap: wrap; } -.settings-glossary details > .setting-description { - margin: 10px 0 0; - opacity: 0.95; +.dependency-setup-item-title { + font-weight: 650; + font-size: 1.03rem; + color: var(--text-primary); } -.identity-saved-list { +.dependency-setup-badges { display: flex; - flex-wrap: wrap; + align-items: center; gap: 6px; + flex-wrap: wrap; } -.identity-chip { +.dependency-setup-badge { display: inline-flex; align-items: center; - gap: 6px; - padding: 2px 8px; - border-radius: var(--radius-sm); + border-radius: 999px; border: 1px solid var(--border-color); - background: rgba(255, 255, 255, 0.03); - font-family: var(--font-mono); - font-size: 0.78rem; - max-width: 100%; + padding: 2px 8px; + font-size: 0.75rem; + color: var(--text-secondary); + background: var(--bg-secondary); } -.identity-chip button { - border: none; - background: transparent; - color: var(--text-tertiary); - cursor: pointer; - padding: 0; - line-height: 1; +.dependency-setup-badge.status-ok { + color: var(--accent-success); + border-color: rgba(34, 197, 94, 0.45); } -.identity-chip button:hover { - color: var(--text-secondary); +.dependency-setup-badge.status-missing { + color: var(--accent-warning); + border-color: rgba(245, 158, 11, 0.45); } -.per-terminal-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--space-sm); - margin-bottom: var(--space-xs); - background: var(--bg-tertiary); - border-radius: var(--radius-sm); +.dependency-setup-badge.status-pending { + color: var(--accent-primary); + border-color: rgba(56, 139, 253, 0.45); } -.per-terminal-item .terminal-name { - color: var(--text-primary); - font-weight: 500; - font-family: var(--font-mono); +.dependency-setup-badge.level-required { + color: var(--accent-danger); + border-color: rgba(239, 68, 68, 0.45); } -.per-terminal-item .terminal-override { - color: var(--text-secondary); - font-size: 0.8em; +.dependency-setup-badge.level-recommended { + color: var(--accent-primary); + border-color: rgba(56, 139, 253, 0.45); } -.per-terminal-items { - margin-top: var(--space-sm); +.dependency-setup-badge.level-optional { + color: var(--accent-warning); + border-color: rgba(245, 158, 11, 0.45); } -/* Notifications Panel */ -.notifications-panel { - position: fixed; - top: var(--header-height); - right: 0; - bottom: 0; - width: 380px; +.dependency-setup-item-desc { + color: var(--text-secondary); + margin-bottom: 10px; + line-height: 1.45; +} + +.dependency-setup-item-command { + margin: 0; + padding: 8px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); background: var(--bg-secondary); - border-left: 1px solid var(--border-color); - box-shadow: -4px 0 12px rgba(0, 0, 0, 0.15); - transform: translateX(100%); - transition: transform 0.3s; - z-index: 101; - display: flex; - flex-direction: column; + color: var(--text-secondary); + white-space: pre-wrap; + font-size: 0.85rem; } -.notifications-panel:not(.hidden) { - transform: translateX(0); +.dependency-onboarding-command-wrap { + margin-bottom: 10px; } -.notifications-content { - padding: var(--space-md); - overflow-y: auto; - flex: 1; +.dependency-onboarding-command-label { + margin: 0 0 6px; + font-size: 0.76rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-tertiary); } -.notification-list { +.dependency-setup-item-actions { display: flex; - flex-direction: column; - gap: var(--space-sm); + align-items: center; + gap: 8px; + flex-wrap: wrap; } -.notification-item { - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-tertiary); - padding: var(--space-sm); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; +.dependency-setup-item-actions .btn-secondary { + width: auto; + min-height: 32px; + flex: 0 0 auto; } -.notification-item:hover { - border-color: rgba(56, 139, 253, 0.55); - background: rgba(56, 139, 253, 0.06); +.dependency-setup-item-actions a.btn-secondary { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; } -.notification-item.unread { - border-left: 4px solid var(--accent-primary); +.dependency-setup-actions { + justify-content: flex-start; + flex-wrap: wrap; + gap: 8px; } -.notification-type { - font-size: 0.75rem; - padding: 2px 6px; - border-radius: 999px; - border: 1px solid var(--border-color); +.dependency-setup-actions .btn-secondary { + flex: 0 0 auto; +} + +.dependency-setup-empty { + padding: 12px; + border: 1px dashed var(--border-color); + border-radius: var(--radius-sm); color: var(--text-secondary); } -.notification-type.waiting { - border-color: rgba(245, 158, 11, 0.45); - color: var(--accent-warning); +.dependency-onboarding-progress { + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-tertiary); + padding: 12px; } -.notification-type.completed { - border-color: rgba(34, 197, 94, 0.45); - color: var(--accent-success); +.dependency-onboarding-progress-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 10px; + color: var(--text-secondary); } -.notification-type.error { - border-color: rgba(239, 68, 68, 0.45); - color: var(--accent-danger); +.dependency-onboarding-progress-track { + width: 100%; + height: 8px; + border-radius: 999px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + overflow: hidden; } -.notification-message { - color: var(--text-primary); - line-height: 1.35; +.dependency-onboarding-progress-bar { + height: 100%; + background: var(--accent-primary); + transition: width 180ms ease-in-out; } -.notification-actions { - display: flex; - gap: var(--space-xs); - margin-top: var(--space-sm); - justify-content: flex-end; +.dependency-onboarding-step { + margin-top: 12px; } -.notification-action { - padding: 4px 8px; - font-size: 0.8rem; - width: auto; +.dependency-onboarding-step-card { + background: + linear-gradient(150deg, rgba(56, 139, 253, 0.09), rgba(34, 197, 94, 0.03)), + var(--bg-tertiary); } -.terminal-controls { - display: flex; - align-items: center; - gap: var(--space-sm); +.dependency-onboarding-step-kicker { + margin-bottom: 6px; + font-size: 0.74rem; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + color: var(--text-tertiary); } -.terminal-controls label { - font-size: 0.9em; - margin: 0; +.dependency-onboarding-state { + margin: 0 0 8px; + color: var(--text-secondary); + font-weight: 500; } -/* Loading spinner for build button */ -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } +.dependency-onboarding-state.status-ok { + color: var(--accent-success); } -/* Server Launch Settings */ -.server-launch-group { - display: inline-flex; - gap: 4px; - align-items: center; +.dependency-onboarding-state.status-missing { + color: var(--accent-warning); } -#launch-settings-modal { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--overlay-backdrop); - display: flex; - align-items: center; - justify-content: center; - z-index: 2000; +.dependency-onboarding-state.status-pending { + color: var(--accent-primary); } -#launch-settings-modal .modal-content { - background: var(--bg-primary); - border-radius: var(--radius-lg); - max-width: 800px; - width: 90%; - max-height: 90vh; - overflow-y: auto; - box-shadow: var(--shadow-modal); +.dependency-gh-login-helper { + margin: 0 0 10px; + padding: 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: + linear-gradient(145deg, rgba(56, 139, 253, 0.1), rgba(56, 139, 253, 0.02)), + var(--bg-tertiary); } -#launch-settings-modal .modal-header { - padding: var(--space-lg); - border-bottom: 1px solid var(--border-color); - display: flex; - justify-content: space-between; - align-items: center; +.dependency-git-identity-helper { + margin: 0 0 10px; + padding: 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: + linear-gradient(145deg, rgba(34, 197, 94, 0.08), rgba(34, 197, 94, 0.02)), + var(--bg-tertiary); } -#launch-settings-modal .modal-header h2 { - margin: 0; - font-size: 1.5rem; - color: var(--text-primary); +.dependency-git-identity-fields { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); + gap: 8px; + margin-bottom: 8px; } -#launch-settings-modal .close-btn { - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 1.5rem; - cursor: pointer; - padding: 0; - width: 32px; - height: 32px; +.dependency-git-identity-field { display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-sm); + flex-direction: column; + gap: 4px; + color: var(--text-secondary); + font-size: 0.86rem; } -#launch-settings-modal .close-btn:hover { +.dependency-git-identity-field input { + width: 100%; + min-height: 34px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); background: var(--bg-secondary); + color: var(--text-primary); + padding: 0 10px; + font-size: 0.9rem; } -#launch-settings-modal .modal-body { - padding: var(--space-lg); -} - -#launch-settings-modal .settings-section { - margin-bottom: var(--space-xl); -} - -#launch-settings-modal .settings-section:last-child { - margin-bottom: 0; -} - -#launch-settings-modal .settings-section h3 { - color: var(--accent-primary); - font-size: 1.1rem; - margin-bottom: var(--space-md); +.dependency-git-identity-field input:focus { + outline: none; + border-color: var(--accent-primary); } -#launch-settings-modal .setting-group { - margin-bottom: var(--space-md); +.dependency-git-identity-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; } -#launch-settings-modal .setting-group label { - display: block; - color: var(--text-secondary); - font-size: 0.9rem; - margin-bottom: var(--space-xs); +.dependency-git-help-btn { + min-width: 32px; + width: 32px; + padding: 0; + font-weight: 700; } -#launch-settings-modal .setting-group input { - width: 100%; - padding: var(--space-sm) var(--space-md); - background: var(--bg-secondary); +.dependency-git-help-inline { + margin-top: 8px; + padding: 10px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); - color: var(--text-primary); - font-family: monospace; + background: var(--bg-secondary); } -#launch-settings-modal .setting-group input:focus { - outline: none; - border-color: var(--accent-primary); +.dependency-git-help-line { + color: var(--text-secondary); + line-height: 1.45; } -#launch-settings-modal .setting-group small { - display: block; - color: var(--text-tertiary); - font-size: 0.8rem; - margin-top: var(--space-xs); +.dependency-git-help-line + .dependency-git-help-line { + margin-top: 6px; } -#launch-settings-modal .preset-checkboxes { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: var(--space-md); +.dependency-gh-login-helper-text { + color: var(--text-secondary); + margin-bottom: 8px; } -#launch-settings-modal .preset-checkbox { +.dependency-gh-login-code-wrap { display: flex; align-items: center; - padding: var(--space-md); - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - cursor: pointer; - transition: all 0.2s; - user-select: none; -} - -#launch-settings-modal .preset-checkbox:hover { - background: var(--bg-tertiary); - border-color: var(--accent-primary); + gap: 8px; + flex-wrap: wrap; } -#launch-settings-modal .preset-checkbox input[type="checkbox"] { - margin-right: var(--space-sm); - width: 18px; - height: 18px; - cursor: pointer; +.dependency-gh-login-helper-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + margin-top: 8px; } -#launch-settings-modal .preset-checkbox input[type="checkbox"]:checked + span { - color: var(--accent-primary); +.dependency-gh-login-code { + display: inline-block; + padding: 7px 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: var(--bg-secondary); + color: var(--text-primary); + letter-spacing: 0.06em; font-weight: 600; } -#launch-settings-modal .preset-checkbox span { - font-size: 0.9rem; - transition: all 0.2s; +.dependency-setup-item-output { + max-height: 150px; + overflow: auto; } -#launch-settings-modal .modal-footer { - padding: var(--space-lg); - border-top: 1px solid var(--border-color); +.dependency-onboarding-nav { display: flex; + align-items: center; justify-content: flex-end; - gap: var(--space-md); + gap: 8px; + margin-top: 12px; } -#launch-settings-modal .btn-save, -#launch-settings-modal .btn-cancel { - padding: var(--space-sm) var(--space-lg); - border-radius: var(--radius-sm); - cursor: pointer; - font-size: 0.9rem; - transition: all 0.2s; +.dependency-onboarding-nav .btn-secondary, +.dependency-onboarding-nav .btn-primary { + width: auto; + min-height: 34px; + flex: 0 0 auto; } -#launch-settings-modal .btn-save { - background: var(--accent-primary); - color: white; - border: none; -} +@media (max-width: 700px) { + body.dependency-onboarding-active #dependency-setup-modal { + padding: 14px; + } -#launch-settings-modal .btn-save:hover { - background: var(--accent-secondary); -} + .dependency-setup-content { + width: min(760px, 96vw); + } -#launch-settings-modal .btn-cancel { - background: var(--bg-secondary); - color: var(--text-secondary); - border: 1px solid var(--border-color); + .dependency-setup-actions { + justify-content: flex-start; + } + + .dependency-onboarding-nav { + justify-content: flex-end; + flex-wrap: wrap; + } } -#launch-settings-modal .btn-cancel:hover { - background: var(--bg-tertiary); +.settings-toolbar input { + flex: 1 1 auto; + min-width: 0; + padding: 0 10px; } -.loading-spinner { - display: inline-block; - width: 14px; - height: 14px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top: 2px solid #fff; - border-radius: 50%; - animation: spin 1s linear infinite; +.settings-toolbar select { + flex: 0 0 auto; + min-width: 120px; + padding: 0 8px; } -.control-btn.building { - background: var(--color-warning); - cursor: not-allowed; - opacity: 0.8; +.settings-filter-hidden { + display: none !important; } -.clear-override-btn { - background: var(--bg-primary); - color: var(--text-secondary); +/* Review Console */ +.review-console-warning { + margin: 10px 12px 14px; + padding: 10px 12px; border: 1px solid var(--border-color); - border-radius: var(--radius-sm); - padding: var(--space-xs); - cursor: pointer; - font-size: 0.8em; - transition: background-color 0.2s; + border-left: 4px solid var(--accent-warning); + background: rgba(210, 153, 34, 0.10); + border-radius: 10px; } -.clear-override-btn:hover { - background: var(--bg-tertiary); - color: var(--text-primary); +.setting-group { + margin-bottom: var(--space-md); } -.template-actions { +.setting-group label { display: flex; + align-items: center; gap: var(--space-sm); - margin-top: var(--space-sm); + cursor: pointer; } -.template-btn { - padding: var(--space-sm) var(--space-md); +.setting-group select { + background: var(--bg-tertiary); + color: var(--text-primary); border: 1px solid var(--border-color); + padding: var(--space-xs) var(--space-sm); border-radius: var(--radius-sm); - cursor: pointer; - font-size: 0.9em; - transition: all 0.2s; + margin-left: var(--space-sm); } -.template-btn.primary { - background: var(--accent-primary); - color: white; - border-color: var(--accent-primary); +.setting-section { + margin: var(--space-lg) 0; + padding-top: var(--space-lg); + border-top: 1px solid var(--border-color); } -.template-btn.primary:hover { - background: var(--accent-primary-hover); - border-color: var(--accent-primary-hover); +.setting-section h4 { + color: var(--text-primary); + margin-bottom: var(--space-md); + font-size: 1.1em; } -.template-btn.secondary { - background: var(--bg-primary); +.setting-section h5 { color: var(--text-secondary); - border-color: var(--border-color); + margin-bottom: var(--space-sm); + font-size: 0.9em; + font-weight: 600; } -.template-btn.secondary:hover { - background: var(--bg-tertiary); - color: var(--text-primary); +.setting-group label small { + display: block; + color: var(--text-tertiary); + font-size: 0.8em; + margin-top: var(--space-xs); + margin-left: var(--space-lg); } -.update-notification { - margin-top: var(--space-sm); - padding: var(--space-sm); - background: var(--bg-tertiary); - border: 1px solid var(--accent-warning); +.setting-description { + color: var(--text-tertiary); + font-size: 0.85em; + margin-bottom: var(--space-md); +} + +.settings-glossary details { + border: 1px solid var(--border-color); border-radius: var(--radius-sm); - transition: all 0.3s; + padding: 8px 10px; + background: var(--bg-tertiary); + margin: 10px 0; } -.update-notification.hidden { - display: none; +.settings-glossary details[open] { + background: rgba(255, 255, 255, 0.02); } -.notification-content { +.settings-glossary summary { + cursor: pointer; display: flex; align-items: center; - gap: var(--space-sm); + gap: 8px; + list-style: none; } -.notification-icon { - font-size: 1.1em; +.settings-glossary summary::-webkit-details-marker { + display: none; } -.notification-text { - flex: 1; - color: var(--text-primary); - font-size: 0.9em; +.settings-glossary details > .setting-description { + margin: 10px 0 0; + opacity: 0.95; } -.dismiss-btn { - background: none; +.identity-saved-list { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.identity-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 2px 8px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.03); + font-family: var(--font-mono); + font-size: 0.78rem; + max-width: 100%; +} + +.identity-chip button { border: none; - color: var(--text-secondary); + background: transparent; + color: var(--text-tertiary); cursor: pointer; - font-size: 1.2em; padding: 0; - width: 24px; - height: 24px; + line-height: 1; +} + +.identity-chip button:hover { + color: var(--text-secondary); +} + +.per-terminal-item { display: flex; align-items: center; - justify-content: center; + justify-content: space-between; + padding: var(--space-sm); + margin-bottom: var(--space-xs); + background: var(--bg-tertiary); border-radius: var(--radius-sm); - transition: all 0.2s; } -.dismiss-btn:hover { - background: var(--bg-primary); +.per-terminal-item .terminal-name { color: var(--text-primary); + font-weight: 500; + font-family: var(--font-mono); } -/* Loading */ -.loading-message { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - text-align: center; +.per-terminal-item .terminal-override { + color: var(--text-secondary); + font-size: 0.8em; } -.spinner { - width: 40px; - height: 40px; - border: 3px solid var(--bg-tertiary); - border-top-color: var(--accent-primary); - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto var(--space-md); +.per-terminal-items { + margin-top: var(--space-sm); } -@keyframes spin { - to { transform: rotate(360deg); } +/* Notifications Panel */ +.notifications-panel { + position: fixed; + top: var(--header-height); + right: 0; + bottom: 0; + width: min(420px, 96vw); + background: + linear-gradient(180deg, rgba(18, 24, 38, 0.94), rgba(10, 15, 27, 0.94)); + border-left: 1px solid rgba(147, 197, 253, 0.22); + box-shadow: -20px 0 44px rgba(2, 6, 16, 0.58); + backdrop-filter: blur(12px); + transform: translateX(100%); + transition: transform 0.24s ease; + z-index: 12040; + display: flex; + flex-direction: column; } -@keyframes pulse { - from { opacity: 0.5; } - to { opacity: 1; } +.notifications-panel:not(.hidden) { + transform: translateX(0); } -/* Toast Notifications */ -.ready-toast { - position: fixed; - top: calc(var(--header-height) + 20px); - right: 20px; - background: var(--accent-success); - color: white; - padding: var(--space-sm) var(--space-md); - border-radius: var(--radius-md); - box-shadow: var(--shadow-soft); - z-index: 1000; - animation: slideInRight 0.3s ease-out, fadeOutRight 0.3s ease-in 2.7s forwards; +.notifications-panel .panel-header { + padding: 14px 16px; + border-bottom: 1px solid rgba(147, 197, 253, 0.2); + background: + linear-gradient(180deg, rgba(35, 51, 74, 0.64), rgba(26, 38, 58, 0.38)); } -.toast-content { - display: flex; - align-items: center; - gap: var(--space-sm); +.notifications-panel .panel-header h3 { + margin: 0; + font-size: 1rem; + font-weight: 640; + letter-spacing: 0.01em; + color: #f4f8ff; } -.toast-icon { - font-size: 1.2rem; +.notifications-panel .panel-actions .icon-button { + width: 28px; + height: 28px; + border-radius: 8px; + border: 1px solid rgba(147, 197, 253, 0.24); + background: rgba(7, 12, 22, 0.56); + color: rgba(244, 248, 255, 0.9); } -.toast-text { - font-weight: 500; - font-size: 0.875rem; +.notifications-panel .panel-actions .icon-button:hover { + border-color: rgba(147, 197, 253, 0.44); + background: rgba(29, 78, 216, 0.2); } -@keyframes slideInRight { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } +.notifications-content { + padding: 12px 14px 18px; + overflow-y: auto; + overscroll-behavior: contain; + flex: 1; } -@keyframes fadeOutRight { - from { - transform: translateX(0); - opacity: 1; - } - to { - transform: translateX(100%); - opacity: 0; - } +.notification-list { + display: flex; + flex-direction: column; + gap: 10px; } -/* Responsive - ONLY apply if data-visible-count is not set */ -@media (max-width: 1200px) { - .terminal-grid:not([data-visible-count]) { - grid-template-columns: repeat(2, 1fr); - } +.notifications-panel .empty-message { + border: 1px dashed rgba(147, 197, 253, 0.24); + border-radius: 12px; + background: rgba(12, 19, 33, 0.62); + color: rgba(223, 231, 245, 0.76); } -@media (max-width: 768px) { - .projects-chats-shell { - grid-template-columns: 1fr; - min-height: 70vh; - } +.notifications-panel .notification-item { + border: 1px solid rgba(147, 197, 253, 0.2); + border-radius: 14px; + background: + linear-gradient(180deg, rgba(17, 25, 40, 0.92), rgba(10, 16, 28, 0.92)); + padding: 12px 12px 10px; + cursor: pointer; + transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; + box-shadow: 0 8px 18px rgba(2, 6, 16, 0.34); +} - .sidebar-toggle { - display: inline-flex; - } +.notifications-panel .notification-item:hover { + transform: translateY(-1px); + border-color: rgba(96, 165, 250, 0.5); + box-shadow: 0 12px 24px rgba(9, 30, 66, 0.42); +} - header { - padding: 0 var(--space-md); - } +.notifications-panel .notification-item.unread { + border-left: 3px solid #60a5fa; + box-shadow: 0 0 0 1px rgba(96, 165, 250, 0.22), 0 12px 24px rgba(9, 30, 66, 0.38); +} - .header-content { - gap: var(--space-md); - } +.notifications-panel .notification-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 7px; + gap: 8px; +} + +.notifications-panel .notification-time { + font-size: 0.75rem; + letter-spacing: 0.01em; + color: rgba(223, 231, 245, 0.66); +} + +.notifications-panel .notification-type { + font-size: 0.69rem; + font-weight: 620; + letter-spacing: 0.055em; + text-transform: uppercase; + padding: 3px 8px; + border-radius: 999px; + border: 1px solid rgba(148, 163, 184, 0.3); + background: rgba(15, 23, 42, 0.65); + color: rgba(226, 232, 240, 0.9); +} + +.notifications-panel .notification-type.waiting { + border-color: rgba(245, 158, 11, 0.45); + background: rgba(245, 158, 11, 0.14); + color: #fcd34d; +} + +.notifications-panel .notification-type.completed { + border-color: rgba(34, 197, 94, 0.45); + background: rgba(34, 197, 94, 0.14); + color: #86efac; +} + +.notifications-panel .notification-type.error { + border-color: rgba(239, 68, 68, 0.45); + background: rgba(239, 68, 68, 0.12); + color: #fca5a5; +} + +.notifications-panel .notification-message { + color: rgba(239, 245, 255, 0.94); + line-height: 1.42; + font-size: 0.89rem; + margin: 0; +} + +.notifications-panel .notification-meta { + font-size: 0.75rem; + color: rgba(190, 206, 230, 0.72); + margin-top: 6px; +} + +.notifications-panel .notification-actions { + display: flex; + gap: 8px; + margin-top: 10px; + justify-content: flex-end; +} + +.notifications-panel .notification-action { + min-width: 0; + width: auto; + border-radius: 8px; + border: 1px solid rgba(147, 197, 253, 0.24); + background: rgba(13, 20, 35, 0.6); + color: rgba(239, 245, 255, 0.9); + padding: 4px 10px; + font-size: 0.78rem; +} + +.notifications-panel .notification-action:hover { + border-color: rgba(147, 197, 253, 0.45); + background: rgba(37, 99, 235, 0.24); +} + +.terminal-controls { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.terminal-controls label { + font-size: 0.9em; + margin: 0; +} + +/* Loading spinner for build button */ +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Server Launch Settings */ +.server-launch-group { + display: inline-flex; + gap: 4px; + align-items: center; +} + +#launch-settings-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--overlay-backdrop); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; +} + +#launch-settings-modal .modal-content { + background: var(--bg-primary); + border-radius: var(--radius-lg); + max-width: 800px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + box-shadow: var(--shadow-modal); +} + +#launch-settings-modal .modal-header { + padding: var(--space-lg); + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; +} + +#launch-settings-modal .modal-header h2 { + margin: 0; + font-size: 1.5rem; + color: var(--text-primary); +} + +#launch-settings-modal .close-btn { + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 1.5rem; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); +} + +#launch-settings-modal .close-btn:hover { + background: var(--bg-secondary); +} + +#launch-settings-modal .modal-body { + padding: var(--space-lg); +} + +#launch-settings-modal .settings-section { + margin-bottom: var(--space-xl); +} + +#launch-settings-modal .settings-section:last-child { + margin-bottom: 0; +} + +#launch-settings-modal .settings-section h3 { + color: var(--accent-primary); + font-size: 1.1rem; + margin-bottom: var(--space-md); +} + +#launch-settings-modal .setting-group { + margin-bottom: var(--space-md); +} + +#launch-settings-modal .setting-group label { + display: block; + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: var(--space-xs); +} + +#launch-settings-modal .setting-group input { + width: 100%; + padding: var(--space-sm) var(--space-md); + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: monospace; +} + +#launch-settings-modal .setting-group input:focus { + outline: none; + border-color: var(--accent-primary); +} + +#launch-settings-modal .setting-group small { + display: block; + color: var(--text-tertiary); + font-size: 0.8rem; + margin-top: var(--space-xs); +} + +#launch-settings-modal .preset-checkboxes { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: var(--space-md); +} + +#launch-settings-modal .preset-checkbox { + display: flex; + align-items: center; + padding: var(--space-md); + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s; + user-select: none; +} + +#launch-settings-modal .preset-checkbox:hover { + background: var(--bg-tertiary); + border-color: var(--accent-primary); +} + +#launch-settings-modal .preset-checkbox input[type="checkbox"] { + margin-right: var(--space-sm); + width: 18px; + height: 18px; + cursor: pointer; +} + +#launch-settings-modal .preset-checkbox input[type="checkbox"]:checked + span { + color: var(--accent-primary); + font-weight: 600; +} + +#launch-settings-modal .preset-checkbox span { + font-size: 0.9rem; + transition: all 0.2s; +} + +#launch-settings-modal .modal-footer { + padding: var(--space-lg); + border-top: 1px solid var(--border-color); + display: flex; + justify-content: flex-end; + gap: var(--space-md); +} + +#launch-settings-modal .btn-save, +#launch-settings-modal .btn-cancel { + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 0.9rem; + transition: all 0.2s; +} + +#launch-settings-modal .btn-save { + background: var(--accent-primary); + color: white; + border: none; +} + +#launch-settings-modal .btn-save:hover { + background: var(--accent-secondary); +} + +#launch-settings-modal .btn-cancel { + background: var(--bg-secondary); + color: var(--text-secondary); + border: 1px solid var(--border-color); +} + +#launch-settings-modal .btn-cancel:hover { + background: var(--bg-tertiary); +} + +.loading-spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top: 2px solid #fff; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.control-btn.building { + background: var(--color-warning); + cursor: not-allowed; + opacity: 0.8; +} + +.clear-override-btn { + background: var(--bg-primary); + color: var(--text-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + padding: var(--space-xs); + cursor: pointer; + font-size: 0.8em; + transition: background-color 0.2s; +} + +.clear-override-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.template-actions { + display: flex; + gap: var(--space-sm); + margin-top: var(--space-sm); +} + +.template-btn { + padding: var(--space-sm) var(--space-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 0.9em; + transition: all 0.2s; +} + +.template-btn.primary { + background: var(--accent-primary); + color: white; + border-color: var(--accent-primary); +} + +.template-btn.primary:hover { + background: var(--accent-primary-hover); + border-color: var(--accent-primary-hover); +} + +.template-btn.secondary { + background: var(--bg-primary); + color: var(--text-secondary); + border-color: var(--border-color); +} + +.template-btn.secondary:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.update-notification { + margin-top: var(--space-sm); + padding: var(--space-sm); + background: var(--bg-tertiary); + border: 1px solid var(--accent-warning); + border-radius: var(--radius-sm); + transition: all 0.3s; +} + +.update-notification.hidden { + display: none; +} + +.notification-content { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.notification-icon { + font-size: 1.1em; +} + +.notification-text { + flex: 1; + color: var(--text-primary); + font-size: 0.9em; +} + +.dismiss-btn { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + font-size: 1.2em; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + transition: all 0.2s; +} + +.dismiss-btn:hover { + background: var(--bg-primary); + color: var(--text-primary); +} + +/* Loading */ +.loading-message { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +.spinner { + width: 40px; + height: 40px; + border: 3px solid var(--bg-tertiary); + border-top-color: var(--accent-primary); + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto var(--space-md); +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +@keyframes pulse { + from { opacity: 0.5; } + to { opacity: 1; } +} + +/* Toast Notifications */ +.toast-stack { + position: fixed; + top: calc(var(--header-height) + 20px); + right: 20px; + width: min(420px, calc(100vw - 24px)); + display: flex; + flex-direction: column; + gap: 10px; + z-index: 12060; + pointer-events: none; +} + +.toast, +.ready-toast { + --toast-accent: 96, 165, 250; + pointer-events: auto; + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + border-radius: 14px; + border: 1px solid rgba(var(--toast-accent), 0.42); + background: + linear-gradient(180deg, rgba(17, 25, 40, 0.95), rgba(9, 14, 24, 0.95)); + box-shadow: 0 14px 32px rgba(2, 6, 16, 0.5); + color: rgba(244, 248, 255, 0.96); + padding: 11px 12px; + transform: translateX(20px); + opacity: 0; + transition: opacity 0.22s ease, transform 0.22s ease; +} + +.toast.is-visible, +.ready-toast.is-visible { + transform: translateX(0); + opacity: 1; +} + +.toast.is-leaving, +.ready-toast.is-leaving { + transform: translateX(16px); + opacity: 0; +} + +.toast.toast-success, +.ready-toast { + --toast-accent: 74, 222, 128; +} + +.toast.toast-warning { + --toast-accent: 251, 191, 36; +} + +.toast.toast-error { + --toast-accent: 248, 113, 113; +} + +.toast-content { + display: flex; + align-items: flex-start; + gap: 10px; + min-width: 0; + flex: 1; +} + +.toast-icon { + width: 24px; + height: 24px; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(var(--toast-accent), 0.2); + color: rgb(var(--toast-accent)); + flex: 0 0 auto; +} + +.toast-icon svg { + width: 14px; + height: 14px; + display: block; +} + +.toast-text { + font-weight: 500; + font-size: 0.88rem; + line-height: 1.38; + color: rgba(242, 247, 255, 0.95); + word-break: break-word; +} + +.toast-close { + width: 22px; + height: 22px; + border-radius: 7px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgba(15, 23, 42, 0.58); + color: rgba(230, 237, 249, 0.84); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.72rem; + line-height: 1; + padding: 0; + flex: 0 0 auto; +} + +.toast-close:hover { + background: rgba(51, 65, 85, 0.62); + color: rgba(255, 255, 255, 0.97); + border-color: rgba(255, 255, 255, 0.3); +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes fadeOutRight { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +/* Responsive - ONLY apply if data-visible-count is not set */ +@media (max-width: 1200px) { + .terminal-grid:not([data-visible-count]) { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .projects-chats-shell { + grid-template-columns: 1fr; + min-height: 70vh; + } + + .sidebar-toggle { + display: inline-flex; + } + + header { + padding: 0 var(--space-md); + } + + .header-content { + gap: var(--space-md); + } header h1 { font-size: 1rem; @@ -7438,25 +8201,26 @@ header h1 { top: calc(var(--header-height) + 20px); right: 20px; width: 300px; - z-index: 1001; + z-index: 12050; pointer-events: none; } .cross-workspace-notification { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-left: 4px solid var(--accent-warning); - border-radius: var(--radius-md); + background: + linear-gradient(180deg, rgba(18, 24, 38, 0.94), rgba(10, 15, 27, 0.94)); + border: 1px solid rgba(245, 158, 11, 0.28); + border-left: 3px solid rgba(245, 158, 11, 0.85); + border-radius: 12px; margin-bottom: var(--space-md); - box-shadow: var(--shadow-soft); + box-shadow: 0 14px 28px rgba(2, 6, 16, 0.42); pointer-events: auto; - animation: slideInRight 0.3s ease-out; + animation: slideInRight 0.24s ease-out; } -.notification-header { +.cross-workspace-notification .notification-header { padding: var(--space-sm) var(--space-md); - background: var(--bg-tertiary); - border-bottom: 1px solid var(--border-color); + background: rgba(245, 158, 11, 0.1); + border-bottom: 1px solid rgba(245, 158, 11, 0.24); display: flex; justify-content: space-between; align-items: center; @@ -7468,7 +8232,7 @@ header h1 { color: var(--text-primary); } -.notification-close { +.cross-workspace-notification .notification-close { background: none; border: none; color: var(--text-secondary); @@ -7481,17 +8245,17 @@ header h1 { justify-content: center; } -.notification-body { +.cross-workspace-notification .notification-body { padding: var(--space-md); } -.notification-message { +.cross-workspace-notification .notification-message { font-size: 0.9rem; color: var(--text-primary); margin-bottom: var(--space-md); } -.notification-actions { +.cross-workspace-notification .notification-actions { display: flex; gap: var(--space-sm); } @@ -9260,395 +10024,661 @@ header h1 { font-size: 0.75rem; } -.detail-row-full { +.detail-row-full { + display: flex; + gap: var(--space-sm); + align-items: flex-start; +} + +.detail-row-full .detail-label { + color: var(--text-muted); + flex-shrink: 0; + min-width: 80px; +} + +.folder-path-full { + color: #90cdf4; + word-break: break-all; + font-family: monospace; + font-size: 0.75rem; + background: rgba(99, 179, 237, 0.1); + padding: 4px 8px; + border-radius: 4px; +} + +/* Conversation details grid */ +.conv-details-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: var(--space-xs) var(--space-md); + margin: var(--space-sm) 0; + padding: var(--space-sm); + background: var(--bg-primary); + border-radius: var(--radius-xs); + font-size: 0.75rem; +} + +.detail-row { + display: flex; + gap: var(--space-xs); + align-items: baseline; +} + +.detail-label { + color: var(--text-muted); + min-width: 60px; + flex-shrink: 0; +} + +.detail-value { + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.detail-value.folder-path { + color: #63b3ed; + cursor: help; +} + +.detail-value.repo-name { + color: #9f7aea; +} + +.detail-value.branch-name { + color: #48bb78; +} + +.detail-value.model-name { + color: #f6ad55; +} + +.conv-actions { + display: flex; + gap: var(--space-sm); + flex-wrap: wrap; + margin-top: var(--space-sm); +} + +.conv-actions .btn-small { + padding: 6px 12px; + font-size: 0.8rem; +} + +.conv-actions .btn-small.primary { + background: var(--accent-primary); + color: var(--text-on-accent); + border: none; +} + +.conv-actions .btn-small.primary:hover { + background: var(--accent-primary-hover); +} + +.conv-actions .btn-small.secondary { + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-secondary); +} + +.conv-actions .btn-small.secondary:hover { + background: var(--bg-tertiary); +} + +.browser-footer { + padding: var(--space-sm) var(--space-lg); + border-top: 1px solid var(--border-color); + font-size: 0.8rem; + color: var(--text-muted); +} + +.no-results, .loading, .error { + text-align: center; + padding: var(--space-xl); + color: var(--text-muted); +} + +/* Conversation Details Modal */ +.conversation-details-modal .details-content { + max-width: 700px; + width: 90vw; + max-height: 85vh; + display: flex; + flex-direction: column; +} + +.details-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-md) var(--space-lg); + border-bottom: 1px solid var(--border-color); +} + +.details-meta { + padding: var(--space-md) var(--space-lg); + background: var(--bg-tertiary); + font-size: 0.85rem; +} + +.details-meta p { + margin: var(--space-xs) 0; +} + +.details-summary { + padding: var(--space-md) var(--space-lg); + border-bottom: 1px solid var(--border-color); + font-style: italic; + color: var(--text-secondary); +} + +.details-messages { + flex: 1; + overflow-y: auto; + padding: var(--space-md) var(--space-lg); +} + +.details-messages h4 { + margin-bottom: var(--space-md); +} + +.messages-list { + display: flex; + flex-direction: column; + gap: var(--space-sm); +} + +.messages-list .message { + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-sm); + font-size: 0.85rem; +} + +.messages-list .message.user { + background: var(--bg-tertiary); + margin-left: var(--space-lg); +} + +.messages-list .message.assistant { + background: var(--accent-primary); + background: rgba(59, 130, 246, 0.2); + margin-right: var(--space-lg); +} + +.message-header { + display: flex; + justify-content: space-between; + font-size: 0.75rem; + color: var(--text-muted); + margin-bottom: var(--space-xs); +} + +.message-content { + white-space: pre-wrap; + word-break: break-word; +} + +.tool-uses { + margin-top: var(--space-xs); + font-size: 0.7rem; + color: var(--text-muted); +} + +.more-messages { + text-align: center; + padding: var(--space-md); + color: var(--text-muted); + font-style: italic; +} + +.details-actions { + padding: var(--space-md) var(--space-lg); + border-top: 1px solid var(--border-color); + display: flex; + justify-content: flex-end; +} + +/* ============================================ + Ports Panel Styles + ============================================ */ + +.ports-modal .ports-content { + max-width: 1100px; + width: 96vw; + max-height: 92vh; + display: flex; + flex-direction: column; +} + +.ports-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-md) var(--space-lg); + border-bottom: 1px solid var(--border-color); +} + +.ports-header h2 { + margin: 0; + font-size: 1.2rem; +} + +.ports-info { + padding: var(--space-sm) var(--space-lg); + background: var(--bg-tertiary); + font-size: 0.85rem; + color: var(--text-muted); +} + +.ports-list { + flex: 1; + overflow-y: auto; + padding: var(--space-lg); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: var(--space-md); + align-content: start; +} + +.port-item { display: flex; + flex-direction: column; gap: var(--space-sm); - align-items: flex-start; + padding: var(--space-md); + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + border-left: 3px solid var(--border-color); + transition: all 0.15s; } -.detail-row-full .detail-label { - color: var(--text-muted); - flex-shrink: 0; - min-width: 80px; +.port-item:hover { + background: var(--bg-primary); } -.folder-path-full { - color: #90cdf4; - word-break: break-all; - font-family: monospace; - font-size: 0.75rem; - background: rgba(99, 179, 237, 0.1); - padding: 4px 8px; - border-radius: 4px; +.port-item.orchestrator, +.port-item.orchestrator-dev { + border-left-color: #9f7aea; } -/* Conversation details grid */ -.conv-details-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: var(--space-xs) var(--space-md); - margin: var(--space-sm) 0; - padding: var(--space-sm); - background: var(--bg-primary); - border-radius: var(--radius-xs); - font-size: 0.75rem; +.port-item.client, +.port-item.client-dev { + border-left-color: #63b3ed; } -.detail-row { - display: flex; - gap: var(--space-xs); - align-items: baseline; +.port-item.vite, +.port-item.react { + border-left-color: #48bb78; } -.detail-label { - color: var(--text-muted); - min-width: 60px; - flex-shrink: 0; +.port-item.game-server { + border-left-color: #f6ad55; } -.detail-value { - color: var(--text-secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.port-item.python, +.port-item.flask { + border-left-color: #ffd93d; } -.detail-value.folder-path { - color: #63b3ed; - cursor: help; +.port-item.node { + border-left-color: #68d391; } -.detail-value.repo-name { - color: #9f7aea; +.port-icon { + font-size: 1.2rem; + flex-shrink: 0; } -.detail-value.branch-name { - color: #48bb78; +.port-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-md); + min-width: 0; } -.detail-value.model-name { - color: #f6ad55; +.port-main { + display: flex; + align-items: flex-start; + gap: var(--space-md); + min-width: 0; + flex: 1; } -.conv-actions { - display: flex; - gap: var(--space-sm); - flex-wrap: wrap; - margin-top: var(--space-sm); +.port-details { + flex: 1; + min-width: 0; } -.conv-actions .btn-small { - padding: 6px 12px; - font-size: 0.8rem; +.port-name { + display: block; + font-weight: 500; + color: var(--text-primary); + font-size: 0.9rem; } -.conv-actions .btn-small.primary { - background: var(--accent-primary); - color: var(--text-on-accent); - border: none; +.port-process { + font-size: 0.75rem; + color: var(--text-muted); } -.conv-actions .btn-small.primary:hover { - background: var(--accent-primary-hover); +.port-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: var(--space-xs); + flex-wrap: wrap; } -.conv-actions .btn-small.secondary { - background: var(--bg-primary); +.port-action-btn { border: 1px solid var(--border-color); - color: var(--text-secondary); + background: var(--bg-primary); + color: var(--text-primary); + border-radius: var(--radius-xs); + padding: 4px 8px; + font-size: 0.75rem; + cursor: pointer; + line-height: 1.1; + transition: background 0.15s, border-color 0.15s; } -.conv-actions .btn-small.secondary:hover { - background: var(--bg-tertiary); +.port-action-btn:hover { + background: var(--bg-secondary); + border-color: var(--accent-primary); } -.browser-footer { - padding: var(--space-sm) var(--space-lg); +.ports-footer { + padding: var(--space-md) var(--space-lg); border-top: 1px solid var(--border-color); - font-size: 0.8rem; - color: var(--text-muted); + display: flex; + justify-content: flex-end; } -.no-results, .loading, .error { +.no-ports { text-align: center; - padding: var(--space-xl); + padding: var(--space-lg); color: var(--text-muted); } -/* Conversation Details Modal */ -.conversation-details-modal .details-content { - max-width: 700px; - width: 90vw; - max-height: 85vh; +/* ============================================ + PRs Panel Styles + ============================================ */ + +.prs-modal .prs-content { + max-width: 1100px; + width: 96vw; + max-height: 92vh; display: flex; flex-direction: column; } -.details-header { +.prs-toolbar { + padding: var(--space-sm) var(--space-lg); + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); display: flex; - justify-content: space-between; + gap: var(--space-md); align-items: center; - padding: var(--space-md) var(--space-lg); - border-bottom: 1px solid var(--border-color); + flex-wrap: wrap; } -.details-meta { - padding: var(--space-md) var(--space-lg); - background: var(--bg-tertiary); - font-size: 0.85rem; +.prs-toolbar-group { + display: flex; + gap: 8px; + align-items: center; + flex-wrap: wrap; } -.details-meta p { - margin: var(--space-xs) 0; +.prs-label { + font-size: 0.75rem; + color: var(--text-secondary); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; } -.details-summary { - padding: var(--space-md) var(--space-lg); - border-bottom: 1px solid var(--border-color); - font-style: italic; - color: var(--text-secondary); +.prs-search { + min-width: 220px; + flex: 1; } -.details-messages { +.prs-input { + min-width: 220px; + flex: 0 1 320px; +} + +.prs-list { flex: 1; overflow-y: auto; - padding: var(--space-md) var(--space-lg); + padding: var(--space-lg); + display: flex; + flex-direction: column; + gap: var(--space-sm); } -.details-messages h4 { - margin-bottom: var(--space-md); +.pr-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + padding: var(--space-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-secondary); } -.messages-list { +.pr-main { display: flex; flex-direction: column; - gap: var(--space-sm); + gap: 6px; + min-width: 0; + flex: 1; } -.messages-list .message { - padding: var(--space-sm) var(--space-md); - border-radius: var(--radius-sm); - font-size: 0.85rem; +.pr-title { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; } -.messages-list .message.user { - background: var(--bg-tertiary); - margin-left: var(--space-lg); +.pr-repo { + font-family: var(--font-mono); + font-size: 0.8rem; + color: var(--text-secondary); } -.messages-list .message.assistant { - background: var(--accent-primary); - background: rgba(59, 130, 246, 0.2); - margin-right: var(--space-lg); +.pr-number { + font-family: var(--font-mono); + font-size: 0.8rem; + color: var(--text-secondary); } -.message-header { - display: flex; - justify-content: space-between; +.pr-badge { + font-family: var(--font-mono); font-size: 0.75rem; - color: var(--text-muted); - margin-bottom: var(--space-xs); + padding: 3px 6px; + border-radius: 999px; + border: 1px solid var(--border-color); + background: var(--bg-tertiary); + color: var(--text-secondary); } -.message-content { - white-space: pre-wrap; - word-break: break-word; +.pr-badge.draft { + border-color: var(--accent-warning); } -.tool-uses { - margin-top: var(--space-xs); - font-size: 0.7rem; - color: var(--text-muted); +.pr-subtitle { + font-weight: 600; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.more-messages { - text-align: center; - padding: var(--space-md); - color: var(--text-muted); - font-style: italic; +.pr-meta { + font-size: 0.75rem; + color: var(--text-tertiary); } -.details-actions { - padding: var(--space-md) var(--space-lg); - border-top: 1px solid var(--border-color); +.pr-actions { display: flex; - justify-content: flex-end; + gap: var(--space-sm); + flex-shrink: 0; } /* ============================================ - Ports Panel Styles + Tasks Panel Styles ============================================ */ -.ports-modal .ports-content { - max-width: 1100px; - width: 96vw; - max-height: 92vh; - display: flex; - flex-direction: column; -} - -.ports-header { +.queue-summary { display: flex; - justify-content: space-between; - align-items: center; - padding: var(--space-md) var(--space-lg); - border-bottom: 1px solid var(--border-color); -} - -.ports-header h2 { - margin: 0; - font-size: 1.2rem; -} - -.ports-info { - padding: var(--space-sm) var(--space-lg); - background: var(--bg-tertiary); - font-size: 0.85rem; - color: var(--text-muted); -} - -.ports-list { - flex: 1; - overflow-y: auto; - padding: var(--space-lg); - display: grid; - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); - gap: var(--space-md); - align-content: start; + gap: 8px; + flex-wrap: wrap; + padding: 0 0 10px 0; } -.port-item { +.tasks-modal .tasks-content { + /* Tasks is a primary workflow: prefer a robust, full-viewport panel. */ + max-width: none; + box-sizing: border-box; + width: 100%; + max-width: 100vw; + max-height: 100vh; + height: 100%; display: flex; flex-direction: column; - gap: var(--space-sm); + overflow: hidden; + position: relative; + border-radius: 0; padding: var(--space-md); - background: var(--bg-tertiary); - border-radius: var(--radius-sm); - border-left: 3px solid var(--border-color); - transition: all 0.15s; + --tasks-board-accent: var(--accent-primary); + direction: ltr; + text-align: left; } -.port-item:hover { - background: var(--bg-primary); +.tasks-modal .tasks-content.tasks-has-board-accent .tasks-toolbar { + border-bottom-color: color-mix(in srgb, var(--border-color) 55%, var(--tasks-board-accent) 45%); } -.port-item.orchestrator, -.port-item.orchestrator-dev { - border-left-color: #9f7aea; +.tasks-modal .tasks-content.tasks-has-board-accent .tasks-column-header { + background: color-mix(in srgb, var(--bg-tertiary) 86%, var(--tasks-board-accent) 14%); } -.port-item.client, -.port-item.client-dev { - border-left-color: #63b3ed; +.tasks-modal .tasks-content.tasks-has-board-accent .tasks-column-header:hover { + background: color-mix(in srgb, var(--bg-primary) 86%, var(--tasks-board-accent) 14%); } -.port-item.vite, -.port-item.react { - border-left-color: #48bb78; +.tasks-modal .tasks-body { + direction: ltr; + text-align: left; } -.port-item.game-server { - border-left-color: #f6ad55; +.tasks-modal { + background: transparent; } -.port-item.python, -.port-item.flask { - border-left-color: #ffd93d; +.tasks-modal.tasks-theme-light { + --bg-primary: #ffffff; + --bg-secondary: #f6f8fa; + --bg-tertiary: #e6e8eb; + --text-primary: #24292f; + --text-secondary: #57606a; + --text-tertiary: #6e7781; + --border-color: #d0d7de; } -.port-item.node { - border-left-color: #68d391; +.tasks-modal .modal-header { + position: sticky; + top: 0; + z-index: 5; + background: var(--bg-secondary); + padding-bottom: var(--space-sm); } -.port-icon { - font-size: 1.2rem; - flex-shrink: 0; +.tasks-modal .tasks-close-btn { + width: 44px; + height: 44px; + font-size: 1.6rem; + color: var(--accent-danger); + border: 1px solid rgba(248, 81, 73, 0.35); + background: rgba(248, 81, 73, 0.12); } -.port-card-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: var(--space-md); - min-width: 0; +.tasks-modal .tasks-close-btn:hover { + background: rgba(248, 81, 73, 0.2); + border-color: rgba(248, 81, 73, 0.55); + color: var(--accent-danger); } -.port-main { - display: flex; - align-items: flex-start; - gap: var(--space-md); - min-width: 0; - flex: 1; +.tasks-modal .tasks-close-btn:focus-visible { + outline: 2px solid var(--accent-danger); + outline-offset: 2px; } -.port-details { - flex: 1; - min-width: 0; +.tasks-view-toggle { + display: flex; + gap: 6px; + align-items: center; } -.port-name { - display: block; - font-weight: 500; - color: var(--text-primary); - font-size: 0.9rem; +.tasks-view-btn.active { + border-color: var(--accent-primary); + box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.15); } -.port-process { - font-size: 0.75rem; - color: var(--text-muted); +.tasks-filter { + position: relative; } -.port-actions { - display: flex; - align-items: center; - justify-content: flex-end; - gap: var(--space-xs); - flex-wrap: wrap; +.tasks-filter summary { + list-style: none; } -.port-action-btn { - border: 1px solid var(--border-color); - background: var(--bg-primary); - color: var(--text-primary); - border-radius: var(--radius-xs); - padding: 4px 8px; - font-size: 0.75rem; - cursor: pointer; - line-height: 1.1; - transition: background 0.15s, border-color 0.15s; +.tasks-filter summary::-webkit-details-marker { + display: none; } -.port-action-btn:hover { +.tasks-filter-popover { + position: absolute; + top: calc(100% + 8px); + left: 0; + min-width: 240px; + max-height: 320px; + overflow: auto; + z-index: 5; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); background: var(--bg-secondary); - border-color: var(--accent-primary); + box-shadow: 0 10px 24px rgba(0,0,0,0.12); + padding: 10px; } -.ports-footer { - padding: var(--space-md) var(--space-lg); - border-top: 1px solid var(--border-color); +.tasks-filter-actions { display: flex; - justify-content: flex-end; + gap: 8px; + margin-bottom: 10px; } -.no-ports { - text-align: center; - padding: var(--space-lg); - color: var(--text-muted); +.tasks-filter-list { + display: flex; + flex-direction: column; + gap: 6px; } -/* ============================================ - PRs Panel Styles - ============================================ */ - -.prs-modal .prs-content { - max-width: 1100px; - width: 96vw; - max-height: 92vh; +.tasks-filter-item { display: flex; - flex-direction: column; + align-items: center; + gap: 8px; + font-size: 0.85rem; + color: var(--text-primary); +} + +.tasks-filter-item input { + accent-color: var(--accent-primary); } -.prs-toolbar { +.tasks-toolbar { padding: var(--space-sm) var(--space-lg); border-bottom: 1px solid var(--border-color); background: var(--bg-tertiary); @@ -9658,201 +10688,255 @@ header h1 { flex-wrap: wrap; } -.prs-toolbar-group { - display: flex; - gap: 8px; +.tasks-launch-defaults { + display: inline-flex; align-items: center; - flex-wrap: wrap; + gap: 6px; + padding: 4px 8px; + border: 1px solid var(--border-color); + border-radius: 999px; + background: var(--bg-secondary); } -.prs-label { - font-size: 0.75rem; - color: var(--text-secondary); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; +.tasks-launch-default-tier-group { + padding: 0; + border: none; + background: transparent; } -.prs-search { - min-width: 220px; - flex: 1; +.tasks-launch-default-agent-group { + padding: 0; + border: none; + background: transparent; } -.prs-input { - min-width: 220px; - flex: 0 1 320px; +.tasks-launch-default-mode-group { + padding: 0; + border: none; + background: transparent; } -.prs-list { - flex: 1; - overflow-y: auto; - padding: var(--space-lg); - display: flex; - flex-direction: column; - gap: var(--space-sm); +.tasks-launch-defaults-label { + font-weight: 900; + font-size: 0.85rem; + color: var(--text-secondary); } -.pr-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-md); - padding: var(--space-md); +.tasks-toggle.tasks-toggle-mini { + font-size: 0.75rem; + gap: 6px; +} + +.tasks-toggle.tasks-toggle-mini span { + opacity: 0.9; +} + +.tasks-board-accent { + width: 12px; + height: 12px; + border-radius: 999px; + background: var(--tasks-board-accent); border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); + box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.12); } -.pr-main { - display: flex; - flex-direction: column; - gap: 6px; - min-width: 0; - flex: 1; +.tasks-board-accent.is-hidden { + display: none; } -.pr-title { - display: flex; +.tasks-board-picker { + position: relative; + display: inline-flex; align-items: center; gap: 8px; - flex-wrap: wrap; } -.pr-repo { - font-family: var(--font-mono); - font-size: 0.8rem; - color: var(--text-secondary); +.tasks-board-btn { + min-width: 200px; + text-align: left; + justify-content: flex-start; } -.pr-number { - font-family: var(--font-mono); - font-size: 0.8rem; - color: var(--text-secondary); +.tasks-select-hidden { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + opacity: 0; + pointer-events: none; } -.pr-badge { - font-family: var(--font-mono); - font-size: 0.75rem; - padding: 3px 6px; - border-radius: 999px; - border: 1px solid var(--border-color); +.tasks-board-menu { + position: absolute; + top: calc(100% + 6px); + left: 0; + z-index: 10; + min-width: 320px; + max-width: 420px; + max-height: 60vh; + overflow: auto; background: var(--bg-tertiary); - color: var(--text-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + box-shadow: 0 14px 32px rgba(0, 0, 0, 0.35); + padding: 6px; } -.pr-badge.draft { - border-color: var(--accent-warning); +.tasks-board-menu-search-wrap { + position: sticky; + top: 0; + z-index: 1; + background: var(--bg-tertiary); + padding: 6px; + border-bottom: 1px solid var(--border-color); + margin: -6px -6px 6px; } -.pr-subtitle { - font-weight: 600; +.tasks-board-menu-search { + width: 100%; + background: var(--bg-secondary); color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 8px 10px; + font-size: 0.85rem; } -.pr-meta { - font-size: 0.75rem; - color: var(--text-tertiary); +.tasks-board-menu-search:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; } -.pr-actions { +.tasks-board-menu.hidden { + display: none; +} + +.tasks-board-menu-item { + width: 100%; display: flex; - gap: var(--space-sm); - flex-shrink: 0; + align-items: center; + gap: 10px; + background: transparent; + border: 1px solid transparent; + color: var(--text-primary); + border-radius: var(--radius-md); + padding: 8px 10px; + cursor: pointer; + text-align: left; } -/* ============================================ - Tasks Panel Styles - ============================================ */ +.tasks-board-menu-item:hover { + background: rgba(31, 111, 235, 0.12); + border-color: rgba(31, 111, 235, 0.25); +} -.queue-summary { - display: flex; - gap: 8px; - flex-wrap: wrap; - padding: 0 0 10px 0; +.tasks-board-menu-item.is-active { + border-color: rgba(31, 111, 235, 0.5); } -.tasks-modal .tasks-content { - /* Tasks is a primary workflow: prefer a robust, full-viewport panel. */ - max-width: none; - box-sizing: border-box; - width: 100%; - max-width: 100vw; - max-height: 100vh; - height: 100%; - display: flex; - flex-direction: column; +.tasks-board-menu-item.is-selected { + background: rgba(31, 111, 235, 0.18); + border-color: rgba(31, 111, 235, 0.35); +} + +.tasks-board-menu-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 999px; + border: 1px solid var(--border-color); + flex: 0 0 auto; +} + +.tasks-board-menu-dot.is-hidden { + visibility: hidden; +} + +.tasks-board-menu-label { overflow: hidden; - position: relative; - border-radius: 0; - padding: var(--space-md); - --tasks-board-accent: var(--accent-primary); - direction: ltr; - text-align: left; + text-overflow: ellipsis; + white-space: nowrap; } -.tasks-modal .tasks-content.tasks-has-board-accent .tasks-toolbar { - border-bottom-color: color-mix(in srgb, var(--border-color) 55%, var(--tasks-board-accent) 45%); +.tasks-board-menu-empty { + padding: 10px 12px; + color: var(--text-tertiary); + font-size: 0.85rem; } -.tasks-modal .tasks-content.tasks-has-board-accent .tasks-column-header { - background: color-mix(in srgb, var(--bg-tertiary) 86%, var(--tasks-board-accent) 14%); +.tasks-hotkeys-overlay { + position: absolute; + inset: 0; + background: var(--overlay-backdrop); + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + z-index: 20; } -.tasks-modal .tasks-content.tasks-has-board-accent .tasks-column-header:hover { - background: color-mix(in srgb, var(--bg-primary) 86%, var(--tasks-board-accent) 14%); +.tasks-launch-popover-overlay { + position: absolute; + inset: 0; + background: var(--overlay-backdrop-faint); + z-index: 19; +} + +.tasks-launch-popover { + position: absolute; + width: min(460px, 92vw); + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + box-shadow: var(--shadow-float); + padding: 12px; } -.tasks-modal .tasks-body { - direction: ltr; - text-align: left; +.tasks-launch-popover-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 8px; } -.tasks-modal { - background: transparent; +.tasks-launch-popover-title { + font-weight: 800; + color: var(--text-primary); } -.tasks-modal.tasks-theme-light { - --bg-primary: #ffffff; - --bg-secondary: #f6f8fa; - --bg-tertiary: #e6e8eb; - --text-primary: #24292f; - --text-secondary: #57606a; - --text-tertiary: #6e7781; - --border-color: #d0d7de; +.tasks-launch-popover-meta { + font-size: 0.8rem; + color: var(--text-tertiary); + margin-bottom: 10px; } -.tasks-modal .modal-header { - position: sticky; - top: 0; - z-index: 5; +.tasks-launch-popover-warn { + font-size: 0.85rem; + color: var(--text-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 10px 12px; background: var(--bg-secondary); - padding-bottom: var(--space-sm); -} - -.tasks-modal .tasks-close-btn { - width: 44px; - height: 44px; - font-size: 1.6rem; - color: var(--accent-danger); - border: 1px solid rgba(248, 81, 73, 0.35); - background: rgba(248, 81, 73, 0.12); + margin-bottom: 10px; } -.tasks-modal .tasks-close-btn:hover { - background: rgba(248, 81, 73, 0.2); - border-color: rgba(248, 81, 73, 0.55); - color: var(--accent-danger); +.tasks-launch-popover-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + align-items: end; } -.tasks-modal .tasks-close-btn:focus-visible { - outline: 2px solid var(--accent-danger); - outline-offset: 2px; +.tasks-launch-popover-field { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 0.8rem; + color: var(--text-secondary); } -.tasks-view-toggle { +.tasks-launch-popover-actions { display: flex; gap: 6px; align-items: center; @@ -9860,9 +10944,15 @@ header h1 { max-width: 100%; } -.tasks-view-btn.active { - border-color: var(--accent-primary); - box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.15); +.tasks-hotkeys-card { + width: min(860px, 96vw); + max-height: min(80vh, 760px); + overflow: auto; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45); + padding: 14px; } .tasks-view-btn { @@ -9873,50 +10963,58 @@ header h1 { position: relative; } -.tasks-filter summary { - list-style: none; +.tasks-hotkeys-title { + font-weight: 800; + font-size: 1rem; + color: var(--text-primary); } -.tasks-filter summary::-webkit-details-marker { - display: none; +.tasks-hotkeys-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 12px; } -.tasks-filter-popover { - position: absolute; - top: calc(100% + 8px); - left: 0; - min-width: 240px; - max-height: 320px; - overflow: auto; - z-index: 5; +.tasks-hotkeys-group { border: 1px solid var(--border-color); - border-radius: var(--radius-md); background: var(--bg-secondary); - box-shadow: 0 10px 24px rgba(0,0,0,0.12); + border-radius: var(--radius-md); padding: 10px; } -.tasks-filter-actions { - display: flex; - gap: 8px; - margin-bottom: 10px; +.tasks-hotkeys-group-title { + font-weight: 800; + font-size: 0.85rem; + color: var(--text-primary); + margin-bottom: 8px; } -.tasks-filter-list { - display: flex; - flex-direction: column; - gap: 6px; +.tasks-hotkeys-row { + font-size: 0.85rem; + color: var(--text-secondary); + line-height: 1.3; + margin-bottom: 6px; } -.tasks-filter-item { - display: flex; +.tasks-hotkeys-row code { + font-size: 0.8rem; + background: rgba(31, 111, 235, 0.12); + border: 1px solid rgba(31, 111, 235, 0.2); + border-radius: 6px; + padding: 2px 6px; + color: var(--text-primary); +} + +.tasks-toggle { + display: inline-flex; align-items: center; gap: 8px; - font-size: 0.85rem; - color: var(--text-primary); + font-size: 0.8rem; + color: var(--text-secondary); + user-select: none; } -.tasks-filter-item input { +.tasks-toggle input { accent-color: var(--accent-primary); } @@ -9927,7 +11025,11 @@ header h1 { display: flex; gap: var(--space-sm); align-items: center; - flex-wrap: wrap; + gap: 6px; + padding: 4px 6px; + border: 1px solid var(--border-color); + border-radius: 999px; + background: var(--bg-secondary); } #queue-panel .tasks-toolbar { @@ -9999,389 +11101,460 @@ header h1 { align-items: center; gap: 6px; padding: 4px 8px; - border: 1px solid var(--border-color); border-radius: 999px; - background: var(--bg-secondary); + font-size: 0.8rem; + color: var(--text-secondary); + cursor: pointer; + user-select: none; } -.tasks-launch-default-tier-group { - padding: 0; - border: none; - background: transparent; +.tasks-radio-option input { + margin: 0; + accent-color: var(--accent-primary); } -.tasks-launch-default-agent-group { - padding: 0; - border: none; - background: transparent; +.tasks-radio-option:has(input:checked) { + background: rgba(31, 111, 235, 0.15); + color: var(--text-primary); } -.tasks-launch-default-mode-group { - padding: 0; - border: none; - background: transparent; +.tasks-radio-option:has(input:focus-visible) { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; } -.tasks-launch-defaults-label { - font-weight: 900; +.tasks-select { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 6px 10px; font-size: 0.85rem; - color: var(--text-secondary); + min-width: 160px; } -.tasks-toggle.tasks-toggle-mini { - font-size: 0.75rem; - gap: 6px; +.tasks-search { + min-width: 220px; + flex: 1; } -.tasks-toggle.tasks-toggle-mini span { - opacity: 0.9; +.tasks-body { + flex: 1; + min-height: 0; + display: grid; + grid-template-columns: 420px 1fr; + grid-template-rows: 1fr; + grid-template-areas: "cards detail"; } -.tasks-board-accent { - width: 12px; - height: 12px; - border-radius: 999px; - background: var(--tasks-board-accent); - border: 1px solid var(--border-color); - box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.12); +.tasks-body.tasks-body-board { + /* Board view: show full-width kanban until a card is selected. */ + grid-template-columns: 1fr; + grid-template-areas: "cards"; + position: relative; + overflow: hidden; +} + +.tasks-body.tasks-body-board .tasks-detail { + display: none; +} + +.tasks-body.tasks-body-board .tasks-cards { + grid-column: 1; + border-left: none; +} + +.tasks-body.tasks-body-board.tasks-kanban-wrap .tasks-cards { + overflow: auto; +} + +.tasks-body.tasks-body-board.tasks-has-detail { + /* When a card is selected, show details as an overlay on the right. */ + grid-template-columns: 1fr; +} + +.tasks-body.tasks-body-board.tasks-has-detail .tasks-detail { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 520px; + max-width: min(520px, 65vw); + border-left: 1px solid var(--border-color); + box-shadow: -10px 0 24px rgba(0, 0, 0, 0.25); + z-index: 5; +} + +.tasks-body.tasks-body-board.tasks-has-detail .tasks-cards { + grid-column: 1; + border-left: none; +} + +.tasks-cards { + grid-area: cards; + grid-column: 1; + grid-row: 1; + overflow-y: auto; + padding: var(--space-md); + border-right: 1px solid var(--border-color); + background: var(--bg-secondary); + display: flex; + flex-direction: column; + gap: var(--space-sm); + min-width: 0; } -.tasks-board-accent.is-hidden { - display: none; +.tasks-body.tasks-body-board .tasks-cards { + border-right: none; + overflow: hidden; + padding: var(--space-sm); + background: var(--bg-primary); } -.tasks-board-picker { - position: relative; - display: inline-flex; - align-items: center; - gap: 8px; +.tasks-board { + height: 100%; + display: flex; + gap: var(--space-md); + justify-content: flex-start; + overflow-x: auto; + overflow-y: hidden; + padding-bottom: var(--space-sm); + scroll-snap-type: x mandatory; } -.tasks-board-btn { - min-width: 200px; - text-align: left; - justify-content: flex-start; +.tasks-board.tasks-board-wrap { + flex-wrap: wrap; + overflow-x: hidden; + overflow-y: auto; + align-content: flex-start; + scroll-snap-type: none; } -.tasks-select-hidden { - position: absolute; - left: -9999px; - width: 1px; - height: 1px; - opacity: 0; - pointer-events: none; +.tasks-board.tasks-board-wrap.tasks-board-grid .tasks-column-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + align-content: start; } -.tasks-board-menu { - position: absolute; - top: calc(100% + 6px); - left: 0; - z-index: 10; - min-width: 320px; - max-width: 420px; - max-height: 60vh; - overflow: auto; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - box-shadow: 0 14px 32px rgba(0, 0, 0, 0.35); - padding: 6px; +.tasks-board.tasks-board-expand .tasks-column-cards { + display: grid; + grid-auto-flow: column; + grid-template-rows: repeat(var(--tasks-card-rows, 1), min-content); + grid-template-columns: repeat(var(--tasks-card-columns, 1), minmax(180px, 1fr)); + align-content: start; + gap: var(--space-sm); + overflow: hidden; + flex: 1; + min-height: 0; } -.tasks-board-menu-search-wrap { - position: sticky; - top: 0; - z-index: 1; - background: var(--bg-tertiary); - padding: 6px; - border-bottom: 1px solid var(--border-color); - margin: -6px -6px 6px; +.tasks-board.tasks-board-grid .task-card-board { + height: fit-content; } -.tasks-board-menu-search { - width: 100%; +.tasks-column { + --tasks-col-expanded: clamp(240px, 22vw, 360px); + --tasks-col-collapsed: 56px; + --tasks-card-columns: 1; + --tasks-card-rows: 1; + width: var(--tasks-col-expanded); + min-width: var(--tasks-col-expanded); background: var(--bg-secondary); - color: var(--text-primary); border: 1px solid var(--border-color); border-radius: var(--radius-md); - padding: 8px 10px; - font-size: 0.85rem; -} - -.tasks-board-menu-search:focus-visible { - outline: 2px solid var(--accent-primary); - outline-offset: 2px; + display: flex; + flex-direction: column; + max-height: 100%; + scroll-snap-align: start; + position: relative; } -.tasks-board-menu.hidden { - display: none; +.tasks-column.hover { + border-color: var(--tasks-board-accent); + box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.15); } -.tasks-board-menu-item { - width: 100%; +.tasks-column-header { + padding: var(--space-sm) var(--space-md); display: flex; - align-items: center; - gap: 10px; - background: transparent; - border: 1px solid transparent; - color: var(--text-primary); - border-radius: var(--radius-md); - padding: 8px 10px; + align-items: baseline; + justify-content: space-between; + gap: var(--space-sm); + width: 100%; + background: var(--bg-tertiary); + border: none; + border-bottom: 1px solid var(--border-color); cursor: pointer; text-align: left; + color: var(--text-primary); } -.tasks-board-menu-item:hover { - background: rgba(31, 111, 235, 0.12); - border-color: rgba(31, 111, 235, 0.25); -} - -.tasks-board-menu-item.is-active { - border-color: rgba(31, 111, 235, 0.5); -} - -.tasks-board-menu-item.is-selected { - background: rgba(31, 111, 235, 0.18); - border-color: rgba(31, 111, 235, 0.35); -} - -.tasks-board-menu-dot { - display: inline-block; - width: 10px; - height: 10px; - border-radius: 999px; - border: 1px solid var(--border-color); - flex: 0 0 auto; +.tasks-column-header:hover { + background: var(--bg-primary); } -.tasks-board-menu-dot.is-hidden { - visibility: hidden; +.tasks-column-header:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; } -.tasks-board-menu-label { +.tasks-column-title { + font-weight: 700; + font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + color: var(--text-primary); } -.tasks-board-menu-empty { - padding: 10px 12px; +.tasks-column-count { + font-family: var(--font-mono); + font-size: 0.8rem; color: var(--text-tertiary); - font-size: 0.85rem; } -.tasks-hotkeys-overlay { - position: absolute; - inset: 0; - background: var(--overlay-backdrop); +.tasks-column-cards { + overflow-y: auto; + padding: var(--space-sm); display: flex; - align-items: center; - justify-content: center; - padding: 16px; - z-index: 20; -} - -.tasks-launch-popover-overlay { - position: absolute; - inset: 0; - background: var(--overlay-backdrop-faint); - z-index: 19; + flex-direction: column; + gap: var(--space-sm); } -.tasks-launch-popover { - position: absolute; - width: min(460px, 92vw); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - box-shadow: var(--shadow-float); - padding: 12px; +.tasks-column.is-collapsed { + width: var(--tasks-col-collapsed); + min-width: var(--tasks-col-collapsed); } -.tasks-launch-popover-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 8px; +.tasks-column.is-collapsed .tasks-column-cards { + display: none; } -.tasks-launch-popover-title { +.tasks-column.is-collapsed .tasks-column-title { + writing-mode: vertical-rl; + transform: rotate(180deg); + white-space: nowrap; + overflow: visible; + text-overflow: unset; + font-size: 0.9rem; font-weight: 800; - color: var(--text-primary); + letter-spacing: 0.04em; + text-transform: uppercase; } -.tasks-launch-popover-meta { - font-size: 0.8rem; - color: var(--text-tertiary); - margin-bottom: 10px; +.tasks-column.is-collapsed .tasks-column-header { + justify-content: flex-start; + align-items: center; + flex-direction: column; + height: 100%; + padding: 10px 8px; + gap: 14px; } -.tasks-launch-popover-warn { - font-size: 0.85rem; - color: var(--text-secondary); +.tasks-column.is-collapsed .tasks-column-count { + display: inline-flex; + min-width: 30px; + height: 30px; + padding: 0 10px; + border-radius: 999px; border: 1px solid var(--border-color); - border-radius: var(--radius-md); - padding: 10px 12px; background: var(--bg-secondary); - margin-bottom: 10px; + align-items: center; + justify-content: center; + font-weight: 700; + color: var(--text-primary); + order: -1; } -.tasks-launch-popover-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - align-items: end; +.task-card-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 6px; } -.tasks-launch-popover-field { - display: flex; - flex-direction: column; - gap: 6px; - font-size: 0.8rem; - color: var(--text-secondary); +.task-card-top-right { + display: inline-flex; + align-items: center; + gap: 8px; + flex-shrink: 0; } -.tasks-launch-popover-actions { - display: flex; - gap: 10px; - justify-content: flex-end; - margin-top: 12px; +.task-card-quick-actions { + display: inline-flex; + align-items: center; + gap: 6px; } -.tasks-hotkeys-card { - width: min(860px, 96vw); - max-height: min(80vh, 760px); - overflow: auto; - background: var(--bg-primary); +.tasks-quick-tier-group { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px; border: 1px solid var(--border-color); - border-radius: var(--radius-md); - box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45); - padding: 14px; + border-radius: 999px; + background: var(--bg-secondary); } -.tasks-hotkeys-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 12px; +.tasks-quick-tier-btn { + padding: 3px 7px; + font-size: 0.72rem; + font-weight: 900; + line-height: 1; + border-radius: 999px; } -.tasks-hotkeys-title { - font-weight: 800; - font-size: 1rem; +.tasks-quick-tier-btn.is-selected { + border-color: var(--accent-primary); + background: rgba(31, 111, 235, 0.18); color: var(--text-primary); } -.tasks-hotkeys-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 12px; +.tasks-select.tasks-select-mini { + min-width: 64px; + padding: 4px 6px; + font-size: 0.75rem; + font-weight: 800; } -.tasks-hotkeys-group { +.tasks-quick-launch-btn { + padding: 4px 8px; + font-size: 0.8rem; + line-height: 1; +} + +.task-card-labels { + display: inline-flex; + gap: 6px; + flex-wrap: nowrap; + overflow: hidden; + min-width: 0; +} + +.tasks-label { + display: inline-flex; + align-items: center; + padding: 2px 6px; + border-radius: 999px; + font-size: 0.7rem; + line-height: 1.2; border: 1px solid var(--border-color); background: var(--bg-secondary); - border-radius: var(--radius-md); - padding: 10px; + color: var(--text-secondary); + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.tasks-hotkeys-group-title { - font-weight: 800; - font-size: 0.85rem; - color: var(--text-primary); - margin-bottom: 8px; +.tasks-label-editor { + display: flex; + flex-wrap: wrap; + gap: 6px; } -.tasks-hotkeys-row { - font-size: 0.85rem; - color: var(--text-secondary); - line-height: 1.3; - margin-bottom: 6px; +.tasks-label-toggle { + appearance: none; + cursor: pointer; } -.tasks-hotkeys-row code { - font-size: 0.8rem; - background: rgba(31, 111, 235, 0.12); - border: 1px solid rgba(31, 111, 235, 0.2); - border-radius: 6px; - padding: 2px 6px; - color: var(--text-primary); +.tasks-label-toggle.is-selected { + box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.25); } -.tasks-toggle { +.tasks-checkbox { display: inline-flex; align-items: center; gap: 8px; font-size: 0.8rem; color: var(--text-secondary); - user-select: none; } -.tasks-toggle input { +.tasks-checkbox input { + margin: 0; accent-color: var(--accent-primary); } -.tasks-radio { +.tasks-label--green { border-color: #2ea043; background: rgba(46, 160, 67, 0.12); color: var(--text-primary); } +.tasks-label--yellow { border-color: #d29922; background: rgba(210, 153, 34, 0.14); color: var(--text-primary); } +.tasks-label--orange { border-color: #f78166; background: rgba(247, 129, 102, 0.14); color: var(--text-primary); } +.tasks-label--red { border-color: #f85149; background: rgba(248, 81, 73, 0.14); color: var(--text-primary); } +.tasks-label--purple { border-color: #a371f7; background: rgba(163, 113, 247, 0.14); color: var(--text-primary); } +.tasks-label--blue { border-color: #1f6feb; background: rgba(31, 111, 235, 0.14); color: var(--text-primary); } +.tasks-label--sky { border-color: #79c0ff; background: rgba(121, 192, 255, 0.14); color: var(--text-primary); } +.tasks-label--lime { border-color: #7ee787; background: rgba(126, 231, 135, 0.14); color: var(--text-primary); } +.tasks-label--pink { border-color: #ff80c8; background: rgba(255, 128, 200, 0.14); color: var(--text-primary); } +.tasks-label--black { border-color: #30363d; background: rgba(48, 54, 61, 0.6); color: var(--text-primary); } +.tasks-label--more { border-color: var(--border-color); background: var(--bg-tertiary); color: var(--text-primary); font-family: var(--font-mono); } + +.task-card-assignees { display: inline-flex; - align-items: center; gap: 6px; - padding: 4px 6px; - border: 1px solid var(--border-color); - border-radius: 999px; - background: var(--bg-secondary); + flex-shrink: 0; } -.tasks-radio-option { +.tasks-avatar { + width: 22px; + height: 22px; + border-radius: 999px; + border: 1px solid var(--border-color); + background: var(--bg-secondary); display: inline-flex; align-items: center; - gap: 6px; - padding: 4px 8px; - border-radius: 999px; - font-size: 0.8rem; + justify-content: center; + overflow: hidden; color: var(--text-secondary); - cursor: pointer; - user-select: none; + font-size: 0.75rem; + text-decoration: none; } -.tasks-radio-option input { - margin: 0; - accent-color: var(--accent-primary); +.tasks-avatar img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; } -.tasks-radio-option:has(input:checked) { - background: rgba(31, 111, 235, 0.15); - color: var(--text-primary); +.tasks-avatar-more { + font-family: var(--font-mono); + font-size: 0.7rem; } -.tasks-radio-option:has(input:focus-visible) { - outline: 2px solid var(--accent-primary); - outline-offset: 2px; +.task-card-due { + font-family: var(--font-mono); + font-size: 0.72rem; + color: var(--text-secondary); } -.tasks-select { - background: var(--bg-secondary); - color: var(--text-primary); +.tasks-kv { + display: flex; + flex-direction: column; + gap: 6px; +} + +.tasks-kv-row { + display: grid; + grid-template-columns: 140px 1fr; + gap: 10px; + padding: 8px 10px; border: 1px solid var(--border-color); border-radius: var(--radius-md); - padding: 6px 10px; - font-size: 0.85rem; - min-width: 160px; + background: var(--bg-secondary); } -.tasks-search { - min-width: 220px; - flex: 1; +.tasks-kv-row-edit { + align-items: center; } -.tasks-body { - flex: 1; - min-height: 0; - display: grid; - grid-template-columns: 420px 1fr; - grid-template-rows: 1fr; - grid-template-areas: "cards detail"; +.tasks-kv-key { + color: var(--text-secondary); + font-size: 0.8rem; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } #queue-panel .tasks-body { @@ -10407,106 +11580,139 @@ header h1 { margin-top: var(--space-sm); } -.tasks-body.tasks-body-board { - /* Board view: show full-width kanban until a card is selected. */ - grid-template-columns: 1fr; - grid-template-areas: "cards"; - position: relative; - overflow: hidden; +.tasks-body.tasks-body-board { + /* Board view: show full-width kanban until a card is selected. */ + grid-template-columns: 1fr; + grid-template-areas: "cards"; + position: relative; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tasks-kv-val-edit { + overflow: visible; + text-overflow: unset; + white-space: normal; + display: flex; + align-items: center; + gap: 8px; +} + +.task-card-board { + cursor: grab; +} + +.task-card-board.dragging { + opacity: 0.6; + cursor: grabbing; +} + +.task-card-row { + padding: var(--space-sm) var(--space-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-tertiary); + cursor: pointer; + transition: border-color 0.15s, transform 0.15s; +} + +.task-card-list { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; +} + +.task-card-list-main { + min-width: 0; + flex: 1; } -.tasks-body.tasks-body-board .tasks-detail { - display: none; +.task-card-list-actions { + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 6px; } -.tasks-body.tasks-body-board .tasks-cards { - grid-column: 1; - border-left: none; +.task-card-row:hover { + border-color: var(--accent-primary); + transform: translateY(-1px); } -.tasks-body.tasks-body-board.tasks-kanban-wrap .tasks-cards { - overflow: auto; +.task-card-row.active { + border-color: var(--accent-primary); + box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2); } -.tasks-body.tasks-body-board.tasks-has-detail { - /* When a card is selected, show details as an overlay on the right. */ - grid-template-columns: 1fr; +.task-card-title { + font-weight: 600; + color: var(--text-primary); + margin-bottom: 4px; } -.tasks-body.tasks-body-board.tasks-has-detail .tasks-detail { - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 520px; - max-width: min(520px, 65vw); - border-left: 1px solid var(--border-color); - box-shadow: -10px 0 24px rgba(0, 0, 0, 0.25); - z-index: 5; +.tasks-card-board-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 999px; + border: 1px solid var(--border-color); + margin-right: 8px; + transform: translateY(1px); } -.tasks-body.tasks-body-board.tasks-has-detail .tasks-cards { - grid-column: 1; - border-left: none; +.task-card-meta { + font-size: 0.75rem; + color: var(--text-tertiary); } -.tasks-cards { - grid-area: cards; - grid-column: 1; +.tasks-detail { + grid-area: detail; + grid-column: 2; grid-row: 1; overflow-y: auto; - padding: var(--space-md); - border-right: 1px solid var(--border-color); - background: var(--bg-secondary); - display: flex; - flex-direction: column; - gap: var(--space-sm); + padding: var(--space-lg); + background: var(--bg-primary); min-width: 0; } -.tasks-body.tasks-body-board .tasks-cards { - border-right: none; - overflow: hidden; - padding: var(--space-sm); - background: var(--bg-primary); +.tasks-detail-empty { + color: var(--text-secondary); } -.tasks-board { - height: 100%; +.tasks-detail-header { display: flex; + align-items: flex-start; + justify-content: space-between; gap: var(--space-md); - justify-content: flex-start; - overflow-x: auto; - overflow-y: hidden; - padding-bottom: var(--space-sm); - scroll-snap-type: x mandatory; + margin-bottom: var(--space-sm); } -.tasks-board.tasks-board-wrap { - flex-wrap: wrap; - overflow-x: hidden; - overflow-y: auto; - align-content: flex-start; - scroll-snap-type: none; +.tasks-detail-title { + font-weight: 700; + font-size: 1rem; + line-height: 1.2; } -.tasks-board.tasks-board-wrap.tasks-board-grid .tasks-column-cards { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - align-content: start; +.tasks-detail-actions { + display: flex; + gap: var(--space-sm); + align-items: center; + flex-wrap: wrap; + justify-content: flex-end; + flex-shrink: 0; } -.tasks-board.tasks-board-expand .tasks-column-cards { - display: grid; - grid-auto-flow: column; - grid-template-rows: repeat(var(--tasks-card-rows, 1), min-content); - grid-template-columns: repeat(var(--tasks-card-columns, 1), minmax(180px, 1fr)); - align-content: start; - gap: var(--space-sm); - overflow: hidden; - flex: 1; - min-height: 0; +.tasks-input { + width: 100%; + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 8px 10px; + font-size: 0.95rem; + font-weight: 700; } .tasks-board.tasks-board-expand .task-card-title { @@ -10522,1143 +11728,1163 @@ header h1 { height: fit-content; } -.tasks-column { - --tasks-col-expanded: clamp(240px, 22vw, 360px); - --tasks-col-collapsed: 56px; - --tasks-card-columns: 1; - --tasks-card-rows: 1; - width: var(--tasks-col-expanded); - min-width: var(--tasks-col-expanded); +.tasks-textarea { + width: 100%; background: var(--bg-secondary); + color: var(--text-primary); border: 1px solid var(--border-color); border-radius: var(--radius-md); - display: flex; - flex-direction: column; - max-height: 100%; - scroll-snap-align: start; - position: relative; -} - -.tasks-column.hover { - border-color: var(--tasks-board-accent); - box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.15); + padding: 8px 10px; + font-size: 0.85rem; + font-family: var(--font-mono); + resize: vertical; } -.tasks-column-header { - padding: var(--space-sm) var(--space-md); +.tasks-inline-row { display: flex; - align-items: baseline; - justify-content: space-between; gap: var(--space-sm); - width: 100%; - background: var(--bg-tertiary); - border: none; - border-bottom: 1px solid var(--border-color); - cursor: pointer; - text-align: left; - color: var(--text-primary); -} - -.tasks-column-header:hover { - background: var(--bg-primary); -} - -.tasks-column-header:focus-visible { - outline: 2px solid var(--accent-primary); - outline-offset: 2px; -} - -.tasks-column-title { - font-weight: 700; - font-size: 0.9rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--text-primary); -} - -.tasks-column-count { - font-family: var(--font-mono); - font-size: 0.8rem; - color: var(--text-tertiary); + align-items: center; + flex-wrap: wrap; } -.tasks-column-cards { - overflow-y: auto; - padding: var(--space-sm); +.tasks-combined-list { display: flex; flex-direction: column; - gap: var(--space-sm); -} - -.tasks-column.is-collapsed { - width: var(--tasks-col-collapsed); - min-width: var(--tasks-col-collapsed); -} - -.tasks-column.is-collapsed .tasks-column-cards { - display: none; -} - -.tasks-column.is-collapsed .tasks-column-title { - writing-mode: vertical-rl; - transform: rotate(180deg); - white-space: nowrap; - overflow: visible; - text-overflow: unset; - font-size: 0.9rem; - font-weight: 800; - letter-spacing: 0.04em; - text-transform: uppercase; + gap: 8px; } -.tasks-column.is-collapsed .tasks-column-header { - justify-content: flex-start; +.tasks-combined-item { + display: flex; align-items: center; - flex-direction: column; - height: 100%; - padding: 10px 8px; - gap: 14px; -} - -.tasks-column.is-collapsed .tasks-column-count { - display: inline-flex; - min-width: 30px; - height: 30px; - padding: 0 10px; - border-radius: 999px; + justify-content: space-between; + gap: 10px; + padding: 10px 12px; border: 1px solid var(--border-color); + border-radius: var(--radius-md); background: var(--bg-secondary); - align-items: center; - justify-content: center; - font-weight: 700; - color: var(--text-primary); - order: -1; } -.task-card-top { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 6px; +.tasks-combined-label { + font-size: 0.85rem; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.task-card-top-right { +.tasks-combined-actions { display: inline-flex; - align-items: center; - gap: 8px; + gap: 6px; flex-shrink: 0; } -.task-card-quick-actions { +.tasks-chips { display: inline-flex; - align-items: center; gap: 6px; + flex-wrap: wrap; + margin-left: 6px; } -.tasks-quick-tier-group { +.tasks-chip { display: inline-flex; align-items: center; - gap: 4px; - padding: 2px; - border: 1px solid var(--border-color); + gap: 6px; + padding: 4px 8px; border-radius: 999px; + border: 1px solid var(--border-color); background: var(--bg-secondary); + font-size: 0.75rem; + color: var(--text-secondary); } -.tasks-quick-tier-btn { - padding: 3px 7px; - font-size: 0.72rem; - font-weight: 900; - line-height: 1; +.tasks-chip-avatar { + width: 16px; + height: 16px; border-radius: 999px; + object-fit: cover; + display: inline-block; } -.tasks-quick-tier-btn.is-selected { - border-color: var(--accent-primary); - background: rgba(31, 111, 235, 0.18); +.tasks-chip-link { + color: var(--text-secondary); + text-decoration: none; +} + +.tasks-chip-link:hover { color: var(--text-primary); + text-decoration: underline; } -.tasks-select.tasks-select-mini { - min-width: 64px; - padding: 4px 6px; - font-size: 0.75rem; - font-weight: 800; +.tasks-chip-muted { + opacity: 0.7; } -.tasks-quick-launch-btn { - padding: 4px 8px; - font-size: 0.8rem; +.tasks-chip-x { + appearance: none; + border: none; + background: transparent; + color: var(--text-tertiary); + cursor: pointer; + font-size: 0.9rem; line-height: 1; + padding: 0 2px; } -.task-card-labels { - display: inline-flex; - gap: 6px; - flex-wrap: nowrap; - overflow: hidden; - min-width: 0; +.tasks-chip-x:hover { + color: var(--text-primary); } -.tasks-label { - display: inline-flex; +.tasks-detail-block { + margin-top: var(--space-md); +} + +.tasks-detail-block.tasks-dropzone-hover { + outline: 2px dashed var(--accent-color); + outline-offset: 6px; + border-radius: var(--radius-md); +} + +.tasks-detail-block-title { + font-size: 0.8rem; + color: var(--text-secondary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 6px; +} + +.tasks-move-row { + display: flex; + gap: var(--space-sm); align-items: center; - padding: 2px 6px; - border-radius: 999px; - font-size: 0.7rem; - line-height: 1.2; +} + +.tasks-select-inline { + min-width: 240px; +} + +.tasks-comment-row { + display: flex; + flex-direction: column; + gap: var(--space-sm); +} + +.tasks-comments { + display: flex; + flex-direction: column; + gap: var(--space-sm); +} + +.tasks-comment { + padding: var(--space-sm) var(--space-md); border: 1px solid var(--border-color); + border-radius: var(--radius-md); background: var(--bg-secondary); - color: var(--text-secondary); - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } -.tasks-label-editor { +.tasks-comment-meta { + font-size: 0.75rem; + color: var(--text-tertiary); + margin-bottom: 6px; +} + +.tasks-comment-text { + white-space: pre-wrap; + color: var(--text-primary); + line-height: 1.35; + font-size: 0.85rem; +} + +.tasks-deps { display: flex; - flex-wrap: wrap; + flex-direction: column; gap: 6px; } -.tasks-label-toggle { - appearance: none; - cursor: pointer; +.tasks-dep-row { + display: grid; + grid-template-columns: 18px 1fr auto; + gap: 10px; + align-items: center; + padding: 8px 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-secondary); } -.tasks-label-toggle.is-selected { - box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.25); +.tasks-dep-row.done { + opacity: 0.75; } -.tasks-checkbox { - display: inline-flex; - align-items: center; - gap: 8px; - font-size: 0.8rem; - color: var(--text-secondary); +.tasks-cover { + display: block; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-secondary); + overflow: hidden; } -.tasks-checkbox input { - margin: 0; - accent-color: var(--accent-primary); +.tasks-cover img { + display: block; + width: 100%; + max-height: 220px; + object-fit: cover; } -.tasks-label--green { border-color: #2ea043; background: rgba(46, 160, 67, 0.12); color: var(--text-primary); } -.tasks-label--yellow { border-color: #d29922; background: rgba(210, 153, 34, 0.14); color: var(--text-primary); } -.tasks-label--orange { border-color: #f78166; background: rgba(247, 129, 102, 0.14); color: var(--text-primary); } -.tasks-label--red { border-color: #f85149; background: rgba(248, 81, 73, 0.14); color: var(--text-primary); } -.tasks-label--purple { border-color: #a371f7; background: rgba(163, 113, 247, 0.14); color: var(--text-primary); } -.tasks-label--blue { border-color: #1f6feb; background: rgba(31, 111, 235, 0.14); color: var(--text-primary); } -.tasks-label--sky { border-color: #79c0ff; background: rgba(121, 192, 255, 0.14); color: var(--text-primary); } -.tasks-label--lime { border-color: #7ee787; background: rgba(126, 231, 135, 0.14); color: var(--text-primary); } -.tasks-label--pink { border-color: #ff80c8; background: rgba(255, 128, 200, 0.14); color: var(--text-primary); } -.tasks-label--black { border-color: #30363d; background: rgba(48, 54, 61, 0.6); color: var(--text-primary); } -.tasks-label--more { border-color: var(--border-color); background: var(--bg-tertiary); color: var(--text-primary); font-family: var(--font-mono); } +.tasks-cover.tasks-cover-color { + height: 110px; +} -.task-card-assignees { - display: inline-flex; +.tasks-attachments { + display: flex; + flex-direction: column; gap: 6px; - flex-shrink: 0; } -.tasks-avatar { - width: 22px; - height: 22px; - border-radius: 999px; +.tasks-attachment { + display: flex; + gap: 10px; + align-items: center; + padding: 8px 10px; border: 1px solid var(--border-color); + border-radius: var(--radius-md); background: var(--bg-secondary); +} + +.tasks-attachment-thumb { display: inline-flex; + width: 44px; + height: 44px; + border-radius: var(--radius-sm); + overflow: hidden; + border: 1px solid var(--border-color); + background: rgba(0, 0, 0, 0.08); + flex: 0 0 auto; align-items: center; justify-content: center; - overflow: hidden; - color: var(--text-secondary); - font-size: 0.75rem; - text-decoration: none; } -.tasks-avatar img { +.tasks-attachment-thumb.is-empty { + opacity: 0.5; +} + +.tasks-attachment-thumb img { + display: block; width: 100%; height: 100%; object-fit: cover; - display: block; } -.tasks-avatar-more { - font-family: var(--font-mono); - font-size: 0.7rem; +.tasks-attachment-body { + min-width: 0; + flex: 1; } -.task-card-due { - font-family: var(--font-mono); - font-size: 0.72rem; - color: var(--text-secondary); +.tasks-attachment-name { + color: var(--text-primary); + font-size: 0.85rem; + line-height: 1.25; + text-decoration: none; + word-break: break-word; +} + +.tasks-attachment-name:hover { + text-decoration: underline; +} + +.tasks-attachment-meta { + margin-top: 2px; + color: var(--text-tertiary); + font-size: 0.75rem; + line-height: 1.2; + word-break: break-word; } -.tasks-kv { +.tasks-checklists { display: flex; flex-direction: column; - gap: 6px; + gap: var(--space-sm); } -.tasks-kv-row { - display: grid; - grid-template-columns: 140px 1fr; - gap: 10px; - padding: 8px 10px; +.tasks-checklist { border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-secondary); + padding: 10px; } -.tasks-kv-row-edit { +.tasks-checklist-header { + display: flex; + gap: 10px; align-items: center; + justify-content: space-between; } -.tasks-kv-key { - color: var(--text-secondary); - font-size: 0.8rem; +.tasks-checklist-title { font-weight: 700; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.tasks-kv-val { color: var(--text-primary); - font-size: 0.85rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + font-size: 0.9rem; + min-width: 0; + word-break: break-word; } -.tasks-kv-val-edit { - overflow: visible; - text-overflow: unset; - white-space: normal; +.tasks-checklist-actions { display: flex; + gap: 6px; align-items: center; - gap: 8px; } -.task-card-board { - cursor: grab; -} - -.task-card-board.dragging { - opacity: 0.6; - cursor: grabbing; +.tasks-checkitems { + margin-top: 8px; + display: flex; + flex-direction: column; + gap: 6px; } -.task-card-row { - padding: var(--space-sm) var(--space-md); +.tasks-checkitem-row { + display: grid; + grid-template-columns: 18px 1fr auto; + gap: 10px; + align-items: center; + padding: 8px 10px; border: 1px solid var(--border-color); border-radius: var(--radius-md); - background: var(--bg-tertiary); + background: var(--bg-primary); +} + +.tasks-checkitem-row.done { + opacity: 0.75; +} + +.tasks-checkitem-text { + min-width: 0; + color: var(--text-primary); + font-size: 0.85rem; cursor: pointer; - transition: border-color 0.15s, transform 0.15s; + word-break: break-word; } -.task-card-list { +.tasks-checkitem-add, +.tasks-checklist-add { + margin-top: 8px; +} + +.tasks-list-manager-row { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 10px; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-secondary); + margin-bottom: 8px; } -.task-card-list-main { +.tasks-list-manager-name { min-width: 0; flex: 1; + color: var(--text-primary); + font-weight: 600; + word-break: break-word; } -.task-card-list-actions { - flex-shrink: 0; - display: inline-flex; - align-items: center; +.tasks-list-manager-actions { + display: flex; gap: 6px; + align-items: center; } -.task-card-row:hover { - border-color: var(--accent-primary); - transform: translateY(-1px); +.tasks-dep-row.done .tasks-dep-text { + text-decoration: line-through; } -.task-card-row.active { - border-color: var(--accent-primary); - box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2); +.tasks-dep-text a { + color: var(--accent-primary); + text-decoration: none; } -.task-card-title { - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; +.tasks-dep-text a:hover { + text-decoration: underline; } -.tasks-card-board-dot { - display: inline-block; - width: 10px; - height: 10px; - border-radius: 999px; - border: 1px solid var(--border-color); - margin-right: 8px; - transform: translateY(1px); +.tasks-dep-remove { + padding: 4px 8px; + font-weight: 700; } -.task-card-meta { - font-size: 0.75rem; +.tasks-dep-add { + margin-top: var(--space-sm); +} + +.tasks-detail-meta { + font-size: 0.8rem; color: var(--text-tertiary); + margin-bottom: var(--space-md); } -.tasks-detail { - grid-area: detail; - grid-column: 2; - grid-row: 1; - overflow-y: auto; - padding: var(--space-lg); - background: var(--bg-primary); - min-width: 0; +.tasks-detail-desc { + white-space: pre-wrap; + font-family: var(--font-mono); + font-size: 0.85rem; + padding: var(--space-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-secondary); + color: var(--text-primary); } -.tasks-detail-empty { - color: var(--text-secondary); +.tasks-config-hint { + padding: var(--space-lg); + border: 1px dashed var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-tertiary); } -.tasks-detail-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: var(--space-md); +.tasks-config-title { + font-weight: 700; margin-bottom: var(--space-sm); } -.tasks-detail-title { - font-weight: 700; - font-size: 1rem; - line-height: 1.2; +.tasks-config-text { + color: var(--text-secondary); + line-height: 1.4; } -.tasks-detail-actions { - display: flex; - gap: var(--space-sm); - align-items: center; - flex-wrap: wrap; - justify-content: flex-end; - flex-shrink: 0; +/* Port panel - project detection styles */ +.port-name { + cursor: pointer; + transition: color 0.15s; } -.tasks-input { - width: 100%; - background: var(--bg-secondary); - color: var(--text-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - padding: 8px 10px; - font-size: 0.95rem; - font-weight: 700; +.port-name:hover { + color: var(--accent-primary); } -.tasks-input-inline { - width: auto; - min-width: 220px; - display: inline-flex; - margin: 0 6px; - font-weight: 600; - font-size: 0.85rem; - padding: 6px 10px; +.port-name.custom-label { + color: #9f7aea; } -.tasks-textarea { - width: 100%; - background: var(--bg-secondary); - color: var(--text-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - padding: 8px 10px; - font-size: 0.85rem; - font-family: var(--font-mono); - resize: vertical; +.port-path { + display: block; + font-size: 0.7rem; + color: var(--accent-primary); + opacity: 0.8; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.tasks-inline-row { +/* Port context - project/worktree info */ +.port-context { display: flex; - gap: var(--space-sm); align-items: center; - flex-wrap: wrap; + gap: 0.3rem; + font-size: 0.75rem; + margin-top: 2px; +} + +.port-project { + color: #9f7aea; + font-weight: 500; +} + +.port-worktree { + background: var(--accent-primary); + color: white; + padding: 1px 6px; + border-radius: 3px; + font-size: 0.7rem; + font-weight: 500; +} + +.port-subpath { + color: var(--text-muted); + font-size: 0.7rem; } -.tasks-combined-list { - display: flex; - flex-direction: column; - gap: 8px; +/* ============================================ + Sidebar Ports Section + ============================================ */ + +.sidebar-section { + border-top: 1px solid var(--border-color); + margin-top: auto; } -.tasks-combined-item { +.sidebar-section-header { display: flex; align-items: center; - justify-content: space-between; - gap: 10px; - padding: 10px 12px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); + gap: var(--space-sm); + padding: var(--space-sm) var(--space-md); + cursor: pointer; + user-select: none; + font-size: 0.85rem; + font-weight: 500; + color: var(--text-secondary); + transition: background 0.15s; } -.tasks-combined-label { - font-size: 0.85rem; - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.sidebar-section-header:hover { + background: var(--bg-tertiary); } -.tasks-combined-actions { - display: inline-flex; - gap: 6px; - flex-shrink: 0; +.ports-count { + background: var(--accent-primary); + color: white; + padding: 1px 6px; + border-radius: 10px; + font-size: 0.7rem; + font-weight: 600; + min-width: 18px; + text-align: center; } -.tasks-chips { - display: inline-flex; - gap: 6px; - flex-wrap: wrap; - margin-left: 6px; +.collapse-icon { + margin-left: auto; + font-size: 0.7rem; + transition: transform 0.2s; } -.tasks-chip { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border-radius: 999px; - border: 1px solid var(--border-color); - background: var(--bg-secondary); - font-size: 0.75rem; - color: var(--text-secondary); +.sidebar-section.collapsed .collapse-icon { + transform: rotate(-90deg); } -.tasks-chip-avatar { - width: 16px; - height: 16px; - border-radius: 999px; - object-fit: cover; - display: inline-block; +.sidebar-section.collapsed .ports-sidebar-list { + display: none; } -.tasks-chip-link { - color: var(--text-secondary); - text-decoration: none; +.ports-sidebar-list { + max-height: 200px; + overflow-y: auto; + padding: var(--space-xs) 0; } -.tasks-chip-link:hover { - color: var(--text-primary); - text-decoration: underline; +.port-sidebar-item { + display: flex; + align-items: center; + gap: var(--space-xs); + padding: var(--space-xs) var(--space-md); + font-size: 0.75rem; + cursor: pointer; + transition: background 0.15s; + border-left: 2px solid transparent; } -.tasks-chip-muted { - opacity: 0.7; +.port-sidebar-item:hover { + background: var(--bg-tertiary); } -.tasks-chip-x { - appearance: none; - border: none; - background: transparent; - color: var(--text-tertiary); - cursor: pointer; +.port-sidebar-item.orchestrator { border-left-color: #9f7aea; } +.port-sidebar-item.node { border-left-color: #68d391; } +.port-sidebar-item.rails { border-left-color: #f56565; } +.port-sidebar-item.python { border-left-color: #ffd93d; } +.port-sidebar-item.vite { border-left-color: #48bb78; } + +.port-sidebar-icon { font-size: 0.9rem; - line-height: 1; - padding: 0 2px; + flex-shrink: 0; } -.tasks-chip-x:hover { +.port-sidebar-info { + flex: 1; + min-width: 0; + overflow: hidden; +} + +.port-sidebar-name { + display: block; + font-weight: 500; color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.tasks-detail-block { - margin-top: var(--space-md); +.port-sidebar-context { + display: block; + font-size: 0.65rem; + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.tasks-detail-block.tasks-dropzone-hover { - outline: 2px dashed var(--accent-color); - outline-offset: 6px; - border-radius: var(--radius-md); +.port-sidebar-port { + color: var(--accent-primary); + font-weight: 500; + font-size: 0.7rem; + flex-shrink: 0; } -.tasks-detail-block-title { - font-size: 0.8rem; - color: var(--text-secondary); - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.04em; - margin-bottom: 6px; +.ports-sidebar-empty { + padding: var(--space-sm) var(--space-md); + font-size: 0.75rem; + color: var(--text-muted); + font-style: italic; } -.tasks-move-row { - display: flex; - gap: var(--space-sm); - align-items: center; +/* ============================================ + Dashboard Ports Section + ============================================ */ + +.ports-dashboard-section { + margin-top: var(--space-lg); } -.tasks-select-inline { - min-width: 240px; +.ports-dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: var(--space-md); } -.tasks-comment-row { +.port-dashboard-card { display: flex; - flex-direction: column; + align-items: center; gap: var(--space-sm); + padding: var(--space-md); + background: var(--bg-secondary); + border-radius: var(--radius-md); + border-left: 3px solid var(--border-color); + cursor: pointer; + transition: all 0.15s; } -.tasks-comments { - display: flex; - flex-direction: column; - gap: var(--space-sm); +.port-dashboard-card:hover { + background: var(--bg-tertiary); + transform: translateY(-2px); + box-shadow: var(--shadow-soft); } -.tasks-comment { - padding: var(--space-sm) var(--space-md); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); +.port-dashboard-card.orchestrator { border-left-color: #9f7aea; } +.port-dashboard-card.node { border-left-color: #68d391; } +.port-dashboard-card.rails { border-left-color: #f56565; } +.port-dashboard-card.ruby { border-left-color: #f56565; } +.port-dashboard-card.python { border-left-color: #ffd93d; } +.port-dashboard-card.vite { border-left-color: #48bb78; } + +.port-card-icon { + font-size: 1.5rem; + flex-shrink: 0; } -.tasks-comment-meta { - font-size: 0.75rem; - color: var(--text-tertiary); - margin-bottom: 6px; +.port-card-info { + flex: 1; + min-width: 0; + overflow: hidden; } -.tasks-comment-text { - white-space: pre-wrap; +.port-card-name { + display: block; + font-weight: 600; color: var(--text-primary); - line-height: 1.35; - font-size: 0.85rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.tasks-deps { - display: flex; - flex-direction: column; - gap: 6px; +.port-card-context { + display: block; + font-size: 0.75rem; + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.tasks-dep-row { - display: grid; - grid-template-columns: 18px 1fr auto; - gap: 10px; - align-items: center; - padding: 8px 10px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); +.port-card-port { + font-size: 1rem; + font-weight: 700; + color: var(--accent-primary); + flex-shrink: 0; } -.tasks-dep-row.done { - opacity: 0.75; +.ports-empty, +.ports-loading { + grid-column: 1 / -1; + text-align: center; + padding: var(--space-lg); + color: var(--text-muted); + font-style: italic; } -.tasks-cover { - display: block; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); - overflow: hidden; -} +/* ============================================ + Dashboard Split Row Layout + ============================================ */ -.tasks-cover img { - display: block; - width: 100%; - max-height: 220px; - object-fit: cover; +.dashboard-split-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-lg); + margin-top: var(--space-lg); } -.tasks-cover.tasks-cover-color { - height: 110px; +.dashboard-half { + background: var(--bg-secondary); + border-radius: var(--radius-lg); + padding: var(--space-md); + max-height: 400px; + overflow-y: auto; } -.tasks-attachments { - display: flex; - flex-direction: column; - gap: 6px; +.dashboard-half h2 { + margin: 0 0 var(--space-sm) 0; + font-size: 1rem; + position: sticky; + top: 0; + background: var(--bg-secondary); + padding-bottom: var(--space-xs); } -.tasks-attachment { - display: flex; - gap: 10px; - align-items: center; - padding: 8px 10px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); +/* Grid layout for ports in dashboard half */ +.dashboard-half .ports-dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: var(--space-xs); } -.tasks-attachment-thumb { - display: inline-flex; - width: 44px; - height: 44px; - border-radius: var(--radius-sm); - overflow: hidden; - border: 1px solid var(--border-color); - background: rgba(0, 0, 0, 0.08); - flex: 0 0 auto; +.dashboard-half .port-dashboard-card { + padding: var(--space-sm); + flex-direction: column; align-items: center; - justify-content: center; + text-align: center; + gap: var(--space-xs); + min-height: 70px; } -.tasks-attachment-thumb.is-empty { - opacity: 0.5; +.dashboard-half .port-card-icon { + font-size: 1.3rem; } -.tasks-attachment-thumb img { - display: block; +.dashboard-half .port-card-info { width: 100%; - height: 100%; - object-fit: cover; } -.tasks-attachment-body { - min-width: 0; - flex: 1; +.dashboard-half .port-card-name { + font-size: 0.75rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.tasks-attachment-name { - color: var(--text-primary); - font-size: 0.85rem; - line-height: 1.25; - text-decoration: none; - word-break: break-word; +.dashboard-half .port-card-context { + font-size: 0.65rem; } -.tasks-attachment-name:hover { - text-decoration: underline; +.dashboard-half .port-card-port { + font-size: 0.8rem; + margin-top: auto; } -.tasks-attachment-meta { - margin-top: 2px; - color: var(--text-tertiary); - font-size: 0.75rem; - line-height: 1.2; - word-break: break-word; +/* Grid layout for quick links in dashboard half */ +.dashboard-half .quick-links-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: var(--space-xs); } -.tasks-checklists { - display: flex; +.dashboard-half .quick-link-item { flex-direction: column; - gap: var(--space-sm); + align-items: center; + text-align: center; + padding: var(--space-sm); + min-height: 60px; + gap: var(--space-xs); + border-left: none; + border-bottom: 2px solid var(--accent-primary); } -.tasks-checklist { - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); - padding: 10px; +.dashboard-half .quick-link-item .quick-link-icon { + font-size: 1.2rem; } -.tasks-checklist-header { - display: flex; - gap: 10px; - align-items: center; - justify-content: space-between; +.dashboard-half .quick-link-item .quick-link-label { + font-size: 0.7rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; } -.tasks-checklist-title { - font-weight: 700; - color: var(--text-primary); - font-size: 0.9rem; - min-width: 0; - word-break: break-word; +/* Quick links container inside dashboard */ +.dashboard-half .quick-links-container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: var(--space-xs); } -.tasks-checklist-actions { - display: flex; - gap: 6px; - align-items: center; +.dashboard-half .quick-links-section { + display: contents; } -.tasks-checkitems { - margin-top: 8px; - display: flex; - flex-direction: column; - gap: 6px; +.dashboard-half .quick-links-section h3 { + display: none; } -.tasks-checkitem-row { - display: grid; - grid-template-columns: 18px 1fr auto; - gap: 10px; - align-items: center; - padding: 8px 10px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-primary); +@media (max-width: 900px) { + .dashboard-split-row { + grid-template-columns: 1fr; + } } -.tasks-checkitem-row.done { - opacity: 0.75; -} +/* ============================================ + Quick Link Items (for dashboard half) + ============================================ */ -.tasks-checkitem-text { - min-width: 0; - color: var(--text-primary); - font-size: 0.85rem; +.quick-link-item { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-md); + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + border-left: 3px solid var(--accent-primary); cursor: pointer; - word-break: break-word; + transition: all 0.15s; + text-decoration: none; + color: inherit; + border: none; + width: 100%; + text-align: left; + font-size: inherit; + font-family: inherit; } -.tasks-checkitem-add, -.tasks-checklist-add { - margin-top: 8px; +.quick-link-item:hover { + background: var(--bg-primary); + transform: translateX(2px); } -.tasks-list-manager-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - padding: 10px 12px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); - margin-bottom: 8px; +.quick-link-icon { + font-size: 1rem; + flex-shrink: 0; } -.tasks-list-manager-name { - min-width: 0; +.quick-link-label { flex: 1; + font-weight: 500; color: var(--text-primary); - font-weight: 600; - word-break: break-word; } -.tasks-list-manager-actions { - display: flex; - gap: 6px; - align-items: center; -} - -.tasks-dep-row.done .tasks-dep-text { - text-decoration: line-through; +.quick-links-empty { + padding: var(--space-md); + text-align: center; + color: var(--text-muted); + font-style: italic; + grid-column: 1 / -1; } -.tasks-dep-text a { - color: var(--accent-primary); - text-decoration: none; -} +/* ============================================ + Session Recovery Dialog + ============================================ */ -.tasks-dep-text a:hover { - text-decoration: underline; +.recovery-modal .modal-content { + max-width: 600px; + width: 90vw; } -.tasks-dep-remove { - padding: 4px 8px; - font-weight: 700; +.recovery-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-md) var(--space-lg); + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); } -.tasks-dep-add { - margin-top: var(--space-sm); +.recovery-header h2 { + margin: 0; + font-size: 1.1rem; + display: flex; + align-items: center; + gap: var(--space-sm); } -.tasks-detail-meta { +.recovery-info { + padding: var(--space-sm) var(--space-lg); + background: var(--bg-secondary); font-size: 0.8rem; - color: var(--text-tertiary); - margin-bottom: var(--space-md); + color: var(--text-muted); + border-bottom: 1px solid var(--border-color); } -.tasks-detail-desc { - white-space: pre-wrap; - font-family: var(--font-mono); - font-size: 0.85rem; +.recovery-sessions { + max-height: 300px; + overflow-y: auto; padding: var(--space-md); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); - color: var(--text-primary); } -.tasks-config-hint { - padding: var(--space-lg); - border: 1px dashed var(--border-color); - border-radius: var(--radius-md); +.recovery-session { + display: flex; + align-items: flex-start; + gap: var(--space-md); + padding: var(--space-sm) var(--space-md); + margin-bottom: var(--space-xs); background: var(--bg-tertiary); + border-radius: var(--radius-sm); + border-left: 3px solid var(--accent-primary); } -.tasks-config-title { - font-weight: 700; - margin-bottom: var(--space-sm); -} - -.tasks-config-text { - color: var(--text-secondary); - line-height: 1.4; -} - -/* Port panel - project detection styles */ -.port-name { - cursor: pointer; - transition: color 0.15s; +.recovery-session.selected { + border-left-color: #48bb78; + background: rgba(72, 187, 120, 0.1); } -.port-name:hover { - color: var(--accent-primary); +.recovery-checkbox { + margin-top: 2px; } -.port-name.custom-label { - color: #9f7aea; +.recovery-session-info { + flex: 1; + min-width: 0; } -.port-path { - display: block; - font-size: 0.7rem; - color: var(--accent-primary); - opacity: 0.8; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.recovery-session-id { + font-weight: 600; + font-size: 0.9rem; + color: var(--text-primary); } -/* Port context - project/worktree info */ -.port-context { - display: flex; - align-items: center; - gap: 0.3rem; +.recovery-session-details { font-size: 0.75rem; + color: var(--text-muted); margin-top: 2px; } -.port-project { - color: #9f7aea; - font-weight: 500; +.recovery-session-details span { + display: inline-block; + margin-right: var(--space-sm); } -.port-worktree { - background: var(--accent-primary); - color: white; - padding: 1px 6px; - border-radius: 3px; - font-size: 0.7rem; - font-weight: 500; +.recovery-session-cwd { + color: var(--accent-primary); } -.port-subpath { - color: var(--text-muted); - font-size: 0.7rem; +.recovery-session-agent { + background: var(--bg-primary); + padding: 1px 6px; + border-radius: 3px; } -/* ============================================ - Sidebar Ports Section - ============================================ */ - -.sidebar-section { +.recovery-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-md) var(--space-lg); border-top: 1px solid var(--border-color); - margin-top: auto; + gap: var(--space-md); } -.sidebar-section-header { +.recovery-actions { display: flex; - align-items: center; gap: var(--space-sm); +} + +.recovery-skip { + color: var(--text-muted); + font-size: 0.8rem; +} + +.btn-recovery { padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-sm); + border: none; cursor: pointer; - user-select: none; - font-size: 0.85rem; font-weight: 500; - color: var(--text-secondary); - transition: background 0.15s; + transition: all 0.15s; } -.sidebar-section-header:hover { - background: var(--bg-tertiary); +.btn-recovery-all { + background: #48bb78; + color: white; } -.ports-count { +.btn-recovery-all:hover { + background: #38a169; +} + +.btn-recovery-selected { background: var(--accent-primary); - color: white; - padding: 1px 6px; - border-radius: 10px; - font-size: 0.7rem; - font-weight: 600; - min-width: 18px; - text-align: center; + color: var(--text-on-accent); } -.collapse-icon { - margin-left: auto; - font-size: 0.7rem; - transition: transform 0.2s; +.btn-recovery-selected:hover { + background: var(--accent-primary-hover); } -.sidebar-section.collapsed .collapse-icon { - transform: rotate(-90deg); +.btn-recovery-clear { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); } -.sidebar-section.collapsed .ports-sidebar-list { - display: none; +.btn-recovery-clear:hover { + border-color: var(--accent-danger); + background: rgba(248, 81, 73, 0.14); } -.ports-sidebar-list { - max-height: 200px; - overflow-y: auto; - padding: var(--space-xs) 0; +.btn-recovery-skip { + background: var(--bg-tertiary); + color: var(--text-secondary); } -.port-sidebar-item { - display: flex; - align-items: center; - gap: var(--space-xs); - padding: var(--space-xs) var(--space-md); - font-size: 0.75rem; - cursor: pointer; - transition: background 0.15s; - border-left: 2px solid transparent; +.btn-recovery-skip:hover { + background: var(--bg-primary); } -.port-sidebar-item:hover { - background: var(--bg-tertiary); +.no-recovery { + text-align: center; + padding: var(--space-lg); + color: var(--text-muted); } -.port-sidebar-item.orchestrator { border-left-color: #9f7aea; } -.port-sidebar-item.node { border-left-color: #68d391; } -.port-sidebar-item.rails { border-left-color: #f56565; } -.port-sidebar-item.python { border-left-color: #ffd93d; } -.port-sidebar-item.vite { border-left-color: #48bb78; } +/* ============================================ + Activity Feed Styles + ============================================ */ -.port-sidebar-icon { - font-size: 0.9rem; - flex-shrink: 0; +.activity-feed-modal .activity-feed-content { + max-width: 1200px; + width: 94vw; + max-height: 90vh; + display: flex; + flex-direction: column; } -.port-sidebar-info { +.activity-list { flex: 1; - min-width: 0; - overflow: hidden; + overflow-y: auto; + padding: var(--space-md) var(--space-lg); } -.port-sidebar-name { - display: block; - font-weight: 500; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.activity-empty { + padding: var(--space-lg); + color: var(--text-muted); } -.port-sidebar-context { - display: block; - font-size: 0.65rem; - color: var(--text-muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.activity-event { + border: 1px solid var(--border-color); + background: var(--bg-secondary); + border-radius: var(--radius-md); + padding: var(--space-md); + margin-bottom: var(--space-sm); +} + +.activity-event.activity-failed { + border-color: rgba(248, 81, 73, 0.55); + box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.18); +} + +.activity-meta { + display: flex; + gap: var(--space-sm); + align-items: center; + justify-content: space-between; } -.port-sidebar-port { - color: var(--accent-primary); - font-weight: 500; - font-size: 0.7rem; - flex-shrink: 0; +.activity-actions { + display: flex; + gap: var(--space-xs); + align-items: center; + margin-left: auto; } -.ports-sidebar-empty { - padding: var(--space-sm) var(--space-md); +.activity-action-btn { + padding: 4px 8px; font-size: 0.75rem; - color: var(--text-muted); - font-style: italic; + line-height: 1; } -/* ============================================ - Dashboard Ports Section - ============================================ */ - -.ports-dashboard-section { - margin-top: var(--space-lg); +.activity-time { + color: var(--text-muted); + font-size: 0.8rem; } -.ports-dashboard-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: var(--space-md); +.activity-kind { + font-family: var(--font-mono); + font-size: 0.8rem; + padding: 2px 8px; + border-radius: 999px; + border: 1px solid transparent; + background: var(--bg-tertiary); + color: var(--text-secondary); } -.port-dashboard-card { - display: flex; - align-items: center; - gap: var(--space-sm); - padding: var(--space-md); - background: var(--bg-secondary); - border-radius: var(--radius-md); - border-left: 3px solid var(--border-color); - cursor: pointer; - transition: all 0.15s; +.activity-kind-agent { + border-color: var(--accent-primary); + color: var(--accent-primary); + background: rgba(31, 111, 235, 0.12); } -.port-dashboard-card:hover { - background: var(--bg-tertiary); - transform: translateY(-2px); - box-shadow: var(--shadow-soft); +.activity-kind-session { + border-color: var(--accent-success); + color: var(--accent-success); + background: rgba(63, 185, 80, 0.12); } -.port-dashboard-card.orchestrator { border-left-color: #9f7aea; } -.port-dashboard-card.node { border-left-color: #68d391; } -.port-dashboard-card.rails { border-left-color: #f56565; } -.port-dashboard-card.ruby { border-left-color: #f56565; } -.port-dashboard-card.python { border-left-color: #ffd93d; } -.port-dashboard-card.vite { border-left-color: #48bb78; } +.activity-kind-server { + border-color: var(--accent-warning); + color: var(--accent-warning); + background: rgba(210, 153, 34, 0.12); +} -.port-card-icon { - font-size: 1.5rem; - flex-shrink: 0; +.activity-kind-git { + border-color: var(--accent-success); + color: var(--accent-success); + background: rgba(63, 185, 80, 0.12); } -.port-card-info { - flex: 1; - min-width: 0; - overflow: hidden; +.activity-kind-pr { + border-color: var(--accent-primary); + color: var(--accent-primary); + background: rgba(31, 111, 235, 0.12); } -.port-card-name { - display: block; - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.activity-kind-tests { + border-color: var(--accent-warning); + color: var(--accent-warning); + background: rgba(210, 153, 34, 0.12); } -.port-card-context { - display: block; - font-size: 0.75rem; - color: var(--text-muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.activity-kind-build { + border-color: var(--accent-warning); + color: var(--accent-warning); + background: rgba(210, 153, 34, 0.12); } -.port-card-port { - font-size: 1rem; - font-weight: 700; - color: var(--accent-primary); - flex-shrink: 0; +.activity-summary { + margin-top: var(--space-sm); + color: var(--text-primary); + font-size: 0.95rem; } -.ports-empty, -.ports-loading { - grid-column: 1 / -1; - text-align: center; - padding: var(--space-lg); +.activity-data { + margin-top: var(--space-sm); color: var(--text-muted); - font-style: italic; + font-family: var(--font-mono); + font-size: 0.8rem; + white-space: pre-wrap; + word-break: break-word; } -/* ============================================ - Dashboard Split Row Layout - ============================================ */ +/* ========================================================== + ONBOARDING OVERLAY (PERF-OPTIMIZED) + ========================================================== */ -.dashboard-split-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--space-lg); - margin-top: var(--space-lg); +.onboarding-overlay { + position: fixed; + inset: 0; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: + radial-gradient(90% 60% at 8% 12%, rgba(31, 111, 235, 0.22), transparent 75%), + radial-gradient(78% 55% at 92% 88%, rgba(0, 195, 255, 0.14), transparent 80%), + rgba(5, 8, 15, 0.86); + overflow: auto; + transition: opacity 0.18s ease-out, visibility 0.18s linear; } .dashboard-half { @@ -11669,13 +12895,18 @@ header h1 { overflow-y: auto; } -.dashboard-half h2 { - margin: 0 0 var(--space-sm) 0; - font-size: 1rem; - position: sticky; - top: 0; - background: var(--bg-secondary); - padding-bottom: var(--space-xs); +.onboarding-bg-glow { + position: absolute; + width: 52vw; + height: 52vw; + max-width: 640px; + max-height: 640px; + border-radius: 50%; + top: -16%; + left: -6%; + background: radial-gradient(circle, rgba(31, 111, 235, 0.2) 0%, rgba(31, 111, 235, 0) 72%); + opacity: 0.75; + pointer-events: none; } /* Grid layout for ports in dashboard half */ @@ -11698,8 +12929,9 @@ header h1 { font-size: 1.1rem; } -.dashboard-half .port-card-info { - width: 100%; +.onboarding-header { + margin-bottom: 4px; + text-align: center; } .dashboard-half .port-card-name { @@ -11725,9 +12957,14 @@ header h1 { gap: var(--space-xs); } -.dashboard-half .quick-link-item { - flex-direction: column; - align-items: center; +.onboarding-welcome-card { + padding: 28px; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: + linear-gradient(155deg, rgba(56, 139, 253, 0.13), rgba(20, 29, 44, 0.42)), + rgba(255, 255, 255, 0.04); + box-shadow: 0 12px 26px rgba(0, 0, 0, 0.28); text-align: center; padding: 6px; min-height: 52px; @@ -11755,99 +12992,100 @@ header h1 { gap: var(--space-xs); } -.dashboard-half .quick-links-section { - display: contents; +.onboarding-welcome-notes { + margin-top: 14px; + display: grid; + gap: 6px; + text-align: center; } -.dashboard-half .quick-links-section h3 { - display: none; +.onboarding-welcome-notes p { + margin: 0; + color: rgba(255, 255, 255, 0.74); + font-size: 0.9rem; + line-height: 1.45; } -@media (max-width: 900px) { - .dashboard-split-row { - grid-template-columns: 1fr; - } +.onboarding-welcome-actions { + margin-top: 46px; + justify-content: center; } -/* ============================================ - Quick Link Items (for dashboard half) - ============================================ */ - -.quick-link-item { +.onboarding-stepper-row { display: flex; align-items: center; - gap: var(--space-sm); - padding: var(--space-sm) var(--space-md); - background: var(--bg-tertiary); - border-radius: var(--radius-sm); - border-left: 3px solid var(--accent-primary); - cursor: pointer; - transition: all 0.15s; - text-decoration: none; - color: inherit; - border: none; + justify-content: center; width: 100%; - text-align: left; - font-size: inherit; - font-family: inherit; + gap: 14px; + margin-bottom: 18px; + flex-wrap: wrap; + row-gap: 12px; } -.quick-link-item:hover { - background: var(--bg-primary); - transform: translateX(2px); +.onboarding-stepper-item { + display: flex; + align-items: center; + justify-content: center; } -.quick-link-icon { - font-size: 1rem; - flex-shrink: 0; +.stepper-icon-box { + display: flex; + align-items: center; + position: relative; } -.quick-link-label { - flex: 1; - font-weight: 500; - color: var(--text-primary); +.stepper-diamond { + width: 14px; + height: 14px; + border-radius: 3px; + transform: rotate(45deg); + transition: transform 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease; } -.quick-links-empty { - padding: var(--space-md); - text-align: center; - color: var(--text-muted); - font-style: italic; - grid-column: 1 / -1; +.stepper-upcoming .stepper-diamond { + background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); + box-shadow: inset 0 0 0 1px rgba(229, 231, 235, 0.26); + opacity: 0.95; } -/* ============================================ - Session Recovery Dialog - ============================================ */ - -.recovery-modal .modal-content { - max-width: 600px; - width: 90vw; +.stepper-active .stepper-icon-box { + gap: 0; } -.recovery-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--space-md) var(--space-lg); - border-bottom: 1px solid var(--border-color); - background: var(--bg-tertiary); +.stepper-active .stepper-diamond { + background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); + box-shadow: 0 0 10px rgba(59, 130, 246, 0.4), inset 0 0 0 1px rgba(255, 255, 255, 0.34); + transform: rotate(45deg) scale(1.15); + opacity: 1; } -.recovery-header h2 { - margin: 0; - font-size: 1.1rem; - display: flex; - align-items: center; - gap: var(--space-sm); +.stepper-done .stepper-diamond { + background: linear-gradient(135deg, #34d399 0%, #16a34a 100%); + box-shadow: 0 0 10px rgba(34, 197, 94, 0.38), inset 0 0 0 1px rgba(236, 253, 245, 0.34); + opacity: 1; } -.recovery-info { - padding: var(--space-sm) var(--space-lg); - background: var(--bg-secondary); - font-size: 0.8rem; - color: var(--text-muted); - border-bottom: 1px solid var(--border-color); +.stepper-active-label { + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); + font-size: 0.88rem; + color: rgba(255, 255, 255, 0.9); + font-weight: 520; + margin-left: 0; + white-space: nowrap; +} + +.onboarding-step-card { + position: relative; + overflow: hidden; + padding: 26px; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.045); + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.28); + transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease; } .recovery-metrics { @@ -11893,254 +13131,318 @@ header h1 { padding: var(--space-md); } -.recovery-session { +.onboarding-step-icon { + width: 42px; + height: 42px; + margin-bottom: 18px; + border-radius: 12px; + background: rgba(255, 255, 255, 0.11); + color: rgba(255, 255, 255, 0.94); display: flex; - align-items: flex-start; - gap: var(--space-md); - padding: var(--space-sm) var(--space-md); - margin-bottom: var(--space-xs); - background: var(--bg-tertiary); - border-radius: var(--radius-sm); - border-left: 3px solid var(--accent-primary); + align-items: center; + justify-content: center; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12); } -.recovery-session.selected { - border-left-color: #48bb78; - background: rgba(72, 187, 120, 0.1); +.onboarding-step-icon-svg { + width: 22px; + height: 22px; + display: block; } -.recovery-checkbox { - margin-top: 2px; +.onboarding-step-icon-svg * { + vector-effect: non-scaling-stroke; } -.recovery-session-info { - flex: 1; - min-width: 0; +.onboarding-step-title { + font-size: clamp(1.42rem, 2.9vw, 1.78rem); + font-weight: 620; + color: #ffffff; + margin-bottom: 10px; } -.recovery-session-id { - font-weight: 600; - font-size: 0.9rem; - color: var(--text-primary); +.onboarding-step-status-row { + display: flex; + align-items: flex-start; + gap: 10px; + margin-bottom: 20px; } -.recovery-session-details { - font-size: 0.75rem; - color: var(--text-muted); - margin-top: 2px; +.onboarding-check { + margin-top: 3px; + flex-shrink: 0; + color: #3fb950; } -.recovery-session-details span { - display: inline-block; - margin-right: var(--space-sm); +.onboarding-step-desc { + margin: 0; + font-size: 1rem; + line-height: 1.48; + color: rgba(255, 255, 255, 0.78); } -.recovery-session-cwd { - color: var(--accent-primary); +.onboarding-inline-status { + display: inline-block; + margin-left: 8px; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.9em; + background: rgba(0, 0, 0, 0.3); } -.recovery-session-agent { - background: var(--bg-primary); - padding: 1px 6px; - border-radius: 3px; -} +.onboarding-inline-status.status-ok { color: #3fb950; } +.onboarding-inline-status.status-missing { color: #f85149; } +.onboarding-inline-status.status-pending { color: #58a6ff; } -.recovery-footer { +.onboarding-step-actions { display: flex; - justify-content: space-between; - align-items: center; - padding: var(--space-md) var(--space-lg); - border-top: 1px solid var(--border-color); - gap: var(--space-md); + gap: 10px; + flex-wrap: wrap; } -.recovery-actions { - display: flex; - gap: var(--space-sm); +.onboarding-btn-secondary, +.onboarding-btn-back, +.onboarding-btn-primary { + border-radius: 10px; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease; } -.recovery-skip { - color: var(--text-muted); - font-size: 0.8rem; +.onboarding-btn-secondary { + padding: 10px 18px; + border: 1px solid rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.09); + color: rgba(255, 255, 255, 0.92); + font-size: 0.94rem; + font-weight: 520; } -.btn-recovery { - padding: var(--space-sm) var(--space-md); - border-radius: var(--radius-sm); - border: none; - cursor: pointer; - font-weight: 500; - transition: all 0.15s; +.onboarding-btn-secondary:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.16); + border-color: rgba(255, 255, 255, 0.26); + transform: translateY(-1px); } -.btn-recovery-all { - background: #48bb78; - color: white; +.onboarding-btn-secondary:disabled { + opacity: 0.52; + cursor: not-allowed; } -.btn-recovery-all:hover { - background: #38a169; +.onboarding-step-actions [data-setup-run] { + border-color: rgba(74, 222, 128, 0.55); + background: linear-gradient(135deg, #34d399 0%, #16a34a 100%); + color: #052e16; + box-shadow: 0 6px 16px rgba(22, 163, 74, 0.3); } -.btn-recovery-selected { - background: var(--accent-primary); - color: var(--text-on-accent); +.onboarding-step-actions [data-setup-run]:hover:not(:disabled) { + border-color: rgba(134, 239, 172, 0.92); + background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%); + color: #052e16; + box-shadow: 0 8px 18px rgba(22, 163, 74, 0.38); } -.btn-recovery-selected:hover { - background: var(--accent-primary-hover); +.onboarding-step-actions [data-setup-run]:disabled { + border-color: rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.55); + box-shadow: none; } -.btn-recovery-clear { - background: var(--bg-tertiary); - color: var(--text-primary); - border: 1px solid var(--border-color); +.onboarding-nav-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 6px; } -.btn-recovery-clear:hover { - border-color: var(--accent-danger); - background: rgba(248, 81, 73, 0.14); +.onboarding-nav-row.onboarding-welcome-actions { + justify-content: center; } -.btn-recovery-skip { - background: var(--bg-tertiary); - color: var(--text-secondary); +.onboarding-btn-back { + padding: 13px 24px; + border: 1px solid rgba(255, 255, 255, 0.11); + background: rgba(255, 255, 255, 0.055); + color: rgba(255, 255, 255, 0.74); + font-size: 0.98rem; + font-weight: 600; } -.btn-recovery-skip:hover { - background: var(--bg-primary); +.onboarding-btn-back:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.9); } -.no-recovery { - text-align: center; - padding: var(--space-lg); - color: var(--text-muted); +.onboarding-btn-primary { + padding: 13px 28px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: linear-gradient(135deg, #388bfd 0%, #1f6feb 100%); + color: #ffffff; + font-size: 0.98rem; + font-weight: 620; + box-shadow: 0 4px 13px rgba(31, 111, 235, 0.34); } -/* ============================================ - Activity Feed Styles - ============================================ */ - -.activity-feed-modal .activity-feed-content { - max-width: 1200px; - width: 94vw; - max-height: 90vh; - display: flex; - flex-direction: column; +.onboarding-btn-primary:hover:not(:disabled) { + background: linear-gradient(135deg, #58a6ff 0%, #388bfd 100%); + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(31, 111, 235, 0.44); } -.activity-list { - flex: 1; - overflow-y: auto; - padding: var(--space-md) var(--space-lg); +.onboarding-btn-primary:disabled { + border-color: transparent; + background: rgba(255, 255, 255, 0.11); + color: rgba(255, 255, 255, 0.45); + box-shadow: none; + cursor: not-allowed; } -.activity-empty { - padding: var(--space-lg); - color: var(--text-muted); +.dependency-git-identity-fields { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 18px; } -.activity-event { - border: 1px solid var(--border-color); - background: var(--bg-secondary); - border-radius: var(--radius-md); - padding: var(--space-md); - margin-bottom: var(--space-sm); +.dependency-git-identity-field { + display: flex; + flex-direction: column; + gap: 4px; } -.activity-event.activity-failed { - border-color: rgba(248, 81, 73, 0.55); - box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.18); +.dependency-git-identity-field span { + font-size: 0.83rem; + color: rgba(255, 255, 255, 0.66); } -.activity-meta { - display: flex; - gap: var(--space-sm); - align-items: center; - justify-content: space-between; +.dependency-git-identity-field input { + padding: 10px 13px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.16); + background: rgba(0, 0, 0, 0.28); + color: #fff; + font-family: var(--font-sans); + font-size: 0.98rem; + transition: border-color 0.14s ease, background-color 0.14s ease, box-shadow 0.14s ease; } -.activity-actions { - display: flex; - gap: var(--space-xs); - align-items: center; - margin-left: auto; +.dependency-git-identity-field input:focus { + outline: none; + border-color: #388bfd; + background: rgba(0, 0, 0, 0.38); + box-shadow: 0 0 0 2px rgba(56, 139, 253, 0.2); } -.activity-action-btn { - padding: 4px 8px; - font-size: 0.75rem; - line-height: 1; +.dependency-gh-login-helper-text { + margin-bottom: 10px; + font-size: 0.93rem; + color: rgba(255, 255, 255, 0.82); } -.activity-time { - color: var(--text-muted); - font-size: 0.8rem; +.dependency-onboarding-command-wrap { + margin: 18px 0; + padding: 14px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.09); + background: rgba(0, 0, 0, 0.32); } -.activity-kind { +.dependency-onboarding-command-wrap pre { + margin: 0; + color: #a5d6ff; font-family: var(--font-mono); - font-size: 0.8rem; - padding: 2px 8px; - border-radius: 999px; - border: 1px solid transparent; - background: var(--bg-tertiary); - color: var(--text-secondary); + font-size: 0.83rem; + white-space: pre-wrap; } -.activity-kind-agent { - border-color: var(--accent-primary); - color: var(--accent-primary); - background: rgba(31, 111, 235, 0.12); +.dependency-gh-login-code-wrap { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; } -.activity-kind-session { - border-color: var(--accent-success); - color: var(--accent-success); - background: rgba(63, 185, 80, 0.12); +.dependency-gh-login-helper-actions { + margin-top: 10px; } -.activity-kind-server { - border-color: var(--accent-warning); - color: var(--accent-warning); - background: rgba(210, 153, 34, 0.12); +.dependency-gh-login-code { + padding: 8px 14px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(0, 0, 0, 0.5); + font-size: 1.1rem; + letter-spacing: 2px; + color: #fff; } -.activity-kind-git { - border-color: var(--accent-success); - color: var(--accent-success); - background: rgba(63, 185, 80, 0.12); +.onboarding-close-btn { + position: absolute; + top: 18px; + right: 18px; + z-index: 100; + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.78); + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; } -.activity-kind-pr { - border-color: var(--accent-primary); - color: var(--accent-primary); - background: rgba(31, 111, 235, 0.12); +.onboarding-close-btn:hover { + background: rgba(255, 255, 255, 0.18); + border-color: rgba(255, 255, 255, 0.22); + color: #fff; } -.activity-kind-tests { - border-color: var(--accent-warning); - color: var(--accent-warning); - background: rgba(210, 153, 34, 0.12); +.onboarding-close-btn.hidden { + display: none; } -.activity-kind-build { - border-color: var(--accent-warning); - color: var(--accent-warning); - background: rgba(210, 153, 34, 0.12); -} +@media (max-width: 800px) { + .onboarding-overlay { + padding: 12px; + } -.activity-summary { - margin-top: var(--space-sm); - color: var(--text-primary); - font-size: 0.95rem; + .onboarding-container { + padding: 20px; + max-height: calc(100vh - 24px); + } + + .onboarding-step-card { + padding: 20px; + } + + .onboarding-nav-row { + gap: 10px; + flex-wrap: wrap; + } + + .onboarding-btn-back, + .onboarding-btn-primary { + flex: 1 1 100%; + justify-content: center; + } } -.activity-data { - margin-top: var(--space-sm); - color: var(--text-muted); - font-family: var(--font-mono); - font-size: 0.8rem; - white-space: pre-wrap; - word-break: break-word; +@media (prefers-reduced-motion: reduce) { + .onboarding-overlay, + .onboarding-container, + .stepper-diamond, + .onboarding-step-card, + .onboarding-btn-secondary, + .onboarding-btn-back, + .onboarding-btn-primary, + .dependency-git-identity-field input, + .onboarding-close-btn { + animation: none !important; + transition: none !important; + } } diff --git a/client/styles/tabs.css b/client/styles/tabs.css index 970f58cc..044628f4 100644 --- a/client/styles/tabs.css +++ b/client/styles/tabs.css @@ -11,6 +11,8 @@ z-index: 100; overflow-x: auto; overflow-y: hidden; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); } .workspace-tabs { @@ -233,20 +235,27 @@ /* Scrollbar for tab overflow */ .workspace-tabs-container::-webkit-scrollbar { - height: 4px; + height: var(--scrollbar-size); } .workspace-tabs-container::-webkit-scrollbar-track { - background: var(--bg-secondary); + background: var(--scrollbar-track); + border-radius: var(--scrollbar-radius); } .workspace-tabs-container::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 2px; + background: var(--scrollbar-thumb); + border-radius: var(--scrollbar-radius); + border: 2px solid var(--scrollbar-thumb-border); + background-clip: padding-box; } .workspace-tabs-container::-webkit-scrollbar-thumb:hover { - background: var(--text-secondary); + background: var(--scrollbar-thumb-hover); +} + +.workspace-tabs-container::-webkit-scrollbar-thumb:active { + background: var(--scrollbar-thumb-active); } /* Mobile/Small Screen Adjustments */ diff --git a/server/diagnosticsService.js b/server/diagnosticsService.js index 7021c243..93e88416 100644 --- a/server/diagnosticsService.js +++ b/server/diagnosticsService.js @@ -9,14 +9,34 @@ const execFileAsync = util.promisify(execFile); async function checkCommand(command, args, options = {}) { const timeout = Number(options.timeoutMs) || 2500; try { - const { stdout, stderr } = await execFileAsync(command, args, { + const runOptions = { timeout, windowsHide: true, maxBuffer: 1024 * 1024 - }); + }; + + const commandStr = String(command || '').trim(); + const argsArr = Array.isArray(args) ? args : []; + let result; + try { + result = await execFileAsync(commandStr, argsArr, runOptions); + } catch (error) { + const isWindowsScript = process.platform === 'win32' && /\.(cmd|bat)$/i.test(commandStr); + const shouldRetryWithCmd = isWindowsScript && (error?.code === 'EINVAL' || error?.code === 'ENOENT'); + if (!shouldRetryWithCmd) throw error; + result = await execFileAsync('cmd.exe', ['/d', '/c', commandStr, ...argsArr], runOptions); + } + + const { stdout, stderr } = result || {}; const output = String(stdout || stderr || '').trim(); const firstLine = output.split(/\r?\n/).find(Boolean) || ''; - return { ok: true, command, args, version: firstLine || null }; + return { + ok: true, + command, + args, + version: firstLine || null, + output: output || null + }; } catch (error) { const code = error?.code || null; const message = String(error?.message || error || '').trim(); @@ -35,6 +55,86 @@ async function checkFirstAvailable(candidates) { return await checkCommand(last.command, last.args, last.options); } +function uniqueCommandCandidates(candidates = []) { + const seen = new Set(); + const out = []; + for (const candidate of candidates) { + const command = String(candidate?.command || '').trim(); + if (!command) continue; + const args = Array.isArray(candidate?.args) ? candidate.args : []; + const key = `${command}::${JSON.stringify(args)}`; + if (seen.has(key)) continue; + seen.add(key); + out.push({ command, args, options: candidate?.options }); + } + return out; +} + +async function checkNpmGlobalPackage(npmCommand, packageName) { + const npm = String(npmCommand || '').trim(); + const pkg = String(packageName || '').trim(); + if (!npm || !pkg) { + return { ok: false, error: 'Missing npm command or package name' }; + } + + const res = await checkCommand(npm, ['list', '-g', pkg, '--depth=0'], { timeoutMs: 7000 }); + const combined = String(res?.output || res?.version || '').trim(); + const pkgPattern = new RegExp(`${pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}@([^\\s]+)`, 'i'); + const versionMatch = combined.match(pkgPattern); + if (!res.ok || !versionMatch?.[1]) { + return { + ok: false, + command: npm, + args: ['list', '-g', pkg, '--depth=0'], + error: String(res?.error || `Package ${pkg} not found in npm global list`) + }; + } + + return { + ok: true, + command: `npm-global:${pkg}`, + args: ['list', '-g', pkg, '--depth=0'], + version: `${pkg}@${versionMatch[1]} (npm global)` + }; +} + +async function checkGitIdentity(gitCommand, gitInstalled) { + const command = String(gitCommand || 'git').trim() || 'git'; + if (!gitInstalled) { + return { + ok: false, + command, + args: ['config', '--global', '--get', 'user.name'], + error: 'Git is not installed' + }; + } + + const nameCheck = await checkCommand(command, ['config', '--global', '--get', 'user.name']); + const emailCheck = await checkCommand(command, ['config', '--global', '--get', 'user.email']); + const name = String(nameCheck?.version || '').trim(); + const email = String(emailCheck?.version || '').trim(); + + if (name && email) { + return { + ok: true, + command, + args: ['config', '--global', '--get', 'user.name,user.email'], + version: `${name} <${email}>` + }; + } + + const missing = []; + if (!name) missing.push('user.name'); + if (!email) missing.push('user.email'); + + return { + ok: false, + command, + args: ['config', '--global', '--get', 'user.name,user.email'], + error: `Missing global Git setting(s): ${missing.join(', ')}` + }; +} + function findTool(tools, id) { if (!Array.isArray(tools)) return null; return tools.find((tool) => String(tool?.id || '') === String(id || '')) || null; @@ -86,47 +186,128 @@ async function collectDiagnostics() { const tools = []; + const nodeCandidates = uniqueCommandCandidates([ + { command: 'node', args: ['--version'] }, + { command: platform === 'win32' ? 'node.exe' : 'node', args: ['--version'] }, + platform === 'win32' ? { command: path.join(process.env.ProgramFiles || '', 'nodejs', 'node.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env['ProgramFiles(x86)'] || '', 'nodejs', 'node.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Programs', 'nodejs', 'node.exe'), args: ['--version'] } : null, + { command: process.execPath || 'node', args: ['--version'] } + ]); + const nodeCheck = await checkFirstAvailable(nodeCandidates); + const nodeCommand = String(nodeCheck?.command || '').trim(); + const nodeDir = nodeCommand ? path.dirname(nodeCommand) : ''; + + const npmCandidates = uniqueCommandCandidates([ + { command: platform === 'win32' ? 'npm.cmd' : 'npm', args: ['--version'] }, + platform === 'win32' ? { command: 'npm', args: ['--version'] } : null, + platform === 'win32' && nodeDir ? { command: path.join(nodeDir, 'npm.cmd'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.ProgramFiles || '', 'nodejs', 'npm.cmd'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env['ProgramFiles(x86)'] || '', 'nodejs', 'npm.cmd'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Programs', 'nodejs', 'npm.cmd'), args: ['--version'] } : null + ]); + const npmCheck = await checkFirstAvailable(npmCandidates); + tools.push({ id: 'node', name: 'Node.js', - ...(await checkCommand(process.execPath || 'node', ['--version'])) + ...nodeCheck }); tools.push({ id: 'npm', name: 'npm', - ...(await checkCommand(platform === 'win32' ? 'npm.cmd' : 'npm', ['--version'])) + ...npmCheck }); + const gitCandidates = uniqueCommandCandidates([ + { command: 'git', args: ['--version'] }, + platform === 'win32' ? { command: 'git.exe', args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.ProgramFiles || '', 'Git', 'cmd', 'git.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.ProgramFiles || '', 'Git', 'bin', 'git.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env['ProgramFiles(x86)'] || '', 'Git', 'cmd', 'git.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env['ProgramFiles(x86)'] || '', 'Git', 'bin', 'git.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Git', 'cmd', 'git.exe'), args: ['--version'] } : null + ]); + tools.push({ id: 'git', name: 'Git', - ...(await checkCommand('git', ['--version'])) + ...(await checkFirstAvailable(gitCandidates)) + }); + const gitTool = tools[tools.length - 1]; + tools.push({ + id: 'gitIdentity', + name: 'Git identity', + ...(await checkGitIdentity(gitTool?.command, !!gitTool?.ok)) }); + const ghCandidates = uniqueCommandCandidates([ + { command: 'gh', args: ['--version'] }, + platform === 'win32' ? { command: 'gh.exe', args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.ProgramFiles || '', 'GitHub CLI', 'gh.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env['ProgramFiles(x86)'] || '', 'GitHub CLI', 'gh.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Programs', 'GitHub CLI', 'gh.exe'), args: ['--version'] } : null + ]); + const ghCheck = await checkFirstAvailable(ghCandidates); tools.push({ id: 'gh', name: 'GitHub CLI', - ...(await checkCommand('gh', ['--version'])) + ...ghCheck }); // Auth status is the most common root cause of "0 files/commits" in PR tooling on Windows. // We keep it lightweight: first line of `gh auth status` is enough to spot "not logged in". + const ghAuthCheck = ghCheck?.ok + ? await checkCommand(String(ghCheck.command || 'gh'), ['auth', 'status']) + : { + ok: false, + command: String(ghCheck?.command || 'gh'), + args: ['auth', 'status'], + error: 'GitHub CLI is not installed' + }; tools.push({ id: 'ghAuth', name: 'GitHub CLI auth', - ...(await checkCommand('gh', ['auth', 'status'])) + ...ghAuthCheck }); + const claudeCandidates = uniqueCommandCandidates([ + { command: 'claude', args: ['--version'] }, + platform === 'win32' ? { command: 'claude.cmd', args: ['--version'] } : null, + platform === 'win32' ? { command: 'claude.exe', args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.APPDATA || '', 'npm', 'claude.cmd'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.APPDATA || '', 'npm', 'claude'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WinGet', 'Links', 'claude.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.USERPROFILE || '', '.local', 'bin', 'claude.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.USERPROFILE || '', '.claude', 'local', 'claude.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Claude', 'claude.exe'), args: ['--version'] } : null + ]); tools.push({ id: 'claude', name: 'Claude Code', - ...(await checkCommand('claude', ['--version'])) + ...(await checkFirstAvailable(claudeCandidates)) }); + const codexCandidates = uniqueCommandCandidates([ + { command: 'codex', args: ['--version'] }, + platform === 'win32' ? { command: 'codex.cmd', args: ['--version'] } : null, + platform === 'win32' ? { command: 'codex.exe', args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.APPDATA || '', 'npm', 'codex.cmd'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.APPDATA || '', 'npm', 'codex'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WinGet', 'Links', 'codex.exe'), args: ['--version'] } : null, + platform === 'win32' ? { command: path.join(process.env.USERPROFILE || '', '.local', 'bin', 'codex.exe'), args: ['--version'] } : null + ]); + let codexCheck = await checkFirstAvailable(codexCandidates); + if (!codexCheck?.ok && npmCheck?.ok) { + const npmPackageCheck = await checkNpmGlobalPackage(String(npmCheck.command || '').trim(), '@openai/codex'); + if (npmPackageCheck?.ok) { + codexCheck = npmPackageCheck; + } + } tools.push({ id: 'codex', name: 'Codex CLI', - ...(await checkCommand('codex', ['--version'])) + ...codexCheck }); tools.push({ diff --git a/server/index.js b/server/index.js index e3a9a62b..0fe86de2 100644 --- a/server/index.js +++ b/server/index.js @@ -99,6 +99,13 @@ const voiceCommandService = require('./voiceCommandService'); const whisperService = require('./whisperService'); const sessionRecoveryService = require('./sessionRecoveryService'); const { collectDiagnostics, collectFirstRunDiagnostics, collectInstallWizard, runFirstRunRepair, runFirstRunSafeRepairs } = require('./diagnosticsService'); +const { + getSetupActions, + runSetupAction, + getSetupActionRun, + getLatestSetupActionRun, + configureGitIdentity +} = require('./setupActionService'); const { PluginLoaderService } = require('./pluginLoaderService'); const { SchedulerService } = require('./schedulerService'); const { PagerService } = require('./pagerService'); @@ -4050,6 +4057,123 @@ app.get('/api/lifecycle/policy', (req, res) => { } }); +// Setup helper actions for first-run dependency wizard. +app.get('/api/setup-actions', (req, res) => { + try { + const platform = process.platform; + const actions = getSetupActions(platform); + res.json({ ok: true, platform, actions }); + } catch (error) { + logger.error('Failed to get setup actions', { error: error.message, stack: error.stack }); + res.status(500).json({ ok: false, error: 'Failed to get setup actions' }); + } +}); + +app.post('/api/setup-actions/run', (req, res) => { + try { + const actionId = String(req.body?.actionId || '').trim(); + if (!actionId) { + return res.status(400).json({ ok: false, error: 'actionId is required' }); + } + + const result = runSetupAction(actionId, process.platform); + res.json({ ok: true, ...result }); + } catch (error) { + const code = String(error?.code || ''); + const status = (code === 'unsupported_platform' || code === 'unknown_action' || code === 'not_runnable') ? 400 : 500; + logger.error('Failed to run setup action', { actionId: req.body?.actionId, error: error.message, stack: error.stack }); + res.status(status).json({ ok: false, error: String(error?.message || 'Failed to run setup action') }); + } +}); + +app.post('/api/setup-actions/configure-git-identity', async (req, res) => { + try { + const name = String(req.body?.name || '').trim(); + const email = String(req.body?.email || '').trim(); + const result = await configureGitIdentity({ name, email }, process.platform); + res.json({ ok: true, ...result }); + } catch (error) { + const code = String(error?.code || ''); + const status = ( + code === 'unsupported_platform' + || code === 'invalid_input' + || code === 'missing_git' + || code === 'verify_failed' + ) ? 400 : 500; + logger.error('Failed to configure git identity', { + error: error.message, + stack: error.stack + }); + res.status(status).json({ ok: false, error: String(error?.message || 'Failed to configure git identity') }); + } +}); + +app.get('/api/setup-actions/run-status', (req, res) => { + try { + const runId = String(req.query?.runId || '').trim(); + const actionId = String(req.query?.actionId || '').trim(); + const run = runId ? getSetupActionRun(runId) : getLatestSetupActionRun(actionId); + if (!run) { + return res.status(404).json({ ok: false, error: 'Setup action run not found' }); + } + res.json({ ok: true, run }); + } catch (error) { + logger.error('Failed to get setup action run status', { + runId: req.query?.runId, + actionId: req.query?.actionId, + error: error.message, + stack: error.stack + }); + res.status(500).json({ ok: false, error: 'Failed to get setup action run status' }); + } +}); + +app.post('/api/setup-actions/open-url', (req, res) => { + try { + const rawUrl = String(req.body?.url || '').trim(); + if (!rawUrl) { + return res.status(400).json({ ok: false, error: 'url is required' }); + } + + let parsed; + try { + parsed = new URL(rawUrl); + } catch { + return res.status(400).json({ ok: false, error: 'Invalid URL' }); + } + + if (!['http:', 'https:'].includes(String(parsed.protocol || '').toLowerCase())) { + return res.status(400).json({ ok: false, error: 'Only http/https URLs are supported' }); + } + + const targetUrl = parsed.toString(); + const { execFile } = require('child_process'); + + const finish = (error) => { + if (error) { + logger.error('Failed to open setup URL', { url: targetUrl, error: error.message, stack: error.stack }); + return res.status(500).json({ ok: false, error: 'Failed to open URL' }); + } + res.json({ ok: true, opened: targetUrl }); + }; + + if (process.platform === 'win32') { + execFile('explorer.exe', [targetUrl], { windowsHide: true }, finish); + return; + } + + if (process.platform === 'darwin') { + execFile('open', [targetUrl], { windowsHide: true }, finish); + return; + } + + execFile('xdg-open', [targetUrl], { windowsHide: true }, finish); + } catch (error) { + logger.error('Failed to open setup URL', { error: error.message, stack: error.stack }); + res.status(500).json({ ok: false, error: 'Failed to open URL' }); + } +}); + // Port registry API endpoints app.get('/api/ports', (req, res) => { try { diff --git a/server/setupActionService.js b/server/setupActionService.js new file mode 100644 index 00000000..a781c997 --- /dev/null +++ b/server/setupActionService.js @@ -0,0 +1,542 @@ +const crypto = require('crypto'); +const util = require('util'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const { spawn, execFile } = require('child_process'); + +const execFileAsync = util.promisify(execFile); + +const setupActionRuns = new Map(); +const latestRunByActionId = new Map(); +const MAX_OUTPUT_LINES = 180; +const GH_LOGIN_CODE_PATTERN = /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/i; +const GH_LOGIN_URL_PATTERN = /https:\/\/github\.com\/login\/device(?:\S*)?/i; +const GH_LOGIN_HINT_PATTERN = /one[-\s]?time code|login\/device|authenticate in your web browser|copied to your clipboard|open this url/i; + +function stripAnsi(value) { + return String(value || '').replace(/\u001b\[[0-9;]*m/g, ''); +} + +function getGhLoginDebugLogPath() { + const customDataDir = String(process.env.ORCHESTRATOR_DATA_DIR || '').trim(); + if (customDataDir) { + return path.join(customDataDir, 'logs', 'gh-login-debug.log'); + } + return path.join(os.tmpdir(), 'orchestrator-gh-login-debug.log'); +} + +function appendGhLoginDebugLog(event, payload = {}) { + const line = JSON.stringify({ + at: new Date().toISOString(), + event: String(event || '').trim() || 'event', + ...(payload && typeof payload === 'object' ? payload : {}) + }); + const logPath = getGhLoginDebugLogPath(); + try { + fs.mkdirSync(path.dirname(logPath), { recursive: true }); + fs.appendFileSync(logPath, `${line}\n`, 'utf8'); + } catch { + // Best-effort debug logging; never block setup flow. + } +} + +function uniqueStrings(values = []) { + const seen = new Set(); + const out = []; + values.forEach((value) => { + const item = String(value || '').trim(); + if (!item || seen.has(item)) return; + seen.add(item); + out.push(item); + }); + return out; +} + +async function checkExecutable(command, args = ['--version']) { + const commandStr = String(command || '').trim(); + if (!commandStr) return { ok: false, error: 'Missing command' }; + + const runOptions = { + windowsHide: true, + timeout: 3000, + maxBuffer: 1024 * 1024 + }; + + try { + await execFileAsync(commandStr, Array.isArray(args) ? args : [], runOptions); + return { ok: true }; + } catch (error) { + const isWindowsScript = process.platform === 'win32' && /\.(cmd|bat)$/i.test(commandStr); + if (isWindowsScript && (error?.code === 'EINVAL' || error?.code === 'ENOENT')) { + try { + await execFileAsync('cmd.exe', ['/d', '/c', commandStr, ...(Array.isArray(args) ? args : [])], runOptions); + return { ok: true }; + } catch (fallbackError) { + return { + ok: false, + error: String(fallbackError?.message || fallbackError || 'Command check failed') + }; + } + } + return { + ok: false, + error: String(error?.message || error || 'Command check failed') + }; + } +} + +function getGitCommandCandidates(platform = process.platform) { + if (platform !== 'win32') { + return ['git']; + } + + return uniqueStrings([ + 'git', + 'git.exe', + path.join(process.env.ProgramFiles || '', 'Git', 'cmd', 'git.exe'), + path.join(process.env.ProgramFiles || '', 'Git', 'bin', 'git.exe'), + path.join(process.env['ProgramFiles(x86)'] || '', 'Git', 'cmd', 'git.exe'), + path.join(process.env['ProgramFiles(x86)'] || '', 'Git', 'bin', 'git.exe'), + path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Git', 'cmd', 'git.exe') + ]); +} + +async function resolveGitCommand(platform = process.platform) { + const candidates = getGitCommandCandidates(platform); + for (const command of candidates) { + const check = await checkExecutable(command, ['--version']); + if (check.ok) return command; + } + return ''; +} + +async function runGitCommand(command, args = []) { + try { + const result = await execFileAsync(command, Array.isArray(args) ? args : [], { + windowsHide: true, + timeout: 9000, + maxBuffer: 1024 * 1024 + }); + return String(result?.stdout || result?.stderr || ''); + } catch (error) { + const stderr = String(error?.stderr || '').trim(); + const stdout = String(error?.stdout || '').trim(); + const message = stderr || stdout || String(error?.message || error || 'Git command failed'); + const err = new Error(message); + err.code = String(error?.code || 'git_command_failed'); + throw err; + } +} + +function firstNonEmptyLine(text) { + return String(text || '') + .replace(/\r/g, '') + .split('\n') + .map((line) => line.trim()) + .find(Boolean) || ''; +} + +function getSetupActions(platform = process.platform) { + if (platform !== 'win32') { + return []; + } + + return [ + { + id: 'install-git', + title: 'Git Integration', + description: 'Required for repository and worktree access.', + command: 'winget install --id Git.Git --exact --source winget --accept-source-agreements --accept-package-agreements', + docsUrl: 'https://git-scm.com/download/win', + required: true, + runSupported: true + }, + { + id: 'configure-git-identity', + title: 'Git Identity', + description: 'Set your name and email for accurate commits.', + command: 'git config --global user.name "Your Name"\ngit config --global user.email "you@example.com"', + docsUrl: 'https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup', + required: false, + optional: true, + runSupported: false + }, + { + id: 'install-node', + title: 'Node.js LTS', + description: 'Required core dependency for running agents.', + command: 'winget install --id OpenJS.NodeJS.LTS --exact --source winget --accept-source-agreements --accept-package-agreements', + docsUrl: 'https://nodejs.org/en/download', + required: false, + runSupported: true + }, + { + id: 'install-gh', + title: 'GitHub CLI', + description: 'Optional. Install now, then continue to GitHub login in the next step.', + command: 'winget install --id GitHub.cli --exact --source winget --accept-source-agreements --accept-package-agreements', + docsUrl: 'https://cli.github.com/', + required: false, + optional: true, + runSupported: true + }, + { + id: 'gh-login', + title: 'GitHub Authentication', + description: 'Optional after GitHub CLI install. Sign in to enable PR and repo actions.', + command: [ + "$ErrorActionPreference = 'Stop'", + '$env:NO_COLOR = "1"', + '$env:GH_PAGER = ""', + '$gh = ""', + '$cmd = Get-Command gh -ErrorAction SilentlyContinue', + 'if ($cmd -and $cmd.Source) { $gh = $cmd.Source }', + 'if (-not $gh) {', + ' $candidates = @(', + ' "$env:ProgramFiles\\GitHub CLI\\gh.exe",', + ' "$env:ProgramFiles(x86)\\GitHub CLI\\gh.exe",', + ' "$env:LOCALAPPDATA\\Programs\\GitHub CLI\\gh.exe"', + ' )', + ' foreach ($candidate in $candidates) {', + ' if (Test-Path $candidate) { $gh = $candidate; break }', + ' }', + '}', + 'if (-not $gh) { throw "GitHub CLI executable not found. Install GitHub CLI first." }', + '$prevErrorAction = $ErrorActionPreference', + '$ErrorActionPreference = "Continue"', + '& $gh auth status --hostname github.com *> $null', + '$authStatusExitCode = $LASTEXITCODE', + '$ErrorActionPreference = $prevErrorAction', + 'if ($authStatusExitCode -eq 0) { Write-Output "GitHub CLI is already authenticated."; exit 0 }', + 'Write-Output "Starting GitHub CLI web login..."', + 'Write-Output "Expect a one-time code and https://github.com/login/device below."', + '& $gh auth login --hostname github.com --git-protocol https --web --skip-ssh-key', + 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }' + ].join('\n'), + docsUrl: 'https://cli.github.com/manual/gh_auth_login', + required: false, + optional: true, + runSupported: true + }, + { + id: 'install-claude', + title: 'Claude Code CLI', + description: 'Primary AI agent powered by Anthropic.', + command: 'winget install --id Anthropic.ClaudeCode --exact --source winget --accept-source-agreements --accept-package-agreements', + docsUrl: 'https://docs.claude.com/en/docs/claude-code/setup', + required: false, + optional: true, + runSupported: true + }, + { + id: 'install-codex', + title: 'Codex CLI', + description: 'Alternative AI agent tool for development.', + command: [ + "$ErrorActionPreference = 'Stop'", + '$npm = ""', + '$cmd = Get-Command npm -ErrorAction SilentlyContinue', + 'if ($cmd -and $cmd.Source) { $npm = $cmd.Source }', + 'if (-not $npm) {', + ' $candidates = @(', + ' "$env:ProgramFiles\\nodejs\\npm.cmd",', + ' "$env:ProgramFiles(x86)\\nodejs\\npm.cmd",', + ' "$env:LOCALAPPDATA\\Programs\\nodejs\\npm.cmd",', + ' "$env:APPDATA\\npm\\npm.cmd"', + ' )', + ' foreach ($candidate in $candidates) {', + ' if (Test-Path $candidate) { $npm = $candidate; break }', + ' }', + '}', + 'if (-not $npm) { throw "npm was not found. Install Node.js LTS first, then run this step again." }', + '& $npm install -g @openai/codex', + 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }' + ].join('\n'), + docsUrl: 'https://developers.openai.com/codex/cli', + required: false, + runSupported: true + } + ]; +} + +function getSetupActionById(actionId, platform = process.platform) { + const id = String(actionId || '').trim(); + if (!id) return null; + return getSetupActions(platform).find((action) => action.id === id) || null; +} + +function createRunId(actionId) { + return `setup-${String(actionId || 'action')}-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`; +} + +function getRunSummary(run) { + if (!run) return null; + return { + runId: run.runId, + actionId: run.actionId, + title: run.title, + command: run.command, + status: run.status, + startedAt: run.startedAt, + endedAt: run.endedAt || null, + pid: Number.isFinite(run.pid) ? run.pid : null, + exitCode: Number.isInteger(run.exitCode) ? run.exitCode : null, + error: run.error || null, + output: Array.isArray(run.output) ? run.output.slice(-25) : [], + ghDeviceCode: run.ghDeviceCode || null, + ghDeviceUrl: run.ghDeviceUrl || null, + ghHasDeviceHint: !!run.ghHasDeviceHint, + ghDebugLogPath: run.ghDebugLogPath || null, + updatedAt: run.updatedAt || run.startedAt + }; +} + +function appendRunOutput(run, chunk, stream = 'stdout') { + if (!run) return; + const text = String(chunk || ''); + if (!text) return; + const lines = text + .replace(/\r/g, '') + .split('\n') + .map((line) => stripAnsi(line).trimEnd()) + .filter(Boolean); + if (!lines.length) return; + const at = new Date().toISOString(); + lines.forEach((line) => { + const cleanLine = String(line || '').slice(0, 1600); + run.output.push({ at, stream, line: cleanLine }); + if (run.actionId === 'gh-login') { + const codeMatch = cleanLine.match(GH_LOGIN_CODE_PATTERN); + const urlMatch = cleanLine.match(GH_LOGIN_URL_PATTERN); + if (codeMatch?.[1]) run.ghDeviceCode = String(codeMatch[1]).toUpperCase(); + if (urlMatch?.[0]) run.ghDeviceUrl = String(urlMatch[0]).trim(); + if (GH_LOGIN_HINT_PATTERN.test(cleanLine)) run.ghHasDeviceHint = true; + appendGhLoginDebugLog('output', { + runId: run.runId, + stream, + line: cleanLine + }); + } + }); + if (run.output.length > MAX_OUTPUT_LINES) { + run.output.splice(0, run.output.length - MAX_OUTPUT_LINES); + } + run.updatedAt = at; +} + +function launchPowerShellCommand(action) { + const runId = createRunId(action.id); + const run = { + runId, + actionId: action.id, + title: action.title, + command: action.command, + status: 'running', + startedAt: new Date().toISOString(), + endedAt: null, + pid: null, + exitCode: null, + error: null, + output: [], + ghDeviceCode: null, + ghDeviceUrl: null, + ghHasDeviceHint: false, + ghDebugLogPath: action.id === 'gh-login' ? getGhLoginDebugLogPath() : null, + updatedAt: null + }; + run.updatedAt = run.startedAt; + if (action.id === 'gh-login') { + appendGhLoginDebugLog('run_started', { + runId: run.runId, + title: run.title + }); + } + + setupActionRuns.set(runId, run); + latestRunByActionId.set(action.id, runId); + + try { + const child = spawn( + 'powershell.exe', + ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', String(action.command || '')], + { + detached: false, + windowsHide: true, + stdio: ['ignore', 'pipe', 'pipe'] + } + ); + run.pid = Number.isFinite(child?.pid) ? child.pid : null; + run.updatedAt = new Date().toISOString(); + + child.stdout.on('data', (chunk) => appendRunOutput(run, chunk, 'stdout')); + child.stderr.on('data', (chunk) => appendRunOutput(run, chunk, 'stderr')); + + child.on('error', (error) => { + run.status = 'failed'; + run.error = String(error?.message || error || 'Failed to launch setup action'); + run.endedAt = new Date().toISOString(); + run.updatedAt = run.endedAt; + if (action.id === 'gh-login') { + appendGhLoginDebugLog('run_error', { + runId: run.runId, + error: run.error + }); + } + }); + + child.on('close', (code) => { + run.exitCode = Number.isInteger(code) ? code : null; + run.status = code === 0 ? 'success' : 'failed'; + if (code !== 0 && !run.error) { + run.error = `Setup action exited with code ${String(code)}`; + } + run.endedAt = new Date().toISOString(); + run.updatedAt = run.endedAt; + if (action.id === 'gh-login') { + appendGhLoginDebugLog('run_closed', { + runId: run.runId, + status: run.status, + exitCode: run.exitCode, + error: run.error || null, + parsedCode: run.ghDeviceCode || null, + parsedUrl: run.ghDeviceUrl || null, + sawHint: !!run.ghHasDeviceHint + }); + } + }); + } catch (error) { + run.status = 'failed'; + run.error = String(error?.message || error || 'Failed to launch setup action'); + run.endedAt = new Date().toISOString(); + run.updatedAt = run.endedAt; + if (action.id === 'gh-login') { + appendGhLoginDebugLog('run_launch_failed', { + runId: run.runId, + error: run.error + }); + } + } + + return run; +} + +function getSetupActionRun(runId) { + const key = String(runId || '').trim(); + if (!key) return null; + return getRunSummary(setupActionRuns.get(key)); +} + +function getLatestSetupActionRun(actionId) { + const id = String(actionId || '').trim(); + if (!id) return null; + const runId = latestRunByActionId.get(id); + if (!runId) return null; + return getRunSummary(setupActionRuns.get(runId)); +} + +function runSetupAction(actionId, platform = process.platform) { + if (platform !== 'win32') { + const err = new Error('Setup actions are currently implemented for Windows only.'); + err.code = 'unsupported_platform'; + throw err; + } + + const action = getSetupActionById(actionId, platform); + if (!action) { + const err = new Error(`Unknown setup action: ${String(actionId || '')}`); + err.code = 'unknown_action'; + throw err; + } + + if (!action.runSupported || !action.command) { + const err = new Error(`Action "${action.id}" cannot be launched from the app.`); + err.code = 'not_runnable'; + throw err; + } + + const latestRun = getLatestSetupActionRun(action.id); + if (latestRun && latestRun.status === 'running') { + return { + id: action.id, + title: action.title, + started: true, + alreadyRunning: true, + run: latestRun, + message: `${action.title} is already running.` + }; + } + + const run = launchPowerShellCommand(action); + const runSummary = getRunSummary(run); + + return { + id: action.id, + title: action.title, + started: true, + alreadyRunning: false, + run: runSummary, + message: `Started ${action.title}. Progress updates are now tracked in onboarding.` + }; +} + +async function configureGitIdentity({ name, email } = {}, platform = process.platform) { + if (platform !== 'win32') { + const err = new Error('Git identity setup is currently implemented for Windows only.'); + err.code = 'unsupported_platform'; + throw err; + } + + const normalizedName = String(name || '').trim(); + const normalizedEmail = String(email || '').trim(); + if (!normalizedName || !normalizedEmail) { + const err = new Error('Both name and email are required.'); + err.code = 'invalid_input'; + throw err; + } + + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) { + const err = new Error('Enter a valid email address.'); + err.code = 'invalid_input'; + throw err; + } + + const gitCommand = await resolveGitCommand(platform); + if (!gitCommand) { + const err = new Error('Git is not installed or not available on PATH.'); + err.code = 'missing_git'; + throw err; + } + + await runGitCommand(gitCommand, ['config', '--global', 'user.name', normalizedName]); + await runGitCommand(gitCommand, ['config', '--global', 'user.email', normalizedEmail]); + + const savedName = firstNonEmptyLine(await runGitCommand(gitCommand, ['config', '--global', '--get', 'user.name'])); + const savedEmail = firstNonEmptyLine(await runGitCommand(gitCommand, ['config', '--global', '--get', 'user.email'])); + + if (!savedName || !savedEmail) { + const err = new Error('Git identity was saved, but verification failed.'); + err.code = 'verify_failed'; + throw err; + } + + return { + id: 'configure-git-identity', + title: 'Configure Git identity', + ok: true, + gitCommand, + name: savedName, + email: savedEmail, + summary: `${savedName} <${savedEmail}>`, + message: 'Git identity saved successfully.' + }; +} + +module.exports = { + getSetupActions, + getSetupActionById, + runSetupAction, + getSetupActionRun, + getLatestSetupActionRun, + configureGitIdentity +}; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 076c0231..97dbed8b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,6 +10,14 @@ use uuid::Uuid; use tauri_plugin_updater::UpdaterExt; use url::Url; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; + +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x08000000; +#[cfg(target_os = "windows")] +const DETACHED_PROCESS: u32 = 0x00000008; + mod terminal; mod file_watcher; use terminal::{TerminalManager, TerminalOutput}; @@ -602,6 +610,8 @@ fn main() { cmd.arg(entry); cmd.current_dir(&data_dir); cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::null()); + cmd.stderr(Stdio::null()); cmd.env("ORCHESTRATOR_HOST", "127.0.0.1"); cmd.env("ORCHESTRATOR_PORT", port.to_string()); cmd.env("AUTH_TOKEN", token.clone()); @@ -612,6 +622,11 @@ fn main() { cmd.env("AUTO_START_DIFF_VIEWER", "false"); } + #[cfg(target_os = "windows")] + { + cmd.creation_flags(CREATE_NO_WINDOW | DETACHED_PROCESS); + } + match cmd.spawn() { Err(err) => { let details = format!( From d696ca7d65430e097a8f7921ab2f585ca144bc85 Mon Sep 17 00:00:00 2001 From: web3dev1337 Date: Mon, 2 Mar 2026 21:20:55 -0800 Subject: [PATCH 02/15] fix(windows): prevent updater plugin startup panic --- src-tauri/tauri.conf.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e6e4e6c7..e920716f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -42,5 +42,10 @@ "resources/backend/client/*", "resources/backend/node_modules" ] + }, + "plugins": { + "updater": { + "pubkey": "" + } } } From 37c09c965bdb61243c88e47070ea74ae51b7b148 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:31:12 -0600 Subject: [PATCH 03/15] fix(onboarding): prevent welcome modal reopening after completion --- client/app.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/app.js b/client/app.js index e4f8dec4..692d1ad2 100644 --- a/client/app.js +++ b/client/app.js @@ -10057,7 +10057,7 @@ class ClaudeOrchestrator { const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0))); - const loadAndRender = async ({ open = false, forceAutoShow = false, bootstrap = false } = {}) => { + const loadAndRender = async ({ open = false, forceAutoShow = false, bootstrap = false, explicitOpen = false } = {}) => { if (state.loading) return false; setLoading(true); try { @@ -10097,8 +10097,11 @@ class ClaudeOrchestrator { applyOnboardingLockUI(); if (view.req?.coreReady) writeDismissed(false); - const shouldAutoShow = forceAutoShow || (!readDismissed() && (!readCompleted() || !(view.req?.coreReady))); - if (open || shouldAutoShow) { + const hasCompletedOnboarding = readCompleted(); + const coreReady = !!view.req?.coreReady; + const shouldAutoShow = (!hasCompletedOnboarding || !coreReady) && (forceAutoShow || !readDismissed()); + const shouldKeepVisible = open && !modal.classList.contains('hidden'); + if (explicitOpen || shouldKeepVisible || shouldAutoShow) { openModal(); } else { setBootstrapPending(false); @@ -10107,7 +10110,8 @@ class ClaudeOrchestrator { } catch (err) { summaryEl.textContent = `Dependency check failed: ${String(err?.message || err)}`; listEl.innerHTML = '
Unable to load setup actions right now.
'; - if (open) openModal(); + const shouldOpenOnError = explicitOpen || (open && !modal.classList.contains('hidden')); + if (shouldOpenOnError) openModal(); else if (!bootstrap) setBootstrapPending(false); return false; } finally { @@ -10634,7 +10638,7 @@ class ClaudeOrchestrator { openBtn.addEventListener('click', () => { writeDismissed(false); setCurrentStep(0); - loadAndRender({ open: true, forceAutoShow: true }); + loadAndRender({ open: true, forceAutoShow: true, explicitOpen: true }); }); } if (closeBtn) { From 67ab3f3a741c43c550e857b62b75d507c55e0473 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:01:26 -0600 Subject: [PATCH 04/15] fix(onboarding): harden setup wizard gating and route policy --- client/app.js | 8 +++++++- client/index.html | 1 - server/index.js | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/client/app.js b/client/app.js index 692d1ad2..23850196 100644 --- a/client/app.js +++ b/client/app.js @@ -9356,6 +9356,10 @@ class ClaudeOrchestrator { body?.classList?.remove?.('dependency-onboarding-booting'); }; setBootstrapPending(true); + if (!isWindowsHost) { + setBootstrapPending(false); + return; + } const dismissKey = 'orchestrator-dependency-setup-dismissed-v3'; const completedKey = 'orchestrator-dependency-onboarding-completed-v2'; @@ -9653,6 +9657,8 @@ class ClaudeOrchestrator { }; const isOnboardingLocked = () => { + if (!isWindowsHost) return false; + if (!Array.isArray(state.actions) || state.actions.length === 0) return false; const toolsMap = toToolMap(state.diagnostics); const req = getRequirementState(toolsMap); if (!req?.coreReady) return true; @@ -10099,7 +10105,7 @@ class ClaudeOrchestrator { const hasCompletedOnboarding = readCompleted(); const coreReady = !!view.req?.coreReady; - const shouldAutoShow = (!hasCompletedOnboarding || !coreReady) && (forceAutoShow || !readDismissed()); + const shouldAutoShow = isWindowsHost && (!hasCompletedOnboarding || !coreReady) && (forceAutoShow || !readDismissed()); const shouldKeepVisible = open && !modal.classList.contains('hidden'); if (explicitOpen || shouldKeepVisible || shouldAutoShow) { openModal(); diff --git a/client/index.html b/client/index.html index c27ae39b..99b37010 100644 --- a/client/index.html +++ b/client/index.html @@ -679,7 +679,6 @@

Scheduler

Click โ€œRefresh schedulerโ€.
-

Pager / Pollcat

diff --git a/server/index.js b/server/index.js index 0fe86de2..7f589ae0 100644 --- a/server/index.js +++ b/server/index.js @@ -4069,7 +4069,7 @@ app.get('/api/setup-actions', (req, res) => { } }); -app.post('/api/setup-actions/run', (req, res) => { +app.post('/api/setup-actions/run', requirePolicyAction('write'), express.json(), (req, res) => { try { const actionId = String(req.body?.actionId || '').trim(); if (!actionId) { @@ -4086,7 +4086,7 @@ app.post('/api/setup-actions/run', (req, res) => { } }); -app.post('/api/setup-actions/configure-git-identity', async (req, res) => { +app.post('/api/setup-actions/configure-git-identity', requirePolicyAction('write'), express.json(), async (req, res) => { try { const name = String(req.body?.name || '').trim(); const email = String(req.body?.email || '').trim(); @@ -4128,7 +4128,7 @@ app.get('/api/setup-actions/run-status', (req, res) => { } }); -app.post('/api/setup-actions/open-url', (req, res) => { +app.post('/api/setup-actions/open-url', requirePolicyAction('write'), express.json(), (req, res) => { try { const rawUrl = String(req.body?.url || '').trim(); if (!rawUrl) { From 9e15a296e218939805875757f0188f8748c89415 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:30:59 -0600 Subject: [PATCH 05/15] fix: restore startup workspace and defer session init until workspace ready --- server/index.js | 39 ++++++++++++++++++++++-------------- server/workspaceManager.js | 41 ++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/server/index.js b/server/index.js index 7f589ae0..0a4e1831 100644 --- a/server/index.js +++ b/server/index.js @@ -407,6 +407,7 @@ sessionManager.setGitHelper(gitHelper); // Initialize workspace system let workspaceInitialized = false; +let workspaceSystemReady = null; async function initializeWorkspaceSystem() { try { logger.info('Initializing workspace system...'); @@ -432,21 +433,23 @@ async function initializeWorkspaceSystem() { } // Initialize workspace system before starting server -initializeWorkspaceSystem().then(() => { - logger.info('Workspace system initialized'); - loadPlugins() - .then((status) => { - logger.info('Plugin loader finished', { - loaded: Array.isArray(status?.loaded) ? status.loaded.length : 0, - failed: Array.isArray(status?.failed) ? status.failed.length : 0 - }); - }) - .catch((error) => { - logger.error('Plugin loader failed', { error: error.message, stack: error.stack }); +workspaceSystemReady = initializeWorkspaceSystem() + .then(() => { + logger.info('Workspace system initialized'); + return true; + }) + .then(() => loadPlugins()) + .then((status) => { + logger.info('Plugin loader finished', { + loaded: Array.isArray(status?.loaded) ? status.loaded.length : 0, + failed: Array.isArray(status?.failed) ? status.failed.length : 0 }); -}).catch(error => { - logger.error('Workspace system initialization failed', { error: error.message, stack: error.stack }); -}); + return true; + }) + .catch(error => { + logger.error('Workspace system initialization failed', { error: error.message, stack: error.stack }); + return false; + }); // WebSocket connection handling io.on('connection', (socket) => { @@ -7960,7 +7963,13 @@ httpServer.listen(PORT, HOST, () => { } })(); - sessionManager.initializeSessions() + workspaceSystemReady + .then((workspaceReady) => { + if (!workspaceReady) { + return; + } + return sessionManager.initializeSessions(); + }) .then(() => { if (!shouldAutoEnsureDiscordServices) return; // Donโ€™t block server startup; just best-effort keep Services running after restarts. diff --git a/server/workspaceManager.js b/server/workspaceManager.js index 06c2aaf3..86de1346 100644 --- a/server/workspaceManager.js +++ b/server/workspaceManager.js @@ -707,20 +707,31 @@ class WorkspaceManager { // 3. First available workspace // 4. None (show dashboard) - // Don't auto-select workspace - let user choose from dashboard - // if (this.config.activeWorkspace && this.workspaces.has(this.config.activeWorkspace)) { - // this.activeWorkspace = this.workspaces.get(this.config.activeWorkspace); - // logger.info(`Set active workspace from config: ${this.activeWorkspace.name}`); - // return; - // } - - // Don't auto-select first workspace - show dashboard instead - // if (this.workspaces.size > 0) { - // const firstWorkspace = Array.from(this.workspaces.values())[0]; - // this.activeWorkspace = firstWorkspace; - // logger.info(`Set active workspace (first available): ${this.activeWorkspace.name}`); - // return; - // } + const rememberLastWorkspace = this.config?.ui?.rememberLastWorkspace !== false; + const configuredWorkspaceId = String(this.config?.activeWorkspace || '').trim(); + + if (rememberLastWorkspace && configuredWorkspaceId && this.workspaces.has(configuredWorkspaceId)) { + this.activeWorkspace = this.workspaces.get(configuredWorkspaceId); + logger.info(`Set active workspace from config: ${this.activeWorkspace.name}`); + return; + } + + if (rememberLastWorkspace && configuredWorkspaceId && !this.workspaces.has(configuredWorkspaceId)) { + logger.warn(`Configured active workspace missing: ${configuredWorkspaceId}`); + } + + if (rememberLastWorkspace && this.workspaces.size > 0) { + const sorted = Array.from(this.workspaces.values()) + .sort((a, b) => { + const aTime = a.lastAccess ? new Date(a.lastAccess).getTime() : 0; + const bTime = b.lastAccess ? new Date(b.lastAccess).getTime() : 0; + return bTime - aTime; + }); + const firstWorkspace = sorted[0]; + this.activeWorkspace = firstWorkspace; + logger.info(`Set active workspace by fallback: ${this.activeWorkspace.name}`); + return; + } logger.info('No active workspace set (no workspaces available)'); } @@ -1004,7 +1015,7 @@ class WorkspaceManager { enabled: true, count: pairs, namingPattern: 'work{n}', - autoCreate: false + autoCreate: true }, terminals: { pairs From 8a004fd8b459770fe9f857de3350473b8118818c Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:54:47 -0600 Subject: [PATCH 06/15] fix: harden windows tauri node packaging --- .nvmrc | 2 +- package-lock.json | 22 +++--- package.json | 2 +- scripts/ensure-pty.js | 52 ++++++++++++-- scripts/tauri/prepare-backend-resources.js | 79 +++++++++++++++++++--- server/sessionManager.js | 9 ++- 6 files changed, 138 insertions(+), 28 deletions(-) diff --git a/.nvmrc b/.nvmrc index 25649a2b..a45fd52c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.9.0 +24 diff --git a/package-lock.json b/package-lock.json index a416406c..351617ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "express": "^4.18.2", "http-proxy-middleware": "^3.0.5", "multer": "^2.0.2", - "node-pty": "^1.0.0", + "node-pty": "^1.1.0", "socket.io": "^4.6.1", "winston": "^3.11.0" }, @@ -4773,12 +4773,6 @@ "node": ">= 10.16.0" } }, - "node_modules/nan": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", - "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", - "license": "MIT" - }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -4811,6 +4805,12 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4819,13 +4819,13 @@ "license": "MIT" }, "node_modules/node-pty": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", - "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", + "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "nan": "^2.17.0" + "node-addon-api": "^7.1.0" } }, "node_modules/node-releases": { diff --git a/package.json b/package.json index d491bcea..6acf6e46 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "express": "^4.18.2", "http-proxy-middleware": "^3.0.5", "multer": "^2.0.2", - "node-pty": "^1.0.0", + "node-pty": "^1.1.0", "socket.io": "^4.6.1", "winston": "^3.11.0" }, diff --git a/scripts/ensure-pty.js b/scripts/ensure-pty.js index ae04856c..b3179598 100644 --- a/scripts/ensure-pty.js +++ b/scripts/ensure-pty.js @@ -1,6 +1,41 @@ #!/usr/bin/env node -const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const { spawnSync } = require('child_process'); + +const NODE_BINARY = path.resolve(process.env.ORCHESTRATOR_NODE_PATH || process.env.TAURI_NODE_PATH || process.execPath || process.argv[0]); +const NPM_CLI = path.join(path.dirname(NODE_BINARY), 'node_modules', 'npm', 'bin', 'npm-cli.js'); + +function runCommand(command, args) { + const logParts = [command, ...args].join(' '); + const result = spawnSync(command, args, { stdio: 'inherit', cwd: process.cwd() }); + + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error(`Command "${logParts}" failed with exit code ${result.status}`); + } +} + +function runNpm(command, args) { + if (fs.existsSync(NPM_CLI)) { + console.log('[node-pty] running:', NODE_BINARY, path.basename(NPM_CLI), command, ...args); + const result = spawnSync(NODE_BINARY, [NPM_CLI, command, ...args], { stdio: 'inherit', cwd: process.cwd() }); + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error(`npm rebuild failed with exit code ${result.status}`); + } + return; + } + + const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + console.log('[node-pty] running:', npmCommand, command, ...args); + runCommand(npmCommand, [command, ...args]); +} function tryRequirePty() { try { @@ -20,11 +55,20 @@ if (firstTry === true) { const message = firstTry && firstTry.message ? firstTry.message : String(firstTry); console.warn('node-pty load failed, attempting rebuild...', message); +const attemptRebuild = (command, args) => { + runNpm(command, args); +}; + try { - execSync('npm rebuild node-pty', { stdio: 'inherit' }); + attemptRebuild('rebuild', ['node-pty']); } catch (error) { - console.error('npm rebuild node-pty failed'); - process.exit(1); + console.warn('[node-pty] ABI mismatch rebuild failed, retrying from source:', error.message); + try { + attemptRebuild('rebuild', ['node-pty', '--build-from-source']); + } catch (sourceError) { + console.error('[node-pty] rebuild failed:', sourceError.message); + process.exit(1); + } } const secondTry = tryRequirePty(); diff --git a/scripts/tauri/prepare-backend-resources.js b/scripts/tauri/prepare-backend-resources.js index d73354d5..f04bf552 100644 --- a/scripts/tauri/prepare-backend-resources.js +++ b/scripts/tauri/prepare-backend-resources.js @@ -37,16 +37,45 @@ function run(cmd, args, opts) { } } -function runNpm(args, opts) { - // When invoked via `npm run ...`, npm provides the JS entry path, which is - // the most reliable cross-platform invocation target. - const npmExecPath = String(process.env.npm_execpath || '').trim(); - if (npmExecPath) { - run(process.execPath, [npmExecPath, ...args], opts); +function getNodeExecutable(rawPath) { + const candidate = String(rawPath || '').trim(); + if (!candidate) { + return ''; + } + return path.resolve(candidate); +} + +function getBundledNpmPath(nodeExecutable) { + const nodePath = getNodeExecutable(nodeExecutable); + if (!nodePath) { + return ''; + } + + const npmCli = path.join(path.dirname(nodePath), 'node_modules', 'npm', 'bin', 'npm-cli.js'); + if (fs.existsSync(npmCli)) { + return npmCli; + } + + return ''; +} + +function runNpmWithNode(nodeExecutable, args, opts) { + const npmCli = getBundledNpmPath(nodeExecutable); + if (npmCli) { + run(nodeExecutable, [npmCli, ...args], opts); return; } + const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - run(npmCmd, args, opts); + run(npmCmd, args, { + env: { + ...process.env, + // Keep the node source of truth explicit for nested helpers like ensure-pty. + ORCHESTRATOR_NODE_PATH: getNodeExecutable(nodeExecutable), + TAURI_NODE_PATH: getNodeExecutable(nodeExecutable) + }, + ...opts + }); } function main() { @@ -83,7 +112,17 @@ function main() { // Default: bundle the Node runtime weโ€™re currently running on. // This makes `npm run tauri:build` much more โ€œit just worksโ€ on Windows. - const bundledNodePathRaw = bundledNodePathRawFromEnv || (shouldBundleNode ? process.execPath : ''); + const bundledNodePathRaw = getNodeExecutable( + bundledNodePathRawFromEnv || (shouldBundleNode ? process.execPath : '') + ); + + const nodeEnv = bundledNodePathRaw + ? { + ...process.env, + ORCHESTRATOR_NODE_PATH: bundledNodePathRaw, + TAURI_NODE_PATH: bundledNodePathRaw + } + : process.env; if (clean && fs.existsSync(outDir)) { fs.rmSync(outDir, { recursive: true, force: true }); @@ -116,12 +155,32 @@ function main() { if (installProd) { try { - runNpm(['ci', '--omit=dev', '--no-audit', '--no-fund'], { cwd: outDir }); + runNpmWithNode( + bundledNodePathRaw || process.execPath, + ['ci', '--omit=dev', '--no-audit', '--no-fund'], + { cwd: outDir, env: nodeEnv } + ); } catch (error) { // Some Windows setups have issues with `npm ci` for native modules. // Fall back to `npm install` so contributors can still build installers. console.warn('[tauri] NOTE: npm ci failed, falling back to npm install --omit=dev'); - runNpm(['install', '--omit=dev', '--no-audit', '--no-fund'], { cwd: outDir }); + runNpmWithNode( + bundledNodePathRaw || process.execPath, + ['install', '--omit=dev', '--no-audit', '--no-fund'], + { cwd: outDir, env: nodeEnv } + ); + } + + try { + const nodePath = bundledNodePathRaw || process.execPath; + const ensureScriptPath = path.join(repoRoot, 'scripts', 'ensure-pty.js'); + run(nodePath, [ensureScriptPath], { + cwd: outDir, + env: nodeEnv + }); + } catch (error) { + console.error('[tauri] NOTE: node-pty compatibility check failed after install:', error.message); + throw error; } } diff --git a/server/sessionManager.js b/server/sessionManager.js index d439f289..d7fdbe28 100644 --- a/server/sessionManager.js +++ b/server/sessionManager.js @@ -2364,7 +2364,14 @@ class SessionManager extends EventEmitter { if (process.platform === 'win32') { const { execFile } = require('child_process'); const psCmd = `(Get-CimInstance Win32_Process -Filter "ParentProcessId=${pid}").Count`; - execFile('powershell.exe', ['-NoProfile', '-Command', psCmd], { timeout: 2000 }, (err, stdout) => { + execFile( + 'powershell.exe', + ['-NoProfile', '-Command', psCmd], + { + timeout: 2000, + windowsHide: true + }, + (err, stdout) => { if (err) return; const processCount = parseInt(String(stdout || '').trim(), 10); if (!Number.isFinite(processCount)) return; From 740c6eb58df41103dbb28e5b1f08ee891d7ae5d9 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:36:53 -0600 Subject: [PATCH 07/15] fix: dedupe workspace switch retries --- client/workspace-tab-manager.js | 5 ----- server/index.js | 23 +++++++++++++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/client/workspace-tab-manager.js b/client/workspace-tab-manager.js index e9a33d90..037aa480 100644 --- a/client/workspace-tab-manager.js +++ b/client/workspace-tab-manager.js @@ -173,11 +173,6 @@ class WorkspaceTabManager { console.log(`Created tab ${tabId} for workspace ${workspace.name}`); - // If this is the first tab, activate it - if (this.tabs.size === 1) { - this.switchTab(tabId); - } - return tabId; } diff --git a/server/index.js b/server/index.js index 0a4e1831..4be824cc 100644 --- a/server/index.js +++ b/server/index.js @@ -454,6 +454,7 @@ workspaceSystemReady = initializeWorkspaceSystem() // WebSocket connection handling io.on('connection', (socket) => { logger.info('Client connected', { socketId: socket.id }); + let inFlightWorkspaceSwitchId = null; // Send workspace info const activeWorkspace = workspaceManager.getActiveWorkspace(); @@ -865,17 +866,27 @@ io.on('connection', (socket) => { // Workspace management handlers socket.on('switch-workspace', async ({ workspaceId }) => { + const requestedWorkspaceId = String(workspaceId || '').trim(); + if (requestedWorkspaceId && inFlightWorkspaceSwitchId === requestedWorkspaceId) { + logger.info('Ignoring duplicate workspace switch request while switch is already in progress', { + workspaceId: requestedWorkspaceId, + socketId: socket.id + }); + return; + } + + inFlightWorkspaceSwitchId = requestedWorkspaceId || null; try { const previous = workspaceManager.getActiveWorkspace?.() || null; activityFeed.track('workspace.switch.requested', { fromWorkspaceId: previous?.id || null, - toWorkspaceId: String(workspaceId || '').trim() || null, + toWorkspaceId: requestedWorkspaceId || null, socketId: socket.id }); - logger.info('Workspace switch requested', { workspaceId }); + logger.info('Workspace switch requested', { workspaceId: requestedWorkspaceId }); - const newWorkspace = await workspaceManager.switchWorkspace(workspaceId); + const newWorkspace = await workspaceManager.switchWorkspace(requestedWorkspaceId); // Ensure worktrees exist for the new workspace logger.info('Ensuring worktrees exist for new workspace'); @@ -916,12 +927,16 @@ io.on('connection', (socket) => { }); } catch (error) { activityFeed.track('workspace.switch.failed', { - toWorkspaceId: String(workspaceId || '').trim() || null, + toWorkspaceId: requestedWorkspaceId || null, socketId: socket.id, error: error.message }); logger.error('Failed to switch workspace', { error: error.message, stack: error.stack }); socket.emit('error', { message: 'Failed to switch workspace', error: error.message, stack: error.stack }); + } finally { + if (inFlightWorkspaceSwitchId === requestedWorkspaceId) { + inFlightWorkspaceSwitchId = null; + } } }); From eb9e62c59ca206a06d141df8da53467fa7f58bd9 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:20:24 -0600 Subject: [PATCH 08/15] fix: streamline windows desktop startup --- CODEBASE_DOCUMENTATION.md | 1 + client/app.js | 57 ++++++++++++++++-- server/claudeVersionChecker.js | 5 +- server/sessionManager.js | 13 ----- server/userSettingsService.js | 21 +++++++ .../sessionManager.initializeSessions.test.js | 58 +++++++++++++++++++ tests/unit/userSettingsDefaults.test.js | 31 ++++++++++ 7 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 tests/unit/sessionManager.initializeSessions.test.js diff --git a/CODEBASE_DOCUMENTATION.md b/CODEBASE_DOCUMENTATION.md index 41397f7b..420974ad 100644 --- a/CODEBASE_DOCUMENTATION.md +++ b/CODEBASE_DOCUMENTATION.md @@ -56,6 +56,7 @@ server/notificationService.js - System notification manager server/claudeVersionChecker.js - Claude Code version detection server/tokenCounter.js - Token usage tracking (if applicable) server/userSettingsService.js - User preferences and settings management +โ”œโ”€ Desktop onboarding state: persists Windows/Tauri dependency-onboarding completion in `global.ui.onboarding.desktopDependencySetup` server/sessionRecoveryService.js - Session recovery state persistence (CWD, agents, conversations) โ”œโ”€ Recovery filtering: stale/non-configured session entries are pruned when requested by workspace-scoped APIs โ”œโ”€ Agent clearing: `clearAgent()` resets stale `lastAgent` markers when a Claude/Codex terminal falls back to plain shell diff --git a/client/app.js b/client/app.js index 23850196..0ba18a77 100644 --- a/client/app.js +++ b/client/app.js @@ -1001,6 +1001,8 @@ class ClaudeOrchestrator { // Load user settings from server await this.loadUserSettings(); + this.syncDependencySetupWizardPreferences?.(); + void this.bootstrapDependencySetupWizard?.(); this.applySidebarDesktopCollapsedFromPrefs(); this.refreshLicenseStatus?.().catch(() => {}); this.syncTerminalFiltersFromUserSettings(); @@ -1369,6 +1371,7 @@ class ClaudeOrchestrator { this.applyThemeFromUserSettings(); this.applySimpleModeConfig(); this.maybeAutoOpenSimpleMode(); + this.syncDependencySetupWizardPreferences?.(); }); // Workspace events @@ -9345,6 +9348,8 @@ class ClaudeOrchestrator { return false; } })(); + const isDesktopWindowsApp = isWindowsHost && !!window.__TAURI__; + let desktopCompleted = false; const setBootstrapPending = (pending) => { if (!isWindowsHost) return; @@ -9355,11 +9360,11 @@ class ClaudeOrchestrator { } body?.classList?.remove?.('dependency-onboarding-booting'); }; - setBootstrapPending(true); if (!isWindowsHost) { setBootstrapPending(false); return; } + setBootstrapPending(false); const dismissKey = 'orchestrator-dependency-setup-dismissed-v3'; const completedKey = 'orchestrator-dependency-onboarding-completed-v2'; @@ -9381,6 +9386,13 @@ class ClaudeOrchestrator { gitIdentityHelpVisible: false }; + const syncDesktopCompleted = () => { + if (!isDesktopWindowsApp) return false; + desktopCompleted = !!this.userSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completed; + return desktopCompleted; + }; + syncDesktopCompleted(); + const readDismissed = () => { try { return localStorage.getItem(dismissKey) === 'true'; @@ -9399,6 +9411,9 @@ class ClaudeOrchestrator { }; const readCompleted = () => { + if (isDesktopWindowsApp) { + return syncDesktopCompleted(); + } try { return localStorage.getItem(completedKey) === 'true'; } catch { @@ -9406,7 +9421,18 @@ class ClaudeOrchestrator { } }; - const writeCompleted = (value) => { + const writeCompleted = async (value) => { + if (isDesktopWindowsApp) { + const next = !!value; + desktopCompleted = next; + if (this.userSettings) { + await this.updateGlobalUserSetting('ui.onboarding.desktopDependencySetup', { + completed: next, + completedAt: next ? new Date().toISOString() : null + }); + } + return; + } try { if (value) localStorage.setItem(completedKey, 'true'); else localStorage.removeItem(completedKey); @@ -10340,6 +10366,11 @@ class ClaudeOrchestrator { }; const runBootstrapLoad = async () => { + if (isDesktopWindowsApp && readCompleted()) { + setBootstrapPending(false); + return; + } + setBootstrapPending(true); const delaysMs = [0, 240, 420, 700, 1050, 1450, 1900]; for (let attempt = 0; attempt < delaysMs.length; attempt += 1) { if (attempt > 0) { @@ -10550,7 +10581,7 @@ class ClaudeOrchestrator { setStepSkipped(currentStep?.id, false); } if (state.currentStep >= (total - 1)) { - writeCompleted(true); + await writeCompleted(true); writeDismissed(false); closeModal({ force: true }); this.showToast('Dependency onboarding complete.', 'success'); @@ -10661,7 +10692,24 @@ class ClaudeOrchestrator { closeModal(); }); - runBootstrapLoad(); + this.syncDependencySetupWizardPreferences = () => { + syncDesktopCompleted(); + if (isDesktopWindowsApp && desktopCompleted) { + setBootstrapPending(false); + } + applyOnboardingLockUI(); + }; + this.bootstrapDependencySetupWizard = () => { + syncDesktopCompleted(); + if (isDesktopWindowsApp && readCompleted()) { + setBootstrapPending(false); + return Promise.resolve(false); + } + return runBootstrapLoad(); + }; + if (this.userSettings) { + void this.bootstrapDependencySetupWizard(); + } } notifyWorkflow({ type = 'info', message = '', sessionId = null, metadata = null } = {}) { @@ -16251,6 +16299,7 @@ class ClaudeOrchestrator { this.applySimpleModeConfig(); this.maybeAutoOpenSimpleMode(); this.applyUiVisibility(); + this.syncDependencySetupWizardPreferences?.(); this.refreshBranchLabels(); this.updateTierFilterButtons(); } else { diff --git a/server/claudeVersionChecker.js b/server/claudeVersionChecker.js index 5b1e45f7..89978c78 100644 --- a/server/claudeVersionChecker.js +++ b/server/claudeVersionChecker.js @@ -19,7 +19,8 @@ class ClaudeVersionChecker { return new Promise((resolve) => { const process = spawn('claude', ['--version'], { stdio: ['ignore', 'pipe', 'pipe'], - timeout: 5000 + timeout: 5000, + windowsHide: true }); let stdout = ''; @@ -102,4 +103,4 @@ class ClaudeVersionChecker { } } -module.exports = { ClaudeVersionChecker }; \ No newline at end of file +module.exports = { ClaudeVersionChecker }; diff --git a/server/sessionManager.js b/server/sessionManager.js index d7fdbe28..d92422ce 100644 --- a/server/sessionManager.js +++ b/server/sessionManager.js @@ -455,19 +455,6 @@ class SessionManager extends EventEmitter { }) ); - // Add git branch update to promises array - if (this.gitHelper) { - sessionPromises.push( - Promise.resolve().then(() => { - return this.updateGitBranch(worktree.id, worktree.path); - }).catch(error => { - logger.error('Failed to update git branch', { - worktree: worktree.id, - error: error.message - }); - }) - ); - } } } diff --git a/server/userSettingsService.js b/server/userSettingsService.js index 1bf9c88f..d6a22707 100644 --- a/server/userSettingsService.js +++ b/server/userSettingsService.js @@ -153,6 +153,12 @@ class UserSettingsService { skin: 'blue', // 0..100 (applied as 0..1 multiplier for skin tint in CSS) skinIntensity: 100, + onboarding: { + desktopDependencySetup: { + completed: false, + completedAt: null + } + }, visibility: { processBanner: false, header: { @@ -690,6 +696,21 @@ class UserSettingsService { }; } + if (ui.onboarding && typeof ui.onboarding === 'object') { + const defaultsOnboarding = (uiDefaults.onboarding && typeof uiDefaults.onboarding === 'object') + ? uiDefaults.onboarding + : {}; + const nextOnboarding = ui.onboarding || {}; + merged.global.ui.onboarding = { + ...defaultsOnboarding, + ...nextOnboarding, + desktopDependencySetup: { + ...(defaultsOnboarding.desktopDependencySetup || {}), + ...(nextOnboarding.desktopDependencySetup || {}) + } + }; + } + if (ui.visibility && typeof ui.visibility === 'object') { const defaultsVisibility = uiDefaults.visibility || {}; const nextVisibility = ui.visibility || {}; diff --git a/tests/unit/sessionManager.initializeSessions.test.js b/tests/unit/sessionManager.initializeSessions.test.js new file mode 100644 index 00000000..9f8d161e --- /dev/null +++ b/tests/unit/sessionManager.initializeSessions.test.js @@ -0,0 +1,58 @@ +jest.mock('../../server/claudeVersionChecker', () => ({ + ClaudeVersionChecker: { + checkVersion: jest.fn().mockResolvedValue({ + version: '1.0.24', + isCompatible: true + }) + } +})); + +const fs = require('fs'); +const { SessionManager } = require('../../server/sessionManager'); + +describe('SessionManager.initializeSessions', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('updates each single-repo worktree branch only once during initialization', async () => { + jest.spyOn(fs.promises, 'access').mockResolvedValue(undefined); + + const io = { emit: jest.fn() }; + const agentManager = { getAllAgents: () => [] }; + const sm = new SessionManager(io, agentManager); + + sm.workspace = { + name: 'test', + worktrees: { enabled: false, autoCreate: false }, + terminals: { pairs: 2 } + }; + sm.worktrees = [ + { id: 'work1', path: '/tmp/test/work1' }, + { id: 'work2', path: '/tmp/test/work2' } + ]; + sm.sessions = new Map(); + sm.gitHelper = {}; + sm.cleanupAllSessions = jest.fn(); + sm.stopBranchRefresh = jest.fn(); + sm.cleanupGitWatchers = jest.fn(); + sm.startBranchRefresh = jest.fn(); + sm.setupGitWatchers = jest.fn(); + sm.createSession = jest.fn((sessionId, config) => { + sm.sessions.set(sessionId, { + id: sessionId, + type: config.type, + worktreeId: config.worktreeId, + config + }); + }); + sm.updateGitBranch = jest.fn().mockResolvedValue(undefined); + + await sm.initializeSessions({ preserveExisting: true }); + + expect(sm.createSession).toHaveBeenCalledTimes(4); + expect(sm.updateGitBranch).toHaveBeenCalledTimes(2); + expect(sm.updateGitBranch).toHaveBeenNthCalledWith(1, 'work1', '/tmp/test/work1'); + expect(sm.updateGitBranch).toHaveBeenNthCalledWith(2, 'work2', '/tmp/test/work2'); + }); +}); diff --git a/tests/unit/userSettingsDefaults.test.js b/tests/unit/userSettingsDefaults.test.js index e2b29f2b..5e64ca90 100644 --- a/tests/unit/userSettingsDefaults.test.js +++ b/tests/unit/userSettingsDefaults.test.js @@ -46,6 +46,14 @@ describe('UserSettingsService defaults', () => { expect(defaults.global.ui.workflow.notifications.mode).toBeTruthy(); }); + test('includes desktop onboarding defaults', () => { + const defaults = UserSettingsService.prototype.getDefaultSettings.call({}); + const onboarding = defaults?.global?.ui?.onboarding?.desktopDependencySetup; + expect(onboarding).toBeTruthy(); + expect(onboarding.completed).toBe(false); + expect(onboarding.completedAt).toBeNull(); + }); + test('includes ui.worktrees auto-create defaults', () => { const defaults = UserSettingsService.prototype.getDefaultSettings.call({}); expect(defaults?.global?.ui?.worktrees).toBeTruthy(); @@ -186,6 +194,10 @@ describe('UserSettingsService defaults', () => { // Does not drop workflow defaults when only mode is provided. expect(merged.global.ui.workflow.focus).toBeTruthy(); expect(merged.global.ui.workflow.notifications).toBeTruthy(); + // Keeps onboarding defaults while allowing desktop completion to persist. + expect(merged.global.ui.onboarding).toBeTruthy(); + expect(merged.global.ui.onboarding.desktopDependencySetup.completed).toBe(false); + expect(merged.global.ui.onboarding.desktopDependencySetup.completedAt).toBeNull(); // Keeps ui.skin when provided. expect(merged.global.ui.skin).toBe('blue'); // Keeps simpleMode defaults while allowing partial override. @@ -214,4 +226,23 @@ describe('UserSettingsService defaults', () => { expect(merged.global.pager.doneCheck.enabled).toBe(true); expect(typeof merged.global.pager.doneCheck.token).toBe('string'); }); + + test('mergeSettings deep-merges desktop onboarding state', () => { + const defaults = UserSettingsService.prototype.getDefaultSettings.call({}); + const merged = UserSettingsService.prototype.mergeSettings.call({}, defaults, { + global: { + ui: { + onboarding: { + desktopDependencySetup: { + completed: true, + completedAt: '2026-03-06T00:00:00.000Z' + } + } + } + } + }); + + expect(merged.global.ui.onboarding.desktopDependencySetup.completed).toBe(true); + expect(merged.global.ui.onboarding.desktopDependencySetup.completedAt).toBe('2026-03-06T00:00:00.000Z'); + }); }); From 5d0873557027a0e4dc6e687bf5219f4a2f71cb91 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:54:02 -0600 Subject: [PATCH 09/15] fix: calm windows desktop startup --- client/app.js | 8 ++++++-- server/githubRepoService.js | 8 ++++++-- server/worktreeHelper.js | 14 +++++++++----- tests/unit/githubRepoService.listRepos.test.js | 6 ++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/client/app.js b/client/app.js index 0ba18a77..3282a188 100644 --- a/client/app.js +++ b/client/app.js @@ -10129,9 +10129,13 @@ class ClaudeOrchestrator { applyOnboardingLockUI(); if (view.req?.coreReady) writeDismissed(false); - const hasCompletedOnboarding = readCompleted(); + let hasCompletedOnboarding = readCompleted(); const coreReady = !!view.req?.coreReady; - const shouldAutoShow = isWindowsHost && (!hasCompletedOnboarding || !coreReady) && (forceAutoShow || !readDismissed()); + if (isDesktopWindowsApp && coreReady && !hasCompletedOnboarding) { + await writeCompleted(true); + hasCompletedOnboarding = readCompleted(); + } + const shouldAutoShow = isWindowsHost && !hasCompletedOnboarding && (forceAutoShow || !readDismissed()); const shouldKeepVisible = open && !modal.classList.contains('hidden'); if (explicitOpen || shouldKeepVisible || shouldAutoShow) { openModal(); diff --git a/server/githubRepoService.js b/server/githubRepoService.js index 13a2aff3..e3027576 100644 --- a/server/githubRepoService.js +++ b/server/githubRepoService.js @@ -2,8 +2,12 @@ const https = require('https'); const { execFile } = require('child_process'); const winston = require('winston'); -const execFileAsync = (command, args, options) => new Promise((resolve, reject) => { - execFile(command, args, options, (error, stdout, stderr) => { +const execFileAsync = (command, args, options = {}) => new Promise((resolve, reject) => { + const runOptions = { + ...options, + windowsHide: options.windowsHide ?? true + }; + execFile(command, args, runOptions, (error, stdout, stderr) => { if (error) { reject(error); return; diff --git a/server/worktreeHelper.js b/server/worktreeHelper.js index d57967c2..89f92e6d 100644 --- a/server/worktreeHelper.js +++ b/server/worktreeHelper.js @@ -268,20 +268,24 @@ class WorktreeHelper { executeGitCommand(command, cwd) { return new Promise((resolve, reject) => { const [cmd, ...args] = command.split(' '); - const process = spawn(cmd, args, { cwd, stdio: 'pipe' }); + const child = spawn(cmd, args, { + cwd, + stdio: 'pipe', + windowsHide: globalThis.process.platform === 'win32' + }); let stdout = ''; let stderr = ''; - process.stdout.on('data', (data) => { + child.stdout.on('data', (data) => { stdout += data.toString(); }); - process.stderr.on('data', (data) => { + child.stderr.on('data', (data) => { stderr += data.toString(); }); - process.on('close', (code) => { + child.on('close', (code) => { if (code === 0) { resolve(stdout.trim()); } else { @@ -289,7 +293,7 @@ class WorktreeHelper { } }); - process.on('error', (error) => { + child.on('error', (error) => { reject(new Error(`Failed to execute git command: ${command}\nError: ${error.message}`)); }); }); diff --git a/tests/unit/githubRepoService.listRepos.test.js b/tests/unit/githubRepoService.listRepos.test.js index b5d7a78f..7100755c 100644 --- a/tests/unit/githubRepoService.listRepos.test.js +++ b/tests/unit/githubRepoService.listRepos.test.js @@ -26,6 +26,12 @@ describe('GitHubRepoService listRepos', () => { { nameWithOwner: 'foo/bar', name: 'bar', owner: 'foo', isPrivate: false, isFork: true, visibility: 'public' }, { nameWithOwner: 'acme/secret', name: 'secret', owner: 'acme', isPrivate: true, isFork: false, visibility: 'private' } ]); + expect(execFile).toHaveBeenCalledWith( + 'gh', + ['repo', 'list', '--limit', '50', '--json', 'nameWithOwner,name,owner,isPrivate,visibility,isFork'], + expect.objectContaining({ windowsHide: true }), + expect.any(Function) + ); }); it('caches list results (no force)', async () => { From 2f86a92e6fb5f8952ccc5cf7476fbc581e971cc6 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:24:13 -0600 Subject: [PATCH 10/15] debug: trace windows desktop launch --- client/app.js | 275 +++++++++++++++++++++++++++- client/dashboard.js | 2 +- client/greenfield-wizard.js | 2 +- client/quick-links.js | 2 +- client/workspace-switcher.js | 4 +- client/workspace-tab-manager.js | 2 +- server/desktopLaunchTraceService.js | 74 ++++++++ server/index.js | 134 +++++++++++++- server/sessionManager.js | 120 +++++++++++- 9 files changed, 594 insertions(+), 21 deletions(-) create mode 100644 server/desktopLaunchTraceService.js diff --git a/client/app.js b/client/app.js index 3282a188..f4e5e952 100644 --- a/client/app.js +++ b/client/app.js @@ -116,9 +116,130 @@ class ClaudeOrchestrator { // navigate Prev/Next without reopening the Queue overlay. this.reviewConsoleNav = null; // { source, createdAtMs, items: [{ id, kind, title, url, ... }], index } + this.currentTabId = null; + this.desktopLaunchTrace = { + id: this.createDesktopLaunchTraceId(), + enabled: this.shouldEnableDesktopLaunchTrace(), + seq: 0, + startedAt: new Date().toISOString() + }; + this.init(); } + shouldEnableDesktopLaunchTrace() { + try { + const platform = String(navigator?.platform || '').toLowerCase(); + const userAgent = String(navigator?.userAgent || '').toLowerCase(); + return !!window.__TAURI__ && (platform.includes('win') || userAgent.includes('windows')); + } catch { + return false; + } + } + + createDesktopLaunchTraceId() { + return `launch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + } + + sanitizeDesktopTraceValue(value, depth = 0, seen = new WeakSet()) { + if (value == null) return value; + if (typeof value === 'boolean' || typeof value === 'number') return value; + if (typeof value === 'string') { + return value.length > 500 ? `${value.slice(0, 484)}...[truncated]` : value; + } + if (typeof value === 'function') return `[function:${value.name || 'anonymous'}]`; + if (depth >= 4) return '[depth-limit]'; + if (Array.isArray(value)) { + const items = value.slice(0, 15).map((item) => this.sanitizeDesktopTraceValue(item, depth + 1, seen)); + if (value.length > 15) items.push(`[+${value.length - 15} more]`); + return items; + } + if (typeof value === 'object') { + if (seen.has(value)) return '[circular]'; + seen.add(value); + const out = {}; + const keys = Object.keys(value).slice(0, 20); + keys.forEach((key) => { + out[key] = this.sanitizeDesktopTraceValue(value[key], depth + 1, seen); + }); + if (Object.keys(value).length > keys.length) { + out.__truncatedKeys = Object.keys(value).length - keys.length; + } + seen.delete(value); + return out; + } + return String(value); + } + + getDesktopLaunchTraceContext() { + return { + appStartedAt: this.desktopLaunchTrace?.startedAt || null, + currentWorkspaceId: this.currentWorkspace?.id || null, + currentWorkspaceName: this.currentWorkspace?.name || null, + currentTabId: this.currentTabId || null, + isDashboardMode: !!this.isDashboardMode, + socketConnected: !!this.socket?.connected, + visibilityState: document?.visibilityState || null, + location: window?.location?.pathname || null + }; + } + + withLaunchTraceHeaders(init = {}) { + if (!this.desktopLaunchTrace?.enabled) { + return init || {}; + } + return { + ...(init || {}), + headers: { + ...(init?.headers || {}), + 'x-launch-trace-id': this.desktopLaunchTrace.id + } + }; + } + + async traceDesktopLaunch(event, details = {}) { + if (!this.desktopLaunchTrace?.enabled) return false; + + const safeDetails = this.sanitizeDesktopTraceValue(details); + const payload = { + traceId: this.desktopLaunchTrace.id, + seq: (this.desktopLaunchTrace.seq += 1), + event: String(event || '').trim() || 'client.event', + source: 'desktop-app', + details: (safeDetails && typeof safeDetails === 'object' && !Array.isArray(safeDetails)) + ? { ...this.getDesktopLaunchTraceContext(), ...safeDetails } + : { ...this.getDesktopLaunchTraceContext(), value: safeDetails } + }; + + try { + await fetch('/api/desktop-launch-trace', this.withLaunchTraceHeaders({ + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + keepalive: true + })); + return true; + } catch { + return false; + } + } + + emitWorkspaceSwitch(workspaceId, source = 'unknown', extra = {}) { + const id = String(workspaceId || '').trim(); + if (!id || !this.socket) return; + void this.traceDesktopLaunch('client.workspace-switch.emitted', { + source, + workspaceId: id, + extra + }); + this.socket.emit('switch-workspace', { + workspaceId: id, + source, + traceId: this.desktopLaunchTrace?.enabled ? this.desktopLaunchTrace.id : null, + ...extra + }); + } + isMobileLayout() { try { return window.matchMedia && window.matchMedia('(max-width: 768px)').matches; @@ -848,6 +969,12 @@ class ClaudeOrchestrator { async init() { try { + void this.traceDesktopLaunch('client.init.start', { + tauri: !!window.__TAURI__, + userAgent: String(navigator?.userAgent || ''), + platform: String(navigator?.platform || '') + }); + // Initialize managers this.terminalManager = new TerminalManager(this); this.terminalManager.autosuggestEnabled = this.settings.autoSuggestions !== false; @@ -1030,6 +1157,9 @@ class ClaudeOrchestrator { } catch (error) { console.error('Failed to initialize:', error); + void this.traceDesktopLaunch('client.init.failed', { + error: String(error?.message || error) + }); this.showError('Failed to initialize application'); } } @@ -1050,12 +1180,20 @@ class ClaudeOrchestrator { // Connection events this.socket.on('connect', () => { console.log('Connected to server'); + void this.traceDesktopLaunch('client.socket.connected', { + serverUrl, + socketId: this.socket?.id || null + }); this.updateConnectionStatus(true); resolve(); }); this.socket.on('connect_error', (error) => { console.error('Connection error:', error); + void this.traceDesktopLaunch('client.socket.connect-error', { + serverUrl, + error: String(error?.message || error) + }); this.updateConnectionStatus(false); if (error.message === 'Authentication failed') { @@ -1066,12 +1204,19 @@ class ClaudeOrchestrator { this.socket.on('disconnect', () => { console.log('Disconnected from server'); + void this.traceDesktopLaunch('client.socket.disconnected', { + socketId: this.socket?.id || null + }); this.updateConnectionStatus(false); }); // Session events this.socket.on('sessions', async (sessionStates) => { console.log('Received sessions event:', sessionStates); + void this.traceDesktopLaunch('client.sessions.received', { + sessionCount: Object.keys(sessionStates || {}).length, + sessionIds: Object.keys(sessionStates || {}) + }); // Pre-fetch worktree configs if we have an active workspace if (this.currentWorkspace) { @@ -1366,6 +1511,10 @@ class ClaudeOrchestrator { this.socket.on('user-settings-updated', (settings) => { console.log('User settings updated:', settings); + void this.traceDesktopLaunch('client.user-settings.updated', { + onboardingCompleted: settings?.global?.ui?.onboarding?.desktopDependencySetup?.completed === true, + onboardingCompletedAt: settings?.global?.ui?.onboarding?.desktopDependencySetup?.completedAt || null + }); this.userSettings = settings; this.syncUserSettingsUI(); this.applyThemeFromUserSettings(); @@ -1377,6 +1526,12 @@ class ClaudeOrchestrator { // Workspace events this.socket.on('workspace-info', async ({ active, available, config, workspaceTypes, frameworks, cascadedConfigs }) => { console.log('Received workspace info:', { active, available, config, workspaceTypes, frameworks, cascadedConfigs }); + void this.traceDesktopLaunch('client.workspace-info.received', { + activeWorkspaceId: active?.id || null, + activeWorkspaceName: active?.name || null, + availableWorkspaceCount: Array.isArray(available) ? available.length : 0, + startupDashboard: config?.ui?.startupDashboard === true + }); this.currentWorkspace = active; this.availableWorkspaces = available; this.orchestratorConfig = config; @@ -1452,6 +1607,11 @@ class ClaudeOrchestrator { this.socket.on('workspace-changed', async ({ workspace, sessions }) => { console.log('Workspace changed:', workspace.name); + void this.traceDesktopLaunch('client.workspace-changed.received', { + workspaceId: workspace?.id || null, + workspaceName: workspace?.name || null, + sessionCount: Object.keys(sessions || {}).length + }); // If tab manager is enabled, create a new tab for this workspace if (this.tabManager) { @@ -3578,6 +3738,11 @@ class ClaudeOrchestrator { handleInitialSessions(sessionStates) { console.log('Received initial sessions:', sessionStates); + void this.traceDesktopLaunch('client.sessions.rendered', { + sessionCount: Object.keys(sessionStates || {}).length, + sessionIds: Object.keys(sessionStates || {}), + workspaceId: this.currentWorkspace?.id || null + }); // Preserve per-workspace worktree visibility (hide/show toggles) when we // receive a sessions refresh for the SAME workspace (e.g. after adding a @@ -9350,6 +9515,9 @@ class ClaudeOrchestrator { })(); const isDesktopWindowsApp = isWindowsHost && !!window.__TAURI__; let desktopCompleted = false; + const traceOnboarding = (event, details = {}) => { + void this.traceDesktopLaunch(`client.onboarding.${event}`, details); + }; const setBootstrapPending = (pending) => { if (!isWindowsHost) return; @@ -9392,6 +9560,11 @@ class ClaudeOrchestrator { return desktopCompleted; }; syncDesktopCompleted(); + traceOnboarding('wizard-ready', { + isWindowsHost, + isDesktopWindowsApp, + desktopCompleted + }); const readDismissed = () => { try { @@ -9425,11 +9598,24 @@ class ClaudeOrchestrator { if (isDesktopWindowsApp) { const next = !!value; desktopCompleted = next; + traceOnboarding('completion-write-start', { + nextCompleted: next + }); if (this.userSettings) { await this.updateGlobalUserSetting('ui.onboarding.desktopDependencySetup', { completed: next, completedAt: next ? new Date().toISOString() : null }); + traceOnboarding('completion-write-success', { + nextCompleted: next, + persistedCompleted: !!this.userSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completed, + persistedCompletedAt: this.userSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completedAt || null + }); + } else { + traceOnboarding('completion-write-skipped', { + nextCompleted: next, + reason: 'user-settings-missing' + }); } return; } @@ -10054,6 +10240,10 @@ class ClaudeOrchestrator { const closeModal = ({ force = false } = {}) => { const locked = applyOnboardingLockUI(); + traceOnboarding('modal-close-requested', { + force, + locked + }); if (!force && locked) { openModal(); return false; @@ -10065,6 +10255,10 @@ class ClaudeOrchestrator { }; const openModal = ({ showWelcome = null } = {}) => { const wasHidden = modal.classList.contains('hidden'); + traceOnboarding('modal-opened', { + wasHidden, + requestedShowWelcome: typeof showWelcome === 'boolean' ? showWelcome : null + }); modal.classList.remove('hidden'); setBootstrapPending(false); body?.classList?.add?.('dependency-onboarding-active'); @@ -10091,11 +10285,17 @@ class ClaudeOrchestrator { const loadAndRender = async ({ open = false, forceAutoShow = false, bootstrap = false, explicitOpen = false } = {}) => { if (state.loading) return false; + traceOnboarding('load-start', { + open, + forceAutoShow, + bootstrap, + explicitOpen + }); setLoading(true); try { const [diagRes, actionsRes] = await Promise.all([ - fetch('/api/diagnostics'), - fetch('/api/setup-actions') + fetch('/api/diagnostics', this.withLaunchTraceHeaders()), + fetch('/api/setup-actions', this.withLaunchTraceHeaders()) ]); const diagData = await diagRes.json().catch(() => ({})); const actionsData = await actionsRes.json().catch(() => ({})); @@ -10132,11 +10332,34 @@ class ClaudeOrchestrator { let hasCompletedOnboarding = readCompleted(); const coreReady = !!view.req?.coreReady; if (isDesktopWindowsApp && coreReady && !hasCompletedOnboarding) { + traceOnboarding('auto-complete-triggered', { + coreReady, + hasCompletedOnboarding + }); await writeCompleted(true); hasCompletedOnboarding = readCompleted(); } + const dismissed = readDismissed(); const shouldAutoShow = isWindowsHost && !hasCompletedOnboarding && (forceAutoShow || !readDismissed()); const shouldKeepVisible = open && !modal.classList.contains('hidden'); + traceOnboarding('load-success', { + open, + forceAutoShow, + bootstrap, + explicitOpen, + coreReady, + hasCompletedOnboarding, + dismissed, + shouldAutoShow, + shouldKeepVisible, + actionIds: state.actions.map((action) => String(action?.id || '').trim()).filter(Boolean), + toolStates: Array.isArray(diagData?.tools) + ? diagData.tools.map((tool) => ({ + id: String(tool?.id || '').trim(), + ok: !!tool?.ok + })).filter((tool) => tool.id) + : [] + }); if (explicitOpen || shouldKeepVisible || shouldAutoShow) { openModal(); } else { @@ -10144,6 +10367,13 @@ class ClaudeOrchestrator { } return true; } catch (err) { + traceOnboarding('load-failed', { + open, + forceAutoShow, + bootstrap, + explicitOpen, + error: String(err?.message || err) + }); summaryEl.textContent = `Dependency check failed: ${String(err?.message || err)}`; listEl.innerHTML = '
Unable to load setup actions right now.
'; const shouldOpenOnError = explicitOpen || (open && !modal.classList.contains('hidden')); @@ -10698,6 +10928,9 @@ class ClaudeOrchestrator { this.syncDependencySetupWizardPreferences = () => { syncDesktopCompleted(); + traceOnboarding('preferences-synced', { + desktopCompleted + }); if (isDesktopWindowsApp && desktopCompleted) { setBootstrapPending(false); } @@ -10705,8 +10938,14 @@ class ClaudeOrchestrator { }; this.bootstrapDependencySetupWizard = () => { syncDesktopCompleted(); + traceOnboarding('bootstrap-requested', { + desktopCompleted + }); if (isDesktopWindowsApp && readCompleted()) { setBootstrapPending(false); + traceOnboarding('bootstrap-skipped', { + reason: 'already-completed' + }); return Promise.resolve(false); } return runBootstrapLoad(); @@ -16294,10 +16533,15 @@ class ClaudeOrchestrator { // User Settings Methods async loadUserSettings() { try { - const response = await fetch('/api/user-settings'); + void this.traceDesktopLaunch('client.user-settings.load-start'); + const response = await fetch('/api/user-settings', this.withLaunchTraceHeaders()); if (response.ok) { this.userSettings = await response.json(); console.log('User settings loaded:', this.userSettings); + void this.traceDesktopLaunch('client.user-settings.load-success', { + onboardingCompleted: this.userSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completed === true, + onboardingCompletedAt: this.userSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completedAt || null + }); this.syncUserSettingsUI(); this.applyThemeFromUserSettings(); this.applySimpleModeConfig(); @@ -16308,9 +16552,15 @@ class ClaudeOrchestrator { this.updateTierFilterButtons(); } else { console.error('Failed to load user settings:', response.statusText); + void this.traceDesktopLaunch('client.user-settings.load-failed', { + statusText: response.statusText || '' + }); } } catch (error) { console.error('Error loading user settings:', error); + void this.traceDesktopLaunch('client.user-settings.load-error', { + error: String(error?.message || error) + }); } } @@ -16340,22 +16590,35 @@ class ClaudeOrchestrator { } current[pathParts[pathParts.length - 1]] = value; - const response = await fetch('/api/user-settings/global', { + const response = await fetch('/api/user-settings/global', this.withLaunchTraceHeaders({ method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ global: newGlobal }) - }); + })); if (response.ok) { const updatedSettings = await response.json(); this.userSettings = updatedSettings; console.log('Global setting updated:', path, '=', value); + void this.traceDesktopLaunch('client.user-settings.global-update-success', { + path, + onboardingCompleted: updatedSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completed === true, + onboardingCompletedAt: updatedSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completedAt || null + }); this.applyUiVisibility(); } else { console.error('Failed to update global setting:', response.statusText); + void this.traceDesktopLaunch('client.user-settings.global-update-failed', { + path, + statusText: response.statusText || '' + }); } } catch (error) { console.error('Error updating global setting:', error); + void this.traceDesktopLaunch('client.user-settings.global-update-error', { + path, + error: String(error?.message || error) + }); } } @@ -17364,7 +17627,7 @@ class ClaudeOrchestrator { switchToWorkspace(workspaceId) { console.log('Switching to workspace:', workspaceId); - this.socket.emit('switch-workspace', { workspaceId }); + this.emitWorkspaceSwitch(workspaceId, 'app.switchToWorkspace'); } async waitForWorkspaceActive(workspaceId, { timeoutMs = 7000 } = {}) { diff --git a/client/dashboard.js b/client/dashboard.js index 829278c7..8b633ff2 100644 --- a/client/dashboard.js +++ b/client/dashboard.js @@ -3775,7 +3775,7 @@ class Dashboard { } // Emit workspace switch event - this.orchestrator.socket.emit('switch-workspace', { workspaceId }); + this.orchestrator.emitWorkspaceSwitch(workspaceId, 'dashboard.openWorkspace'); // Wait for workspace-changed event this.orchestrator.socket.once('workspace-changed', ({ workspace, sessions }) => { diff --git a/client/greenfield-wizard.js b/client/greenfield-wizard.js index 49a56891..c9243076 100644 --- a/client/greenfield-wizard.js +++ b/client/greenfield-wizard.js @@ -897,7 +897,7 @@ class GreenfieldWizard { // Switch to the new workspace if (this.orchestrator && this.orchestrator.socket) { - this.orchestrator.socket.emit('switch-workspace', { workspaceId }); + this.orchestrator.emitWorkspaceSwitch(workspaceId, 'greenfield-wizard.openWorkspace'); } } } diff --git a/client/quick-links.js b/client/quick-links.js index ead9bad4..e6e58fb8 100644 --- a/client/quick-links.js +++ b/client/quick-links.js @@ -682,7 +682,7 @@ class QuickLinks { if (window.orchestrator) { // If we need to switch workspace first if (window.orchestrator.currentWorkspace?.id !== workspaceId) { - window.orchestrator.socket.emit('switch-workspace', { workspaceId }); + window.orchestrator.emitWorkspaceSwitch(workspaceId, 'quick-links.openWorkspace'); } // Track this session access diff --git a/client/workspace-switcher.js b/client/workspace-switcher.js index f4835af6..fa691885 100644 --- a/client/workspace-switcher.js +++ b/client/workspace-switcher.js @@ -218,7 +218,7 @@ class WorkspaceSwitcher { } // Emit switch request - this.orchestrator.socket.emit('switch-workspace', { workspaceId }); + this.orchestrator.emitWorkspaceSwitch(workspaceId, 'workspace-switcher.switchWorkspace'); // Wait for workspace-changed event (handled in app.js) this.orchestrator.socket.once('workspace-changed', () => { @@ -267,4 +267,4 @@ class WorkspaceSwitcher { } // Make available globally -window.WorkspaceSwitcher = WorkspaceSwitcher; \ No newline at end of file +window.WorkspaceSwitcher = WorkspaceSwitcher; diff --git a/client/workspace-tab-manager.js b/client/workspace-tab-manager.js index 037aa480..d67bfdd9 100644 --- a/client/workspace-tab-manager.js +++ b/client/workspace-tab-manager.js @@ -265,7 +265,7 @@ class WorkspaceTabManager { const currentWorkspaceId = this.orchestrator?.currentWorkspace?.id || null; if (this.orchestrator?.socket?.connected && targetTab.workspaceId && targetTab.workspaceId !== currentWorkspaceId) { console.log(`Requesting backend workspace switch for tab ${tabId}: ${currentWorkspaceId} โ†’ ${targetTab.workspaceId}`); - this.orchestrator.socket.emit('switch-workspace', { workspaceId: targetTab.workspaceId }); + this.orchestrator.emitWorkspaceSwitch(targetTab.workspaceId, 'workspace-tab-manager.switchTab'); return; } diff --git a/server/desktopLaunchTraceService.js b/server/desktopLaunchTraceService.js new file mode 100644 index 00000000..3daff2c4 --- /dev/null +++ b/server/desktopLaunchTraceService.js @@ -0,0 +1,74 @@ +const winston = require('winston'); + +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + new winston.transports.File({ + filename: 'logs/desktop-launch.log', + maxsize: 10485760, + maxFiles: 5 + }) + ] +}); + +function clampString(value, maxLength = 600) { + const text = String(value || ''); + if (text.length <= maxLength) return text; + return `${text.slice(0, Math.max(0, maxLength - 16))}...[truncated]`; +} + +function sanitizePayload(value, { depth = 0, seen = new WeakSet() } = {}) { + if (value == null) return value; + if (typeof value === 'boolean' || typeof value === 'number') return value; + if (typeof value === 'string') return clampString(value); + if (typeof value === 'function') return `[function:${value.name || 'anonymous'}]`; + if (depth >= 4) return '[depth-limit]'; + + if (Array.isArray(value)) { + const items = value.slice(0, 20).map((item) => sanitizePayload(item, { depth: depth + 1, seen })); + if (value.length > 20) items.push(`[+${value.length - 20} more]`); + return items; + } + + if (typeof value === 'object') { + if (seen.has(value)) return '[circular]'; + seen.add(value); + const out = {}; + const keys = Object.keys(value).slice(0, 25); + keys.forEach((key) => { + out[key] = sanitizePayload(value[key], { depth: depth + 1, seen }); + }); + if (Object.keys(value).length > keys.length) { + out.__truncatedKeys = Object.keys(value).length - keys.length; + } + seen.delete(value); + return out; + } + + return clampString(String(value)); +} + +function logDesktopLaunch(event, payload = {}) { + const name = String(event || '').trim() || 'event'; + const safePayload = payload && typeof payload === 'object' && !Array.isArray(payload) + ? sanitizePayload(payload) + : { value: sanitizePayload(payload) }; + + try { + logger.info(name, { + event: name, + ...safePayload + }); + } catch { + // Best-effort trace logging should never interrupt app startup. + } +} + +module.exports = { + logDesktopLaunch, + sanitizePayload +}; diff --git a/server/index.js b/server/index.js index 4be824cc..968293cd 100644 --- a/server/index.js +++ b/server/index.js @@ -7,6 +7,7 @@ const fs = require('fs'); const os = require('os'); const crypto = require('crypto'); const winston = require('winston'); +const { logDesktopLaunch } = require('./desktopLaunchTraceService'); // Ensure log directory exists early (some services create file transports at require-time). try { @@ -42,6 +43,56 @@ const logger = winston.createLogger({ ] }); +function getLaunchTraceId(req, extras = {}) { + const extraTraceId = String(extras?.traceId || '').trim(); + if (extraTraceId) return extraTraceId; + return String( + req?.get?.('x-launch-trace-id') + || req?.body?.traceId + || req?.query?.traceId + || '' + ).trim(); +} + +function summarizeDiagnosticTools(data) { + const tools = Array.isArray(data?.tools) ? data.tools : []; + const summary = {}; + tools.forEach((tool) => { + const id = String(tool?.id || '').trim(); + if (!id) return; + summary[id] = { + ok: !!tool?.ok, + version: tool?.version ? String(tool.version) : null + }; + }); + const gitOk = !!summary.git?.ok; + const claudeOk = !!summary.claude?.ok; + const codexOk = !!summary.codex?.ok; + return { + coreReady: gitOk && (claudeOk || codexOk), + tools: summary + }; +} + +function summarizeOnboardingState(settings) { + const onboarding = settings?.global?.ui?.onboarding?.desktopDependencySetup || {}; + return { + completed: onboarding.completed === true, + completedAt: onboarding.completedAt || null + }; +} + +function traceRequest(req, event, payload = {}, extras = {}) { + const traceId = getLaunchTraceId(req, extras); + if (!traceId) return; + logDesktopLaunch(event, { + traceId, + method: req?.method || null, + path: req?.path || null, + ...payload + }); +} + // Import services const { SessionManager } = require('./sessionManager'); const { StatusDetector } = require('./statusDetector'); @@ -215,6 +266,26 @@ app.get('/', (req, res) => { res.sendFile(path.join(__dirname, '../client/index.html')); }); +app.post('/api/desktop-launch-trace', express.json({ limit: '64kb' }), (req, res) => { + const traceId = getLaunchTraceId(req); + if (!traceId) { + return res.status(400).json({ ok: false, error: 'traceId is required' }); + } + + const event = String(req.body?.event || '').trim() || 'client.event'; + logDesktopLaunch(event, { + traceId, + seq: Number.isFinite(Number(req.body?.seq)) ? Number(req.body.seq) : null, + source: String(req.body?.source || '').trim() || 'client', + details: req.body?.details && typeof req.body.details === 'object' ? req.body.details : {}, + userAgent: req.get('user-agent') || '', + origin: req.get('origin') || '', + referer: req.get('referer') || '' + }); + + res.json({ ok: true }); +}); + // Serve static files from client directory (but exclude index files) const clientPath = path.join(__dirname, '../client'); logger.info(`Serving static files from: ${clientPath}`); @@ -865,13 +936,28 @@ io.on('connection', (socket) => { }); // Workspace management handlers - socket.on('switch-workspace', async ({ workspaceId }) => { - const requestedWorkspaceId = String(workspaceId || '').trim(); + socket.on('switch-workspace', async (payload = {}) => { + const requestedWorkspaceId = String(payload?.workspaceId || '').trim(); + const traceId = String(payload?.traceId || '').trim() || null; + const source = String(payload?.source || '').trim() || 'unknown'; + logDesktopLaunch('server.workspace-switch.received', { + traceId, + source, + socketId: socket.id, + requestedWorkspaceId, + currentWorkspaceId: workspaceManager.getActiveWorkspace?.()?.id || null + }); if (requestedWorkspaceId && inFlightWorkspaceSwitchId === requestedWorkspaceId) { logger.info('Ignoring duplicate workspace switch request while switch is already in progress', { workspaceId: requestedWorkspaceId, socketId: socket.id }); + logDesktopLaunch('server.workspace-switch.ignored-duplicate', { + traceId, + source, + socketId: socket.id, + requestedWorkspaceId + }); return; } @@ -885,6 +971,13 @@ io.on('connection', (socket) => { }); logger.info('Workspace switch requested', { workspaceId: requestedWorkspaceId }); + logDesktopLaunch('server.workspace-switch.started', { + traceId, + source, + socketId: socket.id, + previousWorkspaceId: previous?.id || null, + requestedWorkspaceId + }); const newWorkspace = await workspaceManager.switchWorkspace(requestedWorkspaceId); @@ -894,7 +987,12 @@ io.on('connection', (socket) => { // Switch active workspace while preserving existing PTYs for other workspace tabs. const { sessions: newSessions, backlog } = - await sessionManager.switchWorkspacePreservingSessions(newWorkspace); + await sessionManager.switchWorkspacePreservingSessions(newWorkspace, { + reason: 'workspace-switch', + traceId, + source, + socketId: socket.id + }); // Emit success with ONLY the new workspace sessions (active workspace map) logger.info('Sending workspace-changed event', { @@ -919,6 +1017,15 @@ io.on('connection', (socket) => { } logger.info('Workspace switched successfully', { workspace: newWorkspace.name }); + logDesktopLaunch('server.workspace-switch.completed', { + traceId, + source, + socketId: socket.id, + previousWorkspaceId: previous?.id || null, + workspaceId: newWorkspace?.id || null, + sessionCount: Object.keys(newSessions || {}).length, + backlogSessionCount: backlog && typeof backlog === 'object' ? Object.keys(backlog).length : 0 + }); activityFeed.track('workspace.switch.completed', { fromWorkspaceId: previous?.id || null, toWorkspaceId: newWorkspace?.id || null, @@ -926,6 +1033,13 @@ io.on('connection', (socket) => { socketId: socket.id }); } catch (error) { + logDesktopLaunch('server.workspace-switch.failed', { + traceId, + source, + socketId: socket.id, + requestedWorkspaceId, + error: error.message + }); activityFeed.track('workspace.switch.failed', { toWorkspaceId: requestedWorkspaceId || null, socketId: socket.id, @@ -3737,6 +3851,9 @@ app.put('/api/process/automations/pr-review/config', express.json(), async (req, app.get('/api/user-settings', (req, res) => { try { const settings = userSettingsService.getAllSettings(); + traceRequest(req, 'server.user-settings.loaded', { + onboarding: summarizeOnboardingState(settings) + }); res.json(settings); } catch (error) { logger.error('Failed to get user settings', { error: error.message, stack: error.stack }); @@ -3752,6 +3869,10 @@ app.put('/api/user-settings/global', express.json(), (req, res) => { if (success) { const updatedSettings = userSettingsService.getAllSettings(); + traceRequest(req, 'server.user-settings.global-updated', { + onboarding: summarizeOnboardingState(updatedSettings), + updatedKeys: global && typeof global === 'object' ? Object.keys(global).slice(0, 20) : [] + }); res.json(updatedSettings); // Notify all clients about settings change @@ -3996,6 +4117,7 @@ app.get('/api/agents', (req, res) => { app.get('/api/diagnostics', async (req, res) => { try { const data = await collectDiagnostics(); + traceRequest(req, 'server.diagnostics.loaded', summarizeDiagnosticTools(data)); res.json({ ok: true, ...data }); } catch (error) { logger.error('Failed to collect diagnostics', { error: error.message, stack: error.stack }); @@ -4080,6 +4202,10 @@ app.get('/api/setup-actions', (req, res) => { try { const platform = process.platform; const actions = getSetupActions(platform); + traceRequest(req, 'server.setup-actions.loaded', { + platform, + actionIds: actions.map((action) => String(action?.id || '').trim()).filter(Boolean) + }); res.json({ ok: true, platform, actions }); } catch (error) { logger.error('Failed to get setup actions', { error: error.message, stack: error.stack }); @@ -7983,7 +8109,7 @@ httpServer.listen(PORT, HOST, () => { if (!workspaceReady) { return; } - return sessionManager.initializeSessions(); + return sessionManager.initializeSessions({ reason: 'server-startup' }); }) .then(() => { if (!shouldAutoEnsureDiscordServices) return; diff --git a/server/sessionManager.js b/server/sessionManager.js index d92422ce..23a2343f 100644 --- a/server/sessionManager.js +++ b/server/sessionManager.js @@ -13,6 +13,7 @@ const path = require('path'); const { ClaudeVersionChecker } = require('./claudeVersionChecker'); const { UserSettingsService } = require('./userSettingsService'); const { WorktreeHelper } = require('./worktreeHelper'); +const { logDesktopLaunch } = require('./desktopLaunchTraceService'); const sessionRecoveryService = require('./sessionRecoveryService'); const { parseWorktreeKey } = require('./lifecyclePolicyService'); const { @@ -161,7 +162,7 @@ class SessionManager extends EventEmitter { * - Restores (or creates) the session map for the new workspace id as `this.sessions` * - Ensures sessions exist for the new workspace without killing old PTYs */ - async switchWorkspacePreservingSessions(workspace) { + async switchWorkspacePreservingSessions(workspace, options = {}) { if (!workspace?.id) { throw new Error('Workspace missing id'); } @@ -178,7 +179,13 @@ class SessionManager extends EventEmitter { this.workspaceSessionMaps.set(workspace.id, this.sessions); // Ensure sessions exist for the active workspace without clearing existing ones. - await this.initializeSessions({ preserveExisting: true }); + await this.initializeSessions({ + preserveExisting: true, + reason: String(options.reason || '').trim() || 'workspace-switch', + traceId: String(options.traceId || '').trim() || null, + source: String(options.source || '').trim() || null, + socketId: String(options.socketId || '').trim() || null + }); // Return any buffered output that occurred while this workspace was inactive. return { @@ -243,9 +250,24 @@ class SessionManager extends EventEmitter { async initializeSessions(options = {}) { const preserveExisting = !!options.preserveExisting; + const reason = String(options.reason || '').trim() || (preserveExisting ? 'preserve-existing' : 'workspace-initialize'); + const traceId = String(options.traceId || '').trim() || null; + const source = String(options.source || '').trim() || null; + const socketId = String(options.socketId || '').trim() || null; // Set flag to prevent auto-restart during initialization this.isWorkspaceSwitching = true; + logDesktopLaunch('session-manager.initialize.begin', { + traceId, + source, + socketId, + reason, + preserveExisting, + workspaceId: this.workspace?.id || null, + workspaceName: this.workspace?.name || null, + worktreeIds: Array.isArray(this.worktrees) ? this.worktrees.map((worktree) => worktree?.id || null) : [] + }); + if (!preserveExisting) { // Clear ALL existing sessions first logger.info('Clearing existing sessions before workspace initialization'); @@ -305,6 +327,14 @@ class SessionManager extends EventEmitter { // If no workspace is set, skip session creation if (!this.workspace) { logger.warn('No workspace set, skipping session initialization'); + logDesktopLaunch('session-manager.initialize.skipped', { + traceId, + source, + socketId, + reason, + preserveExisting, + cause: 'missing-workspace' + }); this.isWorkspaceSwitching = false; return; } @@ -387,7 +417,11 @@ class SessionManager extends EventEmitter { worktreeId: terminal.worktree, repositoryName: terminal.repository.name, repositoryType: terminal.repository.type, // Add repository type for dynamic launch options - timeoutMs + timeoutMs, + debugSource: reason, + launchTraceId: traceId, + launchSource: source, + launchSocketId: socketId }); }).catch(error => { logger.error(`Failed to initialize ${terminal.terminalType} session`, { @@ -412,7 +446,11 @@ class SessionManager extends EventEmitter { args: buildShellArgs(`cd "${worktree.path}"`), cwd: worktree.path, type: 'claude', - worktreeId: worktree.id + worktreeId: worktree.id, + debugSource: reason, + launchTraceId: traceId, + launchSource: source, + launchSocketId: socketId }); }).catch(error => { logger.error('Failed to initialize Claude session', { @@ -445,7 +483,11 @@ class SessionManager extends EventEmitter { ]), cwd: worktree.path, type: 'server', - worktreeId: worktree.id + worktreeId: worktree.id, + debugSource: reason, + launchTraceId: traceId, + launchSource: source, + launchSocketId: socketId }); }).catch(error => { logger.error('Failed to initialize server session', { @@ -478,6 +520,17 @@ class SessionManager extends EventEmitter { // Wait for all sessions to be created in parallel await Promise.all(sessionPromises); logger.info('All sessions initialized', { count: sessionPromises.length }); + logDesktopLaunch('session-manager.initialize.completed', { + traceId, + source, + socketId, + reason, + preserveExisting, + workspaceId: this.workspace?.id || null, + workspaceName: this.workspace?.name || null, + createdSessionCount: this.sessions.size, + sessionIds: Array.from(this.sessions.keys()) + }); // Keep an authoritative reference from workspace id -> session map for tab switching. if (this.workspace?.id) { @@ -722,10 +775,33 @@ class SessionManager extends EventEmitter { createSession(sessionId, config) { logger.info('Creating session', { sessionId, type: config.type }); + logDesktopLaunch('session-manager.session.create.requested', { + traceId: String(config?.launchTraceId || '').trim() || null, + source: String(config?.launchSource || '').trim() || null, + socketId: String(config?.launchSocketId || '').trim() || null, + debugSource: String(config?.debugSource || '').trim() || null, + workspaceId: this.workspace?.id || null, + sessionId, + sessionType: config?.type || null, + worktreeId: config?.worktreeId || null, + cwd: config?.cwd || null, + command: config?.command || null, + args: Array.isArray(config?.args) ? config.args : [] + }); try { if (!pty) { logger.error('Cannot create session - node-pty unavailable', { sessionId, type: config.type }); + logDesktopLaunch('session-manager.session.create.failed', { + traceId: String(config?.launchTraceId || '').trim() || null, + source: String(config?.launchSource || '').trim() || null, + socketId: String(config?.launchSocketId || '').trim() || null, + debugSource: String(config?.debugSource || '').trim() || null, + workspaceId: this.workspace?.id || null, + sessionId, + sessionType: config?.type || null, + error: 'node-pty unavailable' + }); throw new Error('node-pty unavailable'); } const homeDir = process.env.HOME || os.homedir(); @@ -750,6 +826,17 @@ class SessionManager extends EventEmitter { cwd: config.cwd, env }); + logDesktopLaunch('session-manager.session.spawned', { + traceId: String(config?.launchTraceId || '').trim() || null, + source: String(config?.launchSource || '').trim() || null, + socketId: String(config?.launchSocketId || '').trim() || null, + debugSource: String(config?.debugSource || '').trim() || null, + workspaceId: this.workspace?.id || null, + sessionId, + sessionType: config?.type || null, + worktreeId: config?.worktreeId || null, + pid: Number.isFinite(ptyProcess?.pid) ? ptyProcess.pid : null + }); const initialCwd = config.cwd || process.cwd(); @@ -828,6 +915,18 @@ class SessionManager extends EventEmitter { ptyProcess.onExit(({ exitCode, signal }) => { logger.info('Session exited', { sessionId, exitCode, signal }); const workspaceId = session.workspace || this.workspace?.id || null; + logDesktopLaunch('session-manager.session.exited', { + traceId: String(config?.launchTraceId || '').trim() || null, + source: String(config?.launchSource || '').trim() || null, + socketId: String(config?.launchSocketId || '').trim() || null, + debugSource: String(config?.debugSource || '').trim() || null, + workspaceId, + sessionId, + sessionType: session.type, + worktreeId: session.worktreeId || null, + exitCode, + signal + }); clearTimeout(session.inactivityTimer); if (session.pendingStatusTimer) { @@ -932,6 +1031,17 @@ class SessionManager extends EventEmitter { }, 5000); } catch (error) { + logDesktopLaunch('session-manager.session.create.failed', { + traceId: String(config?.launchTraceId || '').trim() || null, + source: String(config?.launchSource || '').trim() || null, + socketId: String(config?.launchSocketId || '').trim() || null, + debugSource: String(config?.debugSource || '').trim() || null, + workspaceId: this.workspace?.id || null, + sessionId, + sessionType: config?.type || null, + worktreeId: config?.worktreeId || null, + error: error.message + }); logger.error('Failed to create session', { sessionId, error: error.message From d0bde1bb517828aae1f7add3164872dbcc64e5f3 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:43:21 -0600 Subject: [PATCH 11/15] fix: stabilize windows desktop launch --- client/app.js | 58 +++++++++++++++++++++++++++++++------------ src-tauri/Cargo.toml | 1 + src-tauri/src/main.rs | 10 ++++++++ 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/client/app.js b/client/app.js index f4e5e952..a0253115 100644 --- a/client/app.js +++ b/client/app.js @@ -128,17 +128,34 @@ class ClaudeOrchestrator { } shouldEnableDesktopLaunchTrace() { + return this.isDesktopWindowsRuntime(); + } + + createDesktopLaunchTraceId() { + return `launch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + } + + isWindowsHostEnvironment() { try { const platform = String(navigator?.platform || '').toLowerCase(); const userAgent = String(navigator?.userAgent || '').toLowerCase(); - return !!window.__TAURI__ && (platform.includes('win') || userAgent.includes('windows')); + return platform.includes('win') || userAgent.includes('windows'); } catch { return false; } } - createDesktopLaunchTraceId() { - return `launch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + hasDesktopLaunchToken() { + try { + const params = new URLSearchParams(window?.location?.search || ''); + return !!String(params.get('token') || '').trim(); + } catch { + return false; + } + } + + isDesktopWindowsRuntime() { + return this.isWindowsHostEnvironment() && (this.hasDesktopLaunchToken() || !!window.__TAURI__); } sanitizeDesktopTraceValue(value, depth = 0, seen = new WeakSet()) { @@ -227,6 +244,15 @@ class ClaudeOrchestrator { emitWorkspaceSwitch(workspaceId, source = 'unknown', extra = {}) { const id = String(workspaceId || '').trim(); if (!id || !this.socket) return; + if (this.isDesktopWindowsRuntime() && this.userSettings?.global?.ui?.onboarding?.desktopDependencySetup?.completed !== true) { + void this.traceDesktopLaunch('client.workspace-switch.blocked-onboarding', { + source, + workspaceId: id + }); + this.showToast('Finish Orchestrator Setup before opening a workspace.', 'warning'); + this.openDependencySetupWizard?.({ resetStep: false, source: 'workspace-switch-blocked' }); + return; + } void this.traceDesktopLaunch('client.workspace-switch.emitted', { source, workspaceId: id, @@ -971,6 +997,7 @@ class ClaudeOrchestrator { try { void this.traceDesktopLaunch('client.init.start', { tauri: !!window.__TAURI__, + desktopRuntime: this.isDesktopWindowsRuntime(), userAgent: String(navigator?.userAgent || ''), platform: String(navigator?.platform || '') }); @@ -9504,16 +9531,8 @@ class ClaudeOrchestrator { const closeBtn = document.getElementById('dependency-setup-close'); if (!modal || !summaryEl || !listEl) return; const body = document.body; - const isWindowsHost = (() => { - try { - const platform = String(navigator?.platform || '').toLowerCase(); - const userAgent = String(navigator?.userAgent || '').toLowerCase(); - return platform.includes('win') || userAgent.includes('windows'); - } catch { - return false; - } - })(); - const isDesktopWindowsApp = isWindowsHost && !!window.__TAURI__; + const isWindowsHost = this.isWindowsHostEnvironment(); + const isDesktopWindowsApp = this.isDesktopWindowsRuntime(); let desktopCompleted = false; const traceOnboarding = (event, details = {}) => { void this.traceDesktopLaunch(`client.onboarding.${event}`, details); @@ -10907,9 +10926,7 @@ class ClaudeOrchestrator { if (openBtn) { openBtn.addEventListener('click', () => { - writeDismissed(false); - setCurrentStep(0); - loadAndRender({ open: true, forceAutoShow: true, explicitOpen: true }); + this.openDependencySetupWizard?.({ resetStep: true, source: 'manual-open-button' }); }); } if (closeBtn) { @@ -10936,6 +10953,15 @@ class ClaudeOrchestrator { } applyOnboardingLockUI(); }; + this.openDependencySetupWizard = ({ resetStep = true, source = 'manual-open' } = {}) => { + traceOnboarding('manual-open-requested', { + source, + resetStep + }); + writeDismissed(false); + if (resetStep) setCurrentStep(0); + return loadAndRender({ open: true, forceAutoShow: true, explicitOpen: true }); + }; this.bootstrapDependencySetupWizard = () => { syncDesktopCompleted(); traceOnboarding('bootstrap-requested', { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f4ad68bb..f66beca8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,6 +17,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = ["tray-icon"] } tauri-plugin-shell = "2" +tauri-plugin-single-instance = "2" tauri-plugin-updater = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 97dbed8b..6841fc0b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -529,6 +529,16 @@ fn main() { std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "0"); tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { + if let Some(window) = app + .get_webview_window("main") + .or_else(|| app.webview_windows().values().next().cloned()) + { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } + })) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .setup(|app| { From d56d47af389cee92332187d81f86a2953b29d0f8 Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:26:58 -0600 Subject: [PATCH 12/15] fix: hide windows powershell terminal flashes --- server/commanderService.js | 4 +- server/sessionManager.js | 4 +- .../sessionManager.initializeSessions.test.js | 60 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/server/commanderService.js b/server/commanderService.js index ded60491..d96836fb 100644 --- a/server/commanderService.js +++ b/server/commanderService.js @@ -62,7 +62,9 @@ class CommanderService { try { // Detect shell based on platform const shell = process.platform === 'win32' ? 'powershell.exe' : 'bash'; - const shellArgs = process.platform === 'win32' ? ['-NoExit'] : []; + const shellArgs = process.platform === 'win32' + ? ['-WindowStyle', 'Hidden', '-NoLogo', '-NoExit'] + : []; // Spawn Claude Code terminal const ptyProcess = pty.spawn(shell, shellArgs, { diff --git a/server/sessionManager.js b/server/sessionManager.js index 23a2343f..e8f699b5 100644 --- a/server/sessionManager.js +++ b/server/sessionManager.js @@ -50,9 +50,9 @@ function getDefaultShell() { // Helper function to build shell args for executing commands function buildShellArgs(commands) { if (process.platform === 'win32') { - // PowerShell: join commands with ; and use -NoExit -Command to keep shell open + // PowerShell: hide the backing console window while keeping the shell interactive in the PTY. const joined = Array.isArray(commands) ? commands.join('; ') : commands.replace(/&&/g, ';'); - return ['-NoExit', '-Command', joined]; + return ['-WindowStyle', 'Hidden', '-NoLogo', '-NoExit', '-Command', joined]; } else { // Bash: join commands with && and keep the terminal open by exec'ing into an interactive shell. const joined = Array.isArray(commands) ? commands.join(' && ') : commands; diff --git a/tests/unit/sessionManager.initializeSessions.test.js b/tests/unit/sessionManager.initializeSessions.test.js index 9f8d161e..4dab96b0 100644 --- a/tests/unit/sessionManager.initializeSessions.test.js +++ b/tests/unit/sessionManager.initializeSessions.test.js @@ -55,4 +55,64 @@ describe('SessionManager.initializeSessions', () => { expect(sm.updateGitBranch).toHaveBeenNthCalledWith(1, 'work1', '/tmp/test/work1'); expect(sm.updateGitBranch).toHaveBeenNthCalledWith(2, 'work2', '/tmp/test/work2'); }); + + test('uses hidden PowerShell startup args for Windows sessions', async () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + configurable: true, + value: 'win32' + }); + + jest.spyOn(fs.promises, 'access').mockResolvedValue(undefined); + + const io = { emit: jest.fn() }; + const agentManager = { getAllAgents: () => [] }; + const sm = new SessionManager(io, agentManager); + + sm.workspace = { + name: 'test', + worktrees: { enabled: false, autoCreate: false }, + terminals: { pairs: 1 } + }; + sm.worktrees = [ + { id: 'work1', path: 'C:\\test\\work1' } + ]; + sm.sessions = new Map(); + sm.gitHelper = {}; + sm.cleanupAllSessions = jest.fn(); + sm.stopBranchRefresh = jest.fn(); + sm.cleanupGitWatchers = jest.fn(); + sm.startBranchRefresh = jest.fn(); + sm.setupGitWatchers = jest.fn(); + sm.createSession = jest.fn((sessionId, config) => { + sm.sessions.set(sessionId, { + id: sessionId, + type: config.type, + worktreeId: config.worktreeId, + config + }); + }); + sm.updateGitBranch = jest.fn().mockResolvedValue(undefined); + + try { + await sm.initializeSessions({ preserveExisting: true }); + } finally { + Object.defineProperty(process, 'platform', { + configurable: true, + value: originalPlatform + }); + } + + const claudeConfig = sm.createSession.mock.calls.find(([sessionId]) => sessionId === 'work1-claude')?.[1]; + const serverConfig = sm.createSession.mock.calls.find(([sessionId]) => sessionId === 'work1-server')?.[1]; + + expect(claudeConfig).toBeTruthy(); + expect(serverConfig).toBeTruthy(); + expect(claudeConfig.command).toBe('powershell.exe'); + expect(serverConfig.command).toBe('powershell.exe'); + expect(claudeConfig.args.slice(0, 4)).toEqual(['-WindowStyle', 'Hidden', '-NoLogo', '-NoExit']); + expect(serverConfig.args.slice(0, 4)).toEqual(['-WindowStyle', 'Hidden', '-NoLogo', '-NoExit']); + expect(claudeConfig.args).toContain('-Command'); + expect(serverConfig.args).toContain('-Command'); + }); }); From 248a0954fd2bee73b43b29112f41573da9eb483e Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:45:39 -0600 Subject: [PATCH 13/15] fix: stop windows terminal flash probes --- server/index.js | 60 +++++++++++++- server/sessionManager.js | 38 +++++++-- .../sessionManager.initializeSessions.test.js | 82 +++++++++++++++++++ 3 files changed, 168 insertions(+), 12 deletions(-) diff --git a/server/index.js b/server/index.js index 968293cd..0ac7eafe 100644 --- a/server/index.js +++ b/server/index.js @@ -962,6 +962,7 @@ io.on('connection', (socket) => { } inFlightWorkspaceSwitchId = requestedWorkspaceId || null; + let releaseInFlightWorkspaceSwitchInFinally = true; try { const previous = workspaceManager.getActiveWorkspace?.() || null; activityFeed.track('workspace.switch.requested', { @@ -986,12 +987,17 @@ io.on('connection', (socket) => { await worktreeHelper.ensureWorktreesExist(newWorkspace); // Switch active workspace while preserving existing PTYs for other workspace tabs. - const { sessions: newSessions, backlog } = + const { + sessions: newSessions, + backlog, + initializePromise + } = await sessionManager.switchWorkspacePreservingSessions(newWorkspace, { reason: 'workspace-switch', traceId, source, - socketId: socket.id + socketId: socket.id, + deferInitialize: true }); // Emit success with ONLY the new workspace sessions (active workspace map) @@ -1024,7 +1030,8 @@ io.on('connection', (socket) => { previousWorkspaceId: previous?.id || null, workspaceId: newWorkspace?.id || null, sessionCount: Object.keys(newSessions || {}).length, - backlogSessionCount: backlog && typeof backlog === 'object' ? Object.keys(backlog).length : 0 + backlogSessionCount: backlog && typeof backlog === 'object' ? Object.keys(backlog).length : 0, + pendingSessionInitialization: !!initializePromise }); activityFeed.track('workspace.switch.completed', { fromWorkspaceId: previous?.id || null, @@ -1032,6 +1039,51 @@ io.on('connection', (socket) => { toWorkspaceName: newWorkspace?.name || null, socketId: socket.id }); + + if (initializePromise && typeof initializePromise.then === 'function') { + releaseInFlightWorkspaceSwitchInFinally = false; + initializePromise + .then(() => { + const activeWorkspaceId = workspaceManager.getActiveWorkspace?.()?.id || null; + if (activeWorkspaceId !== newWorkspace?.id) { + return; + } + const refreshedSessions = sessionManager.getSessionStates(); + socket.emit('sessions', refreshedSessions); + logDesktopLaunch('server.workspace-switch.sessions-ready', { + traceId, + source, + socketId: socket.id, + workspaceId: newWorkspace?.id || null, + sessionCount: Object.keys(refreshedSessions || {}).length + }); + }) + .catch((error) => { + logger.error('Deferred workspace session initialization failed', { + workspaceId: newWorkspace?.id || null, + error: error.message, + stack: error.stack + }); + logDesktopLaunch('server.workspace-switch.session-init-failed', { + traceId, + source, + socketId: socket.id, + workspaceId: newWorkspace?.id || null, + error: error.message + }); + if (workspaceManager.getActiveWorkspace?.()?.id === newWorkspace?.id) { + socket.emit('error', { + message: 'Failed to initialize workspace sessions', + error: error.message + }); + } + }) + .finally(() => { + if (inFlightWorkspaceSwitchId === requestedWorkspaceId) { + inFlightWorkspaceSwitchId = null; + } + }); + } } catch (error) { logDesktopLaunch('server.workspace-switch.failed', { traceId, @@ -1048,7 +1100,7 @@ io.on('connection', (socket) => { logger.error('Failed to switch workspace', { error: error.message, stack: error.stack }); socket.emit('error', { message: 'Failed to switch workspace', error: error.message, stack: error.stack }); } finally { - if (inFlightWorkspaceSwitchId === requestedWorkspaceId) { + if (releaseInFlightWorkspaceSwitchInFinally && inFlightWorkspaceSwitchId === requestedWorkspaceId) { inFlightWorkspaceSwitchId = null; } } diff --git a/server/sessionManager.js b/server/sessionManager.js index e8f699b5..16465281 100644 --- a/server/sessionManager.js +++ b/server/sessionManager.js @@ -103,6 +103,13 @@ class SessionManager extends EventEmitter { this.worktrees = []; } + shouldMonitorSessionProcesses() { + // Windows desktop builds were flashing visible PowerShell/conhost windows because the + // process-limit probe shells out once per session on an interval. Skip that probe on + // Windows until we have a non-console-backed process inspection path. + return process.platform !== 'win32'; + } + // Determine effective inactivity timeout per session (ms) getSessionTimeout(session) { if (!session) return this.sessionTimeout; @@ -179,7 +186,7 @@ class SessionManager extends EventEmitter { this.workspaceSessionMaps.set(workspace.id, this.sessions); // Ensure sessions exist for the active workspace without clearing existing ones. - await this.initializeSessions({ + const initializePromise = this.initializeSessions({ preserveExisting: true, reason: String(options.reason || '').trim() || 'workspace-switch', traceId: String(options.traceId || '').trim() || null, @@ -187,6 +194,16 @@ class SessionManager extends EventEmitter { socketId: String(options.socketId || '').trim() || null }); + if (options.deferInitialize) { + return { + sessions: this.getSessionStates(), + backlog: this.getUndeliveredOutputAndMarkDelivered(), + initializePromise + }; + } + + await initializePromise; + // Return any buffered output that occurred while this workspace was inactive. return { sessions: this.getSessionStates(), @@ -1022,13 +1039,18 @@ class SessionManager extends EventEmitter { }); } - // Monitor for fork bombs (every 5 seconds) - session.processMonitor = setInterval(() => { - this.checkProcessLimit(session); - // Re-evaluate status even when there is no new output, so sessions can - // transition out of "busy" after quiet periods. - this.refreshSessionStatus(session.id, session); - }, 5000); + // Monitor for fork bombs (every 5 seconds) when the platform supports a + // non-intrusive process probe. + if (this.shouldMonitorSessionProcesses()) { + session.processMonitor = setInterval(() => { + this.checkProcessLimit(session); + // Re-evaluate status even when there is no new output, so sessions can + // transition out of "busy" after quiet periods. + this.refreshSessionStatus(session.id, session); + }, 5000); + } else { + session.processMonitor = null; + } } catch (error) { logDesktopLaunch('session-manager.session.create.failed', { diff --git a/tests/unit/sessionManager.initializeSessions.test.js b/tests/unit/sessionManager.initializeSessions.test.js index 4dab96b0..7a0df02a 100644 --- a/tests/unit/sessionManager.initializeSessions.test.js +++ b/tests/unit/sessionManager.initializeSessions.test.js @@ -115,4 +115,86 @@ describe('SessionManager.initializeSessions', () => { expect(claudeConfig.args).toContain('-Command'); expect(serverConfig.args).toContain('-Command'); }); + + test('skips the external process monitor on Windows', () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + configurable: true, + value: 'win32' + }); + + try { + const io = { emit: jest.fn() }; + const agentManager = { getAllAgents: () => [] }; + const sm = new SessionManager(io, agentManager); + expect(sm.shouldMonitorSessionProcesses()).toBe(false); + } finally { + Object.defineProperty(process, 'platform', { + configurable: true, + value: originalPlatform + }); + } + }); + + test('can defer workspace initialization while returning restored sessions immediately', async () => { + const io = { emit: jest.fn() }; + const agentManager = { getAllAgents: () => [] }; + const sm = new SessionManager(io, agentManager); + + const previousSessions = new Map([ + ['work0-claude', { id: 'work0-claude' }] + ]); + const restoredSessions = new Map([ + ['work1-claude', { id: 'work1-claude' }] + ]); + + sm.workspace = { + id: 'previous', + name: 'previous', + repository: { path: '/tmp/previous' }, + worktrees: { namingPattern: 'work{n}' }, + terminals: { pairs: 1 } + }; + sm.sessions = previousSessions; + sm.workspaceSessionMaps.set('next', restoredSessions); + + let resolveInitialization; + sm.initializeSessions = jest.fn(() => new Promise((resolve) => { + resolveInitialization = resolve; + })); + sm.getSessionStates = jest.fn(() => ({ + 'work1-claude': { id: 'work1-claude' } + })); + sm.getUndeliveredOutputAndMarkDelivered = jest.fn(() => ({ + 'work1-claude': 'buffered output' + })); + + const result = await sm.switchWorkspacePreservingSessions({ + id: 'next', + name: 'next', + repository: { path: '/tmp/next' }, + worktrees: { namingPattern: 'work{n}' }, + terminals: { pairs: 1 } + }, { + deferInitialize: true, + reason: 'workspace-switch' + }); + + expect(sm.workspaceSessionMaps.get('previous')).toBe(previousSessions); + expect(sm.sessions).toBe(restoredSessions); + expect(result.sessions).toEqual({ + 'work1-claude': { id: 'work1-claude' } + }); + expect(result.backlog).toEqual({ + 'work1-claude': 'buffered output' + }); + expect(result.initializePromise).toBeInstanceOf(Promise); + expect(sm.initializeSessions).toHaveBeenCalledWith(expect.objectContaining({ + preserveExisting: true, + reason: 'workspace-switch' + })); + + resolveInitialization(); + await result.initializePromise; + }); }); From 2f5d840dc507854513d814a286223c7be63579bd Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:06:09 -0600 Subject: [PATCH 14/15] fix: reduce windows terminal churn --- server/claudeVersionChecker.js | 64 ++++- server/sessionManager.js | 222 +++++++++++------- tests/unit/claudeVersionChecker.test.js | 56 +++++ .../unit/sessionManager.branchUpdate.test.js | 89 ++++++- .../sessionManager.initializeSessions.test.js | 23 +- 5 files changed, 349 insertions(+), 105 deletions(-) create mode 100644 tests/unit/claudeVersionChecker.test.js diff --git a/server/claudeVersionChecker.js b/server/claudeVersionChecker.js index 89978c78..81f08abd 100644 --- a/server/claudeVersionChecker.js +++ b/server/claudeVersionChecker.js @@ -15,8 +15,45 @@ const logger = winston.createLogger({ }); class ClaudeVersionChecker { - static async checkVersion() { - return new Promise((resolve) => { + static get cacheTtlMs() { + return 5 * 60 * 1000; + } + + static getCachedResult() { + const cached = ClaudeVersionChecker.versionCache; + if (!cached) return null; + if ((Date.now() - cached.timestamp) > ClaudeVersionChecker.cacheTtlMs) { + ClaudeVersionChecker.versionCache = null; + return null; + } + return cached.result; + } + + static setCachedResult(result) { + ClaudeVersionChecker.versionCache = { + result, + timestamp: Date.now() + }; + return result; + } + + static resetCache() { + ClaudeVersionChecker.versionCache = null; + ClaudeVersionChecker.versionPromise = null; + } + + static async checkVersion({ force = false } = {}) { + if (!force) { + const cached = ClaudeVersionChecker.getCachedResult(); + if (cached) { + return cached; + } + if (ClaudeVersionChecker.versionPromise) { + return ClaudeVersionChecker.versionPromise; + } + } + + const versionPromise = new Promise((resolve) => { const process = spawn('claude', ['--version'], { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000, @@ -52,34 +89,43 @@ class ClaudeVersionChecker { }; logger.info('Claude version check', result); - resolve(result); + resolve(ClaudeVersionChecker.setCachedResult(result)); } else { logger.warn('Could not parse Claude version', { stdout, stderr }); - resolve({ + resolve(ClaudeVersionChecker.setCachedResult({ version: null, isCompatible: false, error: 'Could not parse version' - }); + })); } } else { logger.error('Claude version check failed', { code, stderr }); - resolve({ + resolve(ClaudeVersionChecker.setCachedResult({ version: null, isCompatible: false, error: `Exit code ${code}: ${stderr}` - }); + })); } }); process.on('error', (error) => { logger.error('Claude version check error', { error: error.message, stack: error.stack }); - resolve({ + resolve(ClaudeVersionChecker.setCachedResult({ version: null, isCompatible: false, error: error.message - }); + })); }); }); + + ClaudeVersionChecker.versionPromise = versionPromise; + versionPromise.finally(() => { + if (ClaudeVersionChecker.versionPromise === versionPromise) { + ClaudeVersionChecker.versionPromise = null; + } + }); + + return versionPromise; } static generateUpdateInstructions(versionInfo) { diff --git a/server/sessionManager.js b/server/sessionManager.js index 16465281..8f8cb8cf 100644 --- a/server/sessionManager.js +++ b/server/sessionManager.js @@ -61,6 +61,32 @@ function buildShellArgs(commands) { } } +function buildServerTerminalIntroCommands(worktreePath, label) { + const commands = [ + `cd "${worktreePath}"`, + `echo "=== ${label} ==="`, + `echo "Directory: ${worktreePath}"` + ]; + + // Avoid spawning extra Git-for-Windows helper processes during terminal boot on Windows. + if (process.platform !== 'win32') { + commands.push( + getShellKind() === 'powershell' + ? `$b = git branch --show-current 2>$null; if (-not $b) { $b = 'unknown' }; Write-Output "Branch: $b"` + : `echo "Branch: $(git branch --show-current 2>/dev/null || echo 'unknown')"` + ); + } + + commands.push( + `echo ""`, + `echo "Ready to run: bun index.ts"`, + `echo "Available commands: bun, npm, node"`, + `echo ""` + ); + + return commands; +} + const HOME_DIR = process.env.HOME || os.homedir(); class SessionManager extends EventEmitter { @@ -110,6 +136,17 @@ class SessionManager extends EventEmitter { return process.platform !== 'win32'; } + shouldPollBranches() { + return process.platform !== 'win32' && this.branchRefreshMs > 0; + } + + getGitBranchUpdateOptions(overrides = {}) { + return { + branchOnly: process.platform === 'win32', + ...(overrides && typeof overrides === 'object' ? overrides : {}) + }; + } + // Determine effective inactivity timeout per session (ms) getSessionTimeout(session) { if (!session) return this.sessionTimeout; @@ -401,28 +438,21 @@ class SessionManager extends EventEmitter { } else { // Server terminal command = getDefaultShell(); - const header = `=== ${terminal.repository.name}/${terminal.worktree} (${terminal.id}) ===`; if (startCommand) { args = buildShellArgs([ `cd "${worktree.path}"`, - `echo "${header}"`, + `echo "=== ${terminal.repository.name}/${terminal.worktree} (${terminal.id}) ==="`, `echo "Directory: ${worktree.path}"`, `echo ""`, startCommand ]); } else { - args = buildShellArgs([ - `cd "${worktree.path}"`, - `echo "=== Server Terminal for ${terminal.repository.name}/${terminal.worktree} ==="`, - `echo "Directory: ${worktree.path}"`, - getShellKind() === 'powershell' - ? `$b = git branch --show-current 2>$null; if (-not $b) { $b = 'unknown' }; Write-Output "Branch: $b"` - : `echo "Branch: $(git branch --show-current 2>/dev/null || echo 'unknown')"`, - `echo ""`, - `echo "Ready to run: bun index.ts"`, - `echo "Available commands: bun, npm, node"`, - `echo ""` - ]); + args = buildShellArgs( + buildServerTerminalIntroCommands( + worktree.path, + `Server Terminal for ${terminal.repository.name}/${terminal.worktree}` + ) + ); } } @@ -486,18 +516,7 @@ class SessionManager extends EventEmitter { } this.createSession(sessionId, { command: getDefaultShell(), - args: buildShellArgs([ - `cd "${worktree.path}"`, - `echo "=== Server Terminal for ${worktree.id} ==="`, - `echo "Directory: ${worktree.path}"`, - getShellKind() === 'powershell' - ? `$b = git branch --show-current 2>$null; if (-not $b) { $b = 'unknown' }; Write-Output "Branch: $b"` - : `echo "Branch: $(git branch --show-current 2>/dev/null || echo 'unknown')"`, - `echo ""`, - `echo "Ready to run: bun index.ts"`, - `echo "Available commands: bun, npm, node"`, - `echo ""` - ]), + args: buildShellArgs(buildServerTerminalIntroCommands(worktree.path, `Server Terminal for ${worktree.id}`)), cwd: worktree.path, type: 'server', worktreeId: worktree.id, @@ -517,26 +536,15 @@ class SessionManager extends EventEmitter { } } - // Git branch updates for all worktrees (both traditional and mixed-repo) - if (this.gitHelper) { - for (const worktree of this.worktrees) { - sessionPromises.push( - Promise.resolve().then(() => { - const worktreeIdForGit = worktree.worktreeId || worktree.id; - return this.updateGitBranch(worktreeIdForGit, worktree.path); - }).catch(error => { - logger.error('Failed to update git branch', { - worktree: worktree.id, - error: error.message - }); - }) - ); - } - } - // Wait for all sessions to be created in parallel await Promise.all(sessionPromises); logger.info('All sessions initialized', { count: sessionPromises.length }); + + // Emit sessions as soon as terminals exist; git metadata can catch up afterward. + if (this.io) { + this.io.emit('sessions', this.getSessionStates()); + } + logDesktopLaunch('session-manager.initialize.completed', { traceId, source, @@ -562,6 +570,22 @@ class SessionManager extends EventEmitter { // Setup file watchers for instant branch detection this.setupGitWatchers(); + + // Git branch updates for all worktrees (both traditional and mixed-repo) + if (this.gitHelper) { + const branchUpdateOptions = this.getGitBranchUpdateOptions(); + await Promise.all(this.worktrees.map((worktree) => ( + Promise.resolve().then(() => { + const worktreeIdForGit = worktree.worktreeId || worktree.id; + return this.updateGitBranch(worktreeIdForGit, worktree.path, false, branchUpdateOptions); + }).catch(error => { + logger.error('Failed to update git branch', { + worktree: worktree.id, + error: error.message + }); + }) + ))); + } } startBranchRefresh() { @@ -569,6 +593,11 @@ class SessionManager extends EventEmitter { clearInterval(this.branchRefreshInterval); } + if (!this.shouldPollBranches()) { + this.branchRefreshInterval = null; + return; + } + const refreshWorktrees = () => { const refreshedPaths = new Set(); const refreshPath = (worktreeId, cwd) => { @@ -576,7 +605,7 @@ class SessionManager extends EventEmitter { if (!normalized) return; if (refreshedPaths.has(normalized)) return; refreshedPaths.add(normalized); - this.updateGitBranch(worktreeId, normalized, true); + this.updateGitBranch(worktreeId, normalized, true, this.getGitBranchUpdateOptions({ branchOnly: true })); }; this.worktrees.forEach(worktree => { @@ -608,8 +637,6 @@ class SessionManager extends EventEmitter { } }; - // Do an initial refresh immediately (don't wait for the first interval tick). - refreshWorktrees(); this.branchRefreshInterval = setInterval(refreshWorktrees, this.branchRefreshMs); } @@ -678,7 +705,12 @@ class SessionManager extends EventEmitter { setTimeout(() => { logger.debug('File watcher triggered branch update', { worktree: worktree.id }); const worktreeIdForGit = worktree.worktreeId || worktree.id; - this.updateGitBranch(worktreeIdForGit, worktree.path, true); + this.updateGitBranch( + worktreeIdForGit, + worktree.path, + true, + this.getGitBranchUpdateOptions({ branchOnly: true }) + ); }, 50); } }); @@ -1264,7 +1296,12 @@ class SessionManager extends EventEmitter { worktreeId: session.worktreeId, delay: `${delay}ms` }); - this.updateGitBranch(session.worktreeId, this.getSessionCwd(session), true); + this.updateGitBranch( + session.worktreeId, + this.getSessionCwd(session), + true, + this.getGitBranchUpdateOptions({ branchOnly: true }) + ); }, delay); } @@ -2004,11 +2041,44 @@ class SessionManager extends EventEmitter { return this.isSameOrSubpath(a, b) || this.isSameOrSubpath(b, a); } - async updateGitBranch(worktreeId, worktreePath, skipCache = false) { + getSessionsForWorktreeBranchUpdate(worktreeId, worktreePath) { + const sessionsToUpdate = new Set(); + + const claudeId = `${worktreeId}-claude`; + const codexId = `${worktreeId}-codex`; + const serverId = `${worktreeId}-server`; + if (this.sessions.has(claudeId)) sessionsToUpdate.add(claudeId); + if (this.sessions.has(codexId)) sessionsToUpdate.add(codexId); + if (this.sessions.has(serverId)) sessionsToUpdate.add(serverId); + + const normalizedWorktreePath = this.normalizeCwdPath(worktreePath); + if (sessionsToUpdate.size === 0) { + for (const [sessionId, session] of this.sessions) { + if (session.worktreeId === worktreeId && session.config && + this.pathsOverlap(session.config.cwd, normalizedWorktreePath)) { + sessionsToUpdate.add(sessionId); + } + } + } + + if (sessionsToUpdate.size === 0) { + for (const [sessionId, session] of this.sessions) { + if (!session?.config?.cwd) continue; + if (!this.pathsOverlap(session.config.cwd, normalizedWorktreePath)) continue; + if (session.type !== 'claude' && session.type !== 'codex' && session.type !== 'server') continue; + sessionsToUpdate.add(sessionId); + } + } + + return sessionsToUpdate; + } + + async updateGitBranch(worktreeId, worktreePath, skipCache = false, options = {}) { logger.info('๐Ÿ”„ updateGitBranch called', { worktreeId, path: worktreePath, skipCache, + branchOnly: !!options?.branchOnly, timestamp: new Date().toISOString() }); @@ -2019,49 +2089,19 @@ class SessionManager extends EventEmitter { try { const branch = await this.gitHelper.getCurrentBranch(worktreePath, skipCache); - const remoteUrl = await this.gitHelper.getRemoteUrl(worktreePath); - const defaultBranch = await this.gitHelper.getDefaultBranch(worktreePath); - - // Check for existing PR for this branch - const existingPR = await this.gitHelper.checkForExistingPR(remoteUrl, branch); - - // Update claude/codex/server sessions for this worktree - // For mixed-repo workspaces, session IDs have workspace prefix (e.g., "mixed-terminals-work1-claude") - // For traditional workspaces, session IDs are just worktreeId-type (e.g., "work1-claude") - // So we need to search through sessions to find matching ones - const sessionsToUpdate = new Set(); - - // First try direct match (traditional workspaces) - const claudeId = `${worktreeId}-claude`; - const codexId = `${worktreeId}-codex`; - const serverId = `${worktreeId}-server`; - if (this.sessions.has(claudeId)) sessionsToUpdate.add(claudeId); - if (this.sessions.has(codexId)) sessionsToUpdate.add(codexId); - if (this.sessions.has(serverId)) sessionsToUpdate.add(serverId); - - // If no direct match, search by worktreeId AND path (mixed-repo workspaces) - // Important: Must match both worktreeId AND path to avoid cross-contamination - const normalizedWorktreePath = this.normalizeCwdPath(worktreePath); - if (sessionsToUpdate.size === 0) { - for (const [sessionId, session] of this.sessions) { - // Check if this session belongs to the same worktree by comparing paths - if (session.worktreeId === worktreeId && session.config && - this.pathsOverlap(session.config.cwd, normalizedWorktreePath)) { - sessionsToUpdate.add(sessionId); - } - } - } + const sessionsToUpdate = this.getSessionsForWorktreeBranchUpdate(worktreeId, worktreePath); + const matchingSessions = Array.from(sessionsToUpdate) + .map((sessionId) => this.sessions.get(sessionId)) + .filter(Boolean); - // Final fallback: match by path only. - // This handles cases where the worktreeId used for watchers/refresh differs from the - // session's stored worktreeId, but the cwd is authoritative. - if (sessionsToUpdate.size === 0) { - for (const [sessionId, session] of this.sessions) { - if (!session?.config?.cwd) continue; - if (!this.pathsOverlap(session.config.cwd, normalizedWorktreePath)) continue; - if (session.type !== 'claude' && session.type !== 'codex' && session.type !== 'server') continue; - sessionsToUpdate.add(sessionId); - } + let remoteUrl = matchingSessions.find((session) => session?.remoteUrl)?.remoteUrl || null; + let defaultBranch = matchingSessions.find((session) => session?.defaultBranch)?.defaultBranch || null; + let existingPR = matchingSessions.find((session) => session?.existingPR)?.existingPR || null; + + if (!options?.branchOnly) { + remoteUrl = await this.gitHelper.getRemoteUrl(worktreePath); + defaultBranch = await this.gitHelper.getDefaultBranch(worktreePath); + existingPR = await this.gitHelper.checkForExistingPR(remoteUrl, branch); } sessionsToUpdate.forEach(sessionId => { diff --git a/tests/unit/claudeVersionChecker.test.js b/tests/unit/claudeVersionChecker.test.js new file mode 100644 index 00000000..93af67b6 --- /dev/null +++ b/tests/unit/claudeVersionChecker.test.js @@ -0,0 +1,56 @@ +jest.mock('child_process', () => ({ + spawn: jest.fn() +})); + +const { spawn } = require('child_process'); +const { ClaudeVersionChecker } = require('../../server/claudeVersionChecker'); + +describe('ClaudeVersionChecker', () => { + beforeEach(() => { + ClaudeVersionChecker.resetCache(); + spawn.mockReset(); + }); + + test('caches successful version checks', async () => { + let stdoutHandler = null; + let stderrHandler = null; + let closeHandler = null; + let errorHandler = null; + + spawn.mockImplementation(() => ({ + stdout: { + on: (event, handler) => { + if (event === 'data') stdoutHandler = handler; + } + }, + stderr: { + on: (event, handler) => { + if (event === 'data') stderrHandler = handler; + } + }, + on: (event, handler) => { + if (event === 'close') closeHandler = handler; + if (event === 'error') errorHandler = handler; + } + })); + + const firstPromise = ClaudeVersionChecker.checkVersion(); + expect(stdoutHandler).toBeInstanceOf(Function); + expect(stderrHandler).toBeInstanceOf(Function); + expect(closeHandler).toBeInstanceOf(Function); + expect(errorHandler).toBeInstanceOf(Function); + + stdoutHandler(Buffer.from('claude 1.2.3')); + closeHandler(0); + + const first = await firstPromise; + const second = await ClaudeVersionChecker.checkVersion(); + + expect(spawn).toHaveBeenCalledTimes(1); + expect(first).toEqual(expect.objectContaining({ + version: '1.2.3', + isCompatible: true + })); + expect(second).toEqual(first); + }); +}); diff --git a/tests/unit/sessionManager.branchUpdate.test.js b/tests/unit/sessionManager.branchUpdate.test.js index ff45c990..971bf87c 100644 --- a/tests/unit/sessionManager.branchUpdate.test.js +++ b/tests/unit/sessionManager.branchUpdate.test.js @@ -28,8 +28,18 @@ describe('SessionManager branch updates', () => { sessionManager.startBranchRefresh(); jest.advanceTimersByTime(11); - expect(updateSpy).toHaveBeenCalledWith('work2', toPlatformPath('/tmp/repo-a/work2'), true); - expect(updateSpy).toHaveBeenCalledWith('work1', toPlatformPath('/tmp/repo-a/work1'), true); + expect(updateSpy).toHaveBeenCalledWith( + 'work2', + toPlatformPath('/tmp/repo-a/work2'), + true, + expect.objectContaining({ branchOnly: true }) + ); + expect(updateSpy).toHaveBeenCalledWith( + 'work1', + toPlatformPath('/tmp/repo-a/work1'), + true, + expect.objectContaining({ branchOnly: true }) + ); }); test('startBranchRefresh also refreshes loose sessions (not in worktrees)', () => { @@ -53,7 +63,44 @@ describe('SessionManager branch updates', () => { sessionManager.startBranchRefresh(); jest.advanceTimersByTime(11); - expect(updateSpy).toHaveBeenCalledWith('adhoc', toPlatformPath('/tmp/repo-z/adhoc'), true); + expect(updateSpy).toHaveBeenCalledWith( + 'adhoc', + toPlatformPath('/tmp/repo-z/adhoc'), + true, + expect.objectContaining({ branchOnly: true }) + ); + }); + + test('startBranchRefresh is disabled on Windows', () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + configurable: true, + value: 'win32' + }); + + try { + const io = { emit: jest.fn() }; + const sessionManager = new SessionManager(io, null); + sessionManager.branchRefreshMs = 10; + sessionManager.worktrees = [ + { id: 'work1', path: 'C:\\repo\\work1' } + ]; + + const updateSpy = jest + .spyOn(sessionManager, 'updateGitBranch') + .mockImplementation(() => Promise.resolve()); + + sessionManager.startBranchRefresh(); + jest.advanceTimersByTime(25); + + expect(updateSpy).not.toHaveBeenCalled(); + expect(sessionManager.branchRefreshInterval).toBeNull(); + } finally { + Object.defineProperty(process, 'platform', { + configurable: true, + value: originalPlatform + }); + } }); test('updateGitBranch falls back to matching by cwd path', async () => { @@ -161,4 +208,40 @@ describe('SessionManager branch updates', () => { expect.objectContaining({ sessionId: 'repo-a-work2-codex', branch: 'feature/test' }) ); }); + + test('updateGitBranch can skip expensive metadata lookups', async () => { + const io = { emit: jest.fn() }; + const sessionManager = new SessionManager(io, null); + const gitHelper = { + getCurrentBranch: jest.fn(async () => 'feature/test'), + getRemoteUrl: jest.fn(async () => 'git@github.com:owner/repo.git'), + getDefaultBranch: jest.fn(async () => 'main'), + checkForExistingPR: jest.fn(async () => 'https://github.com/owner/repo/pull/123') + }; + sessionManager.setGitHelper(gitHelper); + + sessionManager.sessions.set('work2-claude', { + id: 'work2-claude', + type: 'claude', + worktreeId: 'work2', + config: { cwd: '/tmp/repo-a/work2/' }, + branch: 'unknown', + remoteUrl: 'https://github.com/owner/repo', + defaultBranch: 'main', + existingPR: 'https://github.com/owner/repo/pull/99' + }); + + await sessionManager.updateGitBranch('work2', '/tmp/repo-a/work2', true, { branchOnly: true }); + + expect(gitHelper.getCurrentBranch).toHaveBeenCalledTimes(1); + expect(gitHelper.getRemoteUrl).not.toHaveBeenCalled(); + expect(gitHelper.getDefaultBranch).not.toHaveBeenCalled(); + expect(gitHelper.checkForExistingPR).not.toHaveBeenCalled(); + expect(sessionManager.sessions.get('work2-claude')).toEqual(expect.objectContaining({ + branch: 'feature/test', + remoteUrl: 'https://github.com/owner/repo', + defaultBranch: 'main', + existingPR: 'https://github.com/owner/repo/pull/99' + })); + }); }); diff --git a/tests/unit/sessionManager.initializeSessions.test.js b/tests/unit/sessionManager.initializeSessions.test.js index 7a0df02a..35066caa 100644 --- a/tests/unit/sessionManager.initializeSessions.test.js +++ b/tests/unit/sessionManager.initializeSessions.test.js @@ -52,8 +52,21 @@ describe('SessionManager.initializeSessions', () => { expect(sm.createSession).toHaveBeenCalledTimes(4); expect(sm.updateGitBranch).toHaveBeenCalledTimes(2); - expect(sm.updateGitBranch).toHaveBeenNthCalledWith(1, 'work1', '/tmp/test/work1'); - expect(sm.updateGitBranch).toHaveBeenNthCalledWith(2, 'work2', '/tmp/test/work2'); + expect(sm.updateGitBranch).toHaveBeenNthCalledWith( + 1, + 'work1', + '/tmp/test/work1', + false, + expect.objectContaining({ branchOnly: false }) + ); + expect(sm.updateGitBranch).toHaveBeenNthCalledWith( + 2, + 'work2', + '/tmp/test/work2', + false, + expect.objectContaining({ branchOnly: false }) + ); + expect(io.emit).toHaveBeenCalledWith('sessions', expect.any(Object)); }); test('uses hidden PowerShell startup args for Windows sessions', async () => { @@ -114,6 +127,12 @@ describe('SessionManager.initializeSessions', () => { expect(serverConfig.args.slice(0, 4)).toEqual(['-WindowStyle', 'Hidden', '-NoLogo', '-NoExit']); expect(claudeConfig.args).toContain('-Command'); expect(serverConfig.args).toContain('-Command'); + expect(sm.updateGitBranch).toHaveBeenCalledWith( + 'work1', + 'C:\\test\\work1', + false, + expect.objectContaining({ branchOnly: true }) + ); }); test('skips the external process monitor on Windows', () => { From 581ee80094c105dd5e9d9096719929e4d6c6725b Mon Sep 17 00:00:00 2001 From: AnrokX <192667251+AnrokX@users.noreply.github.com> Date: Sun, 8 Mar 2026 09:14:13 -0600 Subject: [PATCH 15/15] chore: add onboarding patch script and build snapshots --- patch_onboarding.py | 97 ++++++++++++++++++++++ windows-onboarding-clean-main-d696ca7.zip | Bin 0 -> 1739972 bytes windows-onboarding-clean-main.zip | Bin 0 -> 1739945 bytes 3 files changed, 97 insertions(+) create mode 100644 patch_onboarding.py create mode 100644 windows-onboarding-clean-main-d696ca7.zip create mode 100644 windows-onboarding-clean-main.zip diff --git a/patch_onboarding.py b/patch_onboarding.py new file mode 100644 index 00000000..6d083222 --- /dev/null +++ b/patch_onboarding.py @@ -0,0 +1,97 @@ +import re + +with open('client/app.js', 'r') as f: + content = f.read() + +# We need to replace the innerHTML block inside 'const render = () => {' +# Let's find: `listEl.innerHTML = \`` up to the closing `\`;` +pattern = r'(listEl\.innerHTML\s*=\s*`)(.*?)(`;\n\n\s*return\s*\{\s*req,\s*steps,\s*current\s*\};\n\s*\};)' +match = re.search(pattern, content, re.DOTALL) + +if not match: + print("Could not find listEl.innerHTML block!") +else: + new_html = """ +
+ ${steps.map((step, idx) => { + const isActive = idx === state.currentStep; + const isDone = step.done; + let statusClass = 'stepper-upcoming'; + if (isDone) statusClass = 'stepper-done'; + if (isActive) statusClass = 'stepper-active'; + return ` +
+
+ ${isActive ? `Step ${stepNo}` : ''} +
+
+
+ `; + }).join('')} +
+ +
+
+
+
+
+

${currentTitle}

+ +
+ ${current?.done ? '' : ''} +

${currentDesc} ${statusText ? `(${statusText})` : ''}

+
+ + ${isGitIdentityStep ? ` +
+
+ + + +
+
+ ` : ''} + + ${isGhLoginStep && !current?.done ? ` + + ` : ''} + + ${shouldShowInstallerOutput ? ` +
+
${installerOutputText}
+
+ ` : ''} + + ${command && !isGhLoginStep && !isGitIdentityStep && !current?.done ? ` +
+
${command}
+
+ ` : ''} + +
+ ${showRunButton ? `` : ''} + ${!isGhLoginStep && !isGitIdentityStep ? `` : ''} + ${isGhLoginStep && !current?.done && ghLoginUiPhase === 'code' ? `` : ''} +
+
+
+ +
+ + +
""" + + new_content = content[:match.start(2)] + new_html + content[match.start(3):] + with open('client/app.js', 'w') as f: + f.write(new_content) + print("Successfully patched client/app.js") diff --git a/windows-onboarding-clean-main-d696ca7.zip b/windows-onboarding-clean-main-d696ca7.zip new file mode 100644 index 0000000000000000000000000000000000000000..8dca43137549fe09f854167c2f58dd85921c3b98 GIT binary patch literal 1739972 zcmZ^KV|ZoTvUY4+9ox2T+qUhb!;X{Hv2EM7ZQDu5=p~EhlpJ&Xqehk!j zM^(+LS@Ttt0R@8r`mYN*!c_Oa|NP?y1q2O5Z(?iYW@^TurV0)OY|>F`rT8DaBm7Hu zO9uxVm%k7CGvl8%vT#$&PyVc-4-5o^@wY+7ZdSIY^e&eFTv0AwNFhWJPWFM9PIyzlb+IqeLo{o z=RJA_9;fOmm?eW`LcC>9E}sU>6EWN`ktc3yvEM#NazX3c%Pb8%4Hp}5K)KFUL)NfK z=j)Y9nVmg_)=Z9k!={}y57Nu^*^V(%VyKpf{MEoa*g*E-1#z_}UbW9SLV`ai5!TKS z9znX9ynn1E)Is443^0f;w5aYYPI3mh!sE?)XYphfiUg|K?F|D3hud=NHKw+Ppe@}= zDT6Sz2P5U#oShcf;Ypwt*_ZX13PI&aWvp8MwDg4jP3(FEqvItX(|AHf$5W14F5^^K zC_`2kO2mUL_5iN;h-Uh3Eszq5J(6I)Z0~4>J-)uEsd&}H<;Wv_~L%^6yuDW_*0G12@XW} zwqR64TdcG^8zK|W08blChNZ+y#_)=z4sv`UTaifcc&YBh6e-wmYgEsMnE6Dkd*N z`su!Dogi&*A{Ocg>oXGwiy*@RFVc~me{1UqU0?JZZzB2lU(x>VAW;4;nzPA&vU(^k z-epN>AfS0%ARwB5JJ9jp1McebPZeL%+H%<7LjSUz02pQkIHY{CYQfAyR5yxS)GF%Fd6^yh$Ulu-v9CV!nlCgii{S9)kc# zQ%Lymu4^$j-1;?CmtkG&2$q&}E-PS@HqUFpe$q`wlT54tWdPr*8m^wE?<|^6H51})Z z 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 z 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 PNWu9{lD- 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 jlH(v(p<3#5 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