Skip to content

polish: 0.15.0-RC polish round two#460

Merged
pedramamini merged 20 commits intomainfrom
0.15.0-RC-polish-round-two
Feb 25, 2026
Merged

polish: 0.15.0-RC polish round two#460
pedramamini merged 20 commits intomainfrom
0.15.0-RC-polish-round-two

Conversation

@pedramamini
Copy link
Collaborator

@pedramamini pedramamini commented Feb 25, 2026

Summary

  • Director's Notes: old content stays visible and scrollable during regeneration, cache persists regardless of lookback slider changes, renamed "Refresh" to "Regenerate"
  • Reduced collapsed sidebar indicator opacity to 0.25 for less visual noise
  • Header z-index, status dot opacity, and Director's Notes beta badge refinements
  • Context menus prevented from rendering off-screen
  • Orphaned file preview prevention when tab bar is hidden
  • Blocked issue message moved to footer with greyed-out Start button
  • Various bug fixes and performance improvements across stats, UI, accessibility, and session management

Test plan

  • Open Director's Notes, generate synopsis, close and reopen — cached content should appear instantly
  • Change lookback slider — old notes persist, click Regenerate to update
  • During regeneration, verify old notes are scrollable and Save/Copy buttons work
  • Verify collapsed sidebar indicators are subtler (0.25 opacity)
  • Right-click near screen edges — context menus should stay within viewport

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced group chat support with improved group integration across the interface.
    • Improved AI Overview tab caching behavior with better state synchronization.
  • Bug Fixes

    • Fixed PR finalization logic to only allow finalization in "ready_for_review" state.
    • Simplified error handling in data fetching with improved logging.
    • Better file preview rendering in terminal mode to prevent orphaned previews.
  • UX Improvements

    • Renamed "Refresh" button to "Regenerate" in AI Overview for clarity.
    • Content remains visible during regeneration with improved button availability.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

This PR refactors UI state management across the renderer component tree by expanding prop-driven state composition in App.tsx, wiring additional UI state and setters through MainPanel, SessionList, GroupChatPanel and related hooks, while simplifying internal APIs (removing lookbackDays parameter from hasCachedSynopsis) and updating dependent code.

Changes

Cohort / File(s) Summary
API Signature Changes
src/renderer/components/DirectorNotes/AIOverviewTab.tsx, src/renderer/components/DirectorNotes/DirectorNotesModal.tsx, src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx
Removed lookbackDays parameter from hasCachedSynopsis() function; call sites updated to invoke without arguments; cache logic refactored to accept lookbackDays from cached entry instead of requiring parameter match.
Prop Surface Expansion — App.tsx
src/renderer/App.tsx
Extended props wiring across MaestroConsole, MainPanel, SessionList, GroupChatPanel with UI state (activeFocus, outputSearchOpen, outputSearchQuery, markdownEditMode, chatRawTextMode, etc.), modal/focus handlers, context warning thresholds, batch run states, and group chat configuration.
Prop Surface Expansion — MainPanel & GroupChat
src/renderer/components/MainPanel.tsx, src/renderer/components/GroupChatPanel.tsx, src/renderer/components/GroupChatInput.tsx
MainPanelProps interface expanded with fontFamily, search/focus controls, editing modes, context warnings, and corresponding setters; GroupChatPanel/Input added optional groups prop; internal store usage replaced with prop-driven sources.
Prop Surface Expansion — SessionList
src/renderer/components/SessionList.tsx
SessionListProps interface significantly expanded with sessions, groups, shortcuts, web interface settings, modal handlers, batch/auto-run states, group chat controls, context warning thresholds, and numerous setter callbacks; replaced store subscriptions with prop-based composition.
Hook Dependencies Expansion
src/renderer/hooks/props/useMainPanelProps.ts, src/renderer/hooks/props/useSessionListProps.ts
useMainPanelProps dependencies expanded with 20+ UI/state fields and setters; useSessionListProps refactored with new UseSessionListPropsDeps interface encapsulating 50+ fields including group chats, batch runs, web interface, and context thresholds.
Remote Integration Logic Refactor
src/renderer/hooks/remote/useRemoteIntegration.ts
Removed internal applyInputModeToSession helper; replaced with inline logic that conditionally clears activeFileTabId when switching to terminal mode.
Semantic Logic Changes
src/renderer/components/DirectorNotes/AIOverviewTab.tsx
Updated synopsis caching/regeneration behavior: use cached data on mount regardless of lookbackDays, disable lookback slider only during initial generation, enable save/copy buttons when synopsis exists, show progress bar only for first generation, keep generated-at timestamp and error banner visible during regeneration.
Test Updates — API Changes
src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx, src/__tests__/renderer/components/GroupChatInput.test.tsx, src/__tests__/renderer/components/DocumentGraph/NodeContextMenu.test.tsx
Updated test assertions and click targets for "Regenerate" button label; replaced seed-and-render pattern with direct prop rendering; changed error handling assertions from sentry capture to console.error logging.
Formatting & Minor Fixes
src/main/agents/definitions.ts, src/main/ipc/handlers/git.ts, src/renderer/components/Settings/IgnorePatternsSection.tsx, src/renderer/components/SymphonyModal.tsx, src/renderer/components/UsageDashboard/ChartSkeletons.tsx, src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx, src/renderer/components/UsageDashboard/SummaryCards.tsx, src/renderer/components/DocumentGraph/NodeContextMenu.tsx
Whitespace/indentation formatting adjustments; removed request tracking and error state in LongestAutoRunsTable; changed finalize condition in SymphonyModal from 'ready_for_review' | 'completed' to 'ready_for_review' only; replaced sentry exception capture with console.error.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'polish: 0.15.0-RC polish round two' accurately reflects the comprehensive UI polish and refinement work across Director's Notes, session management, context menus, and multiple components documented in the PR objectives.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 0.15.0-RC-polish-round-two

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

@greptile-apps
Copy link

greptile-apps bot commented Feb 25, 2026

Greptile Summary

This polish PR improves Director's Notes UX and fixes an orphaned file preview bug. The Director's Notes cache now persists regardless of lookback slider changes, old content remains visible and scrollable during regeneration, and the button was renamed from "Refresh" to "Regenerate" for clarity. The orphaned file preview fix adds defensive guards across multiple locations (MainPanel, useRemoteIntegration, useSessionRestoration, agentStore) to ensure activeFileTabId is cleared when switching to terminal mode. Additional polish includes reduced collapsed sidebar indicator opacity (0.25), header z-index increase, Beta badge on Director's Notes, and Zustand v4 API migration.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • Well-structured polish changes with comprehensive test coverage. The orphaned file preview fixes are defensive and properly guarded. Director's Notes improvements enhance UX without breaking changes. All changes are incremental refinements with appropriate tests.
  • No files require special attention

