Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4c9f951
fix: re-enable batch state debouncing to resolve Auto Run performance…
Feb 26, 2026
48b8de3
MAESTRO: port agent-inbox shared types from feature/inbox-focus-polish
Feb 26, 2026
efe7dfc
MAESTRO: add unifiedInbox flag to EncoreFeatureFlags
Feb 26, 2026
9268995
MAESTRO: add Unified Inbox toggle card in Settings Encore tab
Feb 26, 2026
e29b425
MAESTRO: port useAgentInbox hook and AgentInbox components from featu…
Feb 26, 2026
615ff25
MAESTRO: integrate Unified Inbox modal into App.tsx with Encore Featu…
Feb 26, 2026
7eff634
MAESTRO: register Option+I keyboard shortcut for Unified Inbox with E…
Feb 26, 2026
d6842b1
MAESTRO: add Unified Inbox menu item to hamburger menu in SessionList
Feb 26, 2026
4403705
MAESTRO: add Unified Inbox action to command palette (QuickActionsModal)
Feb 26, 2026
2a05348
MAESTRO: fix unused-var eslint warning in FocusModeView
Feb 26, 2026
cd0734c
MAESTRO: fix Focus Mode auto-scroll bug with proximity-based approach
Feb 26, 2026
cf06151
MAESTRO: fix CodeRabbit bugs — group header a11y, scoped collapse, se…
Feb 26, 2026
1b65706
MAESTRO: align Inbox + Focus Mode headers with Director's Notes tokens
Feb 26, 2026
c47fca2
MAESTRO: align Focus Mode content spacing + reply input with Director…
Feb 26, 2026
d9698cb
MAESTRO: mark test verification gate as passed — 3 pre-existing Codex…
Feb 26, 2026
26d100b
MAESTRO: mark final verification gate as passed — ESLint clean + all …
Feb 26, 2026
3c388c7
MAESTRO: remove redundant star icons from Inbox + Focus Mode
Feb 26, 2026
b9252ef
MAESTRO: add description field to AITab and tabDescription flag to En…
Feb 26, 2026
d4829ed
MAESTRO: add Tab Description Settings toggle and handleUpdateTabDescr…
Feb 26, 2026
3cf3ccd
MAESTRO: wire tab description handler through prop chain and build in…
Feb 26, 2026
ed2e54b
MAESTRO: constrain tab description to 3-line max with ellipsis overflow
Feb 26, 2026
f494fad
fix: enable fuzzy matching for session filter in Left Bar
Feb 26, 2026
ad846a8
fix: enable fuzzy matching for slash command autocomplete in input area
Feb 26, 2026
f779e9a
fix: update fuzzy autocomplete test, remove duplicate log entry, cons…
Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ENCORE-INBOX-02: Add `unifiedInbox` flag to EncoreFeatureFlags

## Objective
Register the Unified Inbox as an Encore Feature with a type flag and default value.

## Context
- `EncoreFeatureFlags` interface is at `src/renderer/types/index.ts:906` — currently only has `directorNotes: boolean`
- `DEFAULT_ENCORE_FEATURES` is at `src/renderer/stores/settingsStore.ts:110` — currently `{ directorNotes: false }`
- The settings store hydration merges saved values with defaults at `settingsStore.ts:1669-1673` using spread: `{ ...DEFAULT_ENCORE_FEATURES, ...(saved) }` — so new fields with defaults are safe
- Both type AND default MUST be updated in the same task to avoid runtime `undefined`

## Tasks

- [x] In `src/renderer/types/index.ts`, find the `EncoreFeatureFlags` interface at line 906. Add `unifiedInbox: boolean` below `directorNotes`. Also in `src/renderer/stores/settingsStore.ts`, find `DEFAULT_ENCORE_FEATURES` at line 110. Add `unifiedInbox: false` to the object. Both changes must happen together:
```typescript
// types/index.ts:906
export interface EncoreFeatureFlags {
directorNotes: boolean;
unifiedInbox: boolean;
}

// stores/settingsStore.ts:110
export const DEFAULT_ENCORE_FEATURES: EncoreFeatureFlags = {
directorNotes: false,
unifiedInbox: false,
};
```

- [x] Run `npm run lint` to verify the new field doesn't cause type errors. Existing code spreading `encoreFeatures` will pick up the new field automatically via the default merge at line 1669-1673.

