Skip to content

feat: rebuild tab description, unified inbox & fuzzy search#475

Open
felipeggv wants to merge 25 commits intoRunMaestro:mainfrom
felipeggv:feat/rebuild-tif-integration
Open

feat: rebuild tab description, unified inbox & fuzzy search#475
felipeggv wants to merge 25 commits intoRunMaestro:mainfrom
felipeggv:feat/rebuild-tif-integration

Conversation

@felipeggv
Copy link

@felipeggv felipeggv commented Feb 27, 2026

Summary

Complete rebuild of 3 encore features with isolated worktree development:

  • Encore System: Feature flag infrastructure (settingsStore, EncoreFeatureFlags, Settings UI)
  • Tab Description: Edit/display descriptions in tab overlay menu
  • Unified Inbox: CodeRabbit/Greptile fixes (race condition, resize leak, ARIA, perf)
  • Fuzzy Search: Hardened engine with expanded test coverage

CodeRabbit/Greptile Fixes Ported

  • Race condition in reply flow (setTimeout → requestAnimationFrame + ref)
  • Resize listener memory leak in Focus Mode
  • Group key collision (sessionName → sessionId)
  • Duplicate optimistic user log
  • Force AI mode in reply flows
  • ARIA: listbox/option roles, focus indicators, switch roles
  • Performance: O(n*m) → Map lookup, setTimeout → rAF

Test plan

  • npm run lint — zero errors
  • npm run test — zero regressions
  • Toggle each encore feature on/off
  • Tab description: add, edit, save, cancel
  • Inbox: open, filter, sort, focus mode, reply
  • Fuzzy search: file search, tab switcher, @mention

Summary by CodeRabbit

Release Notes

  • New Features

    • Unified Inbox: Centralized view for managing agent sessions with filtering, sorting, and focus modes for detailed item views.
    • Tab Descriptions: Ability to add and edit custom descriptions on AI-generated tabs.
  • Documentation

    • Updated feature documentation and keyboard shortcuts guide with Unified Inbox and Tab Description support.
  • Tests

    • Comprehensive test coverage for inbox components, tab description editing, search validation, and modal state management.

Felipe Gobbi and others added 25 commits February 27, 2026 10:51
Added optional `description?: string` to AITab for user-defined tab
context annotations. This is the data model foundation for the tab
description feature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends the Encore Feature system with two new feature flags for upcoming
Unified Inbox and Tab Description features. tabDescription defaults to
true (enabled by default), unifiedInbox defaults to false.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing tests for scoring hierarchy (exact > substring > scattered),
case-insensitive scored matching, special characters in query (regex
metacharacters, unicode, emoji), and deterministic scoring verification.

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