Important Files Changed

Filename Overview
src/renderer/components/DirectorNotes/AIOverviewTab.tsx Cache behavior improved to persist regardless of lookback slider; UI keeps old content visible during regeneration with enabled Save/Copy buttons
src/renderer/components/MainPanel.tsx Added guards to prevent orphaned file preview in terminal mode; increased header z-index to 20
src/renderer/hooks/remote/useRemoteIntegration.ts Clears activeFileTabId when switching to terminal mode to prevent orphaned file previews
src/renderer/hooks/session/useSessionRestoration.ts Added validation to clear orphaned activeFileTabId when inputMode is not 'ai'
src/renderer/stores/agentStore.ts Clears activeFileTabId during re-authentication to prevent orphaned file preview
src/renderer/components/SessionList.tsx Updated Zustand v4 shallow API usage; reduced status dot opacity to 0.25 for collapsed indicators

Last reviewed commit: d4f4d78

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

🤖 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/components/GroupChatPanel.tsx`:
- Around line 40-41: The GroupChatPanel component has an unused prop named
groups: remove the dead prop from the component props type and from the
destructuring in GroupChatPanel so it is no longer declared or referenced;
specifically, delete the optional groups?: Group[] declaration and remove groups
from the destructured props list in the GroupChatPanel function, leaving
onDraftChange and other used props intact (GroupChatInput already reads groups
from the session store so no forwarding is needed).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b01abc and d4f4d78.

📒 Files selected for processing (17)
  • src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx
  • src/__tests__/renderer/hooks/useRemoteIntegration.test.ts
  • src/__tests__/renderer/hooks/useSessionRestoration.test.ts
  • src/__tests__/renderer/stores/agentStore.test.ts
  • src/renderer/App.tsx
  • src/renderer/components/DirectorNotes/AIOverviewTab.tsx
  • src/renderer/components/DirectorNotes/DirectorNotesModal.tsx
  • src/renderer/components/GroupChatPanel.tsx
  • src/renderer/components/MainPanel.tsx
  • src/renderer/components/SessionList.tsx
  • src/renderer/components/SettingsModal.tsx
  • src/renderer/components/UsageDashboard/AutoRunStats.tsx
  • src/renderer/components/UsageDashboard/SessionStats.tsx
  • src/renderer/components/UsageDashboard/TasksByHourChart.tsx
  • src/renderer/hooks/remote/useRemoteIntegration.ts
  • src/renderer/hooks/session/useSessionRestoration.ts
  • src/renderer/stores/agentStore.ts

Comment on lines +40 to 41
groups?: Group[];
onDraftChange?: (draft: string) => void;
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:

rg -n "groups" --type tsx src/renderer/components/GroupChatInput.tsx -A 3 -B 3

Repository: RunMaestro/Maestro

Length of output: 89


🏁 Script executed:

# Also check the GroupChatPanel to see the full context of how groups is used/passed
rg -n "groups" --type tsx src/renderer/components/GroupChatPanel.tsx -A 2 -B 2

Repository: RunMaestro/Maestro

Length of output: 89


🏁 Script executed:

# Check the interface/type definition of GroupChatInput props
ast-grep --pattern $'interface GroupChatInputProps {
  $$$
}'

Repository: RunMaestro/Maestro

Length of output: 3070


🏁 Script executed:

cat -n src/renderer/components/GroupChatPanel.tsx | sed -n '35,170p'

Repository: RunMaestro/Maestro

Length of output: 4891


🏁 Script executed:

# Check GroupChatHeader and GroupChatMessages props to see if they accept groups
ast-grep --pattern $'interface GroupChatHeaderProps {
  $$$
}'

Repository: RunMaestro/Maestro

Length of output: 1235


🏁 Script executed:

# Also check for GroupChatMessages props
ast-grep --pattern $'interface GroupChatMessagesProps {
  $$$
}'

Repository: RunMaestro/Maestro

Length of output: 977


🏁 Script executed:

rg "groups" src/renderer/components/GroupChatPanel.tsx

Repository: RunMaestro/Maestro

Length of output: 89


🏁 Script executed:

# Also check if GroupChatInput is being updated elsewhere to accept groups
rg "groups" src/renderer/components/GroupChatInput.tsx

Repository: RunMaestro/Maestro

Length of output: 397


groups prop in GroupChatPanel is unused, but not a blocker for @mention features.

groups is destructured at line 91 but not forwarded to any child. However, GroupChatInput does not expect groups as a prop—it reads groups directly from the store via useSessionStore((s) => s.groups). This means the group @mention feature will function correctly despite groups not being passed down. The dead prop is still worth cleaning up, but the feature is not silently broken.

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

In `@src/renderer/components/GroupChatPanel.tsx` around lines 40 - 41, The
GroupChatPanel component has an unused prop named groups: remove the dead prop
from the component props type and from the destructuring in GroupChatPanel so it
is no longer declared or referenced; specifically, delete the optional groups?:
Group[] declaration and remove groups from the destructured props list in the
GroupChatPanel function, leaving onDraftChange and other used props intact
(GroupChatInput already reads groups from the session store so no forwarding is
needed).

pedramamini and others added 20 commits February 25, 2026 14:39
Remove 7 console.log calls from useSessionDebounce that fired at high
frequency during AI streaming. Replace index-based keys with stable
identifiers in 6 components where items can be removed or filtered
(staged images, diff tabs, log entries, quit modal agents).
- QuitConfirmModal: use composite key `${name}-${index}` to prevent
  duplicate React keys when agents share display names
- extensionColors: early-return accent fallback in colorblind mode so
  we never serve non-colorblind-safe colors from EXTENSION_MAP
…y from persistence

Users with large working directories (100K+ files) caused sessions.json to
balloon to 300MB+. Root cause: the full file tree (FileTreeNode[]) was persisted
for every session with no size limits.

Three changes:
1. Strip fileTree, fileTreeStats, filePreviewHistory from persistence — these
   are ephemeral cache data that re-scan automatically on session activation
2. Add configurable local ignore patterns setting (default: .git, node_modules,
   __pycache__) with UI in Settings > Display, reusing a new generic
   IgnorePatternsSection component extracted from SshRemoteIgnoreSection
3. Wire local ignore patterns through loadFileTree for local (non-SSH) scans
Add localHonorGitignore setting (default: true) that parses .gitignore
files and merges their patterns with the user's local ignore patterns
when building file trees. Mirrors the existing SSH remote gitignore
support for local filesystem scans.
…s object, and rescan effect

- Extract parseGitignoreContent() shared between local/remote gitignore handling
- Replace 7 positional params in loadFileTree with LocalFileTreeOptions object
- Export DEFAULT_LOCAL_IGNORE_PATTERNS from settingsStore, use in SettingsModal
- Fix deprecated onKeyPress -> onKeyDown in IgnorePatternsSection
- Add role="checkbox" and aria-checked to honor-gitignore toggle
- Add useEffect to auto-rescan file tree when local ignore patterns change
- Update tests for new loadFileTree signature
…n plan mode (#447)

OpenCode's defaultEnvVars always set OPENCODE_CONFIG_CONTENT with
"*":"allow" blanket permissions. In read-only mode, CLI YOLO flags were
stripped but the env var granting auto-approval was not. Add
readOnlyEnvOverrides to AgentConfig so agents can specify env var
overrides for read-only mode. OpenCode's override strips blanket
permissions while keeping question tool disabled.
React.memo wraps for 29 components (7 core + 22 UsageDashboard charts)
to prevent unnecessary re-renders on parent state changes.

Add compound DB indexes (start_time, agent_type/project_path/source)
for common query patterns in stats aggregation.

Parallelize sequential git rev-parse calls in worktreeInfo and
worktreeSetup handlers with Promise.all (~4x fewer round-trips).

Cache Date.getTime() results in useFilteredAndSortedSessions sort
comparator to avoid repeated date parsing during sort operations.

Replace inline debounce in useStats with shared useDebouncedCallback
hook, adding proper cleanup on unmount.
Blocked issues were completely non-interactive. Now they can be clicked
and keyboard-navigated to explore their documents, while the Start
Symphony button remains hidden to prevent starting blocked work.
Group @mentions: typing @ in group chat now shows agent groups in the
dropdown (visually differentiated with icon and member count badge).
Selecting a group expands to individual @mentions for all member agents.

Navigation history: group chats now participate in back/forward breadcrumb
navigation. Fixed pre-existing dual-instance bug where useNavigationHistory
was instantiated independently in App.tsx and useSessionLifecycle.ts,
creating separate stacks that couldn't communicate.
Replace the read-only provider display with a dropdown selector.
When provider changes, tabs reset to a fresh session and provider-
specific config (path, args, env, model) is cleared. History data
persists since it's keyed by Maestro session ID.
* feat: add archive state to group-chats

* fix: deduplicate GroupChat return type in global.d.ts, make archive callback optional

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Bug fixes: use tasksTotal for AutoRunStats count, add division-by-zero
guard in WeekdayComparisonChart, fix useStats effect cleanup, add v4
stats migration for compound indexes.

Sentry compliance: replace console.error with captureException in
QRCode, AutoRunStats, LongestAutoRunsTable, TasksByHourChart.

Accessibility: add keyboard handlers to AutoRunStats bar items, add
target guard and focus-visible ring to SymphonyModal.

Performance: memo MetricCard, useMemo for PeakHoursChart hasData,
useId for DurationTrendsChart gradient, stable opacities in
ChartSkeletons, stable onRemove ref in Toast, consistent variable
naming in git handler.
Removed inline blocked info box from the detail header. The footer now
shows for blocked issues with a disabled Start Symphony button and a
lock-icon message explaining the dependency block, consistent with the
normal footer layout.
Replace ad-hoc hardcoded pixel buffers across 6 context menu
implementations with a shared useContextMenuPosition hook that
measures actual rendered menu size via getBoundingClientRect in
useLayoutEffect, clamping position to keep menus fully visible.

Two menus (SessionActivityGraph, GroupChatHistoryPanel) previously
had no viewport detection at all.
File preview rendering was independent of inputMode, so switching to
terminal mode (via authenticateAfterError or remote mode switch) without
clearing activeFileTabId left the file preview visible with no tab bar.
The corrupted state persisted across app restarts.

Guard file preview with inputMode='ai', clear activeFileTabId on all
terminal-mode transitions, and auto-heal on session restoration.
- Clear activeFileTabId in onRemoteCommand mode-sync path (same bug
  as onRemoteSwitchMode, caught by CodeRabbit)
- Fix "preserves activeFileTabId" test to use non-null starting value
  so it actually tests preservation behavior
- Add test for onRemoteCommand clearing activeFileTabId on terminal sync
…rector's Notes beta badge

- Add z-20 to header container to prevent overlap issues
- Dim agent status dots for inactive sessions (0.45 opacity)
- Add beta badge to Director's Notes in Encore Features settings
Cache no longer keys on lookbackDays — once generated, notes persist
until explicit Regenerate click. During regeneration, old notes stay
visible and scrollable with stats bar, timestamp, and Save/Copy buttons
remaining enabled. Progress bar only shows on first-ever generation.
Renamed "Refresh" to "Regenerate" for clearer intent.
Add ~37 missing props to useMainPanelProps interface, useMemo return,
and dependency array. Remove state props from useRightPanelProps call
in App.tsx that the refactored hook no longer accepts (RightPanel now
reads state directly from Zustand stores).
@pedramamini pedramamini force-pushed the 0.15.0-RC-polish-round-two branch from d4f4d78 to 9d0936d Compare February 25, 2026 19:56
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/__tests__/renderer/components/DocumentGraph/NodeContextMenu.test.tsx (1)

337-355: ⚠️ Potential issue | 🟡 Minor

Wrap the test body in try-finally to ensure console.error spy is always restored.

The afterEach hook only calls vi.clearAllMocks(), which clears call history but does not restore spies created with vi.spyOn(). If the test throws before reaching consoleErrorSpy.mockRestore() (e.g., assertion failure), the spy remains active and leaks into subsequent tests. Use try-finally to guarantee restoration even on test failure.

Suggested fix
-			const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
-			mockClipboardWriteText.mockRejectedValueOnce(new Error('Clipboard access denied'));
-
-			const onDismiss = vi.fn();
-			const props = createProps({ nodeData: mockDocumentNodeData, onDismiss });
-			render(<NodeContextMenu {...props} />);
-
-			fireEvent.click(screen.getByRole('button', { name: /copy path/i }));
-
-			await waitFor(() => {
-				expect(consoleErrorSpy).toHaveBeenCalledWith(
-					'Failed to copy to clipboard:',
-					expect.any(Error)
-				);
-			});
-			expect(onDismiss).toHaveBeenCalled();
-
-			consoleErrorSpy.mockRestore();
+			const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+			try {
+				mockClipboardWriteText.mockRejectedValueOnce(new Error('Clipboard access denied'));
+
+				const onDismiss = vi.fn();
+				const props = createProps({ nodeData: mockDocumentNodeData, onDismiss });
+				render(<NodeContextMenu {...props} />);
+
+				fireEvent.click(screen.getByRole('button', { name: /copy path/i }));
+
+				await waitFor(() => {
+					expect(consoleErrorSpy).toHaveBeenCalledWith(
+						'Failed to copy to clipboard:',
+						expect.any(Error)
+					);
+				});
+				expect(onDismiss).toHaveBeenCalled();
+			} finally {
+				consoleErrorSpy.mockRestore();
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/components/DocumentGraph/NodeContextMenu.test.tsx`
around lines 337 - 355, The test creates a spy consoleErrorSpy via
vi.spyOn(console, 'error') but only calls consoleErrorSpy.mockRestore() at the
end, which can leak the spy if an assertion throws; wrap the test body (the
logic using mockClipboardWriteText, render(<NodeContextMenu ...>),
fireEvent.click and the waitFor/assertions that expect consoleErrorSpy and
onDismiss) inside a try block and call consoleErrorSpy.mockRestore() in a
finally block so the spy is always restored even on failure; ensure you keep
references to consoleErrorSpy, mockClipboardWriteText, onDismiss and use the
same createProps/NodeContextMenu calls unchanged.
src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx (1)

