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' && (