Adds a new handler for updating AI tab descriptions with trim and
empty-string-to-undefined normalization. Follows existing immutable
state update pattern via useSessionStore. Includes 4 tests covering
set, trim, empty, and whitespace-only cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add early returns in fuzzyMatch and fuzzyMatchWithScore for:
- Empty text (can't match anything)
- Query longer than text (impossible to match all chars)

Empty query already had early return. All 5 consumers already
skip fuzzy matching on empty input. No consumer changes needed.

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

- Import Inbox and FileText icons from lucide-react
- Change Encore tab icon from FlaskConical to Sparkles per spec
- Add Unified Inbox feature card with toggle, Beta badge, left-border accent
- Add Tab Descriptions feature card with toggle, left-border accent
- Both cards follow existing toggle pattern (spread-copy + flip flag)
- All 113 SettingsModal tests pass, lint clean

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

Thread the tab description handler from useTabHandlers through
useMainPanelProps → App.tsx → MainPanel → TabBar. Gate with
encoreFeatures.tabDescription flag (default: false).

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

Add agentInbox shortcut (Alt+I) with encoreFeatures.unifiedInbox guard.
Director's Notes shortcut already had its guard. Register agentInbox modal
in the modal store and wire through App.tsx keyboard handler context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a description section to the AI tab hover overlay menu with two modes:
- Display mode: FileText icon + description text (2-line clamp) or italic placeholder
- Edit mode: auto-focus textarea with Enter to save, Shift+Enter for newline, Escape to cancel

Feature-gated behind onUpdateTabDescription prop. Only calls handler when value actually changed.

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

Add cleanup logic so that if the overlay closes while the user is editing
a tab description, the current draft is saved rather than discarded. Uses
a useRef to track the latest draft value, avoiding stale closure issues.

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

Adds requestAnimationFrame-based focus management so that when
the user saves or cancels description editing, focus returns to
the description display button for keyboard accessibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port 6 inbox files from feat/unified-inbox-encore (which already incorporates
all 8 rounds of CodeRabbit/Greptile review fixes) and integrate into the
existing codebase with full App.tsx handler wiring.

CRITICAL fixes included:
- Race condition: effect-driven pendingInboxQuickReply replaces setTimeout
- Memory leak: resizeCleanupRef + useEffect unmount guard for drag listeners
- Group collision: sessionId as group key, sessionName as display-only label
- Ref binding: inboxProcessInputRef.current = processInput after useInputHandlers
- Duplicate log: quick reply delegates to processInput (no manual log push)
- AI mode: force inputMode='ai' before sending inbox replies
- Modal data: openModal preserves data, updateModalData handles undefined base

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nd unifiedInbox encore feature

- Add onOpenAgentInbox gated prop (App.tsx → AppModals → QuickActionsModal)
- Add AgentInbox rendering placeholder in App.tsx (commented, component TBD)
- Add Unified Inbox entry to QuickActionsModal command palette
- DirectorNotesModal gating was already in place from prior work

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds 2 missing useTabHandlers tests (non-active session isolation,
other tabs isolation) and 7 TabBar description UI tests covering
rendering, edit mode, Enter/Escape behavior, and feature gating.

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

The tabDescription encore feature was defaulting to true, inconsistent
with the encore pattern (all features disabled by default). No handler
or UI exists yet for tab descriptions, so this is purely a default fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reply input auto-focus in FocusModeView used a setTimeout(60ms) fallback
alongside requestAnimationFrame. Replaced with double requestAnimationFrame
which reliably fires after browser paint without arbitrary timing delays.

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

Adds role="switch" and aria-checked for accessibility compliance.
Follows existing Director's Notes toggle pattern.

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

Add onOpenAgentInbox prop through App.tsx → AppModals → QuickActionsModal
chain, gated behind encoreFeatures.unifiedInbox. The action calls the same
setAgentInboxOpen(true) used by the Alt+I keyboard shortcut, ensuring all
entry points share the same open/close state from modalStore.

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

26 tests covering:
- Filter logic: all/unread/read/starred modes
- Sort logic: newest/oldest/grouped/byAgent modes
- Summary truncation at MAX_MESSAGE_LENGTH (90 chars)
- Timestamp fallback chain: log → createdAt → Date.now()
- Exported utilities: truncate() and generateSmartSummary()

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

18 tests across 6 describe blocks covering:
- List mode rendering with ARIA attributes
- Filter segmented control (All/Unread/Read/Starred)
- Keyboard navigation (ArrowUp/Down, Enter to navigate)
- Escape/close behavior (overlay click, close button)
- Focus mode entry (F key, double-click) and exit (Escape, Backspace)
- Reply textarea presence in focus mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
updateModalData now stores data even on unopened modals (needed for
AgentInbox pattern). Updated test expectation accordingly.

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

# Conflicts:
#	src/renderer/stores/settingsStore.ts
#	src/renderer/types/index.ts
…ration

# Conflicts:
#	src/renderer/App.tsx
#	src/renderer/components/AppModals.tsx
#	src/renderer/components/QuickActionsModal.tsx
#	src/renderer/components/SettingsModal.tsx
#	src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
#	src/renderer/stores/modalStore.ts
#	src/renderer/stores/settingsStore.ts
#	src/renderer/types/index.ts
Added Alt+I (Unified Inbox) and Cmd+Shift+O (Director's Notes) to the
Global Shortcuts table in keyboard-shortcuts.md with footnote noting
Encore Feature requirement. Also added Unified Inbox to the Available
Features table in encore-features.md.

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

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

✅ 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 27, 2026

📝 Walkthrough

Walkthrough

This PR introduces two new Encore features: Unified Inbox (a modal inbox UI for browsing AI sessions with list and focus conversation modes) and Tab Descriptions (per-tab description editing). It adds supporting types, hooks, stores, components, keyboard shortcuts, documentation, and comprehensive test coverage for the new functionality.

Changes

Cohort / File(s) Summary
Documentation
docs/encore-features.md, docs/keyboard-shortcuts.md
Added feature entries and keyboard shortcuts (macOS and Windows/Linux) for Unified Inbox and Director's Notes with footnote about feature enablement requirements.
Type Definitions
src/renderer/types/agent-inbox.ts, src/renderer/types/index.ts
Introduced InboxItem interface, filter/sort/view mode unions (InboxFilterMode, InboxSortMode, InboxViewMode), status mappings, and new feature flags (unifiedInbox, tabDescription); added optional description field to AITab.
State & Store Management
src/renderer/stores/modalStore.ts, src/renderer/stores/settingsStore.ts
Added AgentInboxModalData type and agentInbox modal variant with state persistence (filterMode, sortMode, isExpanded); introduced unifiedInbox and tabDescription feature flags to defaults.
Core App Integration
src/renderer/App.tsx
Lazy-loaded AgentInbox component; wired modal state (agentInboxOpen, setAgentInboxOpen), navigation, quick reply, mark-as-read, and thinking toggle handlers; passed onOpenAgentInbox callback and handleUpdateTabDescription to sub-components.
Agent Inbox Components
src/renderer/components/AgentInbox/index.tsx, FocusModeView.tsx, InboxListView.tsx
Implemented modal AgentInbox with dual-view system (list and focus modes), FocusModeView with resizable sidebar, conversation rendering with markdown, reply input, and navigation controls; InboxListView with virtualized rows, filtering, sorting, grouping, keyboard navigation, and status display.
Modal & Panel Components
src/renderer/components/AppModals.tsx, MainPanel.tsx, QuickActionsModal.tsx, SettingsModal.tsx
Added onOpenAgentInbox callback prop propagation; added Unified Inbox and Tab Descriptions feature cards in SettingsModal; integrated Unified Inbox action in QuickActionsModal; threaded onUpdateTabDescription through MainPanel props.
Tab Description UI
src/renderer/components/TabBar.tsx
Implemented tab description editing/preview UI with state management (isEditingDescription, description draft), save/cancel handlers, keyboard interactions (Enter, Escape), and conditional rendering guarded by onUpdateTabDescription prop.
Hooks & State Logic
src/renderer/hooks/useAgentInbox.ts, useTabHandlers.ts, keyboard/useMainKeyboardHandler.ts, props/useMainPanelProps.ts
Introduced useAgentInbox hook with filtering (all/unread/read/starred) and sorting (newest/oldest/grouped/byAgent) logic; added handleUpdateTabDescription tab handler; added agentInbox keyboard shortcut handler; threaded handleUpdateTabDescription through props hook.
Constants & Utilities
src/renderer/constants/modalPriorities.ts, shortcuts.ts, src/renderer/utils/tabDisplayName.ts, search.ts
Added AGENT_INBOX modal priority (555); defined agentInbox shortcut (Alt+I); introduced getTabDisplayName utility for deriving tab names from session IDs; added text length guards in fuzzyMatch/fuzzyMatchWithScore.
Tests
src/__tests__/renderer/components/AgentInbox.test.tsx, TabBar.test.tsx, src/__tests__/renderer/hooks/useAgentInbox.test.ts, useTabHandlers.test.ts, src/__tests__/renderer/stores/modalStore.test.ts, src/__tests__/renderer/utils/search.test.ts
Comprehensive test suites covering AgentInbox component (list mode, focus mode, filters, keyboard navigation), TabBar description feature, useAgentInbox hook logic (filtering, sorting, summaries, timestamps), tab description handlers, modal store data updates, and fuzzy search scoring behaviors.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as MaestroConsoleInner
    participant Modal as AgentInbox Modal
    participant InboxList as InboxListView
    participant FocusView as FocusModeView
    participant Session as Session Data

    User->>App: Trigger Unified Inbox (Alt+I or Menu)
    App->>Modal: setAgentInboxOpen(true)
    Modal->>Session: Fetch sessions & groups
    Session-->>Modal: Return liveItems with filter/sort
    Modal->>InboxList: Render with selected index
    
    User->>InboxList: Arrow keys navigate items
    InboxList->>Modal: setSelectedIndex(newIndex)
    Modal->>InboxList: Re-render with new selection
    
    User->>InboxList: Press F or double-click item
    InboxList->>Modal: Trigger enterFocus(index)
    Modal->>FocusView: Switch viewMode to 'focus'
    
    FocusView->>Session: Load session logs & state
    Session-->>FocusView: Return conversation logs
    FocusView->>User: Render logs + reply input
    
    User->>FocusView: Type & submit reply (Cmd/Ctrl+Enter)
    FocusView->>App: handleAgentInboxOpenAndReply(text)
    App->>Session: Navigate to session, processInput(text)
    Session-->>User: Execute & stream response
    
    User->>FocusView: Press Escape
    FocusView->>Modal: Switch viewMode to 'list'
    Modal->>InboxList: Re-render with updated items
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • PR #404 — Touches useTabHandlers.ts to add handleUpdateTabDescription, directly related to tab description feature wiring.
  • PR #418 — Modifies useTabHandlers.ts for tab handling refactoring, overlaps with the tab description handler addition in this PR.
  • PR #460 — Modifies useMainPanelProps.ts prop plumbing and wiring, directly related to the onUpdateTabDescription prop propagation added here.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.97% 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 pull request title accurately summarizes the three main features being rebuilt: tab description, unified inbox, and fuzzy search improvements, which are the primary changes across the changeset.

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

✨ Finishing Touches
🧪 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 27, 2026

Greptile Summary

Comprehensive rebuild of three encore features with isolated worktree development: Unified Inbox (cross-agent triage system), Tab Description (editable context notes), and Fuzzy Search (hardened edge cases). Successfully ports CodeRabbit/Greptile fixes including race condition resolution (setTimeout → RAF), resize listener memory leak fix, proper ARIA roles, and O(n*m) → Map optimization.

Key improvements:

  • Unified Inbox: Focus mode with frozen snapshots, virtual scrolling, keyboard navigation (Alt+I, Cmd+[/]), quick reply with proper state management
  • Tab Description: Edit in overlay menu, saves on blur/Enter, cancels on Escape, syncs with external updates
  • Search: Added validation for empty text and query-longer-than-text edge cases
  • Modal Store: updateModalData now initializes data for unopened modals (breaking change, intentional, tested)
  • Performance: RAF-based focus management, cleanup of event listeners, Map-based lookups
  • Testing: 1200+ lines of new tests covering edge cases, special characters, unicode, emoji, deterministic behavior

All features are properly gated behind encore flags, include comprehensive test coverage, and follow existing architectural patterns.

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • Score reflects comprehensive test coverage (1200+ lines of new tests), proper cleanup of side effects (resize listeners, RAF), intentional behavior changes with updated tests, performance optimizations (Map lookups, virtual scrolling, RAF over setTimeout), proper feature flagging, and well-structured code following existing patterns
  • No files require special attention - all changes are well-tested and properly implemented

Important Files Changed

Filename Overview
src/renderer/stores/modalStore.ts Updated updateModalData to initialize data even for unopened modals, added AgentInboxModalData type and modal registration. Behavior change is intentional and properly tested.
src/renderer/hooks/useAgentInbox.ts Data aggregation hook with filtering, sorting, and smart summary generation. Properly memoized with exact dependencies. Comprehensive test coverage.
src/renderer/components/AgentInbox/index.tsx Main inbox modal with focus mode and list view. Implements frozen snapshot pattern, proper focus restoration, and keyboard navigation. Uses RAF for focus restoration.
src/renderer/components/AgentInbox/FocusModeView.tsx Focus mode with conversation view, sidebar navigation, and quick reply. Includes cleanup for resize listeners. Comprehensive keyboard shortcuts.
src/renderer/components/AgentInbox/InboxListView.tsx List view with virtual scrolling, grouped headers, filters/sorts, and expandable mode. Uses @tanstack/react-virtual for performance.
src/renderer/App.tsx Added inbox handlers (navigate, quick reply, open & reply, mark as read, toggle thinking) with proper state management. Uses ref pattern for processInput to avoid stale closures.
src/renderer/components/TabBar.tsx Added tab description editing UI in overlay menu. Saves on blur/Enter, cancels on Escape. Properly handles external updates and cleanup when overlay closes.
src/renderer/utils/search.ts Added edge case validation: early return when text is empty or query longer than text. Prevents unnecessary processing.
src/tests/renderer/hooks/useAgentInbox.test.ts New comprehensive test suite covering truncate, generateSmartSummary, filtering (all/unread/read/starred), and sorting (newest/oldest/grouped/byAgent). 384 lines of thorough tests.
src/tests/renderer/components/AgentInbox/AgentInbox.test.tsx New test suite for AgentInbox component with 511 lines covering rendering, interactions, and edge cases.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start([User presses Alt+I]) --> OpenInbox[Open AgentInbox Modal]
    OpenInbox --> CheckMode{View Mode?}
    
    CheckMode -->|List| ListView[InboxListView]
    CheckMode -->|Focus| FocusView[FocusModeView]
    
    ListView --> UseAgentInbox[useAgentInbox Hook]
    UseAgentInbox --> FilterSort[Filter & Sort Sessions/Tabs]
    FilterSort --> BuildItems[Build InboxItem array]
    BuildItems --> VirtualScroll[Virtual Scrolling with react-virtual]
    
    VirtualScroll --> UserAction{User Action}
    UserAction -->|F key| EnterFocus[Enter Focus Mode]
    UserAction -->|Click item| Navigate[Navigate to Session]
    UserAction -->|Change filter| UpdateFilter[Update Filter Mode]
    UserAction -->|Esc| CloseModal[Close Modal & Restore Focus]
    
    EnterFocus --> FreezeSnapshot[Freeze Item Order]
    FreezeSnapshot --> FocusView
    
    FocusView --> DisplayConvo[Display Conversation Logs]
    DisplayConvo --> ShowSidebar[Show Sidebar with Items]
    ShowSidebar --> FocusActions{User Action in Focus}
    
    FocusActions -->|Type & Send| QuickReply[Quick Reply Flow]
    FocusActions -->|Cmd+Bracket| PrevNext[Navigate Prev/Next Item]
    FocusActions -->|Esc or Back| ExitFocus[Exit to List View]
    FocusActions -->|Click item| JumpToItem[Jump to Item in Frozen List]
    
    QuickReply --> SetPending[Set pendingInboxQuickReply]
    SetPending --> SwitchSession[Switch to Target Session]
    SwitchSession --> WaitEffect[useEffect detects active session]
    WaitEffect --> CallProcessInput[Call processInput via Ref]
    CallProcessInput --> RestoreSession[Restore Previous Session]
    
    Navigate --> CloseAndSwitch[Close Modal & Switch Session]
    CloseAndSwitch --> UpdateActiveTab[Update activeTabId & inputMode]
    
    UpdateFilter --> Recompute[Recompute useAgentInbox]
    Recompute --> UpdateList[Update Item List]
    
    ExitFocus --> UnfreezeSnapshot[Clear Frozen Order]
    UnfreezeSnapshot --> ListView
Loading

Last reviewed commit: 2c0b925

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: 6

🧹 Nitpick comments (5)
src/renderer/utils/tabDisplayName.ts (1)

39-39: Consider using "New Agent" for user-facing consistency.

Per coding guidelines, user-facing language should use "agent" while "session" is reserved for provider-level conversation contexts. The fallback "New Session" appears in the UI; "New Agent" may align better with the terminology conventions.

This is a minor terminology nit—feel free to keep as-is if there's a specific reason for the current wording.

Based on learnings: "Use the Session interface in code to represent agents (due to historical naming), but use 'agent' in user-facing language and 'session' for provider-level conversation contexts"

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

In `@src/renderer/utils/tabDisplayName.ts` at line 39, The fallback user-facing
label currently returns "New Session" in tabDisplayName.ts; change this to "New
Agent" so UI terminology uses "agent" (while code can keep Session naming).
Update the return value in the function that produces the tab title (the
fallback branch that currently returns 'New Session') to return 'New Agent' to
align with the project's user-facing language guidelines.
src/__tests__/renderer/components/TabBar.test.tsx (1)

5784-5791: Remove unnecessary as any casts from description tab fixtures.

The AITab interface already defines description?: string (line 429 in src/renderer/types/index.ts), and createTab accepts Partial<AITab>, making the casts at lines 5790 and 5881 redundant. Remove them to maintain type safety in tests.

♻️ Proposed cleanup
-			} as any),
+			}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/components/TabBar.test.tsx` around lines 5784 - 5791,
Tests are using unnecessary "as any" casts on the createTab fixtures even though
AITab defines description?: string and createTab accepts Partial<AITab>; remove
the "as any" casts from the createTab calls that pass description (e.g., the
fixture in TabBar.test.tsx that constructs tabs with id 'tab-1' and the other
occurrence) so the calls use createTab({...}) directly, preserving type safety
for AITab and keeping the description as a plain string.
src/renderer/stores/modalStore.ts (1)

774-777: Type updateAgentInboxData with Partial<AgentInboxModalData> instead of Record<string, unknown>.

Line 777 currently permits arbitrary payload shape, which weakens the modal contract. The AgentInboxModalData interface already exists and matches the call site payload at InboxListView.tsx:517, making this a straightforward improvement to type safety.

🔧 Suggested signature tightening
-		updateAgentInboxData: (data: Record<string, unknown>) => updateModalData('agentInbox', data),
+		updateAgentInboxData: (data: Partial<AgentInboxModalData>) =>
+			updateModalData('agentInbox', data),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/stores/modalStore.ts` around lines 774 - 777, Change the
updateAgentInboxData setter to accept Partial<AgentInboxModalData> instead of
Record<string, unknown> to tighten the modal payload contract: locate the
updateAgentInboxData definition in modalStore.ts and replace its parameter type
with Partial<AgentInboxModalData>, and if necessary import or reference the
AgentInboxModalData type; ensure the call into updateModalData('agentInbox',
...) remains compatible (adjust generics on updateModalData if used).
src/renderer/types/agent-inbox.ts (1)

38-44: Add a StatusColorKey union type to strengthen STATUS_COLORS typing.

The current Record<SessionState, string> allows arbitrary strings and would silently fall back to a default color if a typo is introduced. Constraining to a union type catches invalid color keys at compile time.

♻️ Suggested typing hardening
+type StatusColorKey = 'success' | 'warning' | 'info' | 'textMuted' | 'error';
+
-export const STATUS_COLORS: Record<SessionState, string> = {
+export const STATUS_COLORS: Record<SessionState, StatusColorKey> = {
 	idle: 'success',
 	waiting_input: 'warning',
 	busy: 'info',
 	connecting: 'textMuted',
 	error: 'error',
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/types/agent-inbox.ts` around lines 38 - 44, Add a union type
(e.g., StatusColorKey = 'success' | 'warning' | 'info' | 'textMuted' | 'error')
and use it to tighten STATUS_COLORS typing so it becomes Record<SessionState,
StatusColorKey> (or a mapped type SessionState -> StatusColorKey) instead of
Record<SessionState, string>; update the export if needed and adjust any code
that consumes STATUS_COLORS to expect the new StatusColorKey type. Ensure the
union covers the existing values in STATUS_COLORS and update any tests/types
that assumed arbitrary strings.
src/renderer/components/AgentInbox/InboxListView.tsx (1)

1265-1271: Single-click navigation makes the double-click path effectively unreachable.

Line 1269 navigates/closes immediately, so Line 1270’s double-click “enter focus mode” handler won’t realistically execute. Either remove the double-click behavior or make single-click selection-only.

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

In `@src/renderer/components/AgentInbox/InboxListView.tsx` around lines 1265 -
1271, The single-click on InboxItemCardContent currently calls handleNavigate
which immediately navigates/ closes the list and prevents the onDoubleClick
onEnterFocus handler from firing; change the click behavior so single-click only
toggles selection (e.g., call a selection handler like setSelected or
onSelectRow instead of handleNavigate) and reserve handleNavigate or
onEnterFocus for double-click or explicit open actions, or alternately remove
the onDoubleClick prop; update the JSX for InboxItemCardContent to use a
selection-only onClick and keep onDoubleClick={() => onEnterFocus(row.item)} (or
move navigation to the double-click) so both interactions are reachable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx`:
- Around line 111-126: The test fixture makeSession creates aiTabs via makeTab()
but hardcodes activeTabId to 'default-tab', which can mismatch; update
makeSession to generate the tab first (e.g., const tab = makeTab()), set aiTabs:
[tab], and set activeTabId: tab.id (or fallback to tab.id || 'default-tab') so
activeTabId always matches an existing aiTabs entry; adjust any other places in
makeSession that rely on the previous hardcoded value.

In `@src/renderer/components/AgentInbox/FocusModeView.tsx`:
- Around line 595-597: The reply handlers handleQuickReply and
handleOpenAndReply currently don't call the provided onMarkAsRead callback
despite the comment stating replies should mark messages read; update both
handlers to invoke _onMarkAsRead?.(item.sessionId, item.tabId) at the point
where a reply is performed, and add _onMarkAsRead to each handler's dependency
array so React's memoization picks up the prop change (refer to the
FocusModeView props onMarkAsRead/_onMarkAsRead and the functions
handleQuickReply and handleOpenAndReply).

In `@src/renderer/components/AgentInbox/InboxListView.tsx`:
- Around line 1141-1177: The header button in InboxListView (the button
key={`header-${row.groupKey}`} that calls toggleGroup(row.groupKey)) removes the
default focus outline (outline-none) but doesn't provide any visible focus
replacement, making keyboard focus unclear; restore keyboard accessibility by
adding tabIndex={0}, onFocus/onBlur handlers (e.g., set a local focused state
for that row or call an existing focus handler) and apply a visible focus style
(replace outline-none with a focus style such as a focus ring or visible
border/background change when focused, using isRowSelected or the new focused
state to compute the style) so Enter/Space activation still works and keyboard
focus is visually apparent.

In `@src/renderer/components/AgentInbox/index.tsx`:
- Around line 51-53: The initialization of triggerRef reads
document.activeElement during module/render which can throw in non-DOM
environments; change the useRef initialization to start as null and move the
document.activeElement check into a safe runtime step (e.g., inside a useEffect
or guard with typeof document !== "undefined" or typeof window !== "undefined")
and, if it's an HTMLElement, assign it to triggerRef.current; update references
to triggerRef to rely on the runtime assignment rather than reading document at
module/init time.

In `@src/renderer/components/SettingsModal.tsx`:
- Around line 3687-3690: The new switch buttons in SettingsModal are missing
accessible names; update the button element(s) that use role="switch" (e.g., the
one bound to encoreFeatures.unifiedInbox and the other switch near the same
block) to include an aria-label or aria-labelledby that describes the control
(for example aria-label="Toggle Unified Inbox" or aria-labelledby referencing a
visible label id). Ensure the label text clearly reflects the setting (use the
human-readable name for the feature key, e.g., "Unified Inbox") and keep
consistency for the other switch control(s) in the same component.

In `@src/renderer/components/TabBar.tsx`:
- Around line 763-765: The textarea in TabBar.tsx currently uses an inline ref
callback (ref={(el) => el?.focus()}) which creates a new function each render
and forces focus on every keystroke; replace this behavior by removing the
inline ref callback and using autoFocus on the textarea (or a stable ref with a
one-time focus in a useEffect if explicit focus is required) so editing doesn't
fight the cursor—update the element rendering that references descriptionDraft
to use autoFocus and remove the ref callback to prevent repeated focus calls.

---

Nitpick comments:
In `@src/__tests__/renderer/components/TabBar.test.tsx`:
- Around line 5784-5791: Tests are using unnecessary "as any" casts on the
createTab fixtures even though AITab defines description?: string and createTab
accepts Partial<AITab>; remove the "as any" casts from the createTab calls that
pass description (e.g., the fixture in TabBar.test.tsx that constructs tabs with
id 'tab-1' and the other occurrence) so the calls use createTab({...}) directly,
preserving type safety for AITab and keeping the description as a plain string.

In `@src/renderer/components/AgentInbox/InboxListView.tsx`:
- Around line 1265-1271: The single-click on InboxItemCardContent currently
calls handleNavigate which immediately navigates/ closes the list and prevents
the onDoubleClick onEnterFocus handler from firing; change the click behavior so
single-click only toggles selection (e.g., call a selection handler like
setSelected or onSelectRow instead of handleNavigate) and reserve handleNavigate
or onEnterFocus for double-click or explicit open actions, or alternately remove
the onDoubleClick prop; update the JSX for InboxItemCardContent to use a
selection-only onClick and keep onDoubleClick={() => onEnterFocus(row.item)} (or
move navigation to the double-click) so both interactions are reachable.

In `@src/renderer/stores/modalStore.ts`:
- Around line 774-777: Change the updateAgentInboxData setter to accept
Partial<AgentInboxModalData> instead of Record<string, unknown> to tighten the
modal payload contract: locate the updateAgentInboxData definition in
modalStore.ts and replace its parameter type with Partial<AgentInboxModalData>,
and if necessary import or reference the AgentInboxModalData type; ensure the
call into updateModalData('agentInbox', ...) remains compatible (adjust generics
on updateModalData if used).

In `@src/renderer/types/agent-inbox.ts`:
- Around line 38-44: Add a union type (e.g., StatusColorKey = 'success' |
'warning' | 'info' | 'textMuted' | 'error') and use it to tighten STATUS_COLORS
typing so it becomes Record<SessionState, StatusColorKey> (or a mapped type
SessionState -> StatusColorKey) instead of Record<SessionState, string>; update
the export if needed and adjust any code that consumes STATUS_COLORS to expect
the new StatusColorKey type. Ensure the union covers the existing values in
STATUS_COLORS and update any tests/types that assumed arbitrary strings.

In `@src/renderer/utils/tabDisplayName.ts`:
- Line 39: The fallback user-facing label currently returns "New Session" in
tabDisplayName.ts; change this to "New Agent" so UI terminology uses "agent"
(while code can keep Session naming). Update the return value in the function
that produces the tab title (the fallback branch that currently returns 'New
Session') to return 'New Agent' to align with the project's user-facing language
guidelines.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d612b6 and 2c0b925.

📒 Files selected for processing (29)
  • docs/encore-features.md
  • docs/keyboard-shortcuts.md
  • src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx
  • src/__tests__/renderer/components/TabBar.test.tsx
  • src/__tests__/renderer/hooks/useAgentInbox.test.ts
  • src/__tests__/renderer/hooks/useTabHandlers.test.ts
  • src/__tests__/renderer/stores/modalStore.test.ts
  • src/__tests__/renderer/utils/search.test.ts
  • src/renderer/App.tsx
  • src/renderer/components/AgentInbox/FocusModeView.tsx
  • src/renderer/components/AgentInbox/InboxListView.tsx
  • src/renderer/components/AgentInbox/index.tsx
  • src/renderer/components/AppModals.tsx
  • src/renderer/components/MainPanel.tsx
  • src/renderer/components/QuickActionsModal.tsx
  • src/renderer/components/SettingsModal.tsx
  • src/renderer/components/TabBar.tsx
  • src/renderer/constants/modalPriorities.ts
  • src/renderer/constants/shortcuts.ts
  • src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
  • src/renderer/hooks/props/useMainPanelProps.ts
  • src/renderer/hooks/tabs/useTabHandlers.ts
  • src/renderer/hooks/useAgentInbox.ts
  • src/renderer/stores/modalStore.ts
  • src/renderer/stores/settingsStore.ts
  • src/renderer/types/agent-inbox.ts
  • src/renderer/types/index.ts
  • src/renderer/utils/search.ts
  • src/renderer/utils/tabDisplayName.ts

Comment on lines +111 to +126
const makeSession = (overrides: Partial<Session> = {}): Session =>
({
id: `s-${Math.random().toString(36).slice(2, 8)}`,
name: 'Agent Alpha',
toolType: 'claude-code',
state: 'idle',
cwd: '/test',
fullPath: '/test',
projectRoot: '/test',
port: 0,
aiPid: 0,
terminalPid: 0,
inputMode: 'ai',
aiTabs: [makeTab()],
activeTabId: 'default-tab',
closedTabHistory: [],
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

Keep activeTabId consistent with generated aiTabs in makeSession.

The fixture currently sets activeTabId: 'default-tab' even when that tab ID does not exist. This can make focus/selection behavior tests pass for the wrong reason.

🐛 Suggested fixture fix
-const makeSession = (overrides: Partial<Session> = {}): Session =>
-	({
+const makeSession = (overrides: Partial<Session> = {}): Session => {
+	const aiTabs = (overrides.aiTabs ?? [makeTab()]) as Session['aiTabs'];
+	const activeTabId = overrides.activeTabId ?? aiTabs[0]?.id ?? '';
+	return ({
 		id: `s-${Math.random().toString(36).slice(2, 8)}`,
 		name: 'Agent Alpha',
@@
-		aiTabs: [makeTab()],
-		activeTabId: 'default-tab',
+		aiTabs,
+		activeTabId,
@@
-		...overrides,
-	}) as unknown as Session;
+		...overrides,
+		aiTabs,
+		activeTabId,
+	}) as unknown as Session;
+};
📝 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 makeSession = (overrides: Partial<Session> = {}): Session =>
({
id: `s-${Math.random().toString(36).slice(2, 8)}`,
name: 'Agent Alpha',
toolType: 'claude-code',
state: 'idle',
cwd: '/test',
fullPath: '/test',
projectRoot: '/test',
port: 0,
aiPid: 0,
terminalPid: 0,
inputMode: 'ai',
aiTabs: [makeTab()],
activeTabId: 'default-tab',
closedTabHistory: [],
const makeSession = (overrides: Partial<Session> = {}): Session => {
const aiTabs = (overrides.aiTabs ?? [makeTab()]) as Session['aiTabs'];
const activeTabId = overrides.activeTabId ?? aiTabs[0]?.id ?? '';
return ({
id: `s-${Math.random().toString(36).slice(2, 8)}`,
name: 'Agent Alpha',
toolType: 'claude-code',
state: 'idle',
cwd: '/test',
fullPath: '/test',
projectRoot: '/test',
port: 0,
aiPid: 0,
terminalPid: 0,
inputMode: 'ai',
closedTabHistory: [],
...overrides,
aiTabs,
activeTabId,
}) as unknown as Session;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx` around
lines 111 - 126, The test fixture makeSession creates aiTabs via makeTab() but
hardcodes activeTabId to 'default-tab', which can mismatch; update makeSession
to generate the tab first (e.g., const tab = makeTab()), set aiTabs: [tab], and
set activeTabId: tab.id (or fallback to tab.id || 'default-tab') so activeTabId
always matches an existing aiTabs entry; adjust any other places in makeSession
that rely on the previous hardcoded value.

Comment on lines +595 to +597
onMarkAsRead: _onMarkAsRead,
onToggleThinking,
}: FocusModeViewProps) {
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
# Read the file to see the actual code at the specified lines and surrounding context
wc -l src/renderer/components/AgentInbox/FocusModeView.tsx

Repository: RunMaestro/Maestro

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Get the full file to understand the structure and verify the prop usage
head -650 src/renderer/components/AgentInbox/FocusModeView.tsx | tail -100

Repository: RunMaestro/Maestro

Length of output: 2579


🏁 Script executed:

#!/bin/bash
# Search for onMarkAsRead and _onMarkAsRead usage in the file
rg -n "onMarkAsRead|_onMarkAsRead" src/renderer/components/AgentInbox/FocusModeView.tsx

Repository: RunMaestro/Maestro

Length of output: 159


🏁 Script executed:

#!/bin/bash
# Check if handleQuickReply and handleOpenAndReply are defined and what they call
rg -n "handleQuickReply|handleOpenAndReply" src/renderer/components/AgentInbox/FocusModeView.tsx -A 8

Repository: RunMaestro/Maestro

Length of output: 2344


🏁 Script executed:

#!/bin/bash
# Search for read-state transitions in the broader AgentInbox component
rg -n "hasUnread|mark.*read|markAsRead|onMarkAsRead" src/renderer/components/AgentInbox/ -g '*.ts' -g '*.tsx'

Repository: RunMaestro/Maestro

Length of output: 956


🏁 Script executed:

#!/bin/bash
# Read lines around 595 to see if there are comments explaining the unused prop
sed -n '580,610p' src/renderer/components/AgentInbox/FocusModeView.tsx

Repository: RunMaestro/Maestro

Length of output: 871


🏁 Script executed:

#!/bin/bash
# Check for comments around the handlers area
sed -n '740,780p' src/renderer/components/AgentInbox/FocusModeView.tsx

Repository: RunMaestro/Maestro

Length of output: 1314


🏁 Script executed:

#!/bin/bash
# See how the parent component (index.tsx) uses onMarkAsRead and these handlers
rg -n "onMarkAsRead|handleQuickReply|handleOpenAndReply|onQuickReply|onOpenAndReply" src/renderer/components/AgentInbox/index.tsx -A 3 -B 1

Repository: RunMaestro/Maestro

Length of output: 865


🏁 Script executed:

#!/bin/bash
# Check parent component to see if it marks items as read when onQuickReply/onOpenAndReply are called
sed -n '40,100p' src/renderer/components/AgentInbox/index.tsx

Repository: RunMaestro/Maestro

Length of output: 2331


🏁 Script executed:

#!/bin/bash
# Search for where onQuickReply and onOpenAndReply are implemented in parent
rg -n "const onQuickReply|const onOpenAndReply|function onQuickReply|function onOpenAndReply" src/renderer/components/AgentInbox/index.tsx -A 5

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if the parent marks items as read anywhere, especially in response to replies
rg -n "markAsRead\|_onMarkAsRead\|hasUnread.*false\|setHasUnread" src/renderer/components/AgentInbox/index.tsx -B 2 -A 2

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search more broadly for onQuickReply and onOpenAndReply to see where they come from
rg -n "onQuickReply\|onOpenAndReply" src/renderer/components/AgentInbox/index.tsx -B 2 -A 2

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check the parent component that uses AgentInbox to see if it implements these handlers
fd -e tsx -e ts src/renderer/components | xargs grep -l "AgentInbox" | head -5

Repository: RunMaestro/Maestro

Length of output: 459


🏁 Script executed:

#!/bin/bash
# Look at the prop interface to understand the contract
sed -n '25,50p' src/renderer/components/AgentInbox/index.tsx

Repository: RunMaestro/Maestro

Length of output: 849


🏁 Script executed:

#!/bin/bash
# Find who uses AgentInbox and how they implement the handlers
fd . src/renderer --full-path -name '*.tsx' -o -name '*.ts' | xargs grep -l "AgentInbox" | head -3

Repository: RunMaestro/Maestro

Length of output: 292


🏁 Script executed:

#!/bin/bash
# Check if there are any comments in FocusModeView about read-state transitions
rg -n "read|unread|mark" src/renderer/components/AgentInbox/FocusModeView.tsx -i | head -20

Repository: RunMaestro/Maestro

Length of output: 1209


🏁 Script executed:

#!/bin/bash
# Look at InboxListView to see if there are patterns of how read state is managed
sed -n '1,100p' src/renderer/components/AgentInbox/InboxListView.tsx | grep -n "onMarkAsRead\|onQuickReply\|onOpenAndReply"

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Get the comment and code around line 786
sed -n '780,800p' src/renderer/components/AgentInbox/FocusModeView.tsx

Repository: RunMaestro/Maestro

Length of output: 700


🏁 Script executed:

#!/bin/bash
# Check InboxListView to understand the broader pattern
rg -n "onMarkAsRead|onQuickReply|onOpenAndReply" src/renderer/components/AgentInbox/InboxListView.tsx -B 2 -A 2

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for any implementation of onMarkAsRead in the codebase to understand the expected behavior
rg -n "onMarkAsRead\s*=" src/renderer --type ts --type tsx -B 2 -A 5

Repository: RunMaestro/Maestro

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Search for implementations of onMarkAsRead callback in the actual consumer code
rg -n "onMarkAsRead\s*=\s*" src/renderer -g '*.tsx' -g '*.ts' -A 3

Repository: RunMaestro/Maestro

Length of output: 594


🏁 Script executed:

#!/bin/bash
# Check InboxListView with better pattern matching
rg -n "const onMarkAsRead\|const handleMark\|markAsRead" src/renderer/components/AgentInbox/InboxListView.tsx -A 5

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Find the handleAgentInboxMarkAsRead implementation in App.tsx
rg -n "handleAgentInboxMarkAsRead" src/renderer/App.tsx -A 10

Repository: RunMaestro/Maestro

Length of output: 850


🏁 Script executed:

#!/bin/bash
# Also check what happens with onQuickReply and onOpenAndReply in App.tsx
rg -n "handleAgentInboxQuickReply\|handleAgentInboxOpenAndReply\|onQuickReply\|onOpenAndReply" src/renderer/App.tsx -B 2 -A 5 | head -60

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Verify the handlers are never calling onMarkAsRead - search full function bodies
sed -n '755,771p' src/renderer/components/AgentInbox/FocusModeView.tsx

Repository: RunMaestro/Maestro

Length of output: 521


Wire onMarkAsRead calls in reply handlers to match the stated behavior.

The inline comment at line 786 states "Mark as read only on explicit interaction (reply)", but the handlers don't invoke the callback. Add _onMarkAsRead?.(item.sessionId, item.tabId) to both handleQuickReply and handleOpenAndReply, and include _onMarkAsRead in their dependency arrays.

Suggested fix
const handleQuickReply = useCallback(() => {
	const text = replyText.trim();
	if (!text) return;
	if (onQuickReply) {
		onQuickReply(item.sessionId, item.tabId, text);
	}
+	_onMarkAsRead?.(item.sessionId, item.tabId);
	setReplyText('');
-}, [replyText, item, onQuickReply]);
+}, [replyText, item, onQuickReply, _onMarkAsRead]);

const handleOpenAndReply = useCallback(() => {
	const text = replyText.trim();
	if (!text) return;
	if (onOpenAndReply) {
		onOpenAndReply(item.sessionId, item.tabId, text);
	}
+	_onMarkAsRead?.(item.sessionId, item.tabId);
-}, [replyText, item, onOpenAndReply]);
+}, [replyText, item, onOpenAndReply, _onMarkAsRead]);

Also applies to: 755–770

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

In `@src/renderer/components/AgentInbox/FocusModeView.tsx` around lines 595 - 597,
The reply handlers handleQuickReply and handleOpenAndReply currently don't call
the provided onMarkAsRead callback despite the comment stating replies should
mark messages read; update both handlers to invoke
_onMarkAsRead?.(item.sessionId, item.tabId) at the point where a reply is
performed, and add _onMarkAsRead to each handler's dependency array so React's
memoization picks up the prop change (refer to the FocusModeView props
onMarkAsRead/_onMarkAsRead and the functions handleQuickReply and
handleOpenAndReply).

Comment on lines +1141 to +1177
<button
type="button"
key={`header-${row.groupKey}`}
className="outline-none"
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
display: 'flex',
alignItems: 'center',
paddingLeft: 16,
paddingRight: 16,
fontSize: 13,
fontWeight: 600,
color: isRowSelected ? theme.colors.accent : theme.colors.textDim,
letterSpacing: '0.5px',
textTransform: 'uppercase',
borderBottom: `2px solid ${theme.colors.border}40`,
borderLeft: isRowSelected
? `3px solid ${theme.colors.accent}`
: '3px solid transparent',
backgroundColor: isRowSelected ? `${theme.colors.accent}10` : 'transparent',
cursor: 'pointer',
borderTop: 'none',
borderRight: 'none',
textAlign: 'left',
}}
onClick={() => toggleGroup(row.groupKey)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleGroup(row.groupKey);
}
}}
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

Restore visible keyboard focus on collapsible group headers.

Line 1144 removes default focus styling (outline-none) on a focusable control, but no replacement focus state is applied. That makes keyboard focus location unclear in grouped modes.

♿ Suggested fix
 										<button
 											type="button"
 											key={`header-${row.groupKey}`}
-											className="outline-none"
+											className="outline-none"
+											tabIndex={0}
 											style={{
 												position: 'absolute',
@@
 											onClick={() => toggleGroup(row.groupKey)}
+											onFocus={(e) => {
+												e.currentTarget.style.outline = `2px solid ${theme.colors.accent}`;
+												e.currentTarget.style.outlineOffset = '-2px';
+											}}
+											onBlur={(e) => {
+												e.currentTarget.style.outline = 'none';
+											}}
 											onKeyDown={(e) => {
 												if (e.key === 'Enter' || e.key === ' ') {
As per coding guidelines `src/renderer/components/**/*.{ts,tsx}: Add tabIndex attribute and focus event handlers when implementing components that need keyboard focus`.
📝 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
<button
type="button"
key={`header-${row.groupKey}`}
className="outline-none"
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
display: 'flex',
alignItems: 'center',
paddingLeft: 16,
paddingRight: 16,
fontSize: 13,
fontWeight: 600,
color: isRowSelected ? theme.colors.accent : theme.colors.textDim,
letterSpacing: '0.5px',
textTransform: 'uppercase',
borderBottom: `2px solid ${theme.colors.border}40`,
borderLeft: isRowSelected
? `3px solid ${theme.colors.accent}`
: '3px solid transparent',
backgroundColor: isRowSelected ? `${theme.colors.accent}10` : 'transparent',
cursor: 'pointer',
borderTop: 'none',
borderRight: 'none',
textAlign: 'left',
}}
onClick={() => toggleGroup(row.groupKey)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleGroup(row.groupKey);
}
}}
<button
type="button"
key={`header-${row.groupKey}`}
className="outline-none"
tabIndex={0}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
display: 'flex',
alignItems: 'center',
paddingLeft: 16,
paddingRight: 16,
fontSize: 13,
fontWeight: 600,
color: isRowSelected ? theme.colors.accent : theme.colors.textDim,
letterSpacing: '0.5px',
textTransform: 'uppercase',
borderBottom: `2px solid ${theme.colors.border}40`,
borderLeft: isRowSelected
? `3px solid ${theme.colors.accent}`
: '3px solid transparent',
backgroundColor: isRowSelected ? `${theme.colors.accent}10` : 'transparent',
cursor: 'pointer',
borderTop: 'none',
borderRight: 'none',
textAlign: 'left',
}}
onClick={() => toggleGroup(row.groupKey)}
onFocus={(e) => {
e.currentTarget.style.outline = `2px solid ${theme.colors.accent}`;
e.currentTarget.style.outlineOffset = '-2px';
}}
onBlur={(e) => {
e.currentTarget.style.outline = 'none';
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleGroup(row.groupKey);
}
}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox/InboxListView.tsx` around lines 1141 -
1177, The header button in InboxListView (the button
key={`header-${row.groupKey}`} that calls toggleGroup(row.groupKey)) removes the
default focus outline (outline-none) but doesn't provide any visible focus
replacement, making keyboard focus unclear; restore keyboard accessibility by
adding tabIndex={0}, onFocus/onBlur handlers (e.g., set a local focused state
for that row or call an existing focus handler) and apply a visible focus style
(replace outline-none with a focus style such as a focus ring or visible
border/background change when focused, using isRowSelected or the new focused
state to compute the style) so Enter/Space activation still works and keyboard
focus is visually apparent.

Comment on lines +51 to +53
const triggerRef = useRef<HTMLElement | null>(
document.activeElement instanceof HTMLElement ? document.activeElement : null
);
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

Guard document access during initial render.

Line 52 reads document.activeElement unconditionally; this can throw in non-DOM test/render environments.

🛡️ Suggested fix
-	const triggerRef = useRef<HTMLElement | null>(
-		document.activeElement instanceof HTMLElement ? document.activeElement : null
-	);
+	const triggerRef = useRef<HTMLElement | null>(
+		typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
+			? document.activeElement
+			: null
+	);
📝 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 triggerRef = useRef<HTMLElement | null>(
document.activeElement instanceof HTMLElement ? document.activeElement : null
);
const triggerRef = useRef<HTMLElement | null>(
typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
? document.activeElement
: null
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox/index.tsx` around lines 51 - 53, The
initialization of triggerRef reads document.activeElement during module/render
which can throw in non-DOM environments; change the useRef initialization to
start as null and move the document.activeElement check into a safe runtime step
(e.g., inside a useEffect or guard with typeof document !== "undefined" or
typeof window !== "undefined") and, if it's an HTMLElement, assign it to
triggerRef.current; update references to triggerRef to rely on the runtime
assignment rather than reading document at module/init time.

Comment on lines +3687 to +3690
<button
role="switch"
aria-checked={encoreFeatures.unifiedInbox}
onClick={(e) => {
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

Add accessible names to the new switch controls.

These role="switch" buttons are missing an accessible name, so assistive tech will announce unlabeled controls. Add aria-label (or aria-labelledby) for each.

Suggested patch
 									<button
 										role="switch"
+										aria-label="Toggle Unified Inbox feature"
 										aria-checked={encoreFeatures.unifiedInbox}
 										onClick={(e) => {
@@
 									<button
 										role="switch"
+										aria-label="Toggle Tab Descriptions feature"
 										aria-checked={encoreFeatures.tabDescription}
 										onClick={(e) => {

Also applies to: 3744-3747

🤖 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 3687 - 3690, The new
switch buttons in SettingsModal are missing accessible names; update the button
element(s) that use role="switch" (e.g., the one bound to
encoreFeatures.unifiedInbox and the other switch near the same block) to include
an aria-label or aria-labelledby that describes the control (for example
aria-label="Toggle Unified Inbox" or aria-labelledby referencing a visible label
id). Ensure the label text clearly reflects the setting (use the human-readable
name for the feature key, e.g., "Unified Inbox") and keep consistency for the
other switch control(s) in the same component.

Comment on lines +763 to +765
<textarea
ref={(el) => el?.focus()}
value={descriptionDraft}
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

🧩 Analysis chain

🏁 Script executed:

# Read the relevant section to see full context
cat -n src/renderer/components/TabBar.tsx | sed -n '750,820p'

Repository: RunMaestro/Maestro

Length of output: 2885


🏁 Script executed:

# Check if autoFocus is already used elsewhere in this file
rg -n "autoFocus" src/renderer/components/TabBar.tsx

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

# Verify the exact ref callback pattern
rg -n "ref=\{" src/renderer/components/TabBar.tsx | head -20

Repository: RunMaestro/Maestro

Length of output: 215


Avoid focusing the editor through an inline callback ref.

Line 764 uses a new ref callback each render. Since every keystroke triggers a re-render through the onChange handler, this causes repeated focus calls that can interfere with cursor position during editing. Use autoFocus instead—the textarea is conditionally rendered and will mount fresh when entering edit mode.

Suggested fix
-										<textarea
-											ref={(el) => el?.focus()}
+										<textarea
+											autoFocus
📝 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
<textarea
ref={(el) => el?.focus()}
value={descriptionDraft}
<textarea
autoFocus
value={descriptionDraft}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/TabBar.tsx` around lines 763 - 765, The textarea in
TabBar.tsx currently uses an inline ref callback (ref={(el) => el?.focus()})
which creates a new function each render and forces focus on every keystroke;
replace this behavior by removing the inline ref callback and using autoFocus on
the textarea (or a stable ref with a one-time focus in a useEffect if explicit
focus is required) so editing doesn't fight the cursor—update the element
rendering that references descriptionDraft to use autoFocus and remove the ref
callback to prevent repeated focus calls.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

This PR introduces two new Encore features: Unified Inbox, a virtualized modal inbox for browsing AI agent sessions with list and detail views, and Tab Descriptions, enabling per-AI-tab user-defined descriptions. Implementation includes new components, comprehensive tests, store/hook updates, UI wiring across multiple surfaces, keyboard shortcuts, and documentation updates.

Changes

Cohort / File(s) Summary
Documentation
docs/encore-features.md, docs/keyboard-shortcuts.md
Added Unified Inbox and Tab Descriptions features to feature matrix; introduced keyboard shortcuts (Alt+I for Unified Inbox, platform-specific bindings for Tab Descriptions) with conditional footnote about feature enablement.
Type Definitions & Constants
src/renderer/types/agent-inbox.ts, src/renderer/types/index.ts, src/renderer/constants/shortcuts.ts, src/renderer/constants/modalPriorities.ts
New agent-inbox types (InboxItem, InboxSortMode, InboxFilterMode, STATUS_LABELS, STATUS_COLORS); extended AITab with optional description field and EncoreFeatureFlags with unifiedInbox/tabDescription toggles; added shortcuts.agentInbox and MODAL_PRIORITIES.AGENT_INBOX.
Agent Inbox Components
src/renderer/components/AgentInbox/index.tsx, src/renderer/components/AgentInbox/InboxListView.tsx, src/renderer/components/AgentInbox/FocusModeView.tsx
New AgentInbox modal component with dual-view architecture: InboxListView (virtualized list with sorting/filtering, keyboard navigation, group collapsing) and FocusModeView (resizable two-pane focus detail view with chat logs, reply textarea, thinking toggle, item navigation); supports list↔focus mode transitions with frozen item ordering.
State Management & Hooks
src/renderer/stores/modalStore.ts, src/renderer/stores/settingsStore.ts, src/renderer/hooks/useAgentInbox.ts, src/renderer/hooks/tabs/useTabHandlers.ts, src/renderer/hooks/props/useMainPanelProps.ts
Added Agent Inbox modal data, actions, and selectors to modalStore; initialized unifiedInbox/tabDescription feature flags in settingsStore; introduced useAgentInbox hook with filtering/sorting/summary logic; added handleUpdateTabDescription handler to useTabHandlers; threaded description prop through useMainPanelProps.
UI Component Wiring
src/renderer/App.tsx, src/renderer/components/MainPanel.tsx, src/renderer/components/TabBar.tsx, src/renderer/components/SettingsModal.tsx, src/renderer/components/AppModals.tsx, src/renderer/components/QuickActionsModal.tsx
Extended App.tsx with Agent Inbox lazy loading, modal integration, and navigation/reply callbacks; added onUpdateTabDescription prop to MainPanel and TabBar for tab description editing; integrated Tab Descriptions UI in TabBar with edit mode and state sync; added Unified Inbox and Tab Descriptions toggle cards to SettingsModal; wired onOpenAgentInbox callback through AppModals→QuickActionsModal; added Agent Inbox action to QuickActionsModal.
Utilities & Keyboard
src/renderer/utils/tabDisplayName.ts, src/renderer/utils/search.ts, src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
New getTabDisplayName utility for deriving friendly tab labels from session IDs; added input validation guards to fuzzy search functions; integrated agentInbox shortcut handler (Alt+I) into useMainKeyboardHandler when unifiedInbox feature is enabled.
Test Coverage
src/__tests__/renderer/components/AgentInbox.test.tsx, src/__tests__/renderer/components/TabBar.test.tsx, src/__tests__/renderer/hooks/useAgentInbox.test.ts, src/__tests__/renderer/hooks/useTabHandlers.test.ts, src/__tests__/renderer/stores/modalStore.test.ts, src/__tests__/renderer/utils/search.test.ts
Comprehensive new test suites: AgentInbox component (list/focus views, filtering, keyboard nav, focus mode transitions, reply flows); TabBar description editing (overlay rendering, edit mode, save/cancel, Escape handling); useAgentInbox hook (truncate, summary generation, filtering, sorting, timestamps); useTabHandlers (description updates with trimming, tab isolation); modalStore (data persistence on closed modals); search (scoring, case sensitivity, special characters, determinism).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant ModalStore
    participant AgentInbox
    participant InboxListView
    participant QuickActionsModal

    User->>QuickActionsModal: Press Alt+I or click "Unified Inbox"
    QuickActionsModal->>App: onOpenAgentInbox()
    App->>ModalStore: setAgentInboxOpen(true)
    ModalStore->>AgentInbox: render with modalData
    AgentInbox->>InboxListView: render with items, sortMode, filterMode
    InboxListView->>User: display virtualized list

    User->>InboxListView: ArrowDown/ArrowUp to navigate
    InboxListView->>InboxListView: update selectedIndex
    
    User->>InboxListView: Press Enter or F to focus
    InboxListView->>AgentInbox: onNavigateToFocus(selectedIndex)
    AgentInbox->>AgentInbox: set viewMode='focus'
    AgentInbox->>FocusModeView: render focused item
    FocusModeView->>User: display two-pane detail view
Loading
sequenceDiagram
    participant User
    participant TabBar
    participant Tab
    participant App
    participant Store

    User->>TabBar: hover/click tab description area
    TabBar->>Tab: render description section
    Tab->>Tab: isEditingDescription=false, show description/placeholder
    
    User->>Tab: click Edit/Add description button
    Tab->>Tab: isEditingDescription=true, descriptionDraft=tab.description
    Tab->>User: render textarea with current text

    User->>Tab: type new description
    Tab->>Tab: update descriptionDraft
    
    User->>Tab: Press Enter or blur
    Tab->>Tab: trim descriptionDraft
    Tab->>App: onUpdateTabDescription(tabId, trimmed)
    App->>Store: updateAITab(tabId, {description: trimmed})
    Store->>Tab: tab.description updated
    Tab->>Tab: isEditingDescription=false, sync draft
    Tab->>User: display updated description

    alt User cancels with Escape
        User->>Tab: Press Escape
        Tab->>Tab: isEditingDescription=false
        Tab->>User: revert to original description
    end
Loading
sequenceDiagram
    participant User
    participant FocusModeView
    participant ReplyArea
    participant App
    participant Store
    participant TargetTab

    User->>FocusModeView: view focused inbox item
    FocusModeView->>ReplyArea: render reply textarea
    
    User->>ReplyArea: type reply message
    ReplyArea->>ReplyArea: update replyText
    
    User->>ReplyArea: Press Cmd+Enter or click Quick Reply
    ReplyArea->>App: onQuickReply(reply, targetSession, targetTab)
    App->>Store: save reply to message
    App->>Store: activate targetSession and targetTab
    App->>Store: trigger process commit
    
    alt User presses Shift+Enter for Open & Reply
        User->>ReplyArea: Press Shift+Enter
        ReplyArea->>App: onOpenAndReply(reply, targetSession, targetTab)
        App->>TargetTab: pre-fill input with reply text
        App->>TargetTab: navigate to and focus target tab
        TargetTab->>User: display reply-ready interface
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.97% 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 PR title 'feat: rebuild tab description, unified inbox & fuzzy search' clearly and concisely summarizes the three main features being rebuilt, matching the primary changes across the changeset.

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

✨ Finishing Touches
🧪 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.

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: 5

♻️ Duplicate comments (5)
src/renderer/components/SettingsModal.tsx (1)

3687-3690: ⚠️ Potential issue | 🟠 Major

Add accessible names to the new switch controls.

At Line [3688] and Line [3745], the role="switch" buttons still lack an accessible name, so screen readers announce unlabeled controls.

Suggested patch
 									<button
 										role="switch"
+										aria-label="Toggle Unified Inbox feature"
 										aria-checked={encoreFeatures.unifiedInbox}
 										onClick={(e) => {
@@
 									<button
 										role="switch"
+										aria-label="Toggle Tab Descriptions feature"
 										aria-checked={encoreFeatures.tabDescription}
 										onClick={(e) => {

Also applies to: 3744-3747

🤖 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 3687 - 3690, The
switch buttons in SettingsModal (the button with role="switch" that uses
aria-checked={encoreFeatures.unifiedInbox} and the other similar switch at the
other block) are missing accessible names; add an accessible name by supplying
either aria-label or aria-labelledby that references the visible label for each
switch (e.g., aria-label="Enable Unified Inbox" or
aria-labelledby="{id-of-label}"), keeping aria-checked and onClick logic
unchanged so screen readers announce the control and its state; update both the
unifiedInbox switch and the other role="switch" instance to use descriptive
labels.
src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx (1)

111-139: ⚠️ Potential issue | 🟡 Minor

Fix fixture mismatch between aiTabs and activeTabId.

makeSession can produce invalid sessions because activeTabId is hardcoded (default-tab) while aiTabs uses generated/overridden IDs. This can make keyboard/focus-mode tests pass for the wrong reason.

🐛 Suggested fix
-const makeSession = (overrides: Partial<Session> = {}): Session =>
-	({
+const makeSession = (overrides: Partial<Session> = {}): Session => {
+	const aiTabs = (overrides.aiTabs ?? [makeTab()]) as Session['aiTabs'];
+	const activeTabId = overrides.activeTabId ?? aiTabs[0]?.id ?? 'default-tab';
+	return ({
 		id: `s-${Math.random().toString(36).slice(2, 8)}`,
 		name: 'Agent Alpha',
@@
-		aiTabs: [makeTab()],
-		activeTabId: 'default-tab',
+		aiTabs,
+		activeTabId,
@@
-		...overrides,
-	}) as unknown as Session;
+		...overrides,
+		aiTabs,
+		activeTabId,
+	}) as unknown as Session;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx` around
lines 111 - 139, The fixture can produce invalid sessions because makeSession
sets activeTabId to the hardcoded 'default-tab' while aiTabs contains
generated/overridden tab IDs; update makeSession so it creates a tab (via
makeTab()), capture its id, set aiTabs to that tab by default, and set
activeTabId to either the override's activeTabId if provided or the created
tab's id (ensure this logic also respects an overrides.aiTabs array by using its
first tab id when present) so activeTabId always matches an id present in
aiTabs.
src/renderer/components/AgentInbox/index.tsx (1)

51-53: ⚠️ Potential issue | 🟡 Minor

Guard document.activeElement access during render-time ref initialization.

Line 52 reads document.activeElement unconditionally; this is fragile in non-DOM render/test environments.

🛡️ Suggested fix
-	const triggerRef = useRef<HTMLElement | null>(
-		document.activeElement instanceof HTMLElement ? document.activeElement : null
-	);
+	const triggerRef = useRef<HTMLElement | null>(
+		typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
+			? document.activeElement
+			: null
+	);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox/index.tsx` around lines 51 - 53, The ref
initialization reads document.activeElement during render (triggerRef created
via useRef) which can fail in non-DOM environments; change the initialization to
avoid unconditional access by either (a) checking typeof document !==
"undefined" && document.activeElement instanceof HTMLElement before assigning,
or (b) initialize triggerRef to null and set triggerRef.current inside a
useEffect that runs client-side; update the code around triggerRef/useRef to use
one of these guards so document access only occurs in a safe runtime.
src/renderer/components/AgentInbox/InboxListView.tsx (1)

1141-1177: ⚠️ Potential issue | 🟠 Major

Restore visible keyboard focus on collapsible group headers.

Line 1144 removes the default outline on a focusable control, but there is no replacement focus state.

♿ Suggested fix
 										<button
 											type="button"
 											key={`header-${row.groupKey}`}
 											className="outline-none"
+											tabIndex={0}
 											style={{
 												position: 'absolute',
@@
 											}}
 											onClick={() => toggleGroup(row.groupKey)}
+											onFocus={(e) => {
+												e.currentTarget.style.outline = `2px solid ${theme.colors.accent}`;
+												e.currentTarget.style.outlineOffset = '-2px';
+											}}
+											onBlur={(e) => {
+												e.currentTarget.style.outline = 'none';
+											}}
 											onKeyDown={(e) => {
 												if (e.key === 'Enter' || e.key === ' ') {

As per coding guidelines src/renderer/components/**/*.{ts,tsx}: Add tabIndex attribute and focus event handlers when implementing components that need keyboard focus.

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

In `@src/renderer/components/AgentInbox/InboxListView.tsx` around lines 1141 -
1177, The header button currently removes the default focus outline
("outline-none") but never provides a replacement, so restore visible keyboard
focus by making the header truly focusable and rendering a custom focus style:
add tabIndex={0} to the button, add onFocus and onBlur handlers (e.g., manage a
local isHeaderFocused state) and update the inline style (the button with
virtualRow, row.groupKey, isRowSelected and toggleGroup) to show a clear focus
indicator (border, boxShadow or outline) when isHeaderFocused is true;
alternatively remove the "outline-none" class and use :focus/:focus-visible CSS
to show the focus ring, but keep existing onKeyDown/onClick logic intact.
src/renderer/components/AgentInbox/FocusModeView.tsx (1)

755-770: ⚠️ Potential issue | 🟠 Major

Wire mark-as-read into both reply actions.

Line 786 says replies are the explicit read action, but neither handler marks the item read. Replied threads can remain in unread state.

Suggested fix
 const handleQuickReply = useCallback(() => {
 	const text = replyText.trim();
 	if (!text) return;
 	if (onQuickReply) {
 		onQuickReply(item.sessionId, item.tabId, text);
 	}
+	_onMarkAsRead?.(item.sessionId, item.tabId);
 	setReplyText('');
-}, [replyText, item, onQuickReply]);
+}, [replyText, item, onQuickReply, _onMarkAsRead]);

 const handleOpenAndReply = useCallback(() => {
 	const text = replyText.trim();
 	if (!text) return;
 	if (onOpenAndReply) {
 		onOpenAndReply(item.sessionId, item.tabId, text);
 	}
-}, [replyText, item, onOpenAndReply]);
+	_onMarkAsRead?.(item.sessionId, item.tabId);
+}, [replyText, item, onOpenAndReply, _onMarkAsRead]);
#!/bin/bash
# Verify both reply handlers invoke _onMarkAsRead and include it in dependencies.
rg -n "handleQuickReply|handleOpenAndReply|_onMarkAsRead\\?\\.\\(" src/renderer/components/AgentInbox/FocusModeView.tsx -A10 -B3

Also applies to: 786-787

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

In `@src/renderer/components/AgentInbox/FocusModeView.tsx` around lines 755 - 770,
The reply handlers handleQuickReply and handleOpenAndReply currently send
replies but do not mark the item read; call the mark-as-read handler
(_onMarkAsRead) after a successful reply (e.g., after invoking
onQuickReply/onOpenAndReply) to ensure replied threads are marked read, clear
reply state where appropriate (setReplyText in handleOpenAndReply if desired),
and add _onMarkAsRead to each useCallback dependency array so React sees the
change; update handleQuickReply and handleOpenAndReply to invoke
_onMarkAsRead(item.sessionId, item.tabId) (or the correct signature) and include
_onMarkAsRead in the dependency lists.
🧹 Nitpick comments (2)
src/__tests__/renderer/utils/search.test.ts (1)

434-482: Consolidate overlapping ranking/case test coverage.

This block repeats scenarios already validated earlier in this file (ranking relationships and case-score comparisons). Consider collapsing into a parameterized table-driven suite to reduce maintenance noise and keep signal high.

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

In `@src/__tests__/renderer/utils/search.test.ts` around lines 434 - 482, The
tests duplicate ranking and case-score comparisons; consolidate the two blocks
around fuzzyMatchWithScore into a single parameterized suite using table-driven
tests (e.g., test.each / describe.each) that covers: (1) ranking relationships
(exact > substring > scattered fuzzy) by supplying pairs/triples of input
strings and asserting matches and relative scores, and (2) case-insensitive
matching and case-preference (query case matching scores higher) by supplying
mixed-case variants and expected match/score relationships; update or remove the
existing explicit individual its ("scoring - exact vs fuzzy vs substring
ranking" and "case-insensitive matching in scored search") and replace them with
the compact parameterized tests that reference fuzzyMatchWithScore so
maintenance noise is reduced while preserving all assertions.
src/renderer/types/agent-inbox.ts (1)

1-1: Prefer ToolType over string for toolType.

Typing toolType as string drops compile-time validation and allows invalid values.

Suggested patch
-import type { SessionState } from './index';
+import type { SessionState, ToolType } from './index';
@@
-	toolType: string;
+	toolType: ToolType;

Also applies to: 10-10

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

In `@src/renderer/types/agent-inbox.ts` at line 1, Change the toolType property
from a plain string to the strongly-typed ToolType so consumers get compile-time
validation; update the interface/type that defines toolType in this file (and
the other occurrences noted) to use ToolType instead of string, and import
ToolType from the module that exports it (e.g., add ToolType to the existing
import list from './index' or the correct source) so the property signature and
all references (toolType) use ToolType.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/App.tsx`:
- Around line 4265-4275: The AgentInbox component is missing the enterToSendAI
prop so it ignores the user's Enter vs Cmd+Enter keyboard preference; update the
AgentInbox JSX (where AgentInbox is rendered) to pass the existing enterToSendAI
state/prop (e.g., enterToSendAI={enterToSendAI}) so AgentInbox receives the
setting and behaves consistently with FocusModeView and other consumers.

In `@src/renderer/components/AgentInbox/FocusModeView.tsx`:
- Around line 988-1005: The resize-handle div in FocusModeView is currently
mouse-only; make it keyboard-accessible by adding tabIndex={0}, an appropriate
role/aria-label (e.g., role="separator" aria-orientation="vertical"
aria-label="Resize sidebar"), and onKeyDown handler that listens for
ArrowLeft/ArrowRight (and optionally Home/End) to call the same resize logic
used by handleResizeStart/resize callbacks (or a new helper that adjusts the
sidebar width), plus onFocus/onBlur to apply the same focus styling used in
onMouseEnter/onMouseLeave; use existing symbols handleResizeStart and
isResizingRef to coordinate state and ensure the visual feedback uses
theme.colors.accent like the mouse interactions.

In `@src/renderer/constants/shortcuts.ts`:
- Line 81: Update the agentInbox shortcut definition so it uses the same
Alt+Meta pattern as other shortcuts: change the keys array for the agentInbox
entry (symbol: agentInbox) from ['Alt','i'] to ['Alt','Meta','i'] to avoid
IME/dead-key conflicts and maintain consistency with existing shortcuts.

In `@src/renderer/stores/modalStore.ts`:
- Around line 775-777: setAgentInboxOpen currently calls
closeModal('agentInbox') which clears the modal data and drops persisted prefs
(filterMode/sortMode/isExpanded); fix by changing closeModal to accept an
optional preserveData boolean (or a mode param) that skips clearing data when
true, then change setAgentInboxOpen to call closeModal('agentInbox', true) on
close so agentInbox preferences are preserved; keep openModal('agentInbox') and
updateModalData('agentInbox', ...) behavior unchanged.

In `@src/renderer/utils/tabDisplayName.ts`:
- Around line 13-15: The current return in the tab display name utility returns
tab.name without trimming, so whitespace-only names like "   " appear blank;
update the function in src/renderer/utils/tabDisplayName.ts that checks tab.name
to first do const trimmed = tab.name.trim() and return trimmed if trimmed is
non-empty, otherwise fall back to the existing fallback behavior (i.e., proceed
as if no name was provided).

---

Duplicate comments:
In `@src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx`:
- Around line 111-139: The fixture can produce invalid sessions because
makeSession sets activeTabId to the hardcoded 'default-tab' while aiTabs
contains generated/overridden tab IDs; update makeSession so it creates a tab
(via makeTab()), capture its id, set aiTabs to that tab by default, and set
activeTabId to either the override's activeTabId if provided or the created
tab's id (ensure this logic also respects an overrides.aiTabs array by using its
first tab id when present) so activeTabId always matches an id present in
aiTabs.

In `@src/renderer/components/AgentInbox/FocusModeView.tsx`:
- Around line 755-770: The reply handlers handleQuickReply and
handleOpenAndReply currently send replies but do not mark the item read; call
the mark-as-read handler (_onMarkAsRead) after a successful reply (e.g., after
invoking onQuickReply/onOpenAndReply) to ensure replied threads are marked read,
clear reply state where appropriate (setReplyText in handleOpenAndReply if
desired), and add _onMarkAsRead to each useCallback dependency array so React
sees the change; update handleQuickReply and handleOpenAndReply to invoke
_onMarkAsRead(item.sessionId, item.tabId) (or the correct signature) and include
_onMarkAsRead in the dependency lists.

In `@src/renderer/components/AgentInbox/InboxListView.tsx`:
- Around line 1141-1177: The header button currently removes the default focus
outline ("outline-none") but never provides a replacement, so restore visible
keyboard focus by making the header truly focusable and rendering a custom focus
style: add tabIndex={0} to the button, add onFocus and onBlur handlers (e.g.,
manage a local isHeaderFocused state) and update the inline style (the button
with virtualRow, row.groupKey, isRowSelected and toggleGroup) to show a clear
focus indicator (border, boxShadow or outline) when isHeaderFocused is true;
alternatively remove the "outline-none" class and use :focus/:focus-visible CSS
to show the focus ring, but keep existing onKeyDown/onClick logic intact.

In `@src/renderer/components/AgentInbox/index.tsx`:
- Around line 51-53: The ref initialization reads document.activeElement during
render (triggerRef created via useRef) which can fail in non-DOM environments;
change the initialization to avoid unconditional access by either (a) checking
typeof document !== "undefined" && document.activeElement instanceof HTMLElement
before assigning, or (b) initialize triggerRef to null and set
triggerRef.current inside a useEffect that runs client-side; update the code
around triggerRef/useRef to use one of these guards so document access only
occurs in a safe runtime.

In `@src/renderer/components/SettingsModal.tsx`:
- Around line 3687-3690: The switch buttons in SettingsModal (the button with
role="switch" that uses aria-checked={encoreFeatures.unifiedInbox} and the other
similar switch at the other block) are missing accessible names; add an
accessible name by supplying either aria-label or aria-labelledby that
references the visible label for each switch (e.g., aria-label="Enable Unified
Inbox" or aria-labelledby="{id-of-label}"), keeping aria-checked and onClick
logic unchanged so screen readers announce the control and its state; update
both the unifiedInbox switch and the other role="switch" instance to use
descriptive labels.

---

Nitpick comments:
In `@src/__tests__/renderer/utils/search.test.ts`:
- Around line 434-482: The tests duplicate ranking and case-score comparisons;
consolidate the two blocks around fuzzyMatchWithScore into a single
parameterized suite using table-driven tests (e.g., test.each / describe.each)
that covers: (1) ranking relationships (exact > substring > scattered fuzzy) by
supplying pairs/triples of input strings and asserting matches and relative
scores, and (2) case-insensitive matching and case-preference (query case
matching scores higher) by supplying mixed-case variants and expected
match/score relationships; update or remove the existing explicit individual its
("scoring - exact vs fuzzy vs substring ranking" and "case-insensitive matching
in scored search") and replace them with the compact parameterized tests that
reference fuzzyMatchWithScore so maintenance noise is reduced while preserving
all assertions.

In `@src/renderer/types/agent-inbox.ts`:
- Line 1: Change the toolType property from a plain string to the strongly-typed
ToolType so consumers get compile-time validation; update the interface/type
that defines toolType in this file (and the other occurrences noted) to use
ToolType instead of string, and import ToolType from the module that exports it
(e.g., add ToolType to the existing import list from './index' or the correct
source) so the property signature and all references (toolType) use ToolType.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d612b6 and 2c0b925.

📒 Files selected for processing (29)
  • docs/encore-features.md
  • docs/keyboard-shortcuts.md
  • src/__tests__/renderer/components/AgentInbox/AgentInbox.test.tsx
  • src/__tests__/renderer/components/TabBar.test.tsx
  • src/__tests__/renderer/hooks/useAgentInbox.test.ts
  • src/__tests__/renderer/hooks/useTabHandlers.test.ts
  • src/__tests__/renderer/stores/modalStore.test.ts
  • src/__tests__/renderer/utils/search.test.ts
  • src/renderer/App.tsx
  • src/renderer/components/AgentInbox/FocusModeView.tsx
  • src/renderer/components/AgentInbox/InboxListView.tsx
  • src/renderer/components/AgentInbox/index.tsx
  • src/renderer/components/AppModals.tsx
  • src/renderer/components/MainPanel.tsx
  • src/renderer/components/QuickActionsModal.tsx
  • src/renderer/components/SettingsModal.tsx
  • src/renderer/components/TabBar.tsx
  • src/renderer/constants/modalPriorities.ts
  • src/renderer/constants/shortcuts.ts
  • src/renderer/hooks/keyboard/useMainKeyboardHandler.ts
  • src/renderer/hooks/props/useMainPanelProps.ts
  • src/renderer/hooks/tabs/useTabHandlers.ts
  • src/renderer/hooks/useAgentInbox.ts
  • src/renderer/stores/modalStore.ts
  • src/renderer/stores/settingsStore.ts
  • src/renderer/types/agent-inbox.ts
  • src/renderer/types/index.ts
  • src/renderer/utils/search.ts
  • src/renderer/utils/tabDisplayName.ts

Comment on lines +4265 to +4275
<AgentInbox
sessions={sessions}
groups={groups}
theme={theme}
onClose={() => setAgentInboxOpen(false)}
onNavigateToSession={handleAgentInboxNavigateToSession}
onQuickReply={handleAgentInboxQuickReply}
onOpenAndReply={handleAgentInboxOpenAndReply}
onMarkAsRead={handleAgentInboxMarkAsRead}
onToggleThinking={handleAgentInboxToggleThinking}
/>
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:

# Find AgentInboxProps definition
rg -n "interface AgentInboxProps|type AgentInboxProps" src/renderer/components/AgentInbox -A 20

Repository: RunMaestro/Maestro

Length of output: 1693


🏁 Script executed:

# Check the specific call site and surrounding context
sed -n '4255,4285p' src/renderer/App.tsx

Repository: RunMaestro/Maestro

Length of output: 1103


🏁 Script executed:

# Find all AgentInbox usages in the codebase
rg -n "<AgentInbox" src/renderer -B 2 -A 15

Repository: RunMaestro/Maestro

Length of output: 1174


🏁 Script executed:

# Check if enterToSendAI is defined and used in App.tsx
rg -n "enterToSendAI" src/renderer/App.tsx

Repository: RunMaestro/Maestro

Length of output: 282


🏁 Script executed:

# Check how enterToSendAI is used within AgentInbox component
sed -n '24,200p' src/renderer/components/AgentInbox/index.tsx | grep -n "enterToSendAI"

Repository: RunMaestro/Maestro

Length of output: 108


🏁 Script executed:

# Get more context on AgentInbox component implementation
sed -n '37,150p' src/renderer/components/AgentInbox/index.tsx

Repository: RunMaestro/Maestro

Length of output: 3874


🏁 Script executed:

# Search the entire AgentInbox component for usage of enterToSendAI
rg -n "enterToSendAI" src/renderer/components/AgentInbox/

Repository: RunMaestro/Maestro

Length of output: 712


🏁 Script executed:

# Get full component file to see if enterToSendAI is actually used
wc -l src/renderer/components/AgentInbox/index.tsx

Repository: RunMaestro/Maestro

Length of output: 110


🏁 Script executed:

# Check line 321 context to see what component receives enterToSendAI
sed -n '310,330p' src/renderer/components/AgentInbox/index.tsx

Repository: RunMaestro/Maestro

Length of output: 732


Pass enterToSendAI to AgentInbox to respect user keyboard settings.

The enterToSendAI prop is optional but actively used by FocusModeView to control keyboard behavior (Enter vs. Cmd+Enter to send). Without it, the inbox defaults to Cmd+Enter regardless of the user's setting.

Fix
 						<AgentInbox
 							sessions={sessions}
 							groups={groups}
 							theme={theme}
+							enterToSendAI={enterToSendAI}
 							onClose={() => setAgentInboxOpen(false)}
 							onNavigateToSession={handleAgentInboxNavigateToSession}
 							onQuickReply={handleAgentInboxQuickReply}
 							onOpenAndReply={handleAgentInboxOpenAndReply}
 							onMarkAsRead={handleAgentInboxMarkAsRead}
 							onToggleThinking={handleAgentInboxToggleThinking}
 						/>
📝 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
<AgentInbox
sessions={sessions}
groups={groups}
theme={theme}
onClose={() => setAgentInboxOpen(false)}
onNavigateToSession={handleAgentInboxNavigateToSession}
onQuickReply={handleAgentInboxQuickReply}
onOpenAndReply={handleAgentInboxOpenAndReply}
onMarkAsRead={handleAgentInboxMarkAsRead}
onToggleThinking={handleAgentInboxToggleThinking}
/>
<AgentInbox
sessions={sessions}
groups={groups}
theme={theme}
enterToSendAI={enterToSendAI}
onClose={() => setAgentInboxOpen(false)}
onNavigateToSession={handleAgentInboxNavigateToSession}
onQuickReply={handleAgentInboxQuickReply}
onOpenAndReply={handleAgentInboxOpenAndReply}
onMarkAsRead={handleAgentInboxMarkAsRead}
onToggleThinking={handleAgentInboxToggleThinking}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/App.tsx` around lines 4265 - 4275, The AgentInbox component is
missing the enterToSendAI prop so it ignores the user's Enter vs Cmd+Enter
keyboard preference; update the AgentInbox JSX (where AgentInbox is rendered) to
pass the existing enterToSendAI state/prop (e.g., enterToSendAI={enterToSendAI})
so AgentInbox receives the setting and behaves consistently with FocusModeView
and other consumers.

Comment on lines +988 to +1005
<div
onMouseDown={handleResizeStart}
style={{
width: 4,
cursor: 'col-resize',
backgroundColor: 'transparent',
borderRight: `1px solid ${theme.colors.border}`,
flexShrink: 0,
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = `${theme.colors.accent}30`;
}}
onMouseLeave={(e) => {
if (!isResizingRef.current) {
e.currentTarget.style.backgroundColor = 'transparent';
}
}}
/>
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 the sidebar resize handle keyboard-accessible.

This control is interactive but mouse-only right now, so keyboard users cannot operate it. Add focusability and arrow-key resize behavior.

Suggested fix
 <div
+	tabIndex={0}
+	role="separator"
+	aria-orientation="vertical"
+	aria-label="Resize inbox sidebar"
+	aria-valuemin={160}
+	aria-valuemax={400}
+	aria-valuenow={sidebarWidth}
 	onMouseDown={handleResizeStart}
+	onKeyDown={(e) => {
+		if (e.key === 'ArrowLeft') {
+			e.preventDefault();
+			setSidebarWidth((w) => Math.max(160, w - 16));
+		} else if (e.key === 'ArrowRight') {
+			e.preventDefault();
+			setSidebarWidth((w) => Math.min(400, w + 16));
+		}
+	}}
 	style={{
 		width: 4,
 		cursor: 'col-resize',
 		backgroundColor: 'transparent',
 		borderRight: `1px solid ${theme.colors.border}`,
 		flexShrink: 0,
 	}}
 	onMouseEnter={(e) => {
 		e.currentTarget.style.backgroundColor = `${theme.colors.accent}30`;
 	}}
 	onMouseLeave={(e) => {
 		if (!isResizingRef.current) {
 			e.currentTarget.style.backgroundColor = 'transparent';
 		}
 	}}
+	onFocus={(e) => {
+		e.currentTarget.style.backgroundColor = `${theme.colors.accent}30`;
+	}}
+	onBlur={(e) => {
+		if (!isResizingRef.current) e.currentTarget.style.backgroundColor = 'transparent';
+	}}
 />

As per coding guidelines src/renderer/components/**/*.{ts,tsx}: Add tabIndex attribute and focus event handlers when implementing components that need keyboard focus.

📝 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
<div
onMouseDown={handleResizeStart}
style={{
width: 4,
cursor: 'col-resize',
backgroundColor: 'transparent',
borderRight: `1px solid ${theme.colors.border}`,
flexShrink: 0,
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = `${theme.colors.accent}30`;
}}
onMouseLeave={(e) => {
if (!isResizingRef.current) {
e.currentTarget.style.backgroundColor = 'transparent';
}
}}
/>
<div
tabIndex={0}
role="separator"
aria-orientation="vertical"
aria-label="Resize inbox sidebar"
aria-valuemin={160}
aria-valuemax={400}
aria-valuenow={sidebarWidth}
onMouseDown={handleResizeStart}
onKeyDown={(e) => {
if (e.key === 'ArrowLeft') {
e.preventDefault();
setSidebarWidth((w) => Math.max(160, w - 16));
} else if (e.key === 'ArrowRight') {
e.preventDefault();
setSidebarWidth((w) => Math.min(400, w + 16));
}
}}
style={{
width: 4,
cursor: 'col-resize',
backgroundColor: 'transparent',
borderRight: `1px solid ${theme.colors.border}`,
flexShrink: 0,
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = `${theme.colors.accent}30`;
}}
onMouseLeave={(e) => {
if (!isResizingRef.current) {
e.currentTarget.style.backgroundColor = 'transparent';
}
}}
onFocus={(e) => {
e.currentTarget.style.backgroundColor = `${theme.colors.accent}30`;
}}
onBlur={(e) => {
if (!isResizingRef.current) e.currentTarget.style.backgroundColor = 'transparent';
}}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/AgentInbox/FocusModeView.tsx` around lines 988 -
1005, The resize-handle div in FocusModeView is currently mouse-only; make it
keyboard-accessible by adding tabIndex={0}, an appropriate role/aria-label
(e.g., role="separator" aria-orientation="vertical" aria-label="Resize
sidebar"), and onKeyDown handler that listens for ArrowLeft/ArrowRight (and
optionally Home/End) to call the same resize logic used by
handleResizeStart/resize callbacks (or a new helper that adjusts the sidebar
width), plus onFocus/onBlur to apply the same focus styling used in
onMouseEnter/onMouseLeave; use existing symbols handleResizeStart and
isResizingRef to coordinate state and ensure the visual feedback uses
theme.colors.accent like the mouse interactions.

label: "Director's Notes",
keys: ['Meta', 'Shift', 'o'],
},
agentInbox: { id: 'agentInbox', label: 'Unified Inbox', keys: ['Alt', 'i'] },
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

🧩 Analysis chain

🏁 Script executed:

cat -n src/renderer/constants/shortcuts.ts | head -100

Repository: RunMaestro/Maestro

Length of output: 5424


🏁 Script executed:

rg -nP "keys:\s*\[" src/renderer/constants/shortcuts.ts | head -30

Repository: RunMaestro/Maestro

Length of output: 2476


Use Alt+Meta+i to match established shortcut conventions and avoid IME conflicts.

Line 81 introduces the only Alt-only shortcut in the codebase. Every other shortcut using Alt also includes Meta (['Alt', 'Meta', ...]). This inconsistency also creates risk of IME/dead-key conflicts on systems with language input methods. Change to keys: ['Alt', 'Meta', 'i'] to align with the app's pattern for supplementary actions.

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

In `@src/renderer/constants/shortcuts.ts` at line 81, Update the agentInbox
shortcut definition so it uses the same Alt+Meta pattern as other shortcuts:
change the keys array for the agentInbox entry (symbol: agentInbox) from
['Alt','i'] to ['Alt','Meta','i'] to avoid IME/dead-key conflicts and maintain
consistency with existing shortcuts.

Comment on lines +775 to +777
setAgentInboxOpen: (open: boolean) =>
open ? openModal('agentInbox') : closeModal('agentInbox'),
updateAgentInboxData: (data: Record<string, unknown>) => updateModalData('agentInbox', data),
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

Closing Agent Inbox currently drops the persisted inbox preferences.

setAgentInboxOpen(false) calls closeModal('agentInbox'), and closeModal clears data. That wipes filterMode/sortMode/isExpanded, so state won’t persist across reopen.

Suggested fix
-		setAgentInboxOpen: (open: boolean) =>
-			open ? openModal('agentInbox') : closeModal('agentInbox'),
+		setAgentInboxOpen: (open: boolean) => {
+			if (open) {
+				openModal('agentInbox');
+				return;
+			}
+			useModalStore.setState((state) => {
+				const current = state.modals.get('agentInbox');
+				const newModals = new Map(state.modals);
+				newModals.set('agentInbox', { open: false, data: current?.data });
+				return { modals: newModals };
+			});
+		},
📝 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
setAgentInboxOpen: (open: boolean) =>
open ? openModal('agentInbox') : closeModal('agentInbox'),
updateAgentInboxData: (data: Record<string, unknown>) => updateModalData('agentInbox', data),
setAgentInboxOpen: (open: boolean) => {
if (open) {
openModal('agentInbox');
return;
}
useModalStore.setState((state) => {
const current = state.modals.get('agentInbox');
const newModals = new Map(state.modals);
newModals.set('agentInbox', { open: false, data: current?.data });
return { modals: newModals };
});
},
updateAgentInboxData: (data: Record<string, unknown>) => updateModalData('agentInbox', data),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/stores/modalStore.ts` around lines 775 - 777, setAgentInboxOpen
currently calls closeModal('agentInbox') which clears the modal data and drops
persisted prefs (filterMode/sortMode/isExpanded); fix by changing closeModal to
accept an optional preserveData boolean (or a mode param) that skips clearing
data when true, then change setAgentInboxOpen to call closeModal('agentInbox',
true) on close so agentInbox preferences are preserved; keep
openModal('agentInbox') and updateModalData('agentInbox', ...) behavior
unchanged.

Comment on lines +13 to +15
if (tab.name) {
return tab.name;
}
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

Guard against whitespace-only names.

At Line [13], a value like ' ' passes and renders an empty-looking label. Trim before returning to preserve fallback behavior.

Suggested patch
 export function getTabDisplayName(tab: Pick<AITab, 'name' | 'agentSessionId'>): string {
-	if (tab.name) {
-		return tab.name;
+	if (tab.name?.trim()) {
+		return tab.name.trim();
 	}
📝 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 (tab.name) {
return tab.name;
}
if (tab.name?.trim()) {
return tab.name.trim();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/utils/tabDisplayName.ts` around lines 13 - 15, The current
return in the tab display name utility returns tab.name without trimming, so
whitespace-only names like "   " appear blank; update the function in
src/renderer/utils/tabDisplayName.ts that checks tab.name to first do const
trimmed = tab.name.trim() and return trimmed if trimmed is non-empty, otherwise
fall back to the existing fallback behavior (i.e., proceed as if no name was
provided).

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.

1 participant