Skip to content

feat: Unified Inbox — cross-session notification center#379

Open
felipeggv wants to merge 52 commits intoRunMaestro:mainfrom
felipeggv:feature/unified-inbox
Open

feat: Unified Inbox — cross-session notification center#379
felipeggv wants to merge 52 commits intoRunMaestro:mainfrom
felipeggv:feature/unified-inbox

Conversation

@felipeggv
Copy link

@felipeggv felipeggv commented Feb 15, 2026

Summary

  • Unified Inbox: A new modal (Alt+Cmd+I) that aggregates notifications from all active sessions into a single, keyboard-navigable view
  • 10-phase implementation across 52 commits — types, virtualized list, keyboard navigation, filters, sort, grouping, visual polish, and comprehensive test coverage
  • 168 files changed with full lint and test gate verification at every phase

Features

  • Cross-session aggregation — collects status updates, errors, and action-needed items from all running agents
  • Keyboard-first navigation — Tab cycling, arrow keys, Enter to jump to session, Escape to close
  • Smart filtering — All / Unread / Read filters with sort by time or status
  • Session grouping — Group by session with collapsible chevron toggles
  • Virtualized rendering — react-window for smooth performance with large notification lists
  • Agent context — Each card shows agent type icon, session name, tab name, and timestamp
  • Left Bar integration — Accessible from hamburger menu, renamed from "Agent Inbox" to "Unified Inbox"
  • Theme-aware — Uses design system tokens, no hardcoded colors

Architecture

  • Types: src/shared/agentInboxTypes.ts
  • Data hook: useAgentInbox.ts — aggregation with null guards
  • Modal: AgentInbox.tsx — registered in AppModals.tsx with lazy import
  • Keyboard: Integrated with existing useMainKeyboardHandler.ts
  • State: Zustand modalStore integration
  • Tests: 156+ tests covering all components and hooks

Test plan

  • All 156 tests pass (npm run test)
  • TypeScript lint passes (npm run lint)
  • ESLint passes (npm run lint:eslint)
  • Visual verification in desktop app
  • Keyboard navigation flow test (Alt+Cmd+I → arrows → Enter → Escape)

Screenshots

Screenshot 2026-02-15 at 12 37 07 Screenshot 2026-02-15 at 12 37 22 Screenshot 2026-02-15 at 12 37 30

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Unified Inbox to access pending sessions and unread messages via Alt+Cmd+I keyboard shortcut or menu.
    • Introduced sort (Newest, Oldest, Grouped) and filter (All, Unread, Read) controls for inbox organization.
    • Added keyboard navigation (arrow keys, Enter to navigate) and relative time formatting for inbox items.
    • Integrated Unified Inbox into the left sidebar menu for quick access.
  • Dependencies

    • Added react-window for virtualized list rendering.

Felipe Gobbi and others added 30 commits February 15, 2026 01:02
…aps)

Created src/renderer/types/agent-inbox.ts with InboxItem interface,
InboxSortMode/InboxFilterMode types, and STATUS_LABELS/STATUS_COLORS
constants. Re-exported from types/index.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…elector)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s guard

- Added 'agentInbox' shortcut definition to DEFAULT_SHORTCUTS
- Handler counts sessions with waiting_input state or unread tabs
- Shows "No pending items" toast (1.5s) when no actionable items exist
- Opens agentInbox modal when actionable items are present
- Added shortcut to isSystemUtilShortcut allowlist for modal passthrough
- Exposed setAgentInboxOpen and addToast to keyboard handler context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…placeholder component

- Add lazy import for AgentInbox in AppModals.tsx
- Add agentInboxOpen/onCloseAgentInbox props to AppInfoModalsProps and AppModalsProps
- Render <AgentInbox> wrapped in <Suspense> after ProcessMonitor
- Wire agentInboxOpen state and handleCloseAgentInbox through App.tsx
- Create AgentInbox.tsx placeholder component with typed props

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…complete

All Phase 01 tasks verified: types, modal store, keyboard shortcut, modal
registration, and placeholder component. TypeScript compiles clean, all
19185 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 31 tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nd 36 tests

- Replace placeholder AgentInbox component with full implementation
- Install react-window v2 for virtualized list rendering
- Add MODAL_PRIORITIES.AGENT_INBOX (555) constant
- Implement VariableSizeList with group headers (36px) and item cards (80px)
- Add useModalLayer integration for focus trap and Escape handling
- Add ARIA: role=dialog, aria-modal, aria-live, role=listbox, role=option
- Keyboard nav: ArrowUp/Down with wrap, Enter to navigate, focus restoration
- Segmented controls for sort (Newest/Oldest/Grouped) and filter (All/Needs Input/Ready)
- InboxItemCard with group name, session name, timestamp, last message, badges
- 36 component tests covering rendering, navigation, ARIA, layer stack, filters

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…card tests

Selection now uses background fill only (no outline on selection state).
Outline appears only on focus for accessibility compliance. Added comprehensive
InboxItemCard test coverage: visual hierarchy, selection styling, font sizes,
badge rendering, emoji absence, tabIndex management. 50 total component tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Tab/Shift+Tab key handling to cycle focus between header controls
(sort, filter, close buttons) and the virtualized list container.
Adds headerRef for focusable element discovery and 4 dedicated tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cleanup test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…review pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add resolveContextUsageColor() with green/orange/red thresholds (0-60/60-80/80-100%)
- Render 4px context usage bar at bottom of InboxItemCard with animated width
- Color-code context text to match bar color
- Show "Context: —" placeholder when contextUsage is undefined or NaN
- Add 9 new tests: color thresholds, bar dimensions, clamping, NaN guard, placeholder
- All 63 component tests + 31 hook tests pass; TSC + ESLint clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and 25-char truncation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tics in AgentInbox

Replaces simple extractLastMessage with generateSmartSummary that applies
contextual prefixes based on session state and AI message patterns:
- "Waiting:" prefix for waiting_input sessions
- Direct display for AI questions ending with "?"
- "Done:" prefix with first-sentence extraction for AI statements
- "No activity yet" for empty logs
Includes null guards for undefined/null logs and text fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mps, clock skew, yesterday, and months

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pass clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 150ms transition, 13px group headers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rols — aria-label, aria-pressed, 6 new tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on, per-filter messages, 7 new tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pliant warning color, 7 new tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, 0 failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s covered

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt — 88 tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eTime, summary generation, context bar colors

- Created src/__tests__/renderer/helpers/agentInboxHelpers.test.ts with 17 tests
- Exported generateSmartSummary and truncate from useAgentInbox.ts for direct testing
- Exported resolveContextUsageColor from AgentInbox.tsx for direct testing
- All 17 spec items covered: 9 formatRelativeTime, 5 summary generation, 3 context bar color
- Full suite: 19,331 tests, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lready committed incrementally

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…checks pass clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ks pass clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…1 keyboard handler tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Felipe Gobbi and others added 22 commits February 15, 2026 02:50
… clean (tsc, lint, 19336 tests, build)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nbox core at 91-98%

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Agent Inbox files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…implement, 3 WON'T DO

Evaluated all 6 coverage gaps from Phase 07b against importance/testability matrix.
Auto-implement: findRowIndexForItem fallback (+1.5%), close button hover (+1.0%), setAgentInboxOpen (+0.5%).
Won't do: matchesFilter default (unreachable), focus/blur outlines (already covered), InboxRow null guard (untriggerable).
Agent Inbox coverage: 93% → estimated 96% after implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on (2 tests, 19338 total pass)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…40 total pass)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ts, 19342 total pass)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (19341 total pass)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…% Agent Inbox coverage, 0 PENDING)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Needs Input/Ready filters with Unread/Read binary model.
- InboxFilterMode: 'needs_input'|'ready' → 'unread'|'read'
- matchesFilter: unread=hasUnread, read=!hasUnread+idle/waiting_input
- Updated FILTER_OPTIONS, EMPTY_STATE_MESSAGES, and all 147 tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and item dividers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…3 tests, 0 errors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ugh to SessionList

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…amburger menu

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… all references

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…4 tests, 0 errors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…b icon

- Move agent icon from Row 3 badge to Row 1 (between group separator and session name)
- Add Edit3 pencil icon before tab name in Row 1
- Remove agent-type-badge span from Row 3 (now starts with git branch)
- Add Edit3, ChevronDown, ChevronRight to lucide-react imports
- Update tests: replace data-testid queries with title attribute queries
- All 96 tests pass, zero lint errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Row 1: title + badge + close button
Row 2: sort + filter segmented controls

MODAL_HEADER_HEIGHT increased from 48 to 80 to accommodate the two rows.
All 96 tests pass, zero lint errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ron icons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…6 tests, 0 errors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pedramamini
Copy link
Collaborator

