feat: Unified Inbox — cross-session notification center#379
feat: Unified Inbox — cross-session notification center#379felipeggv wants to merge 52 commits intoRunMaestro:mainfrom
Conversation
…aps) Created src/renderer/types/agent-inbox.ts with InboxItem interface, InboxSortMode/InboxFilterMode types, and STATUS_LABELS/STATUS_COLORS constants. Re-exported from types/index.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…elector) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s guard - Added 'agentInbox' shortcut definition to DEFAULT_SHORTCUTS - Handler counts sessions with waiting_input state or unread tabs - Shows "No pending items" toast (1.5s) when no actionable items exist - Opens agentInbox modal when actionable items are present - Added shortcut to isSystemUtilShortcut allowlist for modal passthrough - Exposed setAgentInboxOpen and addToast to keyboard handler context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…placeholder component - Add lazy import for AgentInbox in AppModals.tsx - Add agentInboxOpen/onCloseAgentInbox props to AppInfoModalsProps and AppModalsProps - Render <AgentInbox> wrapped in <Suspense> after ProcessMonitor - Wire agentInboxOpen state and handleCloseAgentInbox through App.tsx - Create AgentInbox.tsx placeholder component with typed props Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…complete All Phase 01 tasks verified: types, modal store, keyboard shortcut, modal registration, and placeholder component. TypeScript compiles clean, all 19185 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 31 tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nd 36 tests - Replace placeholder AgentInbox component with full implementation - Install react-window v2 for virtualized list rendering - Add MODAL_PRIORITIES.AGENT_INBOX (555) constant - Implement VariableSizeList with group headers (36px) and item cards (80px) - Add useModalLayer integration for focus trap and Escape handling - Add ARIA: role=dialog, aria-modal, aria-live, role=listbox, role=option - Keyboard nav: ArrowUp/Down with wrap, Enter to navigate, focus restoration - Segmented controls for sort (Newest/Oldest/Grouped) and filter (All/Needs Input/Ready) - InboxItemCard with group name, session name, timestamp, last message, badges - 36 component tests covering rendering, navigation, ARIA, layer stack, filters Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…card tests Selection now uses background fill only (no outline on selection state). Outline appears only on focus for accessibility compliance. Added comprehensive InboxItemCard test coverage: visual hierarchy, selection styling, font sizes, badge rendering, emoji absence, tabIndex management. 50 total component tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Tab/Shift+Tab key handling to cycle focus between header controls (sort, filter, close buttons) and the virtualized list container. Adds headerRef for focusable element discovery and 4 dedicated tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cleanup test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…review pass Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add resolveContextUsageColor() with green/orange/red thresholds (0-60/60-80/80-100%) - Render 4px context usage bar at bottom of InboxItemCard with animated width - Color-code context text to match bar color - Show "Context: —" placeholder when contextUsage is undefined or NaN - Add 9 new tests: color thresholds, bar dimensions, clamping, NaN guard, placeholder - All 63 component tests + 31 hook tests pass; TSC + ESLint clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and 25-char truncation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tics in AgentInbox Replaces simple extractLastMessage with generateSmartSummary that applies contextual prefixes based on session state and AI message patterns: - "Waiting:" prefix for waiting_input sessions - Direct display for AI questions ending with "?" - "Done:" prefix with first-sentence extraction for AI statements - "No activity yet" for empty logs Includes null guards for undefined/null logs and text fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mps, clock skew, yesterday, and months Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pass clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 150ms transition, 13px group headers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rols — aria-label, aria-pressed, 6 new tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on, per-filter messages, 7 new tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pliant warning color, 7 new tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, 0 failures) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s covered Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt — 88 tests pass Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eTime, summary generation, context bar colors - Created src/__tests__/renderer/helpers/agentInboxHelpers.test.ts with 17 tests - Exported generateSmartSummary and truncate from useAgentInbox.ts for direct testing - Exported resolveContextUsageColor from AgentInbox.tsx for direct testing - All 17 spec items covered: 9 formatRelativeTime, 5 summary generation, 3 context bar color - Full suite: 19,331 tests, 0 failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lready committed incrementally Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…checks pass clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ks pass clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…1 keyboard handler tests pass Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… clean (tsc, lint, 19336 tests, build) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nbox core at 91-98% Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Agent Inbox files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…implement, 3 WON'T DO Evaluated all 6 coverage gaps from Phase 07b against importance/testability matrix. Auto-implement: findRowIndexForItem fallback (+1.5%), close button hover (+1.0%), setAgentInboxOpen (+0.5%). Won't do: matchesFilter default (unreachable), focus/blur outlines (already covered), InboxRow null guard (untriggerable). Agent Inbox coverage: 93% → estimated 96% after implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on (2 tests, 19338 total pass) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…40 total pass) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ts, 19342 total pass) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (19341 total pass) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…% Agent Inbox coverage, 0 PENDING) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Needs Input/Ready filters with Unread/Read binary model. - InboxFilterMode: 'needs_input'|'ready' → 'unread'|'read' - matchesFilter: unread=hasUnread, read=!hasUnread+idle/waiting_input - Updated FILTER_OPTIONS, EMPTY_STATE_MESSAGES, and all 147 tests pass Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and item dividers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…3 tests, 0 errors) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ugh to SessionList Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…amburger menu Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… all references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…4 tests, 0 errors) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…b icon - Move agent icon from Row 3 badge to Row 1 (between group separator and session name) - Add Edit3 pencil icon before tab name in Row 1 - Remove agent-type-badge span from Row 3 (now starts with git branch) - Add Edit3, ChevronDown, ChevronRight to lucide-react imports - Update tests: replace data-testid queries with title attribute queries - All 96 tests pass, zero lint errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Row 1: title + badge + close button Row 2: sort + filter segmented controls MODAL_HEADER_HEIGHT increased from 48 to 80 to accommodate the two rows. All 96 tests pass, zero lint errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ron icons Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…6 tests, 0 errors) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@felipeggv can you pull down latest main and you'll see there are now notes around "Encore Features" and actually "Direectors Notes" has been moved into this section. can you refactor unified inbox to fit the same pattern? |
|
@greptile review |
🧩 Analysis chain🏁 Script executed: Length of output: 14316 🏁 Script executed: Length of output: 11752 🏁 Script executed: Length of output: 8719 🏁 Script executed: Length of output: 4945
The Unified Inbox feature in this PR will need to be refactored to follow that same pattern—gating all access points (keyboard shortcut, hamburger menu, command palette, modal rendering) with the Let me proceed with the full review. ✅ Actions performedReview triggered.
|
📝 WalkthroughWalkthroughThis pull request introduces a comprehensive Agent Inbox feature for the renderer layer, including type definitions, core hooks and components with virtualization, modal store integration, keyboard shortcuts, keyboard/navigation handlers, and extensive test coverage and documentation across planning, coverage analysis, and UI polish phases. Changes
Sequence DiagramsequenceDiagram
actor User
participant KBHandler as Keyboard Handler
participant SessionCheck as Session Checker
participant ModalStore as Modal Store
participant AgentInbox as AgentInbox Modal
participant UI as UI Renderer
User->>KBHandler: Press Alt+Cmd+I
KBHandler->>SessionCheck: Check for pending items
alt No pending items
SessionCheck-->>KBHandler: count = 0
KBHandler->>UI: Show toast "No pending items"
else Has pending items
SessionCheck-->>KBHandler: count > 0
KBHandler->>ModalStore: setAgentInboxOpen(true)
ModalStore->>AgentInbox: Render with sessions/groups
AgentInbox->>UI: Display inbox with virtualized list
User->>AgentInbox: Navigate & select items
AgentInbox->>UI: Update filtered/sorted view
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryAdds a comprehensive Unified Inbox feature accessible via Alt+Cmd+I that aggregates notifications from all active sessions into a single modal. The implementation is production-ready with robust null guards, keyboard navigation, virtualized rendering, and extensive test coverage. Key Changes:
Implementation Quality:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User: Alt+Cmd+I] --> B{useMainKeyboardHandler}
B --> C{Check actionable items}
C -->|Zero items| D[Show toast: No pending items]
C -->|Has items| E[setAgentInboxOpen true]
E --> F[App.tsx renders AgentInbox modal]
F --> G[useAgentInbox hook]
G --> H[Iterate sessions + groups]
H --> I{Apply filter mode}
I -->|all| J[unread OR waiting_input OR idle]
I -->|unread| K[hasUnread === true]
I -->|read| L[hasUnread === false AND idle/waiting]
J --> M{Apply sort mode}
K --> M
L --> M
M -->|newest| N[Sort by timestamp DESC]
M -->|oldest| O[Sort by timestamp ASC]
M -->|grouped| P[Group by session groupName]
N --> Q[Return InboxItem array]
O --> Q
P --> Q
Q --> R[AgentInbox renders virtualized list]
R --> S[User navigates with arrows/Enter]
S --> T{User action}
T -->|Enter| U[onNavigateToSession]
T -->|Escape| V[handleClose]
U --> W[Close modal + focus session/tab]
V --> X[Restore focus to trigger element]
Last reviewed commit: 7a30e8f |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
playbooks/agent-inbox/UNIFIED-INBOX-07e.md (1)
11-15: Consider adding a language identifier to the fenced code block.The decision logic pseudo-code block would benefit from a language identifier for consistent formatting. Since it's pseudo-code,
textorplaintextwould be appropriate.📝 Suggested fix
**Decision Logic:** -``` +```text IF line_coverage >= 80% → Do NOT reset (TARGET REACHED - EXIT) ELSE IF no PENDING items with (EASY|MEDIUM) + (HIGH|CRITICAL) → Do NOT reset (NO MORE AUTO WORK - EXIT) ELSE → Reset 07a-07d (CONTINUE TO NEXT LOOP)</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@playbooks/agent-inbox/UNIFIED-INBOX-07e.mdaround lines 11 - 15, Add a
language identifier to the fenced pseudo-code block that begins with "IF
line_coverage >= 80% ..." by changing the opening fence fromtotext (orremains ``` and leave the pseudo-code content unchanged.playbooks/agent-inbox/UNIFIED-INBOX-07b.md (1)
14-14: Consider using relative paths in playbook documentation.The absolute path
/Users/felipegobbi/Documents/Vibework/Maestro/playbooks/agent-inbox/is user-specific and won't work for other contributors. Consider using relative paths (e.g.,./LOOP_00001_COVERAGE_REPORT.mdorplaybooks/agent-inbox/LOOP_00001_COVERAGE_REPORT.md) or documenting paths relative to the repository root.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@playbooks/agent-inbox/UNIFIED-INBOX-07b.md` at line 14, The playbook uses absolute, user-specific paths (e.g., "/Users/felipegobbi/Documents/Vibework/Maestro/playbooks/agent-inbox/LOOP_00001_COVERAGE_REPORT.md"); update Agent Inbox playbook content to use repository-relative paths instead — replace absolute paths with relative ones such as "./LOOP_00001_COVERAGE_REPORT.md" or "playbooks/agent-inbox/LOOP_00001_GAPS.md" throughout the file (refer to the checklist lines that reference LOOP_00001_COVERAGE_REPORT.md and LOOP_00001_GAPS.md) so other contributors can open the files regardless of their home directory.src/__tests__/renderer/stores/modalStore.test.ts (1)
400-450: Consider adding'agentInbox'to thevalidIdsarray.The
agentInboxmodal ID is now a valid modal type (as evidenced by the newsetAgentInboxOpentests), but it's missing from thevalidIdsarray in the type safety test. This means the test doesn't verify thatagentInboxcan be opened/closed via the genericopenModal/closeModalAPI.🔧 Suggested addition
const validIds: ModalId[] = [ 'settings', 'about', // ... existing entries ... 'windowsWarning', + 'agentInbox', ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/__tests__/renderer/stores/modalStore.test.ts` around lines 400 - 450, The test's validIds array for ModalId (variable validIds) is missing the new 'agentInbox' modal, so update the validIds array to include 'agentInbox' so the type-safety test covers opening/closing via the generic openModal/closeModal API; verify this aligns with the new setAgentInboxOpen tests and ensure ModalId union still accepts 'agentInbox'.playbooks/agent-inbox/UNIFIED-INBOX-07c.md (1)
14-14: Tighten the repeated “Mark …” phrasing.LanguageTool flagged repeated sentence starts; a small reword improves readability.
✍️ Suggested edit
- Mark EASY/MEDIUM testability + HIGH/CRITICAL importance as `PENDING` for auto-implementation. Mark HIGH/CRITICAL + HARD as `PENDING - MANUAL REVIEW`. Mark VERY HARD or LOW importance as `WON'T DO`. + Mark EASY/MEDIUM testability + HIGH/CRITICAL importance as `PENDING` for auto-implementation. For HIGH/CRITICAL + HARD, use `PENDING - MANUAL REVIEW`. Use `WON'T DO` for VERY HARD or LOW importance.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@playbooks/agent-inbox/UNIFIED-INBOX-07c.md` at line 14, The checklist line repetitively starts sentences with "Mark" (e.g., "Mark EASY/MEDIUM testability...", "Mark HIGH/CRITICAL + HARD...") which reduces readability; update the phrasing in the UNIFIED-INBOX-07c checklist so each rule uses varied verbs or structures (for example: "Label EASY/MEDIUM testability + HIGH/CRITICAL importance as `PENDING`", "Flag HIGH/CRITICAL + HARD as `PENDING - MANUAL REVIEW`", "Classify VERY HARD or LOW importance as `WON'T DO`") while preserving the exact status tokens and criteria, and ensure adjacent sentences (the IMPORTANCE and TESTABILITY definitions and the resulting status rules) remain clear and semantically identical.src/renderer/components/AgentInbox.tsx (1)
507-510: Prefer ref callback for auto‑focus to match renderer focus guideline.Auto-focus is done via
useEffect. The renderer guideline asks forref={(el) => el?.focus()}. Switching to a ref callback keeps the focus behavior but aligns with the standard.As per coding guidelines: `src/renderer/**/*.{tsx,jsx}: Add tabIndex={0} or tabIndex={-1} and outline-none class to ensure focus works correctly. Use ref={(el) => el?.focus()} for auto-focus in React components.`🛠 Suggested fix
- const containerRef = useRef<HTMLDivElement>(null); + const containerRef = useRef<HTMLDivElement>(null); + const setContainerRef = useCallback((el: HTMLDivElement | null) => { + containerRef.current = el; + el?.focus(); + }, []); @@ - // Focus the container on mount for keyboard nav - useEffect(() => { - containerRef.current?.focus(); - }, []); @@ - <div - ref={containerRef} + <div + ref={setContainerRef}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/AgentInbox.tsx` around lines 507 - 510, Replace the useEffect-based autofocus with a ref callback on the container element in the AgentInbox component: remove the useEffect that calls containerRef.current?.focus(), change the container element to use ref={(el) => el?.focus()} (or set the existing containerRef to a callback ref), and ensure the container has tabIndex={0} or tabIndex={-1} plus the outline-none class so it can receive focus following the renderer focus guideline; update or remove references to containerRef/useEffect accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@playbooks/agent-inbox/UNIFIED-INBOX-01.md`:
- Around line 3-5: Update the playbook/title and filter labels from "Agent
Inbox" and "needs_input/ready" to "Unified Inbox" and "all/unread/read": rename
the header text in UNIFIED-INBOX-01.md, update any enumerated filter names/types
and examples in the document to use all/unread/read, and update corresponding
code references in ProcessMonitor.tsx (the component that defines or consumes
the filters) so its filter constants/enum and UI labels match the new strings to
avoid mismatch between doc and implementation.
In `@playbooks/agent-inbox/UNIFIED-INBOX-03.md`:
- Around line 100-106: Replace the user-specific absolute path in the shell
snippet (the `cd ~/Documents/Vibework/Maestro` step) with a repo-root relative
path or a placeholder variable (e.g., `cd $REPO_ROOT` or `cd .`), so the
subsequent commands (`npx tsc --noEmit`, `npm run lint:eslint --
--max-warnings=0`, and the listed src paths) run reliably in CI and for other
developers; update the markdown example to use that repo-relative invocation
instead of the hardcoded home-directory path.
In `@playbooks/agent-inbox/UNIFIED-INBOX-04.md`:
- Around line 40-44: Update the playbook docs to use the actual filter labels
used by the UI: replace "Needs Input" and "Ready" with "Unread" and "Read"
respectively so they match the AgentInbox implementation; verify the text maps
to the InboxFilterMode values ('all' -> "All", 'unread' -> "Unread", 'read' ->
"Read") and adjust the segment list in UNIFIED-INBOX-04.md to reflect those
exact display strings and modes referenced in AgentInbox.tsx and the
InboxFilterMode type.
In `@playbooks/agent-inbox/UNIFIED-INBOX-05.md`:
- Line 25: Condense the repeated "Timestamp ... should be replaced with
Date.now()" lines (items 12–14) into a single concise clause to avoid
repetition; update the list entry for timestamps (referencing the existing
phrases "Timestamp of `0`", "Timestamp of `NaN`", "Timestamp of negative
number") to one line like "Invalid timestamp values (0, NaN, negative) → replace
with Date.now()", and keep remaining items (e.g., `sessionId`, `gitBranch`,
`logs`, `contextUsage`, `session.aiTabs`) unchanged so readers can still locate
the specific edge-case rules.
In `@src/renderer/App.tsx`:
- Around line 10647-10649: Add a new feature flag (e.g., isUnifiedInboxEnabled)
defaulting to false and use it to gate all Unified Inbox entry points: wrap the
keyboard-open logic in useMainKeyboardHandler (the handler that calls
setAgentInboxOpen), the SessionList menu button that calls setAgentInboxOpen
(and any UI that shows addToast or toggles setUsageDashboardOpen for Inbox
flows), and the modal rendering in AppModals that renders the Unified Inbox
component; ensure each place checks isUnifiedInboxEnabled before rendering UI or
invoking setAgentInboxOpen/setUsageDashboardOpen/addToast so the inbox can be
disabled entirely.
In `@src/renderer/components/AgentInbox.tsx`:
- Around line 462-469: The current useMemo for rows always filters out items in
collapsedGroups causing items to remain hidden when sortMode is 'newest' or
'oldest'; modify the rows computation to only apply the collapsedGroups filter
when sortMode is the grouped mode (e.g., 'grouped' or whatever value you use for
grouping). Concretely, in the useMemo that defines rows (referencing
collapsedGroups, allRows, and sortMode), return allRows early if
collapsedGroups.size === 0 OR sortMode is not the grouped value, otherwise apply
the existing filter logic that keeps headers and excludes items whose
item.groupName is in collapsedGroups.
- Around line 502-505: The current useEffect resets selectedIndex when items
change but doesn’t handle collapsing which changes visible rows; add a new sync
that runs when rows change (or modify the existing effect to also depend on
rows) and re-map or clamp the selectedIndex to a visible row: compute the first
visible row index (e.g., 0 if rows non-empty, or clamp the existing
selectedIndex to rows.length - 1) and call setSelectedIndex with that value so
Enter always targets a visible row; reference selectedIndex, setSelectedIndex,
useEffect, items and rows when implementing this fix.
- Around line 370-395: The header row rendering (when row.type === 'header') is
currently a clickable <div> and is not keyboard-accessible; update the element
to be keyboard-toggleable by either rendering a <button> or adding role="button"
and tabIndex={0} to the container used around row.groupName and the Chevron
icons, add an onKeyDown handler that calls onToggleGroup(row.groupName) for
Enter/Space, include the outline-none class (or appropriate focus styling) for
focus visibility, and ensure existing symbols collapsedGroups, onToggleGroup,
ChevronRight, and ChevronDown are preserved and used as before.
In `@src/renderer/components/AppModals.tsx`:
- Around line 265-275: Gate the Unified Inbox behind the Encore feature flag at
all access points: add the same feature check used by the Encore Features
pattern (the project's feature toggle helper) to the keyboard handler in
useMainKeyboardHandler, the SessionList menu item in SessionList.tsx, and the
modal render in AppModals.tsx so AgentInbox only renders when the feature is
enabled; specifically, wrap the existing agentInboxOpen condition that renders
<AgentInbox ... /> with the feature flag check (so the conditional requires both
agentInboxOpen and the unified-inbox feature true) and similarly guard the
keyboard shortcut handler and the SessionList menu item that set
agentInboxOpen/open it, using the project’s standard feature-checking function.
---
Nitpick comments:
In `@playbooks/agent-inbox/UNIFIED-INBOX-07b.md`:
- Line 14: The playbook uses absolute, user-specific paths (e.g.,
"/Users/felipegobbi/Documents/Vibework/Maestro/playbooks/agent-inbox/LOOP_00001_COVERAGE_REPORT.md");
update Agent Inbox playbook content to use repository-relative paths instead —
replace absolute paths with relative ones such as
"./LOOP_00001_COVERAGE_REPORT.md" or "playbooks/agent-inbox/LOOP_00001_GAPS.md"
throughout the file (refer to the checklist lines that reference
LOOP_00001_COVERAGE_REPORT.md and LOOP_00001_GAPS.md) so other contributors can
open the files regardless of their home directory.
In `@playbooks/agent-inbox/UNIFIED-INBOX-07c.md`:
- Line 14: The checklist line repetitively starts sentences with "Mark" (e.g.,
"Mark EASY/MEDIUM testability...", "Mark HIGH/CRITICAL + HARD...") which reduces
readability; update the phrasing in the UNIFIED-INBOX-07c checklist so each rule
uses varied verbs or structures (for example: "Label EASY/MEDIUM testability +
HIGH/CRITICAL importance as `PENDING`", "Flag HIGH/CRITICAL + HARD as `PENDING -
MANUAL REVIEW`", "Classify VERY HARD or LOW importance as `WON'T DO`") while
preserving the exact status tokens and criteria, and ensure adjacent sentences
(the IMPORTANCE and TESTABILITY definitions and the resulting status rules)
remain clear and semantically identical.
In `@playbooks/agent-inbox/UNIFIED-INBOX-07e.md`:
- Around line 11-15: Add a language identifier to the fenced pseudo-code block
that begins with "IF line_coverage >= 80% ..." by changing the opening fence
from ``` to ```text (or ```plaintext) so the block is treated as plain text;
ensure the closing fence remains ``` and leave the pseudo-code content
unchanged.
In `@src/__tests__/renderer/stores/modalStore.test.ts`:
- Around line 400-450: The test's validIds array for ModalId (variable validIds)
is missing the new 'agentInbox' modal, so update the validIds array to include
'agentInbox' so the type-safety test covers opening/closing via the generic
openModal/closeModal API; verify this aligns with the new setAgentInboxOpen
tests and ensure ModalId union still accepts 'agentInbox'.
In `@src/renderer/components/AgentInbox.tsx`:
- Around line 507-510: Replace the useEffect-based autofocus with a ref callback
on the container element in the AgentInbox component: remove the useEffect that
calls containerRef.current?.focus(), change the container element to use
ref={(el) => el?.focus()} (or set the existing containerRef to a callback ref),
and ensure the container has tabIndex={0} or tabIndex={-1} plus the outline-none
class so it can receive focus following the renderer focus guideline; update or
remove references to containerRef/useEffect accordingly.
| > **Feature:** Agent Inbox | ||
| > **Codebase:** `~/Documents/Vibework/Maestro` | **Branch:** `feature/agent-inbox` | ||
| > **Reference:** Process Monitor at `src/renderer/components/ProcessMonitor.tsx` |
There was a problem hiding this comment.
Align playbook naming and filter labels with “Unified Inbox”.
The doc still says “Agent Inbox” and defines filters as needs_input/ready, but the PR objectives and UI describe “Unified Inbox” with filters all/unread/read. Please update these strings/types to prevent confusion for implementers and QA.
Also applies to: 43-45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@playbooks/agent-inbox/UNIFIED-INBOX-01.md` around lines 3 - 5, Update the
playbook/title and filter labels from "Agent Inbox" and "needs_input/ready" to
"Unified Inbox" and "all/unread/read": rename the header text in
UNIFIED-INBOX-01.md, update any enumerated filter names/types and examples in
the document to use all/unread/read, and update corresponding code references in
ProcessMonitor.tsx (the component that defines or consumes the filters) so its
filter constants/enum and UI labels match the new strings to avoid mismatch
between doc and implementation.
| ```bash | ||
| cd ~/Documents/Vibework/Maestro && \ | ||
| npx tsc --noEmit && \ | ||
| npm run lint:eslint -- --max-warnings=0 \ | ||
| src/renderer/components/AgentInbox.tsx \ | ||
| src/renderer/hooks/useAgentInbox.ts \ | ||
| src/renderer/types/agent-inbox.ts |
There was a problem hiding this comment.
Use repo‑relative paths instead of a user‑specific absolute path.
The cd ~/Documents/Vibework/Maestro instruction is brittle and won’t work for other developers or CI. Prefer repo‑root relative commands or a placeholder like $REPO_ROOT.
📎 Suggested doc tweak
- cd ~/Documents/Vibework/Maestro && \
+ cd $REPO_ROOT && \🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@playbooks/agent-inbox/UNIFIED-INBOX-03.md` around lines 100 - 106, Replace
the user-specific absolute path in the shell snippet (the `cd
~/Documents/Vibework/Maestro` step) with a repo-root relative path or a
placeholder variable (e.g., `cd $REPO_ROOT` or `cd .`), so the subsequent
commands (`npx tsc --noEmit`, `npm run lint:eslint -- --max-warnings=0`, and the
listed src paths) run reliably in CI and for other developers; update the
markdown example to use that repo-relative invocation instead of the hardcoded
home-directory path.
| **Segments:** | ||
| - `"All"` (default, active) | ||
| - `"Needs Input"` (NOT "Waiting" — action-oriented label) | ||
| - `"Ready"` (maps to idle + unread) | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for filter label definitions and usage in AgentInbox components
rg -n "Needs Input|Ready|Unread|Read" src/renderer -g '*.tsx' -g '*.ts' -A 2 -B 2 | head -100Repository: RunMaestro/Maestro
Length of output: 7521
🏁 Script executed:
#!/bin/bash
# Search for InboxFilterMode definitions and usage
rg -n "InboxFilterMode|FilterMode" src/renderer -g '*.tsx' -g '*.ts' -A 2 | head -100Repository: RunMaestro/Maestro
Length of output: 6645
🏁 Script executed:
#!/bin/bash
# Search for segment or filter segment definitions in AgentInbox
fd -type f -name '*AgentInbox*' -o -name '*Inbox*' src/renderer | head -20Repository: RunMaestro/Maestro
Length of output: 232
Documentation uses non-existent filter labels; update to reflect actual UI.
The documentation specifies "Needs Input" and "Ready" as filter segments, but the actual AgentInbox component uses "Unread" and "Read". These labels must be updated to match the implementation in src/renderer/components/AgentInbox.tsx (lines 430-432) and the InboxFilterMode type in src/renderer/types/agent-inbox.ts.
The actual filter modes are:
'all'→ displays as "All"'unread'→ displays as "Unread"'read'→ displays as "Read"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@playbooks/agent-inbox/UNIFIED-INBOX-04.md` around lines 40 - 44, Update the
playbook docs to use the actual filter labels used by the UI: replace "Needs
Input" and "Ready" with "Unread" and "Read" respectively so they match the
AgentInbox implementation; verify the text maps to the InboxFilterMode values
('all' -> "All", 'unread' -> "Unread", 'read' -> "Read") and adjust the segment
list in UNIFIED-INBOX-04.md to reflect those exact display strings and modes
referenced in AgentInbox.tsx and the InboxFilterMode type.
| 6. Maps group info correctly (name) from `groupId` | ||
| 7. Handles ungrouped sessions (no `groupId`) | ||
|
|
||
| **Null/corrupted data edge cases (from blind spot CRITICAL #2):** 8. Session with `null` sessionId → should be skipped entirely 9. Session with `undefined` gitBranch → InboxItem.gitBranch should be `undefined` 10. Tab with empty `logs` array → lastMessage should be `"No messages yet"` 11. Tab with `undefined` logs → lastMessage should be `"No messages yet"` 12. Timestamp of `0` → should be replaced with `Date.now()` 13. Timestamp of `NaN` → should be replaced with `Date.now()` 14. Timestamp of negative number → should be replaced with `Date.now()` 15. `contextUsage` of `NaN` → should be `undefined` 16. `session.aiTabs` is `undefined` → should not crash, return empty |
There was a problem hiding this comment.
Reduce repeated phrasing in the edge-case list.
LanguageTool flags repetitive wording on Line 25. Consider rephrasing the consecutive “Timestamp … should be replaced…” clauses to improve readability.
🧰 Tools
🪛 LanguageTool
[style] ~25-~25: You have already used this phrasing in nearby sentences. Consider using an alternative to add variety to your writing.
Context: ...w()13. Timestamp ofNaN→ should be replaced withDate.now()` 14. Timestamp of negative ...
(REP_REPLACEMENT)
[style] ~25-~25: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hould be replaced with Date.now() 14. Timestamp of negative number → should be replaced...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~25-~25: You have already used this phrasing in nearby sentences. Consider using an alternative to add variety to your writing.
Context: ...imestamp of negative number → should be replaced with Date.now() 15. contextUsage of `NaN...
(REP_REPLACEMENT)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@playbooks/agent-inbox/UNIFIED-INBOX-05.md` at line 25, Condense the repeated
"Timestamp ... should be replaced with Date.now()" lines (items 12–14) into a
single concise clause to avoid repetition; update the list entry for timestamps
(referencing the existing phrases "Timestamp of `0`", "Timestamp of `NaN`",
"Timestamp of negative number") to one line like "Invalid timestamp values (0,
NaN, negative) → replace with Date.now()", and keep remaining items (e.g.,
`sessionId`, `gitBranch`, `logs`, `contextUsage`, `session.aiTabs`) unchanged so
readers can still locate the specific edge-case rules.
| setAgentInboxOpen, | ||
| setUsageDashboardOpen, | ||
| addToast, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for Encore features and Unified Inbox usage
echo "=== Searching for encoreFeatures and Encore Features patterns ==="
rg -n "encoreFeatures|Encore Features" src --type ts --type tsx
echo ""
echo "=== Searching for unifiedInbox references ==="
rg -n "unifiedInbox" src --type ts --type tsx
echo ""
echo "=== Searching for Unified Inbox entry points ==="
rg -n "agentInboxOpen|setAgentInboxOpen|AgentInbox" src --type ts --type tsxRepository: RunMaestro/Maestro
Length of output: 325
🏁 Script executed:
#!/bin/bash
# Check for Encore features and Unified Inbox usage - without type flags
echo "=== Searching for encoreFeatures and Encore Features patterns ==="
rg -n "encoreFeatures|Encore Features" src
echo ""
echo "=== Searching for unifiedInbox references ==="
rg -n "unifiedInbox" src
echo ""
echo "=== Searching for Unified Inbox entry points ==="
rg -n "agentInboxOpen|setAgentInboxOpen|AgentInbox" srcRepository: RunMaestro/Maestro
Length of output: 18160
🏁 Script executed:
#!/bin/bash
# Read the specific lines in App.tsx
echo "=== Lines 10645-10655 in App.tsx ==="
sed -n '10645,10655p' src/renderer/App.tsx
echo ""
echo "=== Lines 11382-11390 in App.tsx ==="
sed -n '11382,11390p' src/renderer/App.tsx
echo ""
echo "=== Lines 11666-11675 in App.tsx ==="
sed -n '11666,11675p' src/renderer/App.tsx
echo ""
echo "=== Search for any feature flag or featureFlag patterns ==="
rg -n "featureFlag|feature.*[Ff]lag|Feature.*[Ff]lag" src | head -30Repository: RunMaestro/Maestro
Length of output: 1301
🏁 Script executed:
#!/bin/bash
# Check SettingsModal.tsx for feature flags
echo "=== Feature flags context in SettingsModal.tsx ==="
rg -n "Feature flags" src/renderer/components/SettingsModal.tsx -A 20
echo ""
echo "=== Check if there's any feature flag configuration or pattern in the codebase ==="
rg -n "const.*flag|export.*flag" src --max-count=20Repository: RunMaestro/Maestro
Length of output: 1022
Add feature flag gating for Unified Inbox entry points.
The Unified Inbox entry points (keyboard handler at useMainKeyboardHandler.ts:416, SessionList menu button at SessionList.tsx:661, and modal rendering at AppModals.tsx:266) are currently unconditional. Implement a feature flag to gate these access points (default off) so Unified Inbox can be disabled entirely when needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/App.tsx` around lines 10647 - 10649, Add a new feature flag
(e.g., isUnifiedInboxEnabled) defaulting to false and use it to gate all Unified
Inbox entry points: wrap the keyboard-open logic in useMainKeyboardHandler (the
handler that calls setAgentInboxOpen), the SessionList menu button that calls
setAgentInboxOpen (and any UI that shows addToast or toggles
setUsageDashboardOpen for Inbox flows), and the modal rendering in AppModals
that renders the Unified Inbox component; ensure each place checks
isUnifiedInboxEnabled before rendering UI or invoking
setAgentInboxOpen/setUsageDashboardOpen/addToast so the inbox can be disabled
entirely.
| if (row.type === 'header') { | ||
| const isCollapsed = collapsedGroups.has(row.groupName); | ||
| return ( | ||
| <div | ||
| style={{ | ||
| ...style, | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| paddingLeft: 16, | ||
| fontSize: 13, | ||
| fontWeight: 600, | ||
| color: theme.colors.textDim, | ||
| letterSpacing: '0.5px', | ||
| textTransform: 'uppercase', | ||
| borderBottom: `1px solid ${theme.colors.border}60`, | ||
| cursor: 'pointer', | ||
| }} | ||
| onClick={() => onToggleGroup(row.groupName)} | ||
| > | ||
| {isCollapsed | ||
| ? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} /> | ||
| : <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} /> | ||
| } | ||
| {row.groupName} | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
Make group headers keyboard‑toggleable (role/button + tabIndex).
Group headers are clickable <div> elements, so keyboard users can’t expand/collapse groups. Please render them as a <button> (or add role="button" + tabIndex={0} + key handlers) and include outline-none for focus styling.
🛠 Suggested fix
- return (
- <div
- style={{
- ...style,
- display: 'flex',
- alignItems: 'center',
- paddingLeft: 16,
- fontSize: 13,
- fontWeight: 600,
- color: theme.colors.textDim,
- letterSpacing: '0.5px',
- textTransform: 'uppercase',
- borderBottom: `1px solid ${theme.colors.border}60`,
- cursor: 'pointer',
- }}
- onClick={() => onToggleGroup(row.groupName)}
- >
- {isCollapsed
- ? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
- : <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
- }
- {row.groupName}
- </div>
- );
+ return (
+ <button
+ type="button"
+ className="outline-none"
+ style={{
+ ...style,
+ display: 'flex',
+ alignItems: 'center',
+ paddingLeft: 16,
+ fontSize: 13,
+ fontWeight: 600,
+ color: theme.colors.textDim,
+ letterSpacing: '0.5px',
+ textTransform: 'uppercase',
+ borderBottom: `1px solid ${theme.colors.border}60`,
+ cursor: 'pointer',
+ background: 'transparent',
+ border: 'none',
+ width: '100%',
+ textAlign: 'left',
+ }}
+ onClick={() => onToggleGroup(row.groupName)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onToggleGroup(row.groupName);
+ }
+ }}
+ >
+ {isCollapsed
+ ? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
+ : <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} />
+ }
+ {row.groupName}
+ </button>
+ );📝 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.
| if (row.type === 'header') { | |
| const isCollapsed = collapsedGroups.has(row.groupName); | |
| return ( | |
| <div | |
| style={{ | |
| ...style, | |
| display: 'flex', | |
| alignItems: 'center', | |
| paddingLeft: 16, | |
| fontSize: 13, | |
| fontWeight: 600, | |
| color: theme.colors.textDim, | |
| letterSpacing: '0.5px', | |
| textTransform: 'uppercase', | |
| borderBottom: `1px solid ${theme.colors.border}60`, | |
| cursor: 'pointer', | |
| }} | |
| onClick={() => onToggleGroup(row.groupName)} | |
| > | |
| {isCollapsed | |
| ? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} /> | |
| : <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} /> | |
| } | |
| {row.groupName} | |
| </div> | |
| ); | |
| if (row.type === 'header') { | |
| const isCollapsed = collapsedGroups.has(row.groupName); | |
| return ( | |
| <button | |
| type="button" | |
| className="outline-none" | |
| style={{ | |
| ...style, | |
| display: 'flex', | |
| alignItems: 'center', | |
| paddingLeft: 16, | |
| fontSize: 13, | |
| fontWeight: 600, | |
| color: theme.colors.textDim, | |
| letterSpacing: '0.5px', | |
| textTransform: 'uppercase', | |
| borderBottom: `1px solid ${theme.colors.border}60`, | |
| cursor: 'pointer', | |
| background: 'transparent', | |
| border: 'none', | |
| width: '100%', | |
| textAlign: 'left', | |
| }} | |
| onClick={() => onToggleGroup(row.groupName)} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| onToggleGroup(row.groupName); | |
| } | |
| }} | |
| > | |
| {isCollapsed | |
| ? <ChevronRight style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} /> | |
| : <ChevronDown style={{ width: 14, height: 14, marginRight: 4, flexShrink: 0 }} /> | |
| } | |
| {row.groupName} | |
| </button> | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AgentInbox.tsx` around lines 370 - 395, The header
row rendering (when row.type === 'header') is currently a clickable <div> and is
not keyboard-accessible; update the element to be keyboard-toggleable by either
rendering a <button> or adding role="button" and tabIndex={0} to the container
used around row.groupName and the Chevron icons, add an onKeyDown handler that
calls onToggleGroup(row.groupName) for Enter/Space, include the outline-none
class (or appropriate focus styling) for focus visibility, and ensure existing
symbols collapsedGroups, onToggleGroup, ChevronRight, and ChevronDown are
preserved and used as before.
| const rows = useMemo(() => { | ||
| if (collapsedGroups.size === 0) return allRows; | ||
| return allRows.filter(row => { | ||
| if (row.type === 'header') return true; | ||
| const itemGroup = row.item.groupName ?? 'Ungrouped'; | ||
| return !collapsedGroups.has(itemGroup); | ||
| }); | ||
| }, [allRows, collapsedGroups]); |
There was a problem hiding this comment.
Collapsed groups still hide items in non‑Grouped modes.
rows filters out collapsed groups even when sortMode is newest/oldest. If a user collapses a group, then switches sort modes, items from that group remain hidden with no header indicator. Gate the filter by sortMode.
🛠 Suggested fix
- const rows = useMemo(() => {
- if (collapsedGroups.size === 0) return allRows;
- return allRows.filter(row => {
- if (row.type === 'header') return true;
- const itemGroup = row.item.groupName ?? 'Ungrouped';
- return !collapsedGroups.has(itemGroup);
- });
- }, [allRows, collapsedGroups]);
+ const rows = useMemo(() => {
+ if (sortMode !== 'grouped' || collapsedGroups.size === 0) return allRows;
+ return allRows.filter(row => {
+ if (row.type === 'header') return true;
+ const itemGroup = row.item.groupName ?? 'Ungrouped';
+ return !collapsedGroups.has(itemGroup);
+ });
+ }, [allRows, collapsedGroups, sortMode]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const rows = useMemo(() => { | |
| if (collapsedGroups.size === 0) return allRows; | |
| return allRows.filter(row => { | |
| if (row.type === 'header') return true; | |
| const itemGroup = row.item.groupName ?? 'Ungrouped'; | |
| return !collapsedGroups.has(itemGroup); | |
| }); | |
| }, [allRows, collapsedGroups]); | |
| const rows = useMemo(() => { | |
| if (sortMode !== 'grouped' || collapsedGroups.size === 0) return allRows; | |
| return allRows.filter(row => { | |
| if (row.type === 'header') return true; | |
| const itemGroup = row.item.groupName ?? 'Ungrouped'; | |
| return !collapsedGroups.has(itemGroup); | |
| }); | |
| }, [allRows, collapsedGroups, sortMode]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AgentInbox.tsx` around lines 462 - 469, The current
useMemo for rows always filters out items in collapsedGroups causing items to
remain hidden when sortMode is 'newest' or 'oldest'; modify the rows computation
to only apply the collapsedGroups filter when sortMode is the grouped mode
(e.g., 'grouped' or whatever value you use for grouping). Concretely, in the
useMemo that defines rows (referencing collapsedGroups, allRows, and sortMode),
return allRows early if collapsedGroups.size === 0 OR sortMode is not the
grouped value, otherwise apply the existing filter logic that keeps headers and
excludes items whose item.groupName is in collapsedGroups.
| // Reset selection when items change | ||
| useEffect(() => { | ||
| setSelectedIndex(0); | ||
| }, [items]); |
There was a problem hiding this comment.
Selection can target hidden rows after collapsing a group.
selectedIndex is based on items (pre-collapse). When a group is collapsed, the selected item may disappear, leaving no visible selection and allowing Enter to navigate to a hidden item. Consider re-syncing selectedIndex to the first visible item when rows change.
🛠 Suggested fix
useEffect(() => {
setSelectedIndex(0);
}, [items]);
+
+ useEffect(() => {
+ if (rows.length === 0) return;
+ const visibleIndexes = new Set(
+ rows.filter((row) => row.type === 'item').map((row) => row.index)
+ );
+ if (!visibleIndexes.has(selectedIndex)) {
+ const firstItemRow = rows.find((row) => row.type === 'item');
+ if (firstItemRow && firstItemRow.type === 'item') {
+ setSelectedIndex(firstItemRow.index);
+ }
+ }
+ }, [rows, selectedIndex]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Reset selection when items change | |
| useEffect(() => { | |
| setSelectedIndex(0); | |
| }, [items]); | |
| // Reset selection when items change | |
| useEffect(() => { | |
| setSelectedIndex(0); | |
| }, [items]); | |
| useEffect(() => { | |
| if (rows.length === 0) return; | |
| const visibleIndexes = new Set( | |
| rows.filter((row) => row.type === 'item').map((row) => row.index) | |
| ); | |
| if (!visibleIndexes.has(selectedIndex)) { | |
| const firstItemRow = rows.find((row) => row.type === 'item'); | |
| if (firstItemRow && firstItemRow.type === 'item') { | |
| setSelectedIndex(firstItemRow.index); | |
| } | |
| } | |
| }, [rows, selectedIndex]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AgentInbox.tsx` around lines 502 - 505, The current
useEffect resets selectedIndex when items change but doesn’t handle collapsing
which changes visible rows; add a new sync that runs when rows change (or modify
the existing effect to also depend on rows) and re-map or clamp the
selectedIndex to a visible row: compute the first visible row index (e.g., 0 if
rows non-empty, or clamp the existing selectedIndex to rows.length - 1) and call
setSelectedIndex with that value so Enter always targets a visible row;
reference selectedIndex, setSelectedIndex, useEffect, items and rows when
implementing this fix.
| {/* --- AGENT INBOX (lazy-loaded) --- */} | ||
| {agentInboxOpen && ( | ||
| <Suspense fallback={null}> | ||
| <AgentInbox | ||
| theme={theme} | ||
| sessions={sessions} | ||
| groups={groups} | ||
| onClose={onCloseAgentInbox} | ||
| onNavigateToSession={onNavigateToSession} | ||
| /> | ||
| </Suspense> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check encoreFeatures usage and agentInbox integration
rg -n "encoreFeatures" src/renderer/components/AppModals.tsx -A 3 -B 3Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Search for all agentInbox references
rg -n "agentInbox" src/renderer/components/AppModals.tsx -A 3 -B 3Repository: RunMaestro/Maestro
Length of output: 1449
🏁 Script executed:
#!/bin/bash
# Find where encoreFeatures.unifiedInbox is defined and used
rg -n "unifiedInbox" --type ts --type tsx -A 2 -B 2Repository: RunMaestro/Maestro
Length of output: 89
🏁 Script executed:
#!/bin/bash
# Find where agentInboxOpen state is managed
rg -n "agentInboxOpen" --type ts --type tsx -A 2 -B 2Repository: RunMaestro/Maestro
Length of output: 89
🏁 Script executed:
#!/bin/bash
# Find where agentInboxOpen state is set (grep for useState/setAgentInboxOpen)
rg -n "setAgentInboxOpen|agentInboxOpen.*=" src/renderer/ -A 2 -B 1Repository: RunMaestro/Maestro
Length of output: 6195
🏁 Script executed:
#!/bin/bash
# Search for encoreFeatures without file type restrictions
rg -n "encoreFeatures" src/renderer/ -A 2 -B 1Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Find parent component that passes agentInboxOpen to AppModals
rg -n "AppModals" src/renderer/ --type ts -A 5 -B 5Repository: RunMaestro/Maestro
Length of output: 5730
🏁 Script executed:
#!/bin/bash
# Look for feature flag usage patterns
rg -n "unifiedInbox|encoreFeatures" src/ -A 2Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Search for encoreFeatures definition and usage
find src -name "*.ts" -o -name "*.tsx" | xargs grep -l "encoreFeatures" 2>/dev/null | head -20Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look for feature flag patterns in the codebase
rg -n "feature" src/renderer/ -i | grep -i "flag\|enable\|unified" | head -30Repository: RunMaestro/Maestro
Length of output: 1036
🏁 Script executed:
#!/bin/bash
# Check where AgentInbox is imported and defined
rg -n "AgentInbox" src/renderer/ -B 2 -A 2 | head -50Repository: RunMaestro/Maestro
Length of output: 3415
🏁 Script executed:
#!/bin/bash
# Look at the keyboard handler to see if feature flag is checked there
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | sed -n '410,425p'Repository: RunMaestro/Maestro
Length of output: 637
🏁 Script executed:
#!/bin/bash
# Check SessionList.tsx for the Agent Inbox menu item
cat -n src/renderer/components/SessionList.tsx | sed -n '655,670p'Repository: RunMaestro/Maestro
Length of output: 694
🏁 Script executed:
#!/bin/bash
# Look for encoreFeatures definition - check settings/store files
rg -n "encore" src/ -iRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if there are any feature flag gating patterns used in the codebase
rg -n "if.*feature\|if.*enabled\|feature.*&&" src/renderer/components/ -A 1 | head -40Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check the useAgentInbox hook to see if it has any feature flag checks
cat -n src/renderer/hooks/useAgentInbox.ts | head -50Repository: RunMaestro/Maestro
Length of output: 1897
🏁 Script executed:
#!/bin/bash
# Look at the main keyboard handler context to see what's available
rg -n "setAgentInboxOpen\|useMainKeyboardHandler" src/renderer/hooks/keyboard/useMainKeyboardHandler.ts -B 5 -A 5 | head -50Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Search for any feature flag or settings files
find src -type f \( -name "*config*" -o -name "*settings*" -o -name "*feature*" \) 2>/dev/nullRepository: RunMaestro/Maestro
Length of output: 427
🏁 Script executed:
#!/bin/bash
# Check if there's any pattern of conditional rendering based on feature flags in modals
rg -n "if.*Open.*&&\|{.*Open.*&&" src/renderer/components/AppModals.tsx | head -20Repository: RunMaestro/Maestro
Length of output: 184
🏁 Script executed:
#!/bin/bash
# Look at the complete AppModals render to see if any other modals have conditional gating
cat -n src/renderer/components/AppModals.tsx | sed -n '265,280p'Repository: RunMaestro/Maestro
Length of output: 585
🏁 Script executed:
#!/bin/bash
# Check if there are environment variables or configs for feature flags
find src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) | xargs grep -l "feature\|FEATURE" 2>/dev/null | grep -E "config|env|constant" | head -10Repository: RunMaestro/Maestro
Length of output: 102
🏁 Script executed:
#!/bin/bash
# Check how other similar modals are rendered in AppModals to see if any use conditional logic
cat -n src/renderer/components/AppModals.tsx | sed -n '230,280p'Repository: RunMaestro/Maestro
Length of output: 1788
🏁 Script executed:
#!/bin/bash
# Look for any mention of "encore" in the entire codebase
find src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) -exec grep -l "encore" {} \; 2>/dev/nullRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if there's a settings system where encoreFeatures might be stored
grep -rn "unifiedInbox\|encoreFeatures" src/ 2>/dev/nullRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Examine the keyboard handler context to see what data is available
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | sed -n '1,50p'Repository: RunMaestro/Maestro
Length of output: 2615
🏁 Script executed:
#!/bin/bash
# Check if AgentInbox component has any internal feature flag checks
find src -name "AgentInbox*" -o -name "*AgentInbox*" | head -10Repository: RunMaestro/Maestro
Length of output: 241
🏁 Script executed:
#!/bin/bash
# Look at App.tsx to see if there's any feature flag system or settings being used
rg -n "settings\|flag\|feature" src/renderer/App.tsx -i | grep -E "encore|unified|inbox|feature" | head -20Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if there's any useSettings or feature flag hook being used in App.tsx
rg -n "useSettings\|useFeature" src/renderer/App.tsx -A 3Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look for the AgentInbox component definition
find src/renderer -name "*AgentInbox*" -type fRepository: RunMaestro/Maestro
Length of output: 136
🏁 Script executed:
#!/bin/bash
# Check the AgentInbox component
cat -n src/renderer/components/AgentInbox.tsx | head -60Repository: RunMaestro/Maestro
Length of output: 2949
🏁 Script executed:
#!/bin/bash
# Look at the complete useMainKeyboardHandler to see the context being passed
cat -n src/renderer/hooks/keyboard/useMainKeyboardHandler.ts | sed -n '400,430p'Repository: RunMaestro/Maestro
Length of output: 1381
🏁 Script executed:
#!/bin/bash
# Check the entire keyboard handler to understand what context is available
wc -l src/renderer/hooks/keyboard/useMainKeyboardHandler.tsRepository: RunMaestro/Maestro
Length of output: 119
🏁 Script executed:
#!/bin/bash
# Look at App.tsx to understand where setAgentInboxOpen comes from and if any feature gating exists
rg -n "const agentInboxOpen\|setAgentInboxOpen.*=" src/renderer/App.tsx -B 2 -A 2Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Find where agentInboxOpen is destructured in App.tsx
cat -n src/renderer/App.tsx | grep -A 5 -B 5 "agentInboxOpen"Repository: RunMaestro/Maestro
Length of output: 1038
🏁 Script executed:
#!/bin/bash
# Check the modalStore to see if there's any feature flag checking there
cat -n src/renderer/stores/modalStore.ts | sed -n '520,540p'Repository: RunMaestro/Maestro
Length of output: 965
🏁 Script executed:
#!/bin/bash
# Look for any feature flag or settings integration in the app
rg -n "useSettings\|window\.maestro\.settings" src/renderer/App.tsx | head -20Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if there's a settings or preferences system for features
find src/renderer -name "*settings*" -o -name "*preference*" | grep -E "\.ts$|\.tsx$"Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check the complete flow - look at where modals are defined in modalStore
cat -n src/renderer/stores/modalStore.ts | sed -n '1,50p'Repository: RunMaestro/Maestro
Length of output: 2027
🏁 Script executed:
#!/bin/bash
# Search for any FEATURE, feature, FLAG, flag patterns in the renderer
rg -n "FEATURE|FLAG" src/renderer/ -i | grep -v "node_modules\|\.test\." | head -30Repository: RunMaestro/Maestro
Length of output: 2998
🏁 Script executed:
#!/bin/bash
# Check if there's any condition placed before opening modals anywhere
rg -n "Open.*&&\|if.*Open\|&&.*setAgent" src/renderer/ | head -20Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look at the complete App.tsx initialization to see if settings are being used
cat -n src/renderer/App.tsx | sed -n '1,100p'Repository: RunMaestro/Maestro
Length of output: 4047
🏁 Script executed:
#!/bin/bash
# Look at useSettings hook usage in App.tsx
rg -n "useSettings\|settings\." src/renderer/App.tsx | head -20Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check what useSettings hook does
cat -n src/renderer/hooks/useSettings.ts | head -80Repository: RunMaestro/Maestro
Length of output: 127
🏁 Script executed:
#!/bin/bash
# Look for any encore or feature flag related content in App.tsx more carefully
rg -n "encore\|unified.*feature\|feature.*unified" src/renderer/App.tsx -iRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if there's any PR or commit message related to feature flag for Unified Inbox
git log --oneline --all 2>/dev/null | grep -i "unified\|inbox\|encore\|feature" | head -20Repository: RunMaestro/Maestro
Length of output: 1718
🏁 Script executed:
#!/bin/bash
# Now search for encoreFeatures since we know it exists from git log
rg -n "encoreFeatures" src/renderer/ -A 3 -B 1 | head -100Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look for the settings store where encoreFeatures is defined
rg -n "encoreFeatures" src/renderer/stores/ -A 3Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check all access points to setAgentInboxOpen to see if they should be gated
rg -n "setAgentInboxOpen" src/renderer/ -B 5 -A 1 | grep -E "setAgentInboxOpen|if.*encores|if.*feature|if.*unified"Repository: RunMaestro/Maestro
Length of output: 1319
🏁 Script executed:
#!/bin/bash
# Look at the commit that added Encore Features system
git show 608e4f73 --stat 2>/dev/null | head -50Repository: RunMaestro/Maestro
Length of output: 1613
🏁 Script executed:
#!/bin/bash
# Now search for directorNotes to see how it's gated with encoreFeatures
rg -n "directorNotes\|EncoreFeatureFlags" src/renderer/ -A 2 -B 1 | head -100Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look at the settings hook to see encoreFeatures
cat -n src/renderer/hooks/settings/useSettings.ts 2>/dev/null || echo "File not found, searching for settings"Repository: RunMaestro/Maestro
Length of output: 50374
🏁 Script executed:
#!/bin/bash
# Search for where encoreFeatures is defined in types
rg -n "EncoreFeatureFlags\|encoreFeatures" src/renderer/types/ -A 5Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if unifiedInbox is mentioned in the types as part of encoreFeatures
rg -n "unifiedInbox" src/renderer/ -B 3 -A 3Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Search for encoreFeatures definition in types
rg -n "EncoreFeatureFlags\|encoreFeatures" src/renderer/types/index.ts -A 5Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look for where encoreFeatures is used and managed
rg -n "encoreFeatures" src/renderer/hooks/settings/useSettings.ts -A 3Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if there's a constants file for encore features
find src/renderer -name "*encore*" -o -name "*feature*" | grep -v node_modules | grep -v ".test"Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look at the commit that added encoreFeatures to see what was implemented
git show 52b63e56 --stat 2>/dev/null | head -30Repository: RunMaestro/Maestro
Length of output: 744
🏁 Script executed:
#!/bin/bash
# Let's look at types/index.ts to find EncoreFeatureFlags
cat -n src/renderer/types/index.ts | grep -A 10 -B 5 "encoreFeatures\|EncoreFeatureFlags" | head -50Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Search for directorNotes to see how it's currently gated
rg -n "directorNotes" src/renderer/ -B 2 -A 2 | head -50Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if encoreFeatures is in the settingsStore
rg -n "encoreFeatures" src/renderer/stores/ -B 2 -A 2Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look at all references to feature flags and gating patterns in the whole renderer
rg -n "if.*directorNotes\|if.*encoreFeatures" src/renderer/ | head -20Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Try broader search for any feature flag or gating pattern
rg -n "feature.*flag\|FEATURE\|Feature" src/renderer/ -i | grep -v test | grep -v ".node" | head -30Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Look at App.tsx imports to see what settings are being used
cat -n src/renderer/App.tsx | sed -n '70,150p'Repository: RunMaestro/Maestro
Length of output: 3381
🏁 Script executed:
#!/bin/bash
# Check the latest state of the file by looking at what encoreFeatures is imported/used
git log --oneline --all --grep="encore\|encoreFeatures" 2>/dev/null | head -10Repository: RunMaestro/Maestro
Length of output: 395
🏁 Script executed:
#!/bin/bash
# Search for any TODO or FIXME related to Unified Inbox or feature flags
rg -n "TODO\|FIXME" src/renderer/components/AppModals.tsx src/renderer/hooks/keyboard/useMainKeyboardHandler.ts src/renderer/components/SessionList.tsx -A 2Repository: RunMaestro/Maestro
Length of output: 44
Gate Unified Inbox behind feature flag at all access points.
The Unified Inbox currently renders whenever agentInboxOpen is true without checking a feature flag. All three access points—keyboard shortcut, SessionList menu, and modal render in AppModals.tsx—bypass the feature-gating system that was established for optional features like Director's Notes. For consistency with the Encore Features pattern and to prevent premature exposure, gate the feature at:
- Keyboard handler (useMainKeyboardHandler.ts, line 416)
- SessionList menu item (SessionList.tsx, line 661)
- Modal render condition (AppModals.tsx, line 266)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AppModals.tsx` around lines 265 - 275, Gate the
Unified Inbox behind the Encore feature flag at all access points: add the same
feature check used by the Encore Features pattern (the project's feature toggle
helper) to the keyboard handler in useMainKeyboardHandler, the SessionList menu
item in SessionList.tsx, and the modal render in AppModals.tsx so AgentInbox
only renders when the feature is enabled; specifically, wrap the existing
agentInboxOpen condition that renders <AgentInbox ... /> with the feature flag
check (so the conditional requires both agentInboxOpen and the unified-inbox
feature true) and similarly guard the keyboard shortcut handler and the
SessionList menu item that set agentInboxOpen/open it, using the project’s
standard feature-checking function.
Summary
Features
Architecture
src/shared/agentInboxTypes.tsuseAgentInbox.ts— aggregation with null guardsAgentInbox.tsx— registered inAppModals.tsxwith lazy importuseMainKeyboardHandler.tsTest plan
npm run test)npm run lint)npm run lint:eslint)Screenshots
Summary by CodeRabbit
Release Notes
New Features
Dependencies