207-225: ⚠️ Potential issue | 🟡 Minor

Stale comment on line 222: "Click refresh" should be "Click Regenerate".

The test name and button target were updated, but the inline comment still references the old "refresh" label.

Fix stale comment
-		// Click refresh
+		// Click Regenerate
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx`
around lines 207 - 225, Update the stale inline comment in the test for
AIOverviewTab: change the comment that currently reads "Click refresh" to "Click
Regenerate" so it matches the test name and the button being clicked (the
fireEvent targeting the "Regenerate" button and the mockGenerateSynopsis usage
in the test).
src/renderer/hooks/props/useMainPanelProps.ts (1)

499-501: ⚠️ Potential issue | 🟠 Major

mergeResult is hardcoded to null — merge result UI will never render.

UseMainPanelPropsDeps has no mergeResult field, so deps.mergeResult cannot be forwarded. The MergeResult type is imported and MainPanelProps.mergeResult exists, but the value is always null. Any UI inside MainPanel conditioned on mergeResult will be permanently suppressed.

🐛 Proposed fix — add `mergeResult` to deps and wire it through

Add to UseMainPanelPropsDeps interface:

 // Merge progress
 mergeProgress: GroomingProgress | null;
+mergeResult: MergeResult | null;
 mergeStartTime: number;

Update the useMemo body:

-mergeResult: null as MergeResult | null,
+mergeResult: deps.mergeResult,

Add to the dependency array:

 deps.mergeProgress,
+deps.mergeResult,
 deps.mergeStartTime,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/props/useMainPanelProps.ts` around lines 499 - 501, The