@felipeggv can you pull down latest main and you'll see there are now notes around "Encore Features" and actually "Direectors Notes" has been moved into this section. can you refactor unified inbox to fit the same pattern?

@pedramamini
Copy link
Collaborator

@greptile review
@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for "Encore Features" pattern in the codebase
rg -n -C3 "Encore Features" --type-add 'code:*.{ts,tsx,js,jsx,md}' --type code

# Search for "Directors Notes" or "Director's Notes" pattern
rg -n -C3 "Director" --type-add 'code:*.{ts,tsx,js,jsx,md}' --type code | head -50

# Search for files related to Unified Inbox
fd -e ts -e tsx "unified.*inbox|agentinbox" -i

# Look at the main branch to understand the pattern (check README or docs)
cat README.md 2>/dev/null || echo "README not found"

Length of output: 14316


🏁 Script executed:

#!/bin/bash
# Switch to main branch and look for "Encore Features" or "Directors Notes" pattern
git fetch origin main:refs/remotes/origin/main 2>/dev/null || true
git show origin/main:README.md | rg -n -C5 "Encore|Director" || echo "Pattern not found in main README"

# Check if there are documentation files that might contain this structure
git show origin/main:README.md | head -200

# Look for any recent organizational changes in commit messages
git log origin/main --oneline --grep="Encore\|Director\|refactor.*feature" -n 10 || echo "No matching commits"

Length of output: 11752


🏁 Script executed:

#!/bin/bash
# Check the Encore Features implementation pattern
git show 608e4f73 --stat | head -50

# Look at CLAUDE-PATTERNS.md to understand the documentation pattern
git show origin/main:CLAUDE-PATTERNS.md 2>/dev/null | rg -n -C10 "Encore|Director" || echo "File not found or pattern not in file"

# Check the actual implementation of Encore Features
git show 608e4f73:src/renderer/App.tsx 2>/dev/null | rg -n -C5 "encore|Encore" || echo "Not in App.tsx"

# Look at how Director's Notes was structured
git show 49935ae3 --stat | head -30

# Check if there's a settings or config file that controls Encore Features
git show origin/main:src/renderer/components/Settings.tsx 2>/dev/null | rg -n -C5 "Encore" | head -50 || echo "Not in Settings"

Length of output: 8719


🏁 Script executed:

#!/bin/bash
# Look at how Unified Inbox is currently implemented in this PR
cat src/renderer/components/AgentInbox.tsx | head -100

# Check keyboard handler for Unified Inbox shortcut
rg -n "agentinbox|unifiedinbox|inbox" -i src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | head -20

# Check App.tsx for Unified Inbox integration
rg -n "agentinbox|unifiedinbox" -i src/renderer/App.tsx | head -30

# Check SessionList for hamburger menu item
rg -n "agentinbox|unifiedinbox|inbox" -i src/renderer/components/SessionList.tsx | head -30

# Check QuickActionsModal for command palette entry
rg -n "agentinbox|unifiedinbox|inbox" -i src/renderer/components/QuickActionsModal.tsx | head -30

Length of output: 4945


@pedramamini, I'll review the PR now. I can see from the main branch that you've introduced the Encore Features pattern for optional feature gating (where Director's Notes serves as the reference implementation).

The Unified Inbox feature in this PR will need to be refactored to follow that same pattern—gating all access points (keyboard shortcut, hamburger menu, command palette, modal rendering) with the encoreFeatures.unifiedInbox flag, defaulting to disabled, and adding a toggle in the Settings Encore tab.

Let me proceed with the full review.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive Agent Inbox feature for the renderer layer, including type definitions, core hooks and components with virtualization, modal store integration, keyboard shortcuts, keyboard/navigation handlers, and extensive test coverage and documentation across planning, coverage analysis, and UI polish phases.

Changes

Cohort / File(s) Summary
Type Definitions & Constants
src/renderer/types/agent-inbox.ts, src/renderer/types/index.ts, src/renderer/constants/modalPriorities.ts, src/renderer/constants/shortcuts.ts
Added InboxItem interface, InboxSortMode, InboxFilterMode types, STATUS_LABELS and STATUS_COLORS mappings. Added agentInbox shortcut (Alt+Meta+I) and modal priority entry (555).
Core Hook & Component Implementation
src/renderer/hooks/useAgentInbox.ts, src/renderer/components/AgentInbox.tsx
Introduced useAgentInbox hook with filtering, sorting, and memoization logic. Implemented AgentInbox component with react-window virtualization, keyboard navigation, focus management, group collapse/expand, filter/sort controls, and ARIA accessibility.
Store & State Integration
src/renderer/stores/modalStore.ts, src/renderer/hooks/props/useSessionListProps.ts
Extended modal store to include agentInbox modal ID and setAgentInboxOpen action. Added setAgentInboxOpen dependency to useSessionListProps.
App & Component Wiring
src/renderer/App.tsx, src/renderer/components/AppModals.tsx, src/renderer/components/SessionList.tsx, src/renderer/hooks/index.ts
Wired agent inbox state and handlers through App.tsx, AppModals lazy-load component. Extended SessionList props to include setAgentInboxOpen and added Unified Inbox menu entry. Exported useAgentInbox in hooks index.
Keyboard & Navigation
src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
Added Alt+Cmd+I keyboard shortcut handler with zero-items guard (shows toast if no pending items, opens modal otherwise) and extended system utility shortcuts when modals are open.
Shared Utilities
src/shared/formatters.ts
Updated formatRelativeTime to handle input validation (invalid/NaN timestamps → "—"), compute relative time with new thresholds (1 day → "yesterday", 2–29 days → "Xd ago", 30+ days → "Nmo ago").
Component Tests
src/__tests__/renderer/components/AgentInbox.test.tsx, src/__tests__/renderer/components/SessionList.test.tsx, src/__tests__/renderer/components/AgentSessionsModal.test.tsx, src/__tests__/renderer/components/TabSwitcherModal.test.tsx
Comprehensive AgentInbox test suite (2125 lines) covering rendering, layer stack, keyboard navigation, item interaction, filtering/sorting, empty states, virtualization, and accessibility. Updated SessionList tests to include Inbox icon mock and setAgentInboxOpen prop. Updated relative-time tests to reflect new formatRelativeTime behavior ("yesterday" for 1 day, "Xd ago" for 2–29 days, "Xmo ago" for 30+ days).
Hook & Store Tests
src/__tests__/renderer/hooks/useAgentInbox.test.ts, src/__tests__/renderer/hooks/useMainKeyboardHandler.test.ts, src/__tests__/renderer/stores/modalStore.test.ts
Added useAgentInbox test suite (882 lines) covering filtering, sorting, memoization, and field mapping. Added useMainKeyboardHandler tests for agentInbox zero-items guard behavior. Added modalStore tests for setAgentInboxOpen action.
Helper & Formatter Tests
src/__tests__/renderer/helpers/agentInboxHelpers.test.ts, src/__tests__/shared/formatters.test.ts, src/__tests__/web/mobile/CommandHistoryDrawer.test.tsx, src/__tests__/web/mobile/OfflineQueueBanner.test.tsx
Added agentInboxHelpers test suite (151 lines) for formatRelativeTime, generateSmartSummary, and resolveContextUsageColor. Updated formatters tests and mobile tests to reflect revised relative-time formatting.
Feature Documentation & Planning
playbooks/agent-inbox/UNIFIED-INBOX-*.md, playbooks/agent-inbox/LOOP_*.md, playbooks/agent-inbox/TEST_LOG_maestro.app_*.md
Added 10 phases (01–10) documenting feature foundation, core component, UI enhancements, sorting/filtering, tests, accessibility gate, coverage measurement, gap analysis, prioritization, and UI redesign. Includes test coverage reports, gap analysis, prioritization plans, test logs, and verification gates.
Package Dependency
package.json
Added react-window ^2.2.7 to dependencies for virtualized list rendering in AgentInbox component.

Sequence Diagram

sequenceDiagram
    actor User
    participant KBHandler as Keyboard Handler
    participant SessionCheck as Session Checker
    participant ModalStore as Modal Store
    participant AgentInbox as AgentInbox Modal
    participant UI as UI Renderer

    User->>KBHandler: Press Alt+Cmd+I
    KBHandler->>SessionCheck: Check for pending items
    alt No pending items
        SessionCheck-->>KBHandler: count = 0
        KBHandler->>UI: Show toast "No pending items"
    else Has pending items
        SessionCheck-->>KBHandler: count > 0
        KBHandler->>ModalStore: setAgentInboxOpen(true)
        ModalStore->>AgentInbox: Render with sessions/groups
        AgentInbox->>UI: Display inbox with virtualized list
        User->>AgentInbox: Navigate & select items
        AgentInbox->>UI: Update filtered/sorted view
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: introducing a new cross-session unified inbox notification center. It directly reflects the main feature addition across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Feb 19, 2026

Greptile Summary

Adds a comprehensive Unified Inbox feature accessible via Alt+Cmd+I that aggregates notifications from all active sessions into a single modal. The implementation is production-ready with robust null guards, keyboard navigation, virtualized rendering, and extensive test coverage.

Key Changes:

  • New AgentInbox.tsx modal with keyboard-first navigation (arrows, Enter, Tab cycling)
  • useAgentInbox hook aggregates session/tab data with filter (All/Unread/Read) and sort (Newest/Oldest/Grouped) modes
  • Smart summary generation from AI logs with context-aware prefixes ("Waiting:", "Done:")
  • Virtualized list rendering via react-window for performance with large notification counts
  • Group collapse/expand functionality with chevron toggles
  • Zero-items guard in keyboard handler shows toast instead of empty modal
  • Integration with existing modal system (priority 555, lazy loading, layer stack)
  • Comprehensive test coverage: 156+ tests across components and hooks

Implementation Quality:

  • Follows all CLAUDE.md patterns (tabs for indentation, modal priorities, keyboard shortcuts)
  • Proper focus management and ARIA attributes for accessibility
  • Defensive null guards throughout data aggregation
  • Clean separation: types → hook → component
  • No hardcoded colors, uses theme tokens consistently

Confidence Score: 5/5

  • This PR is safe to merge with no concerns
  • The implementation demonstrates exceptional quality: comprehensive test coverage (3000+ test lines), robust null guards, proper integration with existing systems, and follows all codebase conventions. The phased development approach with verification gates at each step ensures stability. All 156 tests pass and lint gates are clean.
  • No files require special attention

Important Files Changed

Filename Overview
src/renderer/components/AgentInbox.tsx Implements unified inbox modal with keyboard navigation, virtualized list, filters, sort modes, and group collapse. Clean implementation following React best practices with proper focus management and ARIA attributes.
src/renderer/hooks/useAgentInbox.ts Data aggregation hook with comprehensive null guards, filter/sort logic, and memoization. Robust handling of edge cases (empty sessions, missing data).
src/renderer/types/agent-inbox.ts Type definitions for inbox items, filter/sort modes, status labels and colors. Clean, well-documented types.
src/renderer/hooks/keyboard/useMainKeyboardHandler.ts Added Alt+Cmd+I keyboard shortcut with zero-items guard (shows toast when no actionable items). Follows existing patterns for keyboard handler integration.
src/shared/formatters.ts Added formatRelativeTime function for displaying timestamps (just now, 5m ago, 2h ago, etc.). Well-tested implementation.
src/tests/renderer/components/AgentInbox.test.tsx Comprehensive test coverage (2125 lines) covering rendering, keyboard navigation, filters, sort modes, focus management, ARIA, virtualization.
src/tests/renderer/hooks/useAgentInbox.test.ts Thorough hook tests (882 lines) covering empty states, null guards, filter modes, sort modes, grouping, timestamp derivation.
src/renderer/App.tsx Integrated agent inbox state, handlers, and modal rendering. Added to keyboard handler context.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User: Alt+Cmd+I] --> B{useMainKeyboardHandler}
    B --> C{Check actionable items}
    C -->|Zero items| D[Show toast: No pending items]
    C -->|Has items| E[setAgentInboxOpen true]
    E --> F[App.tsx renders AgentInbox modal]
    F --> G[useAgentInbox hook]
    G --> H[Iterate sessions + groups]
    H --> I{Apply filter mode}
    I -->|all| J[unread OR waiting_input OR idle]
    I -->|unread| K[hasUnread === true]
    I -->|read| L[hasUnread === false AND idle/waiting]
    J --> M{Apply sort mode}
    K --> M
    L --> M
    M -->|newest| N[Sort by timestamp DESC]
    M -->|oldest| O[Sort by timestamp ASC]
    M -->|grouped| P[Group by session groupName]
    N --> Q[Return InboxItem array]
    O --> Q
    P --> Q
    Q --> R[AgentInbox renders virtualized list]
    R --> S[User navigates with arrows/Enter]
    S --> T{User action}
    T -->|Enter| U[onNavigateToSession]
    T -->|Escape| V[handleClose]
    U --> W[Close modal + focus session/tab]
    V --> X[Restore focus to trigger element]
