From 152ad9bea79ef3ef1b9bab4e5496b1521d4572a9 Mon Sep 17 00:00:00 2001 From: chr1syy Date: Mon, 6 Apr 2026 08:47:09 +0200 Subject: [PATCH 1/4] MAESTRO: fix hasUnread data pipeline for web UI notification red dots Add hasUnread field to AITabData type, web-server aiTabs mapping, and useRemoteIntegration broadcast hash + payload so unread indicators propagate to web clients. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/web-server/types.ts | 1 + src/main/web-server/web-server-factory.ts | 1 + src/renderer/hooks/remote/useRemoteIntegration.ts | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) 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/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); From 3dfe12e2bacd529a8e17bef19e91b38d8c1eb93d Mon Sep 17 00:00:00 2001 From: chr1syy Date: Mon, 6 Apr 2026 09:25:50 +0200 Subject: [PATCH 2/4] MAESTRO: enhance Auto Run tab with document listing, progress, and stop controls Extracted DocumentCard into shared AutoRunDocumentCard.tsx and rewrote AutoRunTabContent to show a document list with progress cards, running status with task/doc counters and progress bar, stop button, and proper loading/empty states. Threaded sessionId, sendRequest, send, and onOpenDocument props through RightDrawer and RightPanel. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/web/mobile/AutoRunDocumentCard.tsx | 119 ++++++++++ src/web/mobile/AutoRunPanel.tsx | 113 +--------- src/web/mobile/RightDrawer.tsx | 289 ++++++++++++++++++++----- src/web/mobile/RightPanel.tsx | 11 +- 4 files changed, 369 insertions(+), 163 deletions(-) create mode 100644 src/web/mobile/AutoRunDocumentCard.tsx diff --git a/src/web/mobile/AutoRunDocumentCard.tsx b/src/web/mobile/AutoRunDocumentCard.tsx new file mode 100644 index 000000000..fe3380d11 --- /dev/null +++ b/src/web/mobile/AutoRunDocumentCard.tsx @@ -0,0 +1,119 @@ +/** + * 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, 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 ( + + ); +} + +export default DocumentCard; diff --git a/src/web/mobile/AutoRunPanel.tsx b/src/web/mobile/AutoRunPanel.tsx index 5059b43eb..456dbb147 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 */ diff --git a/src/web/mobile/RightDrawer.tsx b/src/web/mobile/RightDrawer.tsx index 5930e33d5..6d62643e5 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 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,84 @@ 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' && ( From 6b36b303b66b43f4b352438fd603486cb3060471 Mon Sep 17 00:00:00 2001 From: chr1syy Date: Mon, 6 Apr 2026 12:16:34 +0200 Subject: [PATCH 3/4] fix: remove dead default export and extract repeated isStopped expression Co-Authored-By: Claude Opus 4.6 (1M context) --- src/web/mobile/AutoRunDocumentCard.tsx | 2 -- src/web/mobile/RightDrawer.tsx | 19 ++++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/web/mobile/AutoRunDocumentCard.tsx b/src/web/mobile/AutoRunDocumentCard.tsx index fe3380d11..b283a6378 100644 --- a/src/web/mobile/AutoRunDocumentCard.tsx +++ b/src/web/mobile/AutoRunDocumentCard.tsx @@ -115,5 +115,3 @@ export function DocumentCard({ document, onTap }: DocumentCardProps) { ); } - -export default DocumentCard; diff --git a/src/web/mobile/RightDrawer.tsx b/src/web/mobile/RightDrawer.tsx index 6d62643e5..2392a7db4 100644 --- a/src/web/mobile/RightDrawer.tsx +++ b/src/web/mobile/RightDrawer.tsx @@ -730,6 +730,7 @@ function AutoRunTabContent({ ); const isRunning = autoRunState?.isRunning ?? false; + const isStopped = isStopping || autoRunState?.isStopping; const totalTasks = autoRunState?.totalTasks; const completedTasks = autoRunState?.completedTasks ?? 0; const currentTaskIndex = autoRunState?.currentTaskIndex ?? 0; @@ -808,8 +809,7 @@ function AutoRunTabContent({ {isRunning && (
- {isStopping || autoRunState?.isStopping ? 'Stopping...' : 'Stop'} + {isStopped ? 'Stopping...' : 'Stop'}
)} From 70b0aa842343ecd644edd4749b9ce27616775b21 Mon Sep 17 00:00:00 2001 From: chr1syy Date: Mon, 6 Apr 2026 13:34:13 +0200 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20?= =?UTF-8?q?add=20hasUnread=20to=20global.d.ts,=20fix=20stale=20path,=20ren?= =?UTF-8?q?ame=20document=20prop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add hasUnread?: boolean to broadcastTabsChange type in global.d.ts - Fix stale .maestro/auto-run/ path to .maestro/playbooks/ in AutoRunPanel - Rename document prop to doc to avoid shadowing window.document - Remove outline: 'none' from DocumentCard button for keyboard accessibility Co-Authored-By: Claude Opus 4.6 (1M context) --- src/renderer/global.d.ts | 1 + src/web/mobile/AutoRunDocumentCard.tsx | 16 +++++++--------- src/web/mobile/AutoRunPanel.tsx | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) 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/web/mobile/AutoRunDocumentCard.tsx b/src/web/mobile/AutoRunDocumentCard.tsx index b283a6378..e54775ea6 100644 --- a/src/web/mobile/AutoRunDocumentCard.tsx +++ b/src/web/mobile/AutoRunDocumentCard.tsx @@ -14,15 +14,14 @@ export interface DocumentCardProps { onTap: (filename: string) => void; } -export function DocumentCard({ document, onTap }: DocumentCardProps) { +export function DocumentCard({ document: doc, onTap }: DocumentCardProps) { const colors = useThemeColors(); - const progress = - document.taskCount > 0 ? Math.round((document.completedCount / document.taskCount) * 100) : 0; + const progress = doc.taskCount > 0 ? Math.round((doc.completedCount / doc.taskCount) * 100) : 0; const handleTap = useCallback(() => { triggerHaptic(HAPTIC_PATTERNS.tap); - onTap(document.filename); - }, [document.filename, onTap]); + onTap(doc.filename); + }, [doc.filename, onTap]); return (