## Gate
- `npm run lint` passes
- `EncoreFeatureFlags` has both `directorNotes` and `unifiedInbox` fields
- `DEFAULT_ENCORE_FEATURES` has `unifiedInbox: false`
84 changes: 84 additions & 0 deletions src/renderer/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
Clapperboard,
HelpCircle,
AppWindow,
Inbox,
} from 'lucide-react';
import { useSettings } from '../hooks';
import type {
Expand Down Expand Up @@ -3638,6 +3639,89 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
);
})()}
</div>

{/* Unified Inbox Feature Section */}
<div
className="rounded-lg border"
style={{
borderColor: encoreFeatures.unifiedInbox
? theme.colors.accent
: theme.colors.border,
backgroundColor: encoreFeatures.unifiedInbox
? `${theme.colors.accent}08`
: 'transparent',
}}
>
{/* Feature Toggle Header */}
<button
className="w-full flex items-center justify-between p-4 text-left"
onClick={() =>
setEncoreFeatures({
...encoreFeatures,
unifiedInbox: !encoreFeatures.unifiedInbox,
})
}
>
<div className="flex items-center gap-3">
<Inbox
className="w-5 h-5"
style={{
color: encoreFeatures.unifiedInbox
? theme.colors.accent
: theme.colors.textDim,
}}
/>
<div>
<div
className="text-sm font-bold flex items-center gap-2"
style={{ color: theme.colors.textMain }}
>
Unified Inbox
<span
className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase"
style={{
backgroundColor: theme.colors.warning + '30',
color: theme.colors.warning,
}}
>
Beta
</span>
</div>
<div className="text-xs mt-0.5" style={{ color: theme.colors.textDim }}>
Cross-session notification center with keyboard-first navigation
</div>
</div>
</div>
<div
className={`relative w-10 h-5 rounded-full transition-colors ${encoreFeatures.unifiedInbox ? '' : 'opacity-50'}`}
style={{
backgroundColor: encoreFeatures.unifiedInbox
? theme.colors.accent
: theme.colors.border,
}}
>
<div
className="absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform"
style={{
transform: encoreFeatures.unifiedInbox
? 'translateX(22px)'
: 'translateX(2px)',
}}
/>
</div>
</button>
Comment on lines +3657 to +3713
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Expose toggle state to assistive technologies.

The Unified Inbox toggle is visually stateful, but Line 3656’s button does not expose pressed/checked state. Add toggle semantics so screen readers can announce enabled/disabled state.

♿ Suggested accessibility fix
 								<button
 									className="w-full flex items-center justify-between p-4 text-left"
 									onClick={() =>
 										setEncoreFeatures({
 											...encoreFeatures,
 											unifiedInbox: !encoreFeatures.unifiedInbox,
 										})
 									}
+									aria-pressed={encoreFeatures.unifiedInbox}
+									aria-label={`Unified Inbox ${encoreFeatures.unifiedInbox ? 'enabled' : 'disabled'}`}
 								>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/SettingsModal.tsx` around lines 3656 - 3712, The
toggle button in SettingsModal (the button that reads Unified Inbox and uses
encoreFeatures.unifiedInbox) lacks accessible toggle semantics; update that
button to expose state to assistive tech by adding appropriate ARIA attributes
and role — for example set role="switch" and
aria-checked={encoreFeatures.unifiedInbox} (or aria-pressed if you prefer a
button semantic) and include a clear accessible name via aria-label or ensure
the visible label is programmatically associated; keep the existing onClick
handler and visual styles but ensure keyboard focus/activation and state updates
remain unchanged.


{/* Unified Inbox Info (shown when enabled) */}
{encoreFeatures.unifiedInbox && (
<div className="px-4 pb-4">
<p className="text-xs" style={{ color: theme.colors.textDim }}>
Access via Option+I (Mac) or Alt+I (Windows). Aggregates status updates,
errors, and action items from all running agents into a single
keyboard-navigable view.
Comment on lines +3719 to +3721
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for the shortcut mentions in TypeScript/TSX files
echo "=== Searching for Option+I / Alt+I mentions ==="
rg -n --type=ts --type=tsx "Option\+I|Alt\+I|Unified Inbox" | head -20

echo ""
echo "=== Searching for keyboard event handlers related to shortcuts ==="
rg -n --type=ts --type=tsx "KeyI|shortcut.*[iI]|hotkey.*[iI]" | head -30

echo ""
echo "=== Searching for inbox open/toggle handlers ==="
rg -n --type=ts --type=tsx "(altKey|metaKey).*(keyCode|key).*73|key.*73.*(altKey|metaKey)" | head -20

echo ""
echo "=== Searching for keyboard event handling in components ==="
rg -n --type=ts --type=tsx "onKeyDown|onKeyUp|addEventListener.*key" | grep -i "component\|modal" | head -20

Repository: RunMaestro/Maestro

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for Option+I / Alt+I mentions ==="
rg -n "Option\+I|Alt\+I|Unified Inbox" --include="*.ts" --include="*.tsx" | head -30

echo ""
echo "=== Searching for keyboard handler patterns ==="
rg -n "KeyI|keyCode.*73|key.*I.*alt|key.*I.*meta" --include="*.ts" --include="*.tsx" | head -30

echo ""
echo "=== Searching for shortcut/hotkey bindings ==="
rg -n "shortcut|hotkey|keybind" --include="*.ts" --include="*.tsx" -i | head -40

echo ""
echo "=== Check SettingsModal specifically for context ==="
rg -n "Option|Alt|shortcut" src/renderer/components/SettingsModal.tsx | head -20

Repository: RunMaestro/Maestro

Length of output: 568


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for Option+I / Alt+I mentions ==="
fd -e ts -e tsx | xargs rg -n "Option\+I|Alt\+I|Unified Inbox" | head -30

echo ""
echo "=== Searching for keyboard handlers with key code 73 (I key) ==="
fd -e ts -e tsx | xargs rg -n "keyCode.*73|key.*[\"']i[\"']" | head -30

echo ""
echo "=== Check SettingsModal file for context around line 3718 ==="
cat -n src/renderer/components/SettingsModal.tsx | sed -n '3710,3730p'

echo ""
echo "=== Search for keyboard event listeners in codebase ==="
fd -e ts -e tsx | xargs rg -n "onKeyDown|onKeyPress|addEventListener.*key" | head -20

echo ""
echo "=== Search for shortcuts/keybindings mention ==="
fd -e ts -e tsx | xargs rg -n "shortcut|keybind|hotkey" -i | head -20

Repository: RunMaestro/Maestro

Length of output: 576


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== View context around line 3718 ==="
cat -n src/renderer/components/SettingsModal.tsx | sed -n '3710,3730p'

echo ""
echo "=== Search for Unified Inbox open/toggle handlers ==="
fd -e ts -e tsx | xargs rg -n "Unified.*Inbox|unifiedInbox|unified_inbox" -i | head -20

echo ""
echo "=== Search for any Alt key handlers ==="
fd -e ts -e tsx | xargs rg -n "altKey|metaKey" | head -20

echo ""
echo "=== Search for keyboard shortcuts/command palette ==="
fd -e ts -e tsx | xargs rg -n "registerShortcut|registerKeybinding|keymap|shortcut.*map" -i | head -20

echo ""
echo "=== Search for event listeners that might handle Alt+I ==="
fd -e ts -e tsx | xargs rg -n "addEventListener|on.*key|handleKey" -i | head -30

Repository: RunMaestro/Maestro

Length of output: 10811


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for where Unified Inbox is actually opened/triggered ==="
fd -e ts -e tsx | xargs rg -n "openUnifiedInbox|showUnifiedInbox|unifiedInbox.*open|inbox.*trigger" -i | head -20

echo ""
echo "=== Search for shortcut definitions/registrations ==="
fd -e ts -e tsx | xargs rg -n "Alt.*I|Option.*I" | head -20

echo ""
echo "=== Check if shortcuts are defined in constants ==="
fd -e ts -e tsx | xargs rg -n "const.*SHORTCUTS|const.*SHORTCUT" -A5 | head -40

echo ""
echo "=== Search in ShortcutEditor or shortcuts config ==="
cat -n src/renderer/components/ShortcutEditor.tsx 2>/dev/null | head -50

echo ""
echo "=== Look for where shortcuts are consumed in handlers ==="
fd -e ts -e tsx | xargs rg -n "handleKeyDown|handleKeyPress" | grep -v test | head -20

Repository: RunMaestro/Maestro

Length of output: 131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List files that might contain shortcuts ==="
fd -e ts -e tsx | xargs grep -l "shortcut" | head -20

echo ""
echo "=== Search more broadly for Inbox opening ==="
fd -e ts -e tsx -e js | xargs grep -l "inbox" -i | head -20

echo ""
echo "=== Check main app files ==="
ls -la src/renderer/ | head -20

echo ""
echo "=== Look at App.tsx or main entry ==="
fd -name "App.tsx" -o -name "main.tsx" -o -name "index.tsx"

Repository: RunMaestro/Maestro

Length of output: 993


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check App.tsx for keyboard handlers ==="
wc -l src/renderer/App.tsx
echo ""
head -100 src/renderer/App.tsx

echo ""
echo "=== Search for keyboard event listener in App.tsx ==="
grep -n "onKeyDown\|onKeyPress\|addEventListener\|shortcut" src/renderer/App.tsx | head -20

echo ""
echo "=== Check settingsStore for shortcut definitions ==="
grep -n "unifiedInbox\|Alt.*I\|Option.*I" src/renderer/stores/settingsStore.ts | head -20

echo ""
echo "=== Search for keyboard handler in AppModals or AppOverlays ==="
grep -n "onKeyDown\|handleKey\|keyboard" src/renderer/components/AppModals.tsx | head -10
grep -n "onKeyDown\|handleKey\|keyboard" src/renderer/components/AppOverlays.tsx | head -10

Repository: RunMaestro/Maestro

Length of output: 5089


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find useMainKeyboardHandler ==="
fd -e ts -e tsx | xargs grep -l "useMainKeyboardHandler" | head -10

echo ""
echo "=== Search for Alt+I in useMainKeyboardHandler ==="
fd -e ts -e tsx | xargs grep -l "useMainKeyboardHandler" | xargs grep -l "Alt.*I\|Option.*I"

echo ""
echo "=== Find the hook definition ==="
fd -name "*useMainKeyboardHandler*"

echo ""
echo "=== Search for where shortcut handlers are defined ==="
fd -e ts -e tsx | xargs grep -l "shortcuts\[" | head -10

echo ""
echo "=== Look for keyboard shortcuts configuration ==="
fd -name "*shortcut*" -type f | head -20

Repository: RunMaestro/Maestro

Length of output: 425


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read useMainKeyboardHandler.ts ==="
wc -l src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
echo ""
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | head -150

echo ""
echo "=== Search for shortcut matching/handling logic ==="
grep -n "shortcut\|Alt\|Option\|metaKey\|altKey" src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | head -40

Repository: RunMaestro/Maestro

Length of output: 11816


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Continue reading useMainKeyboardHandler.ts (rest of file) ==="
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | tail -n +150 | head -100

echo ""
echo "=== Search entire codebase for Alt+I handler ==="
fd -e ts -e tsx | xargs grep -n "altKey.*keyLower.*===.*'i'\|key.*===.*'i'.*altKey\|keyCode.*73" | head -20

echo ""
echo "=== Search for where unifiedInbox shortcut would be handled ==="
fd -e ts -e tsx | xargs grep -n "unifiedInbox" | head -20

echo ""
echo "=== Check if Alt+I appears anywhere as a shortcut ==="
fd -e ts -e tsx | xargs grep -n "'i'" | grep -i "alt\|shift.*i" | head -20

Repository: RunMaestro/Maestro

Length of output: 4535


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for unifiedInbox in keyboard handler ==="
grep -n "unifiedInbox" src/renderer/hooks/keyboard/useMainKeyboardHandler.ts

echo ""
echo "=== Check entire codebase for Alt+I ==="
fd -e ts -e tsx | xargs grep -i "alt.*i\|option.*i" | grep -v "option.*interactive\|option.*int\|optional\|altitude\|alternativ\|allergen" | head -30

echo ""
echo "=== Search for where inbox is opened ==="
fd -e ts -e tsx | xargs grep -n "inbox.*open\|open.*inbox\|show.*inbox" -i | head -20

echo ""
echo "=== Search for all keyboard shortcuts mapped ==="
grep -n "ctx.isShortcut(e," src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | head -40

Repository: RunMaestro/Maestro

Length of output: 113


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check entire codebase for Alt+I or Option+I ==="
fd -e ts -e tsx | xargs grep "Alt.*I\|Option.*I" 2>/dev/null | head -20

echo ""
echo "=== List all shortcuts defined via isShortcut ==="
grep -n "ctx.isShortcut(e," src/renderer/hooks/keyboard/useMainKeyboardHandler.ts

echo ""
echo "=== Check SettingsModal line 3718 in context again ==="
sed -n '3710,3730p' src/renderer/components/SettingsModal.tsx

echo ""
echo "=== Search for shortcuts object definition ==="
fd -e ts | xargs grep -l "shortcuts.*=" | head -10

echo ""
echo "=== Check if there's a default shortcuts object ==="
fd -e ts | xargs grep -n "const.*shortcuts.*=.*{" | head -20

Repository: RunMaestro/Maestro

Length of output: 2115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for shortcuts configuration file ==="
fd -e ts | xargs grep -l "toggleSidebar\|toggleRightPanel" | head -10

echo ""
echo "=== Check settingsStore for all default shortcuts ==="
grep -n -A 50 "const.*shortcuts\|export.*shortcuts" src/renderer/stores/settingsStore.ts | head -80

echo ""
echo "=== Look for keyboard shortcut definitions ==="
fd -e ts -e json | xargs grep -l "shortcut.*Key\|keyboard.*map" | grep -v test | head -10

Repository: RunMaestro/Maestro

Length of output: 2390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read shortcuts.ts ==="
wc -l src/renderer/constants/shortcuts.ts
echo ""
cat -n src/renderer/constants/shortcuts.ts

echo ""
echo "=== Search for unifiedInbox in shortcuts ==="
grep -n "unifiedInbox\|Alt.*I\|Option.*I" src/renderer/constants/shortcuts.ts

Repository: RunMaestro/Maestro

Length of output: 8813


Remove misleading keyboard shortcut documentation; the documented shortcut is not actually implemented.

Line 3718 advertises "Option+I (Mac) or Alt+I (Windows)" to access Unified Inbox, but this shortcut does not exist in the codebase. It is not defined in src/renderer/constants/shortcuts.ts and has no handler in the keyboard event system. Either implement the shortcut binding or remove the shortcut claim from the documentation to avoid misleading users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/SettingsModal.tsx` around lines 3718 - 3720, The
SettingsModal documentation text advertises a non-existent shortcut "Option+I
(Mac) or Alt+I (Windows)" for the Unified Inbox; either remove the shortcut
claim from the copy in SettingsModal.tsx (the string containing "Access via
Option+I (Mac) or Alt+I (Windows). Aggregates status updates...") or implement
the shortcut binding: add a constant in src/renderer/constants/shortcuts.ts
(e.g. UNIFIED_INBOX_SHORTCUT) and register a keyboard handler in the app's
keyboard event system that invokes the existing Unified Inbox action/handler
(the code path that opens the Unified Inbox UI) so the documented shortcut
actually triggers that action. Ensure the text in SettingsModal.tsx and the
shortcuts/constants/handler stay consistent.

</p>
</div>
)}
</div>
</div>
)}
</div>
Expand Down
119 changes: 4 additions & 115 deletions src/renderer/hooks/batch/useBatchProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,28 +220,7 @@ export function useBatchProcessor({
// Dispatch batch actions through the store. The store applies batchReducer
// synchronously, eliminating the need for manual ref syncing.
const dispatch = useCallback((action: BatchAction) => {
const prevStates = useBatchStore.getState().batchRunStates;
useBatchStore.getState().dispatchBatch(action);
const newStates = useBatchStore.getState().batchRunStates;

// DEBUG: Log dispatch to trace state updates
if (
action.type === 'START_BATCH' ||
action.type === 'UPDATE_PROGRESS' ||
action.type === 'SET_STOPPING' ||
action.type === 'COMPLETE_BATCH'
) {
const sessionId = action.sessionId;
console.log('[BatchProcessor:dispatch]', action.type, {
sessionId,
prevIsRunning: prevStates[sessionId]?.isRunning,
newIsRunning: newStates[sessionId]?.isRunning,
prevIsStopping: prevStates[sessionId]?.isStopping,
newIsStopping: newStates[sessionId]?.isStopping,
prevCompleted: prevStates[sessionId]?.completedTasksAcrossAllDocs,
newCompleted: newStates[sessionId]?.completedTasksAcrossAllDocs,
});
}
}, []);

// Custom prompts per session — lives in batchStore
Expand Down Expand Up @@ -274,10 +253,8 @@ export function useBatchProcessor({
// This handles React 18 StrictMode double-render and ensures ref is always correct
useEffect(() => {
isMountedRef.current = true;
console.log('[BatchProcessor] Mounted, isMountedRef set to true');
return () => {
isMountedRef.current = false;
console.log('[BatchProcessor] Unmounting, isMountedRef set to false');

// Reject all pending error resolution promises with 'abort' to unblock any waiting async code
// This prevents memory leaks from promises that would never resolve
Expand Down Expand Up @@ -321,7 +298,7 @@ export function useBatchProcessor({
}, []);

// Use extracted debounce hook for batch state updates (replaces manual debounce logic)
const { scheduleUpdate: _scheduleDebouncedUpdate, flushUpdate: flushDebouncedUpdate } =
const { scheduleUpdate: scheduleDebouncedUpdate, flushUpdate: flushDebouncedUpdate } =
useSessionDebounce<Record<string, BatchRunState>>({
delayMs: BATCH_STATE_DEBOUNCE_MS,
onUpdate: useCallback(
Expand All @@ -341,14 +318,6 @@ export function useBatchProcessor({
const newState = updater(currentState);
newStateForSession = newState[sessionId] || null;

// DEBUG: Log to trace progress updates
console.log('[BatchProcessor:onUpdate] Debounce fired:', {
sessionId,
refHasSession: !!currentState[sessionId],
refCompletedTasks: currentState[sessionId]?.completedTasksAcrossAllDocs,
newCompletedTasks: newStateForSession?.completedTasksAcrossAllDocs,
});

// Dispatch UPDATE_PROGRESS with the computed changes
// For complex state changes, we extract the session's new state and dispatch appropriately
if (newStateForSession) {
Expand Down Expand Up @@ -503,85 +472,11 @@ export function useBatchProcessor({
(
sessionId: string,
updater: (prev: Record<string, BatchRunState>) => Record<string, BatchRunState>,
_immediate: boolean = false
immediate: boolean = false
) => {
// DEBUG: Bypass debouncing entirely to test if that's the issue
// Apply update directly without debouncing
const currentState = useBatchStore.getState().batchRunStates;
const newState = updater(currentState);
const newStateForSession = newState[sessionId] || null;

console.log('[BatchProcessor:updateBatchStateAndBroadcast] DIRECT update (no debounce)', {
sessionId,
prevCompleted: currentState[sessionId]?.completedTasksAcrossAllDocs,
newCompleted: newStateForSession?.completedTasksAcrossAllDocs,
});

if (newStateForSession) {
const prevSessionState = currentState[sessionId] || DEFAULT_BATCH_STATE;

dispatch({
type: 'UPDATE_PROGRESS',
sessionId,
payload: {
currentDocumentIndex:
newStateForSession.currentDocumentIndex !== prevSessionState.currentDocumentIndex
? newStateForSession.currentDocumentIndex
: undefined,
currentDocTasksTotal:
newStateForSession.currentDocTasksTotal !== prevSessionState.currentDocTasksTotal
? newStateForSession.currentDocTasksTotal
: undefined,
currentDocTasksCompleted:
newStateForSession.currentDocTasksCompleted !==
prevSessionState.currentDocTasksCompleted
? newStateForSession.currentDocTasksCompleted
: undefined,
totalTasksAcrossAllDocs:
newStateForSession.totalTasksAcrossAllDocs !==
prevSessionState.totalTasksAcrossAllDocs
? newStateForSession.totalTasksAcrossAllDocs
: undefined,
completedTasksAcrossAllDocs:
newStateForSession.completedTasksAcrossAllDocs !==
prevSessionState.completedTasksAcrossAllDocs
? newStateForSession.completedTasksAcrossAllDocs
: undefined,
totalTasks:
newStateForSession.totalTasks !== prevSessionState.totalTasks
? newStateForSession.totalTasks
: undefined,
completedTasks:
newStateForSession.completedTasks !== prevSessionState.completedTasks
? newStateForSession.completedTasks
: undefined,
currentTaskIndex:
newStateForSession.currentTaskIndex !== prevSessionState.currentTaskIndex
? newStateForSession.currentTaskIndex
: undefined,
sessionIds:
newStateForSession.sessionIds !== prevSessionState.sessionIds
? newStateForSession.sessionIds
: undefined,
accumulatedElapsedMs:
newStateForSession.accumulatedElapsedMs !== prevSessionState.accumulatedElapsedMs
? newStateForSession.accumulatedElapsedMs
: undefined,
lastActiveTimestamp:
newStateForSession.lastActiveTimestamp !== prevSessionState.lastActiveTimestamp
? newStateForSession.lastActiveTimestamp
: undefined,
loopIteration:
newStateForSession.loopIteration !== prevSessionState.loopIteration
? newStateForSession.loopIteration
: undefined,
},
});
}

broadcastAutoRunState(sessionId, newStateForSession);
scheduleDebouncedUpdate(sessionId, updater, immediate);
},
[broadcastAutoRunState]
[scheduleDebouncedUpdate]
);

// Update ref to always have latest updateBatchStateAndBroadcast (fixes HMR stale closure)
Expand Down Expand Up @@ -1658,9 +1553,6 @@ export function useBatchProcessor({
// Critical: Always flush debounced updates and dispatch COMPLETE_BATCH to clean up state.
// These operations are safe regardless of mount state - React handles reducer dispatches gracefully,
// and broadcasts are external calls that don't affect React state.
console.log(
'[BatchProcessor:startBatchRun] Flushing debounced updates before COMPLETE_BATCH'
);
flushDebouncedUpdate(sessionId);

// Reset state for this session using COMPLETE_BATCH action
Expand Down Expand Up @@ -1728,7 +1620,6 @@ export function useBatchProcessor({
*/
const stopBatchRun = useCallback(
(sessionId: string) => {
console.log('[BatchProcessor:stopBatchRun] Called with sessionId:', sessionId);
stopRequestedRefs.current[sessionId] = true;
const errorResolution = errorResolutionRefs.current[sessionId];
if (errorResolution) {
Expand All @@ -1753,8 +1644,6 @@ export function useBatchProcessor({
*/
const killBatchRun = useCallback(
async (sessionId: string) => {
console.log('[BatchProcessor:killBatchRun] Force killing session:', sessionId);

// 1. Kill the agent process and wait for termination before cleaning up state
try {
await window.maestro.process.kill(sessionId);
Expand Down
1 change: 1 addition & 0 deletions src/renderer/stores/settingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const DEFAULT_ONBOARDING_STATS: OnboardingStats = {

export const DEFAULT_ENCORE_FEATURES: EncoreFeatureFlags = {
directorNotes: false,
unifiedInbox: false,
};

export const DEFAULT_DIRECTOR_NOTES_SETTINGS: DirectorNotesSettings = {
Expand Down
45 changes: 45 additions & 0 deletions src/renderer/types/agent-inbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { SessionState } from './index';

export interface InboxItem {
sessionId: string;
tabId: string;
groupId?: string;
groupName?: string;
sessionName: string;
tabName?: string;
toolType: string;
gitBranch?: string;
contextUsage?: number; // 0-100, undefined = unknown
lastMessage: string; // truncated to 90 chars
timestamp: number; // Unix ms, must be validated > 0
state: SessionState;
hasUnread: boolean;
starred?: boolean;
}

/** UI labels: "Newest", "Oldest", "Grouped", "By Agent" */
export type InboxSortMode = 'newest' | 'oldest' | 'grouped' | 'byAgent';

/** UI labels: "All", "Unread", "Read", "Starred" */
export type InboxFilterMode = 'all' | 'unread' | 'read' | 'starred';

/** Human-readable status badges */
export const STATUS_LABELS: Record<SessionState, string> = {
idle: 'Ready',
waiting_input: 'Needs Input',
busy: 'Processing',
connecting: 'Connecting',
error: 'Error',
};

/** Status badge color keys (map to theme.colors.*) */
export const STATUS_COLORS: Record<SessionState, string> = {
idle: 'success',
waiting_input: 'warning',
busy: 'info',
connecting: 'textMuted',
error: 'error',
};

/** View mode inside the AgentInbox modal */
export type InboxViewMode = 'list' | 'focus';
1 change: 1 addition & 0 deletions src/renderer/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,7 @@ export interface LeaderboardSubmitResponse {
// Each key is a feature ID, value indicates whether it's enabled
export interface EncoreFeatureFlags {
directorNotes: boolean;
unifiedInbox: boolean;
}

// Director's Notes settings for synopsis generation
Expand Down