Loading

Last reviewed commit: 7a30e8f

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (5)
playbooks/agent-inbox/UNIFIED-INBOX-07e.md (1)

11-15: Consider adding a language identifier to the fenced code block.

The decision logic pseudo-code block would benefit from a language identifier for consistent formatting. Since it's pseudo-code, text or plaintext would be appropriate.

📝 Suggested fix
 **Decision Logic:**
-```
+```text
 IF line_coverage >= 80%  →  Do NOT reset (TARGET REACHED - EXIT)
 ELSE IF no PENDING items with (EASY|MEDIUM) + (HIGH|CRITICAL)  →  Do NOT reset (NO MORE AUTO WORK - EXIT)
 ELSE  →  Reset 07a-07d (CONTINUE TO NEXT LOOP)
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @playbooks/agent-inbox/UNIFIED-INBOX-07e.md around lines 11 - 15, Add a
language identifier to the fenced pseudo-code block that begins with "IF
line_coverage >= 80% ..." by changing the opening fence from totext (or

remains ``` and leave the pseudo-code content unchanged.
playbooks/agent-inbox/UNIFIED-INBOX-07b.md (1)

14-14: Consider using relative paths in playbook documentation.

The absolute path /Users/felipegobbi/Documents/Vibework/Maestro/playbooks/agent-inbox/ is user-specific and won't work for other contributors. Consider using relative paths (e.g., ./LOOP_00001_COVERAGE_REPORT.md or playbooks/agent-inbox/LOOP_00001_COVERAGE_REPORT.md) or documenting paths relative to the repository root.

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

In `@playbooks/agent-inbox/UNIFIED-INBOX-07b.md` at line 14, The playbook uses
absolute, user-specific paths (e.g.,
"/Users/felipegobbi/Documents/Vibework/Maestro/playbooks/agent-inbox/LOOP_00001_COVERAGE_REPORT.md");
update Agent Inbox playbook content to use repository-relative paths instead —
replace absolute paths with relative ones such as
"./LOOP_00001_COVERAGE_REPORT.md" or "playbooks/agent-inbox/LOOP_00001_GAPS.md"
throughout the file (refer to the checklist lines that reference
LOOP_00001_COVERAGE_REPORT.md and LOOP_00001_GAPS.md) so other contributors can
open the files regardless of their home directory.
src/__tests__/renderer/stores/modalStore.test.ts (1)

400-450: Consider adding 'agentInbox' to the validIds array.

The agentInbox modal ID is now a valid modal type (as evidenced by the new setAgentInboxOpen tests), but it's missing from the validIds array in the type safety test. This means the test doesn't verify that agentInbox can be opened/closed via the generic openModal/closeModal API.

🔧 Suggested addition
 const validIds: ModalId[] = [
   'settings',
   'about',
   // ... existing entries ...
   'windowsWarning',
+  'agentInbox',
 ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/stores/modalStore.test.ts` around lines 400 - 450, The
test's validIds array for ModalId (variable validIds) is missing the new
'agentInbox' modal, so update the validIds array to include 'agentInbox' so the
type-safety test covers opening/closing via the generic openModal/closeModal
API; verify this aligns with the new setAgentInboxOpen tests and ensure ModalId
union still accepts 'agentInbox'.
playbooks/agent-inbox/UNIFIED-INBOX-07c.md (1)

14-14: Tighten the repeated “Mark …” phrasing.

LanguageTool flagged repeated sentence starts; a small reword improves readability.

✍️ Suggested edit
- Mark EASY/MEDIUM testability + HIGH/CRITICAL importance as `PENDING` for auto-implementation. Mark HIGH/CRITICAL + HARD as `PENDING - MANUAL REVIEW`. Mark VERY HARD or LOW importance as `WON'T DO`.
+ Mark EASY/MEDIUM testability + HIGH/CRITICAL importance as `PENDING` for auto-implementation. For HIGH/CRITICAL + HARD, use `PENDING - MANUAL REVIEW`. Use `WON'T DO` for VERY HARD or LOW importance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playbooks/agent-inbox/UNIFIED-INBOX-07c.md` at line 14, The checklist line
repetitively starts sentences with "Mark" (e.g., "Mark EASY/MEDIUM
testability...", "Mark HIGH/CRITICAL + HARD...") which reduces readability;
update the phrasing in the UNIFIED-INBOX-07c checklist so each rule uses varied
verbs or structures (for example: "Label EASY/MEDIUM testability + HIGH/CRITICAL
importance as `PENDING`", "Flag HIGH/CRITICAL + HARD as `PENDING - MANUAL
REVIEW`", "Classify VERY HARD or LOW importance as `WON'T DO`") while preserving
the exact status tokens and criteria, and ensure adjacent sentences (the
IMPORTANCE and TESTABILITY definitions and the resulting status rules) remain
clear and semantically identical.
src/renderer/components/AgentInbox.tsx (1)