mergeResult is hardcoded to null so the MainPanel UI never sees it; update the
UseMainPanelPropsDeps interface to include mergeResult: MergeResult | null, then
change the useMainPanelProps creation to set mergeResult: deps.mergeResult
instead of null (look for useMainPanelProps / MainPanelProps construction) and
add deps.mergeResult to the useMemo dependency array so memo updates when the
merge result changes.
src/renderer/components/MainPanel.tsx (1)

887-896: ⚠️ Potential issue | 🟡 Minor

Missing onFocus handler — keyboard focus won't update the active focus ring.

The outermost panel <div> only reacts to onClick for setActiveFocus('main'). When a user keyboard-tabs into the panel, focus is received but activeFocus is not updated, so the ring (line 888) never renders. SessionList correctly handles both onClick and onFocus.

♿ Proposed fix
 <div
     className={`flex-1 flex flex-col min-w-0 relative ${activeFocus === 'main' ? 'ring-1 ring-inset z-10' : ''}`}
     style={...}
+    tabIndex={-1}
     onClick={() => setActiveFocus('main')}
+    onFocus={() => setActiveFocus('main')}
 >

Based on learnings: "Add tabIndex attribute and focus event handlers when implementing components that need keyboard focus" (applies to src/renderer/components/**/*.{ts,tsx}).

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

In `@src/renderer/components/MainPanel.tsx` around lines 887 - 896, The outer
container div that toggles the focus ring doesn't update activeFocus on keyboard
focus—add keyboard focus support by making the div focusable (add tabIndex={0})
and attach an onFocus handler that calls setActiveFocus('main') and optionally
onBlur to clear or change focus; update the div containing activeFocus,
setActiveFocus, and the ring class so both onClick and onFocus keep behavior
consistent with SessionList.
src/renderer/components/SessionList.tsx (1)

583-583: ⚠️ Potential issue | 🟠 Major

Add null guards to six unguarded shortcuts accesses to prevent runtime crashes.

Line 583, 607, 630, 653, 676, and 2985 access shortcuts.help.keys, shortcuts.settings.keys, shortcuts.systemLogs.keys, shortcuts.processMonitor.keys, shortcuts.usageDashboard.keys, and shortcuts.toggleSidebar.keys without checking if the property exists. Since shortcuts is typed as Record<string, Shortcut>, a missing key returns undefined and accessing .keys throws a TypeError. The optional entries like shortcuts.newInstance (line 491) and shortcuts.openSymphony (line 699) already follow the guard pattern—apply the same approach consistently:

Fix pattern
-{formatShortcutKeys(shortcuts.help.keys)}
+{shortcuts.help ? formatShortcutKeys(shortcuts.help.keys) : '?'}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/SessionList.tsx` at line 583, Multiple places in
SessionList.tsx call formatShortcutKeys(shortcuts.<name>.keys) without guarding
for missing shortcut entries; add null/undefined guards for the six accesses
(shortcuts.help, shortcuts.settings, shortcuts.systemLogs,
shortcuts.processMonitor, shortcuts.usageDashboard, shortcuts.toggleSidebar) so
you pass a safe value to formatShortcutKeys (e.g., use optional chaining like
shortcuts?.help?.keys or check shortcuts.help before accessing .keys, and
provide a sensible fallback such as an empty array or undefined). Update the six
call sites to mirror the existing guarded pattern used for shortcuts.newInstance
and shortcuts.openSymphony to prevent TypeError at runtime.
🧹 Nitpick comments (7)
src/renderer/components/DirectorNotes/DirectorNotesModal.tsx (1)

119-123: Same empty-deps pattern as AIOverviewTab — consider an eslint-disable comment.

cached is a const from the render scope so it won't go stale, but an explicit comment would document intent and silence lint.

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

In `@src/renderer/components/DirectorNotes/DirectorNotesModal.tsx` around lines
119 - 123, In DirectorNotesModal, the useEffect that reads the render-scoped
const cached and calls setOverviewGenerating uses an empty deps array and should
be documented to satisfy lint; add an inline comment and an
eslint-disable-next-line react-hooks/exhaustive-deps above that useEffect (or a
concise explanatory comment inside it) stating that cached is intentionally
stable for this render so the empty dependency array is deliberate to silence
the linter and document intent for future readers.
src/renderer/hooks/props/useSessionListProps.ts (3)

153-282: Prop renaming (leftSidebarWidthleftSidebarWidthState) may confuse maintainers.

Lines 163 and 193 rename deps.leftSidebarWidth to leftSidebarWidthState and deps.setLeftSidebarWidth to setLeftSidebarWidthState in the output props. This implicit rename between the deps interface and the returned props creates a subtle mapping that's easy to overlook during maintenance.

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

In `@src/renderer/hooks/props/useSessionListProps.ts` around lines 153 - 282, The
returned props from useSessionListProps rename deps.leftSidebarWidth →
leftSidebarWidthState and deps.setLeftSidebarWidth → setLeftSidebarWidthState
which creates a surprising API mismatch; to fix, keep names consistent by
mapping deps.leftSidebarWidth to leftSidebarWidth and deps.setLeftSidebarWidth
to setLeftSidebarWidth (or intentionally document and standardize the new names
everywhere), updating the return object in useSessionListProps (and any callers)
so the prop names leftSidebarWidth and setLeftSidebarWidth match the deps
interface and avoid the implicit rename confusion.

1-10: Docstring claim about "primitive values" is misleading.

Lines 8-9 state the hook "uses primitive values in dependency arrays" to minimize re-renders, but the dependency array includes many object/array references (sessions, groups, sortedSessions, shortcuts, groupChats, visibleSessions, participantStates, etc.). The memoization is only effective if these are memoized upstream. Consider updating the comment to reflect this assumption.

Clarify the docstring
- * Key optimization: Uses primitive values in dependency arrays (e.g., activeSessionId
- * instead of activeSession) to minimize re-renders.
+ * Key optimization: Centralizes prop computation so SessionList only re-renders
+ * when dependencies actually change. Callers must ensure non-primitive deps
+ * (sessions, groups, sortedSessions, etc.) are memoized upstream.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/props/useSessionListProps.ts` around lines 1 - 10, The
docstring for useSessionListProps is misleading about using "primitive values in
dependency arrays" because the hook's dependency array still contains many
non-primitive references (sessions, groups, sortedSessions, shortcuts,
groupChats, visibleSessions, participantStates, etc.), so update the comment to
state that the hook minimizes re-renders by preferring primitives (e.g.,
activeSessionId) where possible but relies on those upstream values being
memoized/stable for memoization to be effective; mention useSessionListProps and
list the non-primitive dependencies (sessions, groups, sortedSessions,
shortcuts, groupChats, visibleSessions, participantStates) so readers know the
assumption.

283-377: ~95-item dependency array is a maintenance burden.

With nearly 95 dependencies, it's easy for a future contributor to add a returned prop but forget to add its source to the dep array (or vice versa). Consider a structural guard — e.g., returning deps directly (if the prop names matched) or using a linter rule to enforce parity between the returned object keys and the dependency list.

That said, this is a known tradeoff of the centralized-memoization pattern and works fine as-is.

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

In `@src/renderer/hooks/props/useSessionListProps.ts` around lines 283 - 377, The
huge dependency array in useSessionListProps (the deps[...] list) is brittle;
replace the manual 95-item list by using the deps object itself as the
dependency source (e.g., useMemo/useCallback with deps as the single dependency
or derive dependencies from Object.values(deps)) or add a parity check/linter
that asserts returned prop keys match keys in the dependency list; update
references to the deps variable and the memo that currently lists
deps.theme,...,deps.sidebarContainerRef so it uses the single-source deps
approach (or a generated array from deps) to ensure maintainability.
src/renderer/components/DirectorNotes/AIOverviewTab.tsx (2)

76-81: Minor: setTimeout in copyToClipboard not cleared on unmount.

The 2-second timeout to reset copied state can fire after the component unmounts. This is harmless in practice (React 18 swallows the warning), but for completeness you could clear it via mountedRef.

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

In `@src/renderer/components/DirectorNotes/AIOverviewTab.tsx` around lines 76 -
81, The timeout in copyToClipboard (which calls setCopied(true) then
setTimeout(() => setCopied(false), 2000)) can run after unmount; store the
timeout id (from setTimeout) in a ref (e.g., timeoutRef) when scheduling it
inside copyToClipboard and clear that timeout in a useEffect cleanup (or check a
mountedRef before calling setCopied(false)); update the copyToClipboard closure
to save the id to timeoutRef.current and add a useEffect that returns a cleanup
which clearsTimeout(timeoutRef.current) to prevent the callback running after
unmount.

134-148: Missing generateSynopsis in the useEffect dependency array.

The mount effect closes over generateSynopsis and onSynopsisReady but declares [] as its dependency array. This is intentional ("only on mount"), but it will trigger an ESLint react-hooks/exhaustive-deps warning. Consider either suppressing the lint rule explicitly or extracting the mount logic to avoid the stale closure risk on onSynopsisReady.

If the caller ever changes onSynopsisReady between renders before the mount effect fires (unlikely but possible with strict mode double-mount), the stale reference would be called.

Add eslint-disable to document intent
-	}, []); // Only on mount
+	// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally run only on mount; generateSynopsis captured once
+	}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/DirectorNotes/AIOverviewTab.tsx` around lines 134 -
148, The effect closes over generateSynopsis and onSynopsisReady but
intentionally runs only on mount, so suppress the eslint warning and prevent a
stale callback by making the callbacks stable: add an eslint-disable-next-line
react-hooks/exhaustive-deps immediately above the useEffect, and ensure
generateSynopsis is stable (wrap it in useCallback) or store onSynopsisReady in
a ref (e.g., onSynopsisReadyRef.current) and call that ref inside the effect;
reference the generateSynopsis function, onSynopsisReady handler, and mountedRef
in your changes.
src/renderer/components/MainPanel.tsx (1)

398-446: Remove nine unused props from MainPanel interface and destructuring.

_markdownEditMode, _gitDiffPreview, _fileTreeFilterOpen, _setMarkdownEditMode, _setAboutModalOpen, _batchRunState, _showConfirmation, _fileTreeContainerRef, and _fileTreeFilterInputRef are destructured at lines 398–446 but never referenced in the component. They unnecessarily inflate the public API and expand the memoization dependency surface in useMainPanelProps. Remove these props and their interface entries unless they are deliberately reserved for future use.

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

In `@src/renderer/components/MainPanel.tsx` around lines 398 - 446, Remove the
nine unused props from the MainPanel public API and destructuring:
_markdownEditMode, _gitDiffPreview, _fileTreeFilterOpen, _setMarkdownEditMode,
_setAboutModalOpen, _batchRunState, _showConfirmation, _fileTreeContainerRef,
and _fileTreeFilterInputRef; update the MainPanel props interface/type to drop
these fields and remove them from the destructuring block in the MainPanel
component (the long list that includes markdownEditMode, chatRawTextMode, ...);
also remove any references to these symbols from useMainPanelProps dependency
lists (so memoization dependencies aren’t unnecessarily expanded) and from any
prop forwarding sites so the component signature and usage remain consistent.
🤖 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 424-425: App is currently subscribing to store selectors
showHiddenFiles and setShowHiddenFiles (and similar unused slices referenced
elsewhere) but never uses them, causing unnecessary re-renders; remove these
unused reads from the App component (and the other identical unused selector
usages) or, if they are actually needed, wire them through as props or callbacks
to the components that consume them (reference the showHiddenFiles and
setShowHiddenFiles selector usages inside App and the other identical selector
variables elsewhere and either delete the local reads or forward them to child
components).

In `@src/renderer/components/MainPanel.tsx`:
- Around line 1489-1491: In MainPanel.tsx update the title construction that
uses shortcuts.toggleRightPanel.keys to guard against a missing key the same way
the shortcuts.agentSessions check does; use a conditional or optional chaining
so you only call formatShortcutKeys when shortcuts.toggleRightPanel exists (e.g.
compute a shortcutLabel = shortcuts.toggleRightPanel ?
formatShortcutKeys(shortcuts.toggleRightPanel.keys) : '' and use that in the
title) to prevent accessing shortcuts.toggleRightPanel.keys when undefined.

In `@src/renderer/components/SessionList.tsx`:
- Line 1040: The prop activeFocus is currently typed as string which loses
exhaustiveness checks; change its type to the imported FocusArea (the same union
used by setActiveFocus: (focus: FocusArea) => void and comparisons like
activeFocus === 'sidebar') by replacing "activeFocus: string" with "activeFocus:
FocusArea" so the component uses the FocusArea union everywhere for proper type
safety.

In `@src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx`:
- Line 16: The fetchData calls in LongestAutoRunsTable (triggered by timeRange
changes and onStatsUpdate) can complete out of order and overwrite newer state;
update fetchData to use a latest-request guard or AbortController: create a
requestId or AbortController per invocation (in fetchData or the effect using
timeRange/onStatsUpdate), store it in a ref, and before calling
setSessions/setLoading only apply results if the current requestId matches (or
the fetch wasn’t aborted); ensure previous requests are cancelled or invalidated
in the cleanup of the effect so stale responses do not clear loading or replace
sessions.

---

Outside diff comments:
In `@src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx`:
- Around line 207-225: Update the stale inline comment in the test for
AIOverviewTab: change the comment that currently reads "Click refresh" to "Click
Regenerate" so it matches the test name and the button being clicked (the
fireEvent targeting the "Regenerate" button and the mockGenerateSynopsis usage
in the test).

In `@src/__tests__/renderer/components/DocumentGraph/NodeContextMenu.test.tsx`:
- Around line 337-355: The test creates a spy consoleErrorSpy via
vi.spyOn(console, 'error') but only calls consoleErrorSpy.mockRestore() at the
end, which can leak the spy if an assertion throws; wrap the test body (the
logic using mockClipboardWriteText, render(<NodeContextMenu ...>),
fireEvent.click and the waitFor/assertions that expect consoleErrorSpy and
onDismiss) inside a try block and call consoleErrorSpy.mockRestore() in a
finally block so the spy is always restored even on failure; ensure you keep
references to consoleErrorSpy, mockClipboardWriteText, onDismiss and use the
same createProps/NodeContextMenu calls unchanged.

In `@src/renderer/components/MainPanel.tsx`:
- Around line 887-896: The outer container div that toggles the focus ring
doesn't update activeFocus on keyboard focus—add keyboard focus support by
making the div focusable (add tabIndex={0}) and attach an onFocus handler that
calls setActiveFocus('main') and optionally onBlur to clear or change focus;
update the div containing activeFocus, setActiveFocus, and the ring class so
both onClick and onFocus keep behavior consistent with SessionList.

In `@src/renderer/components/SessionList.tsx`:
- Line 583: Multiple places in SessionList.tsx call
formatShortcutKeys(shortcuts.<name>.keys) without guarding for missing shortcut
entries; add null/undefined guards for the six accesses (shortcuts.help,
shortcuts.settings, shortcuts.systemLogs, shortcuts.processMonitor,
shortcuts.usageDashboard, shortcuts.toggleSidebar) so you pass a safe value to
formatShortcutKeys (e.g., use optional chaining like shortcuts?.help?.keys or
check shortcuts.help before accessing .keys, and provide a sensible fallback
such as an empty array or undefined). Update the six call sites to mirror the
existing guarded pattern used for shortcuts.newInstance and
shortcuts.openSymphony to prevent TypeError at runtime.

In `@src/renderer/hooks/props/useMainPanelProps.ts`:
- Around line 499-501: The mergeResult is hardcoded to null so the MainPanel UI
never sees it; update the UseMainPanelPropsDeps interface to include
mergeResult: MergeResult | null, then change the useMainPanelProps creation to
set mergeResult: deps.mergeResult instead of null (look for useMainPanelProps /
MainPanelProps construction) and add deps.mergeResult to the useMemo dependency
array so memo updates when the merge result changes.

---

Nitpick comments:
In `@src/renderer/components/DirectorNotes/AIOverviewTab.tsx`:
- Around line 76-81: The timeout in copyToClipboard (which calls setCopied(true)
then setTimeout(() => setCopied(false), 2000)) can run after unmount; store the
timeout id (from setTimeout) in a ref (e.g., timeoutRef) when scheduling it
inside copyToClipboard and clear that timeout in a useEffect cleanup (or check a
mountedRef before calling setCopied(false)); update the copyToClipboard closure
to save the id to timeoutRef.current and add a useEffect that returns a cleanup
which clearsTimeout(timeoutRef.current) to prevent the callback running after
unmount.
- Around line 134-148: The effect closes over generateSynopsis and
onSynopsisReady but intentionally runs only on mount, so suppress the eslint
warning and prevent a stale callback by making the callbacks stable: add an
eslint-disable-next-line react-hooks/exhaustive-deps immediately above the
useEffect, and ensure generateSynopsis is stable (wrap it in useCallback) or
store onSynopsisReady in a ref (e.g., onSynopsisReadyRef.current) and call that
ref inside the effect; reference the generateSynopsis function, onSynopsisReady
handler, and mountedRef in your changes.

In `@src/renderer/components/DirectorNotes/DirectorNotesModal.tsx`:
- Around line 119-123: In DirectorNotesModal, the useEffect that reads the
render-scoped const cached and calls setOverviewGenerating uses an empty deps
array and should be documented to satisfy lint; add an inline comment and an
eslint-disable-next-line react-hooks/exhaustive-deps above that useEffect (or a
concise explanatory comment inside it) stating that cached is intentionally
stable for this render so the empty dependency array is deliberate to silence
the linter and document intent for future readers.

In `@src/renderer/components/MainPanel.tsx`:
- Around line 398-446: Remove the nine unused props from the MainPanel public
API and destructuring: _markdownEditMode, _gitDiffPreview, _fileTreeFilterOpen,
_setMarkdownEditMode, _setAboutModalOpen, _batchRunState, _showConfirmation,
_fileTreeContainerRef, and _fileTreeFilterInputRef; update the MainPanel props
interface/type to drop these fields and remove them from the destructuring block
in the MainPanel component (the long list that includes markdownEditMode,
chatRawTextMode, ...); also remove any references to these symbols from
useMainPanelProps dependency lists (so memoization dependencies aren’t
unnecessarily expanded) and from any prop forwarding sites so the component
signature and usage remain consistent.

In `@src/renderer/hooks/props/useSessionListProps.ts`:
- Around line 153-282: The returned props from useSessionListProps rename
deps.leftSidebarWidth → leftSidebarWidthState and deps.setLeftSidebarWidth →
setLeftSidebarWidthState which creates a surprising API mismatch; to fix, keep
names consistent by mapping deps.leftSidebarWidth to leftSidebarWidth and
deps.setLeftSidebarWidth to setLeftSidebarWidth (or intentionally document and
standardize the new names everywhere), updating the return object in
useSessionListProps (and any callers) so the prop names leftSidebarWidth and
setLeftSidebarWidth match the deps interface and avoid the implicit rename
confusion.
- Around line 1-10: The docstring for useSessionListProps is misleading about
using "primitive values in dependency arrays" because the hook's dependency
array still contains many non-primitive references (sessions, groups,
sortedSessions, shortcuts, groupChats, visibleSessions, participantStates,
etc.), so update the comment to state that the hook minimizes re-renders by
preferring primitives (e.g., activeSessionId) where possible but relies on those
upstream values being memoized/stable for memoization to be effective; mention
useSessionListProps and list the non-primitive dependencies (sessions, groups,
sortedSessions, shortcuts, groupChats, visibleSessions, participantStates) so
readers know the assumption.
- Around line 283-377: The huge dependency array in useSessionListProps (the
deps[...] list) is brittle; replace the manual 95-item list by using the deps
object itself as the dependency source (e.g., useMemo/useCallback with deps as
the single dependency or derive dependencies from Object.values(deps)) or add a
parity check/linter that asserts returned prop keys match keys in the dependency
list; update references to the deps variable and the memo that currently lists
deps.theme,...,deps.sidebarContainerRef so it uses the single-source deps
approach (or a generated array from deps) to ensure maintainability.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4f4d78 and 9d0936d.

📒 Files selected for processing (21)
  • src/__tests__/renderer/components/DirectorNotes/AIOverviewTab.test.tsx
  • src/__tests__/renderer/components/DocumentGraph/NodeContextMenu.test.tsx
  • src/__tests__/renderer/components/GroupChatInput.test.tsx
  • src/main/agents/definitions.ts
  • src/main/ipc/handlers/git.ts
  • src/renderer/App.tsx
  • src/renderer/components/DirectorNotes/AIOverviewTab.tsx
  • src/renderer/components/DirectorNotes/DirectorNotesModal.tsx
  • src/renderer/components/DocumentGraph/NodeContextMenu.tsx
  • src/renderer/components/GroupChatInput.tsx
  • src/renderer/components/GroupChatPanel.tsx
  • src/renderer/components/MainPanel.tsx
  • src/renderer/components/SessionList.tsx
  • src/renderer/components/Settings/IgnorePatternsSection.tsx
  • src/renderer/components/SymphonyModal.tsx
  • src/renderer/components/UsageDashboard/ChartSkeletons.tsx
  • src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx
  • src/renderer/components/UsageDashboard/SummaryCards.tsx
  • src/renderer/hooks/props/useMainPanelProps.ts
  • src/renderer/hooks/props/useSessionListProps.ts
  • src/renderer/hooks/remote/useRemoteIntegration.ts
✅ Files skipped from review due to trivial changes (4)
  • src/renderer/components/Settings/IgnorePatternsSection.tsx
  • src/main/agents/definitions.ts
  • src/main/ipc/handlers/git.ts
  • src/renderer/components/UsageDashboard/ChartSkeletons.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/hooks/remote/useRemoteIntegration.ts

Comment on lines +424 to +425
showHiddenFiles,
setShowHiddenFiles,
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

Avoid subscribing App to store slices that are not consumed.

These new reads are not used afterward, but they still subscribe this top-level component and can trigger expensive re-renders on file navigation and Auto Run updates.

♻️ Proposed cleanup (or wire them through if intentionally needed)
@@
-		showHiddenFiles,
-		setShowHiddenFiles,
@@
-	const selectedFileIndex = useFileExplorerStore((s) => s.selectedFileIndex);
@@
-	const { setSelectedFileIndex, setFileTreeFilter, setFileTreeFilterOpen } =
-		useFileExplorerStore.getState();
+	const { setFileTreeFilter, setFileTreeFilterOpen } = useFileExplorerStore.getState();
@@
-	const autoRunIsLoadingDocuments = useBatchStore((s) => s.isLoadingDocuments);
-	const autoRunDocumentTaskCounts = useBatchStore((s) => s.documentTaskCounts);

Also applies to: 601-602, 631-632, 747-748

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

In `@src/renderer/App.tsx` around lines 424 - 425, App is currently subscribing to
store selectors showHiddenFiles and setShowHiddenFiles (and similar unused
slices referenced elsewhere) but never uses them, causing unnecessary
re-renders; remove these unused reads from the App component (and the other
identical unused selector usages) or, if they are actually needed, wire them
through as props or callbacks to the components that consume them (reference the
showHiddenFiles and setShowHiddenFiles selector usages inside App and the other
identical selector variables elsewhere and either delete the local reads or
forward them to child components).

Comment on lines 1489 to 1491
className="p-2 rounded hover:bg-white/5"
title={`Show right panel (${formatShortcutKeys(shortcuts.toggleRightPanel.keys)})`}
>
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:

sed -n '1475,1500p' src/renderer/components/MainPanel.tsx

Repository: RunMaestro/Maestro

Length of output: 1056


🏁 Script executed:

rg -n "shortcuts" src/renderer/components/MainPanel.tsx | head -20

Repository: RunMaestro/Maestro

Length of output: 461


🏁 Script executed:

rg -n "shortcuts\.[a-zA-Z]+" src/renderer/components/MainPanel.tsx -o | grep -v "shortcuts\?" | head -30

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

grep -n "shortcuts\." src/renderer/components/MainPanel.tsx | grep -v "shortcuts={shortcuts}"

Repository: RunMaestro/Maestro

Length of output: 327


🏁 Script executed:

rg -n "type Shortcut|interface Shortcut" src/renderer/components/MainPanel.tsx -A 5

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

head -50 src/renderer/components/MainPanel.tsx

Repository: RunMaestro/Maestro

Length of output: 1366


🏁 Script executed:

rg -n "type Shortcut|interface Shortcut" --type-list | head -5

Repository: RunMaestro/Maestro

Length of output: 451


🏁 Script executed:

rg "type Shortcut|interface Shortcut" -A 3

Repository: RunMaestro/Maestro

Length of output: 890


🏁 Script executed:

rg -B 5 -A 5 "shortcuts.*toggleRightPanel" src/

Repository: RunMaestro/Maestro

Length of output: 6049


🏁 Script executed:

rg -n "DEFAULT_SHORTCUTS" src/ -A 20 | head -60

Repository: RunMaestro/Maestro

Length of output: 4473


🏁 Script executed:

rg -n "export const DEFAULT_SHORTCUTS" src/renderer/constants/shortcuts.ts -A 50

Repository: RunMaestro/Maestro

Length of output: 2870


🏁 Script executed:

rg -n "mergeSettings\|spread.*DEFAULT_SHORTCUTS\|Object.assign.*shortcuts" src/renderer/stores/settingsStore.ts -B 3 -A 3

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

rg -n "shortcuts.*=" src/renderer/stores/settingsStore.ts | head -20

Repository: RunMaestro/Maestro

Length of output: 158


🏁 Script executed:

sed -n '1410,1440p' src/renderer/stores/settingsStore.ts

Repository: RunMaestro/Maestro

Length of output: 1157


🏁 Script executed:

rg -n "function migrateShortcuts\|const migrateShortcuts" src/renderer/stores/settingsStore.ts -A 30

Repository: RunMaestro/Maestro

Length of output: 44


🏁 Script executed:

rg -n "migrateShortcuts" src/renderer/stores/settingsStore.ts -B 2 -A 15

Repository: RunMaestro/Maestro

Length of output: 2188


Add null guard for shortcuts.toggleRightPanel.keys to match the pattern on line 1479.

The shortcuts parameter is typed as Record<string, Shortcut>, making not all keys guaranteed. Line 1479 guards shortcuts.agentSessions with a conditional check, but line 1490 accesses shortcuts.toggleRightPanel.keys without a guard. This inconsistency creates a potential crash if the key is absent.

🛡️ Proposed fix
-title={`Show right panel (${formatShortcutKeys(shortcuts.toggleRightPanel.keys)})`}
+title={`Show right panel${shortcuts.toggleRightPanel ? ` (${formatShortcutKeys(shortcuts.toggleRightPanel.keys)})` : ''}`}
📝 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
className="p-2 rounded hover:bg-white/5"
title={`Show right panel (${formatShortcutKeys(shortcuts.toggleRightPanel.keys)})`}
>
className="p-2 rounded hover:bg-white/5"
title={`Show right panel${shortcuts.toggleRightPanel ? ` (${formatShortcutKeys(shortcuts.toggleRightPanel.keys)})` : ''}`}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/MainPanel.tsx` around lines 1489 - 1491, In
MainPanel.tsx update the title construction that uses
shortcuts.toggleRightPanel.keys to guard against a missing key the same way the
shortcuts.agentSessions check does; use a conditional or optional chaining so
you only call formatShortcutKeys when shortcuts.toggleRightPanel exists (e.g.
compute a shortcutLabel = shortcuts.toggleRightPanel ?
formatShortcutKeys(shortcuts.toggleRightPanel.keys) : '' and use that in the
title) to prevent accessing shortcuts.toggleRightPanel.keys when undefined.

activeSessionId: string;
leftSidebarOpen: boolean;
leftSidebarWidthState: number;
activeFocus: string;
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

activeFocus prop typed as string instead of the imported FocusArea.

FocusArea ('sidebar' | 'main' | 'right') is imported at line 50 and used correctly everywhere else (setActiveFocus: (focus: FocusArea) => void at line 1068, comparisons like activeFocus === 'sidebar'). Typing it as string loses exhaustiveness checking.

🔧 Proposed fix
-activeFocus: string;
+activeFocus: FocusArea;
📝 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
activeFocus: string;
activeFocus: FocusArea;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/SessionList.tsx` at line 1040, The prop activeFocus
is currently typed as string which loses exhaustiveness checks; change its type
to the imported FocusArea (the same union used by setActiveFocus: (focus:
FocusArea) => void and comparisons like activeFocus === 'sidebar') by replacing
"activeFocus: string" with "activeFocus: FocusArea" so the component uses the
FocusArea union everywhere for proper type safety.

*/

import { memo, useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { memo, useState, useEffect, useMemo, useCallback } from 'react';
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

Guard async fetches to prevent stale data overwrites and loading races.

Line 132/133 and Line 137 now accept results from any completed request. If fetchData() overlaps (rapid timeRange changes or frequent onStatsUpdate), an older request can finish last, overwrite newer sessions, and clear loading too early. Please restore a latest-request guard (or abort/cancel strategy) before setSessions/setLoading.

Proposed fix
-import { memo, useState, useEffect, useMemo, useCallback } from 'react';
+import { memo, useState, useEffect, useMemo, useCallback, useRef } from 'react';
@@
 export const LongestAutoRunsTable = memo(function LongestAutoRunsTable({
 	timeRange,
 	theme,
 }: LongestAutoRunsTableProps) {
 	const [sessions, setSessions] = useState<AutoRunSession[]>([]);
 	const [loading, setLoading] = useState(true);
+	const latestRequestIdRef = useRef(0);
+	const mountedRef = useRef(true);
+
+	useEffect(() => {
+		return () => {
+			mountedRef.current = false;
+		};
+	}, []);
 
 	const fetchData = useCallback(async () => {
+		const requestId = ++latestRequestIdRef.current;
 		setLoading(true);
 		try {
 			const autoRunSessions = await window.maestro.stats.getAutoRunSessions(timeRange);
-			setSessions(autoRunSessions);
+			if (!mountedRef.current || requestId !== latestRequestIdRef.current) return;
+			setSessions(autoRunSessions);
 		} catch (err) {
 			captureException(err);
 		} finally {
-			setLoading(false);
+			if (!mountedRef.current || requestId !== latestRequestIdRef.current) return;
+			setLoading(false);
 		}
 	}, [timeRange]);

Also applies to: 129-139

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

In `@src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx` at line 16,
The fetchData calls in LongestAutoRunsTable (triggered by timeRange changes and
onStatsUpdate) can complete out of order and overwrite newer state; update
fetchData to use a latest-request guard or AbortController: create a requestId
or AbortController per invocation (in fetchData or the effect using
timeRange/onStatsUpdate), store it in a ref, and before calling
setSessions/setLoading only apply results if the current requestId matches (or
the fetch wasn’t aborted); ensure previous requests are cancelled or invalidated
in the cleanup of the effect so stale responses do not clear loading or replace
sessions.

@pedramamini pedramamini merged commit d655266 into main Feb 25, 2026
2 of 4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants