diff --git a/src/main/web-server/types.ts b/src/main/web-server/types.ts index 26c120f5f..e67bf89ce 100644 --- a/src/main/web-server/types.ts +++ b/src/main/web-server/types.ts @@ -50,6 +50,7 @@ export interface AITabData { createdAt: number; state: 'idle' | 'busy'; thinkingStartTime?: number | null; + hasUnread?: boolean; } /** diff --git a/src/main/web-server/web-server-factory.ts b/src/main/web-server/web-server-factory.ts index fd2796358..181fa6790 100644 --- a/src/main/web-server/web-server-factory.ts +++ b/src/main/web-server/web-server-factory.ts @@ -140,6 +140,7 @@ export function createWebServerFactory(deps: WebServerFactoryDependencies) { createdAt: tab.createdAt, state: tab.state || 'idle', thinkingStartTime: tab.thinkingStartTime || null, + hasUnread: tab.hasUnread ?? false, })) || []; return { diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 986d4398b..279672a9c 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -551,6 +551,7 @@ interface MaestroAPI { createdAt: number; state: 'idle' | 'busy'; thinkingStartTime?: number | null; + hasUnread?: boolean; }>, activeTabId: string ) => Promise; diff --git a/src/renderer/hooks/remote/useRemoteIntegration.ts b/src/renderer/hooks/remote/useRemoteIntegration.ts index a113c33c0..ce26320d0 100644 --- a/src/renderer/hooks/remote/useRemoteIntegration.ts +++ b/src/renderer/hooks/remote/useRemoteIntegration.ts @@ -840,7 +840,7 @@ export function useRemoteIntegration(deps: UseRemoteIntegrationDeps): UseRemoteI // Create a hash of tab properties that should trigger a broadcast when changed const tabsHash = session.aiTabs - .map((t) => `${t.id}:${t.name || ''}:${t.starred}:${t.state}`) + .map((t) => `${t.id}:${t.name || ''}:${t.starred}:${t.state}:${t.hasUnread ?? false}`) .join('|'); const prev = prevTabsRef.current.get(session.id); @@ -867,6 +867,7 @@ export function useRemoteIntegration(deps: UseRemoteIntegrationDeps): UseRemoteI createdAt: tab.createdAt, state: tab.state, thinkingStartTime: tab.thinkingStartTime, + hasUnread: tab.hasUnread, })); window.maestro.web.broadcastTabsChange(session.id, tabsForBroadcast, current.activeTabId); diff --git a/src/web/mobile/AutoRunDocumentCard.tsx b/src/web/mobile/AutoRunDocumentCard.tsx new file mode 100644 index 000000000..e54775ea6 --- /dev/null +++ b/src/web/mobile/AutoRunDocumentCard.tsx @@ -0,0 +1,115 @@ +/** + * Shared DocumentCard component for Auto Run document listings. + * + * Used by both AutoRunPanel (full-screen) and AutoRunTabContent (inline tab). + */ + +import { useCallback } from 'react'; +import { useThemeColors } from '../components/ThemeProvider'; +import { triggerHaptic, HAPTIC_PATTERNS } from './constants'; +import type { AutoRunDocument } from '../hooks/useAutoRun'; + +export interface DocumentCardProps { + document: AutoRunDocument; + onTap: (filename: string) => void; +} + +export function DocumentCard({ document: doc, onTap }: DocumentCardProps) { + const colors = useThemeColors(); + const progress = doc.taskCount > 0 ? Math.round((doc.completedCount / doc.taskCount) * 100) : 0; + + const handleTap = useCallback(() => { + triggerHaptic(HAPTIC_PATTERNS.tap); + onTap(doc.filename); + }, [doc.filename, onTap]); + + return ( + + ); +} diff --git a/src/web/mobile/AutoRunPanel.tsx b/src/web/mobile/AutoRunPanel.tsx index 5059b43eb..b899def0a 100644 --- a/src/web/mobile/AutoRunPanel.tsx +++ b/src/web/mobile/AutoRunPanel.tsx @@ -8,120 +8,11 @@ import { useState, useCallback, useEffect } from 'react'; import { useThemeColors } from '../components/ThemeProvider'; -import { useAutoRun, type AutoRunDocument } from '../hooks/useAutoRun'; +import { useAutoRun } from '../hooks/useAutoRun'; +import { DocumentCard } from './AutoRunDocumentCard'; import type { AutoRunState, UseWebSocketReturn } from '../hooks/useWebSocket'; import { triggerHaptic, HAPTIC_PATTERNS } from './constants'; -/** - * Document card component for the Auto Run panel - */ -interface DocumentCardProps { - document: AutoRunDocument; - onTap: (filename: string) => void; -} - -function DocumentCard({ document, onTap }: DocumentCardProps) { - const colors = useThemeColors(); - const progress = - document.taskCount > 0 ? Math.round((document.completedCount / document.taskCount) * 100) : 0; - - const handleTap = useCallback(() => { - triggerHaptic(HAPTIC_PATTERNS.tap); - onTap(document.filename); - }, [document.filename, onTap]); - - return ( - - ); -} - /** * Props for AutoRunPanel component */ @@ -520,7 +411,7 @@ export function AutoRunPanel({ borderRadius: '3px', }} > - .maestro/auto-run/ + .maestro/playbooks/ {' '} directory to get started

diff --git a/src/web/mobile/RightDrawer.tsx b/src/web/mobile/RightDrawer.tsx index 5930e33d5..2392a7db4 100644 --- a/src/web/mobile/RightDrawer.tsx +++ b/src/web/mobile/RightDrawer.tsx @@ -11,6 +11,8 @@ import { useThemeColors } from '../components/ThemeProvider'; import { useSwipeGestures } from '../hooks/useSwipeGestures'; import { triggerHaptic, HAPTIC_PATTERNS } from './constants'; import { GitStatusPanel } from './GitStatusPanel'; +import { DocumentCard } from './AutoRunDocumentCard'; +import { useAutoRun } from '../hooks/useAutoRun'; import type { AutoRunState, UseWebSocketReturn } from '../hooks/useWebSocket'; import type { UseGitStatusReturn } from '../hooks/useGitStatus'; @@ -63,10 +65,10 @@ export function RightDrawer({ onClose, onFileSelect, projectPath, - onAutoRunOpenDocument: _onAutoRunOpenDocument, + onAutoRunOpenDocument, onAutoRunOpenSetup, sendRequest, - send: _send, + send, onViewDiff, }: RightDrawerProps) { const colors = useThemeColors(); @@ -239,7 +241,14 @@ export function RightDrawer({ )} {currentTab === 'autorun' && ( - + )} {currentTab === 'git' && ( @@ -659,59 +668,199 @@ function HistoryTabContent({ } /** - * Auto Run tab content - inline auto run info - * Reuses the AutoRunPanel logic but rendered inline + * Auto Run tab content - inline auto run with document listing + * Provides document cards, progress status, and launch/stop controls inline. */ function AutoRunTabContent({ + sessionId, autoRunState, onOpenSetup, + sendRequest, + send, + onOpenDocument, }: { + sessionId: string; autoRunState: AutoRunState | null; onOpenSetup?: () => void; + sendRequest: UseWebSocketReturn['sendRequest']; + send: UseWebSocketReturn['send']; + onOpenDocument?: (filename: string) => void; }) { const colors = useThemeColors(); + + const { documents, isLoadingDocs, loadDocuments, stopAutoRun } = useAutoRun( + sendRequest, + send, + autoRunState + ); + + const [isStopping, setIsStopping] = useState(false); + + // Load documents on mount + useEffect(() => { + loadDocuments(sessionId); + }, [sessionId, loadDocuments]); + + // Reset stopping state when autoRun stops + useEffect(() => { + if (!autoRunState?.isRunning) { + setIsStopping(false); + } + }, [autoRunState?.isRunning]); + + const handleRefresh = useCallback(() => { + triggerHaptic(HAPTIC_PATTERNS.tap); + loadDocuments(sessionId); + }, [sessionId, loadDocuments]); + + const handleStop = useCallback(async () => { + triggerHaptic(HAPTIC_PATTERNS.interrupt); + setIsStopping(true); + const success = await stopAutoRun(sessionId); + if (!success) { + setIsStopping(false); + } + }, [sessionId, stopAutoRun]); + + const handleDocumentTap = useCallback( + (filename: string) => { + onOpenDocument?.(filename); + }, + [onOpenDocument] + ); + const isRunning = autoRunState?.isRunning ?? false; - const totalTasks = autoRunState?.totalTasks ?? 0; + const isStopped = isStopping || autoRunState?.isStopping; + const totalTasks = autoRunState?.totalTasks; const completedTasks = autoRunState?.completedTasks ?? 0; - const progress = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; + const currentTaskIndex = autoRunState?.currentTaskIndex ?? 0; + const progress = + totalTasks != null && totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; + const totalDocs = autoRunState?.totalDocuments; + const currentDocIndex = autoRunState?.currentDocumentIndex; return ( -
- {/* Status */} +
+ {/* Compact toolbar row */} +
+ + + {onOpenSetup && ( + + )} +
+ + {/* Progress section (when running) */} {isRunning && (
+ {/* Progress badge */}
{progress}%
-
- - Task {(autoRunState?.currentTaskIndex ?? 0) + 1}/{totalTasks} - + + {/* Status text + progress bar */} +
+ {totalTasks != null && totalTasks > 0 && ( + + Task {currentTaskIndex + 1}/{totalTasks} + + )} + {totalDocs != null && currentDocIndex != null && totalDocs > 1 && ( + + Doc {currentDocIndex + 1}/{totalDocs} + + )} +
+
@@ -726,46 +875,81 @@ function AutoRunTabContent({ />
-
- )} - - {!isRunning && ( -
-

- Auto Run is not active -

-
- )} - {/* Actions */} -
- {onOpenSetup && ( + {/* Stop button */} - )} -
+
+ )} + + {/* Document list */} + {isLoadingDocs ? ( +
+ Loading documents... +
+ ) : documents.length === 0 ? ( +
+

+ No Auto Run documents found +

+

+ Add documents to{' '} + + .maestro/playbooks/ + {' '} + directory +

+
+ ) : ( +
+ {documents.map((doc) => ( + + ))} +
+ )}
); } diff --git a/src/web/mobile/RightPanel.tsx b/src/web/mobile/RightPanel.tsx index 59a301034..d5e1a73bd 100644 --- a/src/web/mobile/RightPanel.tsx +++ b/src/web/mobile/RightPanel.tsx @@ -54,8 +54,10 @@ export function RightPanel({ onClose, onFileSelect, projectPath, + onAutoRunOpenDocument, onAutoRunOpenSetup, sendRequest, + send, onViewDiff, panelRef, width, @@ -271,7 +273,14 @@ export function RightPanel({ )} {currentTab === 'autorun' && ( - + )} {currentTab === 'git' && (