507-510: Prefer ref callback for auto‑focus to match renderer focus guideline.

Auto-focus is done via useEffect. The renderer guideline asks for ref={(el) => el?.focus()}. Switching to a ref callback keeps the focus behavior but aligns with the standard.

🛠 Suggested fix
-	const containerRef = useRef<HTMLDivElement>(null);
+	const containerRef = useRef<HTMLDivElement>(null);
+	const setContainerRef = useCallback((el: HTMLDivElement | null) => {
+		containerRef.current = el;
+		el?.focus();
+	}, []);
@@
-	// Focus the container on mount for keyboard nav
-	useEffect(() => {
-		containerRef.current?.focus();
-	}, []);
@@
-			<div
-				ref={containerRef}
+			<div
+				ref={setContainerRef}
As per coding guidelines: `src/renderer/**/*.{tsx,jsx}: Add tabIndex={0} or tabIndex={-1} and outline-none class to ensure focus works correctly. Use ref={(el) => el?.focus()} for auto-focus in React components.`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox.tsx` around lines 507 - 510, Replace the
useEffect-based autofocus with a ref callback on the container element in the
AgentInbox component: remove the useEffect that calls
containerRef.current?.focus(), change the container element to use ref={(el) =>
el?.focus()} (or set the existing containerRef to a callback ref), and ensure
the container has tabIndex={0} or tabIndex={-1} plus the outline-none class so
it can receive focus following the renderer focus guideline; update or remove
references to containerRef/useEffect accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@playbooks/agent-inbox/UNIFIED-INBOX-01.md`:
- Around line 3-5: Update the playbook/title and filter labels from "Agent
Inbox" and "needs_input/ready" to "Unified Inbox" and "all/unread/read": rename
the header text in UNIFIED-INBOX-01.md, update any enumerated filter names/types
and examples in the document to use all/unread/read, and update corresponding
code references in ProcessMonitor.tsx (the component that defines or consumes
the filters) so its filter constants/enum and UI labels match the new strings to
avoid mismatch between doc and implementation.

In `@playbooks/agent-inbox/UNIFIED-INBOX-03.md`:
- Around line 100-106: Replace the user-specific absolute path in the shell
snippet (the `cd ~/Documents/Vibework/Maestro` step) with a repo-root relative
path or a placeholder variable (e.g., `cd $REPO_ROOT` or `cd .`), so the
subsequent commands (`npx tsc --noEmit`, `npm run lint:eslint --
--max-warnings=0`, and the listed src paths) run reliably in CI and for other
developers; update the markdown example to use that repo-relative invocation
instead of the hardcoded home-directory path.

In `@playbooks/agent-inbox/UNIFIED-INBOX-04.md`:
- Around line 40-44: Update the playbook docs to use the actual filter labels
used by the UI: replace "Needs Input" and "Ready" with "Unread" and "Read"
respectively so they match the AgentInbox implementation; verify the text maps
to the InboxFilterMode values ('all' -> "All", 'unread' -> "Unread", 'read' ->
"Read") and adjust the segment list in UNIFIED-INBOX-04.md to reflect those
exact display strings and modes referenced in AgentInbox.tsx and the
InboxFilterMode type.

In `@playbooks/agent-inbox/UNIFIED-INBOX-05.md`:
- Line 25: Condense the repeated "Timestamp ... should be replaced with
Date.now()" lines (items 12–14) into a single concise clause to avoid
repetition; update the list entry for timestamps (referencing the existing
phrases "Timestamp of `0`", "Timestamp of `NaN`", "Timestamp of negative
number") to one line like "Invalid timestamp values (0, NaN, negative) → replace
with Date.now()", and keep remaining items (e.g., `sessionId`, `gitBranch`,
`logs`, `contextUsage`, `session.aiTabs`) unchanged so readers can still locate
the specific edge-case rules.

In `@src/renderer/App.tsx`:
- Around line 10647-10649: Add a new feature flag (e.g., isUnifiedInboxEnabled)
defaulting to false and use it to gate all Unified Inbox entry points: wrap the
keyboard-open logic in useMainKeyboardHandler (the handler that calls
setAgentInboxOpen), the SessionList menu button that calls setAgentInboxOpen
(and any UI that shows addToast or toggles setUsageDashboardOpen for Inbox
flows), and the modal rendering in AppModals that renders the Unified Inbox
component; ensure each place checks isUnifiedInboxEnabled before rendering UI or
invoking setAgentInboxOpen/setUsageDashboardOpen/addToast so the inbox can be
disabled entirely.

In `@src/renderer/components/AgentInbox.tsx`:
- Around line 462-469: The current useMemo for rows always filters out items in
collapsedGroups causing items to remain hidden when sortMode is 'newest' or
'oldest'; modify the rows computation to only apply the collapsedGroups filter
when sortMode is the grouped mode (e.g., 'grouped' or whatever value you use for
grouping). Concretely, in the useMemo that defines rows (referencing
collapsedGroups, allRows, and sortMode), return allRows early if
collapsedGroups.size === 0 OR sortMode is not the grouped value, otherwise apply
the existing filter logic that keeps headers and excludes items whose
item.groupName is in collapsedGroups.
- Around line 502-505: The current useEffect resets selectedIndex when items
change but doesn’t handle collapsing which changes visible rows; add a new sync
that runs when rows change (or modify the existing effect to also depend on
rows) and re-map or clamp the selectedIndex to a visible row: compute the first
visible row index (e.g., 0 if rows non-empty, or clamp the existing
selectedIndex to rows.length - 1) and call setSelectedIndex with that value so
Enter always targets a visible row; reference selectedIndex, setSelectedIndex,
useEffect, items and rows when implementing this fix.
- Around line 370-395: The header row rendering (when row.type === 'header') is
currently a clickable <div> and is not keyboard-accessible; update the element
to be keyboard-toggleable by either rendering a <button> or adding role="button"
and tabIndex={0} to the container used around row.groupName and the Chevron
icons, add an onKeyDown handler that calls onToggleGroup(row.groupName) for
Enter/Space, include the outline-none class (or appropriate focus styling) for
focus visibility, and ensure existing symbols collapsedGroups, onToggleGroup,
ChevronRight, and ChevronDown are preserved and used as before.

In `@src/renderer/components/AppModals.tsx`:
- Around line 265-275: Gate the Unified Inbox behind the Encore feature flag at
all access points: add the same feature check used by the Encore Features
pattern (the project's feature toggle helper) to the keyboard handler in
useMainKeyboardHandler, the SessionList menu item in SessionList.tsx, and the
modal render in AppModals.tsx so AgentInbox only renders when the feature is
enabled; specifically, wrap the existing agentInboxOpen condition that renders
<AgentInbox ... /> with the feature flag check (so the conditional requires both
agentInboxOpen and the unified-inbox feature true) and similarly guard the
keyboard shortcut handler and the SessionList menu item that set
agentInboxOpen/open it, using the project’s standard feature-checking function.

---

Nitpick comments:
In `@playbooks/agent-inbox/UNIFIED-INBOX-07b.md`:
- Line 14: The playbook uses absolute, user-specific paths (e.g.,
"/Users/felipegobbi/Documents/Vibework/Maestro/playbooks/agent-inbox/LOOP_00001_COVERAGE_REPORT.md");
update Agent Inbox playbook content to use repository-relative paths instead —
replace absolute paths with relative ones such as
"./LOOP_00001_COVERAGE_REPORT.md" or "playbooks/agent-inbox/LOOP_00001_GAPS.md"
throughout the file (refer to the checklist lines that reference
LOOP_00001_COVERAGE_REPORT.md and LOOP_00001_GAPS.md) so other contributors can
open the files regardless of their home directory.

In `@playbooks/agent-inbox/UNIFIED-INBOX-07c.md`:
- Line 14: The checklist line repetitively starts sentences with "Mark" (e.g.,
"Mark EASY/MEDIUM testability...", "Mark HIGH/CRITICAL + HARD...") which reduces
readability; update the phrasing in the UNIFIED-INBOX-07c checklist so each rule
uses varied verbs or structures (for example: "Label EASY/MEDIUM testability +
HIGH/CRITICAL importance as `PENDING`", "Flag HIGH/CRITICAL + HARD as `PENDING -
MANUAL REVIEW`", "Classify VERY HARD or LOW importance as `WON'T DO`") while
preserving the exact status tokens and criteria, and ensure adjacent sentences
(the IMPORTANCE and TESTABILITY definitions and the resulting status rules)
remain clear and semantically identical.

In `@playbooks/agent-inbox/UNIFIED-INBOX-07e.md`:
- Around line 11-15: Add a language identifier to the fenced pseudo-code block
that begins with "IF line_coverage >= 80% ..." by changing the opening fence
from ``` to ```text (or ```plaintext) so the block is treated as plain text;
ensure the closing fence remains ``` and leave the pseudo-code content
unchanged.

In `@src/__tests__/renderer/stores/modalStore.test.ts`:
- Around line 400-450: The test's validIds array for ModalId (variable validIds)
is missing the new 'agentInbox' modal, so update the validIds array to include
'agentInbox' so the type-safety test covers opening/closing via the generic
openModal/closeModal API; verify this aligns with the new setAgentInboxOpen
tests and ensure ModalId union still accepts 'agentInbox'.

In `@src/renderer/components/AgentInbox.tsx`:
- Around line 507-510: Replace the useEffect-based autofocus with a ref callback
on the container element in the AgentInbox component: remove the useEffect that
calls containerRef.current?.focus(), change the container element to use
ref={(el) => el?.focus()} (or set the existing containerRef to a callback ref),
and ensure the container has tabIndex={0} or tabIndex={-1} plus the outline-none
class so it can receive focus following the renderer focus guideline; update or
remove references to containerRef/useEffect accordingly.

Comment on lines +3 to +5
> **Feature:** Agent Inbox
> **Codebase:** `~/Documents/Vibework/Maestro` | **Branch:** `feature/agent-inbox`
> **Reference:** Process Monitor at `src/renderer/components/ProcessMonitor.tsx`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align playbook naming and filter labels with “Unified Inbox”.

The doc still says “Agent Inbox” and defines filters as needs_input/ready, but the PR objectives and UI describe “Unified Inbox” with filters all/unread/read. Please update these strings/types to prevent confusion for implementers and QA.

Also applies to: 43-45

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

In `@playbooks/agent-inbox/UNIFIED-INBOX-01.md` around lines 3 - 5, Update the
playbook/title and filter labels from "Agent Inbox" and "needs_input/ready" to
"Unified Inbox" and "all/unread/read": rename the header text in
UNIFIED-INBOX-01.md, update any enumerated filter names/types and examples in
the document to use all/unread/read, and update corresponding code references in
ProcessMonitor.tsx (the component that defines or consumes the filters) so its
filter constants/enum and UI labels match the new strings to avoid mismatch
between doc and implementation.

Comment on lines +100 to +106
```bash
cd ~/Documents/Vibework/Maestro && \
npx tsc --noEmit && \
npm run lint:eslint -- --max-warnings=0 \
src/renderer/components/AgentInbox.tsx \
src/renderer/hooks/useAgentInbox.ts \
src/renderer/types/agent-inbox.ts
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use repo‑relative paths instead of a user‑specific absolute path.

The cd ~/Documents/Vibework/Maestro instruction is brittle and won’t work for other developers or CI. Prefer repo‑root relative commands or a placeholder like $REPO_ROOT.

📎 Suggested doc tweak
-  cd ~/Documents/Vibework/Maestro && \
+  cd $REPO_ROOT && \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playbooks/agent-inbox/UNIFIED-INBOX-03.md` around lines 100 - 106, Replace
the user-specific absolute path in the shell snippet (the `cd
~/Documents/Vibework/Maestro` step) with a repo-root relative path or a
placeholder variable (e.g., `cd $REPO_ROOT` or `cd .`), so the subsequent
commands (`npx tsc --noEmit`, `npm run lint:eslint -- --max-warnings=0`, and the
listed src paths) run reliably in CI and for other developers; update the
markdown example to use that repo-relative invocation instead of the hardcoded
home-directory path.

Comment on lines +40 to +44
**Segments:**
- `"All"` (default, active)
- `"Needs Input"` (NOT "Waiting" — action-oriented label)
- `"Ready"` (maps to idle + unread)

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
# Search for filter label definitions and usage in AgentInbox components
rg -n "Needs Input|Ready|Unread|Read" src/renderer -g '*.tsx' -g '*.ts' -A 2 -B 2 | head -100

Repository: RunMaestro/Maestro

Length of output: 7521


🏁 Script executed:

#!/bin/bash
# Search for InboxFilterMode definitions and usage
rg -n "InboxFilterMode|FilterMode" src/renderer -g '*.tsx' -g '*.ts' -A 2 | head -100

Repository: RunMaestro/Maestro

Length of output: 6645


🏁 Script executed:

#!/bin/bash
# Search for segment or filter segment definitions in AgentInbox
fd -type f -name '*AgentInbox*' -o -name '*Inbox*' src/renderer | head -20

Repository: RunMaestro/Maestro

Length of output: 232


Documentation uses non-existent filter labels; update to reflect actual UI.

The documentation specifies "Needs Input" and "Ready" as filter segments, but the actual AgentInbox component uses "Unread" and "Read". These labels must be updated to match the implementation in src/renderer/components/AgentInbox.tsx (lines 430-432) and the InboxFilterMode type in src/renderer/types/agent-inbox.ts.

The actual filter modes are:

  • 'all' → displays as "All"
  • 'unread' → displays as "Unread"
  • 'read' → displays as "Read"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playbooks/agent-inbox/UNIFIED-INBOX-04.md` around lines 40 - 44, Update the
playbook docs to use the actual filter labels used by the UI: replace "Needs
Input" and "Ready" with "Unread" and "Read" respectively so they match the
AgentInbox implementation; verify the text maps to the InboxFilterMode values
('all' -> "All", 'unread' -> "Unread", 'read' -> "Read") and adjust the segment
list in UNIFIED-INBOX-04.md to reflect those exact display strings and modes
referenced in AgentInbox.tsx and the InboxFilterMode type.

6. Maps group info correctly (name) from `groupId`
7. Handles ungrouped sessions (no `groupId`)

**Null/corrupted data edge cases (from blind spot CRITICAL #2):** 8. Session with `null` sessionId → should be skipped entirely 9. Session with `undefined` gitBranch → InboxItem.gitBranch should be `undefined` 10. Tab with empty `logs` array → lastMessage should be `"No messages yet"` 11. Tab with `undefined` logs → lastMessage should be `"No messages yet"` 12. Timestamp of `0` → should be replaced with `Date.now()` 13. Timestamp of `NaN` → should be replaced with `Date.now()` 14. Timestamp of negative number → should be replaced with `Date.now()` 15. `contextUsage` of `NaN` → should be `undefined` 16. `session.aiTabs` is `undefined` → should not crash, return empty
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reduce repeated phrasing in the edge-case list.

LanguageTool flags repetitive wording on Line 25. Consider rephrasing the consecutive “Timestamp … should be replaced…” clauses to improve readability.

🧰 Tools
🪛 LanguageTool

[style] ~25-~25: You have already used this phrasing in nearby sentences. Consider using an alternative to add variety to your writing.
Context: ...w()13. Timestamp ofNaN→ should be replaced withDate.now()` 14. Timestamp of negative ...

(REP_REPLACEMENT)


[style] ~25-~25: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hould be replaced with Date.now() 14. Timestamp of negative number → should be replaced...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~25-~25: You have already used this phrasing in nearby sentences. Consider using an alternative to add variety to your writing.
Context: ...imestamp of negative number → should be replaced with Date.now() 15. contextUsage of `NaN...

(REP_REPLACEMENT)

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

In `@playbooks/agent-inbox/UNIFIED-INBOX-05.md` at line 25, Condense the repeated
"Timestamp ... should be replaced with Date.now()" lines (items 12–14) into a
single concise clause to avoid repetition; update the list entry for timestamps
(referencing the existing phrases "Timestamp of `0`", "Timestamp of `NaN`",
"Timestamp of negative number") to one line like "Invalid timestamp values (0,
NaN, negative) → replace with Date.now()", and keep remaining items (e.g.,
`sessionId`, `gitBranch`, `logs`, `contextUsage`, `session.aiTabs`) unchanged so
readers can still locate the specific edge-case rules.

Comment on lines +10647 to +10649
setAgentInboxOpen,
setUsageDashboardOpen,
addToast,
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
# Check for Encore features and Unified Inbox usage
echo "=== Searching for encoreFeatures and Encore Features patterns ==="
rg -n "encoreFeatures|Encore Features" src --type ts --type tsx

echo ""
echo "=== Searching for unifiedInbox references ==="
rg -n "unifiedInbox" src --type ts --type tsx

echo ""
echo "=== Searching for Unified Inbox entry points ==="
rg -n "agentInboxOpen|setAgentInboxOpen|AgentInbox" src --type ts --type tsx

Repository: RunMaestro/Maestro

Length of output: 325


🏁 Script executed:

#!/bin/bash
# Check for Encore features and Unified Inbox usage - without type flags
echo "=== Searching for encoreFeatures and Encore Features patterns ==="
rg -n "encoreFeatures|Encore Features" src

echo ""
echo "=== Searching for unifiedInbox references ==="
rg -n "unifiedInbox" src

echo ""
echo "=== Searching for Unified Inbox entry points ==="
rg -n "agentInboxOpen|setAgentInboxOpen|AgentInbox" src

Repository: RunMaestro/Maestro

Length of output: 18160


🏁 Script executed:

#!/bin/bash
# Read the specific lines in App.tsx
echo "=== Lines 10645-10655 in App.tsx ==="
sed -n '10645,10655p' src/renderer/App.tsx

echo ""
echo "=== Lines 11382-11390 in App.tsx ==="
sed -n '11382,11390p' src/renderer/App.tsx

echo ""
echo "=== Lines 11666-11675 in App.tsx ==="
sed -n '11666,11675p' src/renderer/App.tsx

echo ""
echo "=== Search for any feature flag or featureFlag patterns ==="
rg -n "featureFlag|feature.*[Ff]lag|Feature.*[Ff]lag" src | head -30

Repository: RunMaestro/Maestro

Length of output: 1301


🏁 Script executed:

#!/bin/bash
# Check SettingsModal.tsx for feature flags
echo "=== Feature flags context in SettingsModal.tsx ==="
rg -n "Feature flags" src/renderer/components/SettingsModal.tsx -A 20

echo ""
echo "=== Check if there's any feature flag configuration or pattern in the codebase ==="
rg -n "const.*flag|export.*flag" src --max-count=20

Repository: RunMaestro/Maestro

Length of output: 1022


Add feature flag gating for Unified Inbox entry points.
The Unified Inbox entry points (keyboard handler at useMainKeyboardHandler.ts:416, SessionList menu button at SessionList.tsx:661, and modal rendering at AppModals.tsx:266) are currently unconditional. Implement a feature flag to gate these access points (default off) so Unified Inbox can be disabled entirely when needed.

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

In `@src/renderer/App.tsx` around lines 10647 - 10649, Add a new feature flag
(e.g., isUnifiedInboxEnabled) defaulting to false and use it to gate all Unified
Inbox entry points: wrap the keyboard-open logic in useMainKeyboardHandler (the
handler that calls setAgentInboxOpen), the SessionList menu button that calls
setAgentInboxOpen (and any UI that shows addToast or toggles
setUsageDashboardOpen for Inbox flows), and the modal rendering in AppModals
that renders the Unified Inbox component; ensure each place checks
isUnifiedInboxEnabled before rendering UI or invoking
setAgentInboxOpen/setUsageDashboardOpen/addToast so the inbox can be disabled
entirely.

Comment on lines +370 to +395
if (row.type === 'header') {
const isCollapsed = collapsedGroups.has(row.groupName);
return (
<div
style={{
...style,
display: 'flex',
alignItems: 'center',
paddingLeft: 16,
fontSize: 13,
fontWeight: 600,
color: theme.colors.textDim,
letterSpacing: '0.5px',
textTransform: 'uppercase',
borderBottom: `1px solid ${theme.colors.border}60`,
cursor: 'pointer',
}}
onClick={() => onToggleGroup(row.groupName)}
>
{isCollapsed
? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
: <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
}
{row.groupName}
</div>
);
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

Make group headers keyboard‑toggleable (role/button + tabIndex).

Group headers are clickable <div> elements, so keyboard users can’t expand/collapse groups. Please render them as a <button> (or add role="button" + tabIndex={0} + key handlers) and include outline-none for focus styling.

🛠 Suggested fix
-	return (
-		<div
-			style={{
-				...style,
-				display: 'flex',
-				alignItems: 'center',
-				paddingLeft: 16,
-				fontSize: 13,
-				fontWeight: 600,
-				color: theme.colors.textDim,
-				letterSpacing: '0.5px',
-				textTransform: 'uppercase',
-				borderBottom: `1px solid ${theme.colors.border}60`,
-				cursor: 'pointer',
-			}}
-			onClick={() => onToggleGroup(row.groupName)}
-		>
-			{isCollapsed
-				? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
-				: <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
-			}
-			{row.groupName}
-		</div>
-	);
+	return (
+		<button
+			type="button"
+			className="outline-none"
+			style={{
+				...style,
+				display: 'flex',
+				alignItems: 'center',
+				paddingLeft: 16,
+				fontSize: 13,
+				fontWeight: 600,
+				color: theme.colors.textDim,
+				letterSpacing: '0.5px',
+				textTransform: 'uppercase',
+				borderBottom: `1px solid ${theme.colors.border}60`,
+				cursor: 'pointer',
+				background: 'transparent',
+				border: 'none',
+				width: '100%',
+				textAlign: 'left',
+			}}
+			onClick={() => onToggleGroup(row.groupName)}
+			onKeyDown={(e) => {
+				if (e.key === 'Enter' || e.key === ' ') {
+					e.preventDefault();
+					onToggleGroup(row.groupName);
+				}
+			}}
+		>
+			{isCollapsed
+				? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
+				: <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
+			}
+			{row.groupName}
+		</button>
+	);
As per coding guidelines: `src/renderer/**/*.{tsx,jsx}: Add tabIndex={0} or tabIndex={-1} and outline-none class to ensure focus works correctly. Use ref={(el) => el?.focus()} for auto-focus in React components.`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (row.type === 'header') {
const isCollapsed = collapsedGroups.has(row.groupName);
return (
<div
style={{
...style,
display: 'flex',
alignItems: 'center',
paddingLeft: 16,
fontSize: 13,
fontWeight: 600,
color: theme.colors.textDim,
letterSpacing: '0.5px',
textTransform: 'uppercase',
borderBottom: `1px solid ${theme.colors.border}60`,
cursor: 'pointer',
}}
onClick={() => onToggleGroup(row.groupName)}
>
{isCollapsed
? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
: <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
}
{row.groupName}
</div>
);
if (row.type === 'header') {
const isCollapsed = collapsedGroups.has(row.groupName);
return (
<button
type="button"
className="outline-none"
style={{
...style,
display: 'flex',
alignItems: 'center',
paddingLeft: 16,
fontSize: 13,
fontWeight: 600,
color: theme.colors.textDim,
letterSpacing: '0.5px',
textTransform: 'uppercase',
borderBottom: `1px solid ${theme.colors.border}60`,
cursor: 'pointer',
background: 'transparent',
border: 'none',
width: '100%',
textAlign: 'left',
}}
onClick={() => onToggleGroup(row.groupName)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onToggleGroup(row.groupName);
}
}}
>
{isCollapsed
? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
: <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
}
{row.groupName}
</button>
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox.tsx` around lines 370 - 395, The header
row rendering (when row.type === 'header') is currently a clickable <div> and is
not keyboard-accessible; update the element to be keyboard-toggleable by either
rendering a <button> or adding role="button" and tabIndex={0} to the container
used around row.groupName and the Chevron icons, add an onKeyDown handler that
calls onToggleGroup(row.groupName) for Enter/Space, include the outline-none
class (or appropriate focus styling) for focus visibility, and ensure existing
symbols collapsedGroups, onToggleGroup, ChevronRight, and ChevronDown are
preserved and used as before.

Comment on lines +462 to +469
const rows = useMemo(() => {
if (collapsedGroups.size === 0) return allRows;
return allRows.filter(row => {
if (row.type === 'header') return true;
const itemGroup = row.item.groupName ?? 'Ungrouped';
return !collapsedGroups.has(itemGroup);
});
}, [allRows, collapsedGroups]);
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

Collapsed groups still hide items in non‑Grouped modes.

rows filters out collapsed groups even when sortMode is newest/oldest. If a user collapses a group, then switches sort modes, items from that group remain hidden with no header indicator. Gate the filter by sortMode.

🛠 Suggested fix
-	const rows = useMemo(() => {
-		if (collapsedGroups.size === 0) return allRows;
-		return allRows.filter(row => {
-			if (row.type === 'header') return true;
-			const itemGroup = row.item.groupName ?? 'Ungrouped';
-			return !collapsedGroups.has(itemGroup);
-		});
-	}, [allRows, collapsedGroups]);
+	const rows = useMemo(() => {
+		if (sortMode !== 'grouped' || collapsedGroups.size === 0) return allRows;
+		return allRows.filter(row => {
+			if (row.type === 'header') return true;
+			const itemGroup = row.item.groupName ?? 'Ungrouped';
+			return !collapsedGroups.has(itemGroup);
+		});
+	}, [allRows, collapsedGroups, sortMode]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const rows = useMemo(() => {
if (collapsedGroups.size === 0) return allRows;
return allRows.filter(row => {
if (row.type === 'header') return true;
const itemGroup = row.item.groupName ?? 'Ungrouped';
return !collapsedGroups.has(itemGroup);
});
}, [allRows, collapsedGroups]);
const rows = useMemo(() => {
if (sortMode !== 'grouped' || collapsedGroups.size === 0) return allRows;
return allRows.filter(row => {
if (row.type === 'header') return true;
const itemGroup = row.item.groupName ?? 'Ungrouped';
return !collapsedGroups.has(itemGroup);
});
}, [allRows, collapsedGroups, sortMode]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox.tsx` around lines 462 - 469, The current
useMemo for rows always filters out items in collapsedGroups causing items to
remain hidden when sortMode is 'newest' or 'oldest'; modify the rows computation
to only apply the collapsedGroups filter when sortMode is the grouped mode
(e.g., 'grouped' or whatever value you use for grouping). Concretely, in the
useMemo that defines rows (referencing collapsedGroups, allRows, and sortMode),
return allRows early if collapsedGroups.size === 0 OR sortMode is not the
grouped value, otherwise apply the existing filter logic that keeps headers and
excludes items whose item.groupName is in collapsedGroups.

Comment on lines +502 to +505
// Reset selection when items change
useEffect(() => {
setSelectedIndex(0);
}, [items]);
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

Selection can target hidden rows after collapsing a group.

selectedIndex is based on items (pre-collapse). When a group is collapsed, the selected item may disappear, leaving no visible selection and allowing Enter to navigate to a hidden item. Consider re-syncing selectedIndex to the first visible item when rows change.

🛠 Suggested fix
 	useEffect(() => {
 		setSelectedIndex(0);
 	}, [items]);
+
+	useEffect(() => {
+		if (rows.length === 0) return;
+		const visibleIndexes = new Set(
+			rows.filter((row) => row.type === 'item').map((row) => row.index)
+		);
+		if (!visibleIndexes.has(selectedIndex)) {
+			const firstItemRow = rows.find((row) => row.type === 'item');
+			if (firstItemRow && firstItemRow.type === 'item') {
+				setSelectedIndex(firstItemRow.index);
+			}
+		}
+	}, [rows, selectedIndex]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Reset selection when items change
useEffect(() => {
setSelectedIndex(0);
}, [items]);
// Reset selection when items change
useEffect(() => {
setSelectedIndex(0);
}, [items]);
useEffect(() => {
if (rows.length === 0) return;
const visibleIndexes = new Set(
rows.filter((row) => row.type === 'item').map((row) => row.index)
);
if (!visibleIndexes.has(selectedIndex)) {
const firstItemRow = rows.find((row) => row.type === 'item');
if (firstItemRow && firstItemRow.type === 'item') {
setSelectedIndex(firstItemRow.index);
}
}
}, [rows, selectedIndex]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox.tsx` around lines 502 - 505, The current
useEffect resets selectedIndex when items change but doesn’t handle collapsing
which changes visible rows; add a new sync that runs when rows change (or modify
the existing effect to also depend on rows) and re-map or clamp the
selectedIndex to a visible row: compute the first visible row index (e.g., 0 if
rows non-empty, or clamp the existing selectedIndex to rows.length - 1) and call
setSelectedIndex with that value so Enter always targets a visible row;
reference selectedIndex, setSelectedIndex, useEffect, items and rows when
implementing this fix.

Comment on lines +265 to +275
{/* --- AGENT INBOX (lazy-loaded) --- */}
{agentInboxOpen && (
<Suspense fallback={null}>
<AgentInbox
theme={theme}
sessions={sessions}
groups={groups}
onClose={onCloseAgentInbox}
onNavigateToSession={onNavigateToSession}
/>
</Suspense>
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
# Check encoreFeatures usage and agentInbox integration
rg -n "encoreFeatures" src/renderer/components/AppModals.tsx -A 3 -B 3

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for all agentInbox references
rg -n "agentInbox" src/renderer/components/AppModals.tsx -A 3 -B 3

Repository: RunMaestro/Maestro

Length of output: 1449


🏁 Script executed:

#!/bin/bash
# Find where encoreFeatures.unifiedInbox is defined and used
rg -n "unifiedInbox" --type ts --type tsx -A 2 -B 2

Repository: RunMaestro/Maestro

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Find where agentInboxOpen state is managed
rg -n "agentInboxOpen" --type ts --type tsx -A 2 -B 2

Repository: RunMaestro/Maestro

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Find where agentInboxOpen state is set (grep for useState/setAgentInboxOpen)
rg -n "setAgentInboxOpen|agentInboxOpen.*=" src/renderer/ -A 2 -B 1

Repository: RunMaestro/Maestro

Length of output: 6195


🏁 Script executed:

#!/bin/bash
# Search for encoreFeatures without file type restrictions
rg -n "encoreFeatures" src/renderer/ -A 2 -B 1

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Find parent component that passes agentInboxOpen to AppModals
rg -n "AppModals" src/renderer/ --type ts -A 5 -B 5

Repository: RunMaestro/Maestro

Length of output: 5730


🏁 Script executed:

#!/bin/bash
# Look for feature flag usage patterns
rg -n "unifiedInbox|encoreFeatures" src/ -A 2

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for encoreFeatures definition and usage
find src -name "*.ts" -o -name "*.tsx" | xargs grep -l "encoreFeatures" 2>/dev/null | head -20

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look for feature flag patterns in the codebase
rg -n "feature" src/renderer/ -i | grep -i "flag\|enable\|unified" | head -30

Repository: RunMaestro/Maestro

Length of output: 1036


🏁 Script executed:

#!/bin/bash
# Check where AgentInbox is imported and defined
rg -n "AgentInbox" src/renderer/ -B 2 -A 2 | head -50

Repository: RunMaestro/Maestro

Length of output: 3415


🏁 Script executed:

#!/bin/bash
# Look at the keyboard handler to see if feature flag is checked there
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | sed -n '410,425p'

Repository: RunMaestro/Maestro

Length of output: 637


🏁 Script executed:

#!/bin/bash
# Check SessionList.tsx for the Agent Inbox menu item
cat -n src/renderer/components/SessionList.tsx | sed -n '655,670p'

Repository: RunMaestro/Maestro

Length of output: 694


🏁 Script executed:

#!/bin/bash
# Look for encoreFeatures definition - check settings/store files
rg -n "encore" src/ -i

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there are any feature flag gating patterns used in the codebase
rg -n "if.*feature\|if.*enabled\|feature.*&&" src/renderer/components/ -A 1 | head -40

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check the useAgentInbox hook to see if it has any feature flag checks
cat -n src/renderer/hooks/useAgentInbox.ts | head -50

Repository: RunMaestro/Maestro

Length of output: 1897


🏁 Script executed:

#!/bin/bash
# Look at the main keyboard handler context to see what's available
rg -n "setAgentInboxOpen\|useMainKeyboardHandler" src/renderer/hooks/keyboard/useMainKeyboardHandler.ts -B 5 -A 5 | head -50

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for any feature flag or settings files
find src -type f \( -name "*config*" -o -name "*settings*" -o -name "*feature*" \) 2>/dev/null

Repository: RunMaestro/Maestro

Length of output: 427


🏁 Script executed:

#!/bin/bash
# Check if there's any pattern of conditional rendering based on feature flags in modals
rg -n "if.*Open.*&&\|{.*Open.*&&" src/renderer/components/AppModals.tsx | head -20

Repository: RunMaestro/Maestro

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Look at the complete AppModals render to see if any other modals have conditional gating
cat -n src/renderer/components/AppModals.tsx | sed -n '265,280p'

Repository: RunMaestro/Maestro

Length of output: 585


🏁 Script executed:

#!/bin/bash
# Check if there are environment variables or configs for feature flags
find src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) | xargs grep -l "feature\|FEATURE" 2>/dev/null | grep -E "config|env|constant" | head -10

Repository: RunMaestro/Maestro

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Check how other similar modals are rendered in AppModals to see if any use conditional logic
cat -n src/renderer/components/AppModals.tsx | sed -n '230,280p'

Repository: RunMaestro/Maestro

Length of output: 1788


🏁 Script executed:

#!/bin/bash
# Look for any mention of "encore" in the entire codebase
find src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) -exec grep -l "encore" {} \; 2>/dev/null

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there's a settings system where encoreFeatures might be stored
grep -rn "unifiedInbox\|encoreFeatures" src/ 2>/dev/null

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Examine the keyboard handler context to see what data is available
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | sed -n '1,50p'

Repository: RunMaestro/Maestro

Length of output: 2615


🏁 Script executed:

#!/bin/bash
# Check if AgentInbox component has any internal feature flag checks
find src -name "AgentInbox*" -o -name "*AgentInbox*" | head -10

Repository: RunMaestro/Maestro

Length of output: 241


🏁 Script executed:

#!/bin/bash
# Look at App.tsx to see if there's any feature flag system or settings being used
rg -n "settings\|flag\|feature" src/renderer/App.tsx -i | grep -E "encore|unified|inbox|feature" | head -20

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there's any useSettings or feature flag hook being used in App.tsx
rg -n "useSettings\|useFeature" src/renderer/App.tsx -A 3

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look for the AgentInbox component definition
find src/renderer -name "*AgentInbox*" -type f

Repository: RunMaestro/Maestro

Length of output: 136


🏁 Script executed:

#!/bin/bash
# Check the AgentInbox component
cat -n src/renderer/components/AgentInbox.tsx | head -60

Repository: RunMaestro/Maestro

Length of output: 2949


🏁 Script executed:

#!/bin/bash
# Look at the complete useMainKeyboardHandler to see the context being passed
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | sed -n '400,430p'

Repository: RunMaestro/Maestro

Length of output: 1381


🏁 Script executed:

#!/bin/bash
# Check the entire keyboard handler to understand what context is available
wc -l src/renderer/hooks/keyboard/useMainKeyboardHandler.ts

Repository: RunMaestro/Maestro

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Look at App.tsx to understand where setAgentInboxOpen comes from and if any feature gating exists
rg -n "const agentInboxOpen\|setAgentInboxOpen.*=" src/renderer/App.tsx -B 2 -A 2

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Find where agentInboxOpen is destructured in App.tsx
cat -n src/renderer/App.tsx | grep -A 5 -B 5 "agentInboxOpen"

Repository: RunMaestro/Maestro

Length of output: 1038


🏁 Script executed:

#!/bin/bash
# Check the modalStore to see if there's any feature flag checking there
cat -n src/renderer/stores/modalStore.ts | sed -n '520,540p'

Repository: RunMaestro/Maestro

Length of output: 965


🏁 Script executed:

#!/bin/bash
# Look for any feature flag or settings integration in the app
rg -n "useSettings\|window\.maestro\.settings" src/renderer/App.tsx | head -20

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there's a settings or preferences system for features
find src/renderer -name "*settings*" -o -name "*preference*" | grep -E "\.ts$|\.tsx$"

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check the complete flow - look at where modals are defined in modalStore
cat -n src/renderer/stores/modalStore.ts | sed -n '1,50p'

Repository: RunMaestro/Maestro

Length of output: 2027


🏁 Script executed:

#!/bin/bash
# Search for any FEATURE, feature, FLAG, flag patterns in the renderer
rg -n "FEATURE|FLAG" src/renderer/ -i | grep -v "node_modules\|\.test\." | head -30

Repository: RunMaestro/Maestro

Length of output: 2998


🏁 Script executed:

#!/bin/bash
# Check if there's any condition placed before opening modals anywhere
rg -n "Open.*&&\|if.*Open\|&&.*setAgent" src/renderer/ | head -20

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at the complete App.tsx initialization to see if settings are being used
cat -n src/renderer/App.tsx | sed -n '1,100p'

Repository: RunMaestro/Maestro

Length of output: 4047


🏁 Script executed:

#!/bin/bash
# Look at useSettings hook usage in App.tsx
rg -n "useSettings\|settings\." src/renderer/App.tsx | head -20

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check what useSettings hook does
cat -n src/renderer/hooks/useSettings.ts | head -80

Repository: RunMaestro/Maestro

Length of output: 127


🏁 Script executed:

#!/bin/bash
# Look for any encore or feature flag related content in App.tsx more carefully
rg -n "encore\|unified.*feature\|feature.*unified" src/renderer/App.tsx -i

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there's any PR or commit message related to feature flag for Unified Inbox
git log --oneline --all 2>/dev/null | grep -i "unified\|inbox\|encore\|feature" | head -20

Repository: RunMaestro/Maestro

Length of output: 1718


🏁 Script executed:

#!/bin/bash
# Now search for encoreFeatures since we know it exists from git log
rg -n "encoreFeatures" src/renderer/ -A 3 -B 1 | head -100

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look for the settings store where encoreFeatures is defined
rg -n "encoreFeatures" src/renderer/stores/ -A 3

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check all access points to setAgentInboxOpen to see if they should be gated
rg -n "setAgentInboxOpen" src/renderer/ -B 5 -A 1 | grep -E "setAgentInboxOpen|if.*encores|if.*feature|if.*unified"

Repository: RunMaestro/Maestro

Length of output: 1319


🏁 Script executed:

#!/bin/bash
# Look at the commit that added Encore Features system
git show 608e4f73 --stat 2>/dev/null | head -50

Repository: RunMaestro/Maestro

Length of output: 1613


🏁 Script executed:

#!/bin/bash
# Now search for directorNotes to see how it's gated with encoreFeatures
rg -n "directorNotes\|EncoreFeatureFlags" src/renderer/ -A 2 -B 1 | head -100

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at the settings hook to see encoreFeatures
cat -n src/renderer/hooks/settings/useSettings.ts 2>/dev/null || echo "File not found, searching for settings"

Repository: RunMaestro/Maestro

Length of output: 50374


🏁 Script executed:

#!/bin/bash
# Search for where encoreFeatures is defined in types
rg -n "EncoreFeatureFlags\|encoreFeatures" src/renderer/types/ -A 5

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if unifiedInbox is mentioned in the types as part of encoreFeatures
rg -n "unifiedInbox" src/renderer/ -B 3 -A 3

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for encoreFeatures definition in types
rg -n "EncoreFeatureFlags\|encoreFeatures" src/renderer/types/index.ts -A 5

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look for where encoreFeatures is used and managed
rg -n "encoreFeatures" src/renderer/hooks/settings/useSettings.ts -A 3

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there's a constants file for encore features
find src/renderer -name "*encore*" -o -name "*feature*" | grep -v node_modules | grep -v ".test"

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at the commit that added encoreFeatures to see what was implemented
git show 52b63e56 --stat 2>/dev/null | head -30

Repository: RunMaestro/Maestro

Length of output: 744


🏁 Script executed:

#!/bin/bash
# Let's look at types/index.ts to find EncoreFeatureFlags
cat -n src/renderer/types/index.ts | grep -A 10 -B 5 "encoreFeatures\|EncoreFeatureFlags" | head -50

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for directorNotes to see how it's currently gated
rg -n "directorNotes" src/renderer/ -B 2 -A 2 | head -50

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if encoreFeatures is in the settingsStore
rg -n "encoreFeatures" src/renderer/stores/ -B 2 -A 2

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at all references to feature flags and gating patterns in the whole renderer
rg -n "if.*directorNotes\|if.*encoreFeatures" src/renderer/ | head -20

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Try broader search for any feature flag or gating pattern
rg -n "feature.*flag\|FEATURE\|Feature" src/renderer/ -i | grep -v test | grep -v ".node" | head -30

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at App.tsx imports to see what settings are being used
cat -n src/renderer/App.tsx | sed -n '70,150p'

Repository: RunMaestro/Maestro

Length of output: 3381


🏁 Script executed:

#!/bin/bash
# Check the latest state of the file by looking at what encoreFeatures is imported/used
git log --oneline --all --grep="encore\|encoreFeatures" 2>/dev/null | head -10

Repository: RunMaestro/Maestro

Length of output: 395


🏁 Script executed:

#!/bin/bash
# Search for any TODO or FIXME related to Unified Inbox or feature flags
rg -n "TODO\|FIXME" src/renderer/components/AppModals.tsx src/renderer/hooks/keyboard/useMainKeyboardHandler.ts src/renderer/components/SessionList.tsx -A 2

Repository: RunMaestro/Maestro

Length of output: 44


Gate Unified Inbox behind feature flag at all access points.

The Unified Inbox currently renders whenever agentInboxOpen is true without checking a feature flag. All three access points—keyboard shortcut, SessionList menu, and modal render in AppModals.tsx—bypass the feature-gating system that was established for optional features like Director's Notes. For consistency with the Encore Features pattern and to prevent premature exposure, gate the feature at:

  • Keyboard handler (useMainKeyboardHandler.ts, line 416)
  • SessionList menu item (SessionList.tsx, line 661)
  • Modal render condition (AppModals.tsx, line 266)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AppModals.tsx` around lines 265 - 275, Gate the
Unified Inbox behind the Encore feature flag at all access points: add the same
feature check used by the Encore Features pattern (the project's feature toggle
helper) to the keyboard handler in useMainKeyboardHandler, the SessionList menu
item in SessionList.tsx, and the modal render in AppModals.tsx so AgentInbox
only renders when the feature is enabled; specifically, wrap the existing
agentInboxOpen condition that renders <AgentInbox ... /> with the feature flag
check (so the conditional requires both agentInboxOpen and the unified-inbox
feature true) and similarly guard the keyboard shortcut handler and the
SessionList menu item that set agentInboxOpen/open it, using the project’s
standard feature-checking function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments