Skip to content

0.15.0-RC polish round two#450

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

0.15.0-RC polish round two#450
pedramamini merged 17 commits intomainfrom
0.15.0-RC-polish-round-two

Conversation

@pedramamini
Copy link
Collaborator

@pedramamini pedramamini commented Feb 24, 2026

Summary

Polish, performance, and UX fixes for the 0.15.0 release candidate.

Performance optimizations (cherry-picked from #385)

  • React.memo wraps — 29 components (Toast, ConfirmModal, RenameTabModal, QRCode, QuickActionsModal, HistoryHelpModal, ContextWarningSash + 22 UsageDashboard chart/skeleton components) to prevent unnecessary re-renders
  • DB compound indexes — Added (start_time, agent_type), (start_time, project_path), (start_time, source) indexes for common stats query patterns
  • Git handler parallelization — worktreeInfo and worktreeSetup now run git rev-parse calls via Promise.all instead of sequentially
  • Session sort optimization — Cache Date.getTime() in useFilteredAndSortedSessions to avoid repeated date parsing in sort comparators
  • useStats debounce cleanup — Replaced inline debounce with shared useDebouncedCallback hook (proper unmount cleanup)
  • Session persistence — Strip file tree and preview history from persisted session data to prevent storage bloat
  • Streaming cleanup — Remove streaming debug logs and fix index-based React keys

Bug fixes

New features

  • Group chat archive state — Archive/unarchive group chats (feat: archive state for group chat  #377)
  • Agent provider switching — Change agent provider in Edit Agent modal
  • Group @mentions — @mention agents in group chat messages, fix navigation history

Improvements

  • Clean up file-scale PR with shared gitignore parser, options object, and rescan effect
  • Honor local .gitignore patterns in file tree indexing
  • Address PR review feedback across stats, UI, and accessibility

Test plan

  • All 20,770 tests pass
  • Verify context menus stay within viewport when right-clicking near screen edges
  • Verify UsageDashboard renders correctly (memo wraps shouldn't change behavior)
  • Verify worktree info/setup still works (parallelized git calls)
  • Verify session list sorting (cached timestamps)
  • Verify stats dashboard real-time updates (debounce hook swap)
  • Verify group chat archive/unarchive works
  • Verify agent provider change in Edit Agent modal

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.
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds agent-level readOnlyEnvOverrides and applies them to process spawn env when readOnlyMode is true; adds tests for that. Also applies many renderer component memoizations, modal/z-index and UI tweaks, parallelizes git subprocess calls, adds DB indexes, and refactors hooks/debounce logic.

Changes

Cohort / File(s) Summary
Agent definitions
src/main/agents/definitions.ts
Add optional readOnlyEnvOverrides?: Record<string,string> to AgentConfig; set readOnlyEnvOverrides for opencode with OPENCODE_CONFIG_CONTENT.
Process spawn handler & tests
src/main/ipc/handlers/process.ts, src/__tests__/main/ipc/handlers/process.test.ts
When readOnlyMode and agent provides readOnlyEnvOverrides, merge them into effective custom env before calling ProcessManager.spawn. Add tests verifying overrides apply only in read-only mode.
Renderer: modal / z-index / file preview
src/renderer/components/BatchRunnerModal.tsx, src/renderer/components/WorktreeConfigModal.tsx, src/renderer/components/FilePreview.tsx
Open WorktreeConfig immediately atop BatchRunner (no queued close); increase WorktreeConfig z-index to z-[10000]; simplify FilePreview header icon/button sizing to fixed classes.
Renderer: many components — memoization
src/renderer/components/..., src/renderer/components/UsageDashboard/... (e.g., ConfirmModal.tsx, Toast.tsx, QRCode.tsx, RenameTabModal.tsx, QuickActionsModal.tsx, HistoryHelpModal.tsx, ContextWarningSash.tsx, UsageDashboard/*, etc.)
Wrap numerous components with React.memo and add memo imports; change exports from function declarations to export const X = memo(function X(...) { ... }). No logic changes beyond memoization.
Git handler parallelization
src/main/ipc/handlers/git.ts
Run several git subprocess queries in parallel via Promise.all (e.g., --git-dir, --git-common-dir, HEAD, --show-toplevel) and use parallel results with prior fallbacks preserved.
DB schema: indexes
src/main/stats/schema.ts
Terminate existing index statement and add three multi-column indexes on query_events: (start_time, agent_type), (start_time, project_path), and (start_time, source).
Renderer hooks & debounce
src/renderer/hooks/agent/useFilteredAndSortedSessions.ts, src/renderer/hooks/stats/useStats.ts
Cache session modified timestamps with useMemo to avoid repeated Date parsing; replace in-file debounce with reusable useDebouncedCallback and add cancelation on unmount.
Symphony / IssueCard behavior
src/renderer/components/SymphonyModal.tsx
Make blocked issues selectable (isSelectable = isAvailable

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Renderer as Renderer (UI)
participant IPC as IPC Handler (process.ts)
participant Agents as Agent Definitions
participant PM as ProcessManager
participant Child as Spawned Process

Renderer->>IPC: request spawn (agentId, customEnvVars, readOnlyMode)
IPC->>Agents: lookup AgentConfig(agentId)
Agents-->>IPC: AgentConfig (may include readOnlyEnvOverrides)
alt readOnlyMode && readOnlyEnvOverrides present
    IPC->>IPC: merge readOnlyEnvOverrides into effectiveCustomEnvVars
else
    IPC->>IPC: use provided customEnvVars
end
IPC->>PM: ProcessManager.spawn(command, effectiveCustomEnvVars)
PM->>Child: spawn process with environment
Child-->>PM: process lifecycle/events

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "0.15.0-RC polish round two" is vague and does not clearly describe the specific changes in the PR. While it references a release version, it uses the generic term "polish" without conveying what actual improvements or fixes are included. Consider using a more specific title that highlights the main categories of changes, such as: "Add React.memo optimizations and read-only mode enforcement for 0.15.0-RC" or "Performance optimizations and bug fixes for 0.15.0-RC release".
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 94.44% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 24, 2026

Greptile Summary

Fixed OpenCode agent ignoring read-only mode by introducing readOnlyEnvOverrides mechanism. Previously, OPENCODE_CONFIG_CONTENT env var always granted blanket "*":"allow" permissions even in plan mode. The fix adds agent-level env override support that strips permission grants when read-only mode is active, while preserving the question tool disable to prevent stdin hangs. Implementation is clean, well-tested, and follows the codebase patterns for agent configuration.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is straightforward and well-tested. It adds a new optional field to the interface without breaking existing functionality, applies overrides with clear conditional logic, and includes comprehensive test coverage verifying both the positive and negative cases. The fix correctly addresses the security issue where read-only mode was being bypassed.
  • No files require special attention

Important Files Changed

Filename Overview
src/main/agents/definitions.ts Added readOnlyEnvOverrides field to AgentConfig interface and configured it for OpenCode agent to strip blanket permissions in plan mode
src/main/ipc/handlers/process.ts Applied read-only env overrides when spawning agents in read-only mode, properly enforcing permission restrictions
src/tests/main/ipc/handlers/process.test.ts Added comprehensive tests verifying read-only overrides are applied correctly in both modes

Last reviewed commit: dfc129a

)

WorktreeConfigModal now layers on top (priority 752 > 720) instead of
replacing the BatchRunner. User returns to their Auto Run setup after
configuring worktrees. Bumped WorktreeConfigModal z-index above
BatchRunner's z-[9999].
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.
@pedramamini pedramamini changed the title fix: enforce read-only env vars for OpenCode (#447) 0.15.0-RC polish round two Feb 24, 2026
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.

Caution

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

⚠️ Outside diff range comments (9)
src/renderer/components/QRCode.tsx (1)

57-61: ⚠️ Potential issue | 🟠 Major

Replace console.error with Sentry reporting in QR generation failure path.

The catch block logs to console but does not use the required Sentry utility for explicit error reporting.

Proposed fix
+import { captureException } from 'src/utils/sentry';
 import { memo, useState, useEffect } from 'react';
 import QRCodeLib from 'qrcode';
@@
 			.catch((err) => {
-				console.error('Failed to generate QR code:', err);
+				captureException(err);
 				setError('Failed to generate QR code');
 				setDataUrl(null);
 			});

As per coding guidelines, "Do NOT silently swallow exceptions with try-catch-console.error blocks. Let unhandled exceptions bubble up to Sentry for error tracking." and "Use Sentry utilities (captureException, captureMessage) from 'src/utils/sentry' for explicit error reporting with context."

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

In `@src/renderer/components/QRCode.tsx` around lines 57 - 61, Replace the
console.error call in the QR generation promise catch block inside the QRCode
component with Sentry reporting: import captureException (and optionally
captureMessage) from 'src/utils/sentry', then call captureException(err, {
extra: { component: 'QRCode', operation: 'generateDataUrl' } }) and/or
captureMessage('Failed to generate QR code') before setting state (setError,
setDataUrl). Update the catch handler in the QRCode component (the .catch((err)
=> { ... }) block) to use these Sentry utilities so the error is reported rather
than only logged to console.
src/renderer/components/UsageDashboard/AutoRunStats.tsx (3)

413-429: ⚠️ Potential issue | 🟡 Minor

Focusable bar items lack keyboard-activation handlers.

Each bar has tabIndex={0}, so keyboard users can focus them via Tab, but there is no onKeyDown handler — pressing Enter or Space does nothing. The tooltip is mouse-only, which breaks keyboard and screen-reader UX.

♿ Proposed fix
 					<div
 						key={day.date}
 						className="flex-1 min-w-[16px] max-w-[40px] rounded-t cursor-pointer transition-all duration-200"
 						style={{ ... }}
 						onMouseEnter={(e) => handleMouseEnter(day, e)}
 						onMouseLeave={handleMouseLeave}
+						onKeyDown={(e) => {
+							if (e.key === 'Enter' || e.key === ' ') {
+								e.preventDefault();
+								setHoveredBar((prev) => (prev?.date === day.date ? null : day));
+								setTooltipPos(null); // fixed-position tooltip not viable for keyboard; toggle aria-expanded state instead
+							}
+						}}
+						onBlur={handleMouseLeave}
 						data-testid={`task-bar-${day.date}`}
 						role="listitem"
 						aria-label={...}
 						tabIndex={0}
 					/>

As per coding guidelines, "Add tabIndex={0} or tabIndex={-1} and outline-none class to ensure focus works correctly."

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

In `@src/renderer/components/UsageDashboard/AutoRunStats.tsx` around lines 413 -
429, The bar div rendered in the map lacks keyboard activation — add an
onKeyDown handler on the same element (the bar div with data-testid
`task-bar-${day.date}`) that listens for Enter and Space and calls the same
logic as handleMouseEnter/handleMouseLeave (e.g., invoke the tooltip/show
details function used by handleMouseEnter for focus and call handleMouseLeave on
blur or Escape), preventDefault for Space to avoid page scroll, and ensure the
element retains tabIndex={0} and appropriate ARIA label (the existing aria-label
from formatFullDate/day counts is fine); update
handleMouseEnter/handleMouseLeave or extract a shared function if needed so
keyboard and mouse both trigger identical behavior.

149-150: ⚠️ Potential issue | 🟠 Major

successCount always equals count, making the success-rate display perpetually 100%.

Both accumulators in groupSessionsByDate are assigned session.tasksCompleted ?? 0, so successCount / count is always 1.0. This propagates to:

  • Line 410: opacity is always 0.7 + 1.0 * 0.3 = 1.0 — the visual encoding is lost.
  • Line 426: aria-label always says "100% successful."
  • Lines 471–472: The tooltip always renders "X successful (100%)."

Likely intent is count += session.tasksTotal ?? 0 (attempts) and successCount += session.tasksCompleted ?? 0 (completed), mirroring the card-level successRate computation on lines 209–210.

🐛 Proposed fix
-		grouped[date].count += session.tasksCompleted ?? 0;
-		grouped[date].successCount += session.tasksCompleted ?? 0;
+		grouped[date].count += session.tasksTotal ?? 0;
+		grouped[date].successCount += session.tasksCompleted ?? 0;

Also update the filter (line 155) if needed to guard on tasksTotal:

-		.filter((entry) => entry.count > 0)
+		.filter((entry) => entry.count > 0 || entry.successCount > 0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/AutoRunStats.tsx` around lines 149 -
150, In groupSessionsByDate, fix the accumulators so grouped[date].count
accumulates attempts (session.tasksTotal ?? 0) while grouped[date].successCount
continues to accumulate completions (session.tasksCompleted ?? 0), and adjust
the pre-filter that selects sessions (the filter around session.tasks...) to
guard on tasksTotal (e.g., only include sessions with tasksTotal > 0 or
tasksTotal != null) so the computed successRate/opacity/aria-label/tooltip
reflect actual completion rate; look for the grouped variable and the
groupSessionsByDate function to apply this change.

175-183: ⚠️ Potential issue | 🟠 Major

Exception swallowed via console.error — violates Sentry guideline.

The catch block logs the error locally and hides it from error-tracking. For an unexpected IPC failure this means no visibility in Sentry.

🛡️ Proposed fix
+import { captureException } from '../../../utils/sentry';
 ...
 		} catch (err) {
-			console.error('Failed to fetch Auto Run stats:', err);
+			captureException(err, { context: 'AutoRunStats.fetchData', timeRange });
 			setError(err instanceof Error ? err.message : 'Failed to load Auto Run stats');
 		}

As per coding guidelines, "Do NOT silently swallow exceptions with try-catch-console.error blocks. Let unhandled exceptions bubble up to Sentry for error tracking. Use Sentry utilities (captureException, captureMessage) from 'src/utils/sentry' for explicit error reporting with context."

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

In `@src/renderer/components/UsageDashboard/AutoRunStats.tsx` around lines 175 -
183, The catch block in AutoRunStats.tsx swallows IPC errors via console.error;
replace that with explicit Sentry reporting: import captureException (and
optionally captureMessage) from src/utils/sentry, and in the catch for
window.maestro.stats.getAutoRunSessions(timeRange) call captureException(err,
{extra: {timeRange}}) (and/or captureMessage with context) before setting state
with setError(err instanceof Error ? err.message : 'Failed to load Auto Run
stats'); keep the existing setLoading(false) in finally and do not suppress the
exception silently—ensure Sentry gets the error and contextual info while
preserving UI error state (symbols: getAutoRunSessions, setSessions, setError,
setLoading, AutoRunStats).
src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx (1)

130-132: ⚠️ Potential issue | 🟠 Major

Replace swallowed fetch errors with Sentry reporting (and rethrow unexpected errors).

Line 130-Line 132 currently catches all errors and logs to console.error, which suppresses centralized error tracking for unexpected failures.

Proposed fix
-		} catch (err) {
-			console.error('Failed to fetch Auto Run sessions for table:', err);
+		} catch (err) {
+			captureException(err, {
+				extra: {
+					component: 'LongestAutoRunsTable',
+					timeRange,
+				},
+			});
+			throw err;
 		} finally {
 			setLoading(false);
 		}
// Add near imports (path may vary by repo aliasing):
import { captureException } from 'src/utils/sentry';

As per coding guidelines, "Do NOT silently swallow exceptions with try-catch-console.error blocks. Let unhandled exceptions bubble up to Sentry for error tracking." and "Use Sentry utilities (captureException, captureMessage) from 'src/utils/sentry' for explicit error reporting with context."

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

In `@src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx` around lines
130 - 132, Replace the console.error swallowing in the catch inside
LongestAutoRunsTable (the try/catch around fetching Auto Run sessions) by
importing captureException from 'src/utils/sentry' and calling
captureException(err, { extra: { context: 'fetchAutoRunSessions' } }) (or
captureMessage for non-exceptions) so the error is reported to Sentry, then
rethrow the error (throw err) so unexpected failures bubble up; update the
import list at the top of LongestAutoRunsTable.tsx to include captureException.
src/renderer/hooks/stats/useStats.ts (1)

135-153: ⚠️ Potential issue | 🟠 Major

Always return a cleanup function from this effect.

On Line 136, mountedRef.current is set to true, but the Line 138-140 early return provides no cleanup path when enabled is false. In enable/disable/unmount races, this can allow late async completions to call state setters after unmount.

Proposed fix
 useEffect(() => {
 	mountedRef.current = true;

-	if (!enabled) {
-		return;
-	}
-
-	// Initial fetch
-	fetchStats();
-
-	// Subscribe to stats updates with stable debounced function
-	const unsubscribe = window.maestro.stats.onStatsUpdate(debouncedUpdate);
+	let unsubscribe: (() => void) | undefined;
+	if (enabled) {
+		// Initial fetch
+		fetchStats();
+		// Subscribe to stats updates with stable debounced function
+		unsubscribe = window.maestro.stats.onStatsUpdate(debouncedUpdate);
+	}

 	return () => {
 		mountedRef.current = false;
 		cancelDebounce();
-		unsubscribe();
+		unsubscribe?.();
 	};
 }, [enabled, fetchStats, debouncedUpdate, cancelDebounce]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/stats/useStats.ts` around lines 135 - 153, The effect sets
mountedRef.current = true but returns early when enabled is false, so no cleanup
runs; ensure the effect always returns a cleanup function that sets
mountedRef.current = false and cancels pending debounce work. Modify the effect
around mountedRef, enabled, fetchStats, debouncedUpdate and cancelDebounce so
that after setting mountedRef.current = true you still return a cleanup function
even if you skip the initial fetch/subscription; when you do subscribe via
window.maestro.stats.onStatsUpdate(debouncedUpdate) capture the unsubscribe and
call it inside that same cleanup so the cleanup always calls cancelDebounce(),
sets mountedRef.current = false, and unsubscribes if a subscription was created.
src/main/stats/schema.ts (1)

52-62: ⚠️ Potential issue | 🟠 Major

A new migration entry is required to apply these indexes to existing databases.

The new compound indexes (lines 58–61) are added to CREATE_QUERY_EVENTS_INDEXES_SQL, which is only executed during migration v1. Since existing databases have already completed v1, they will never receive these indexes. A migration v4 must be added to create these indexes for installations that have already progressed beyond v1.

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

In `@src/main/stats/schema.ts` around lines 52 - 62, Existing installations won't
get the new compound indexes because CREATE_QUERY_EVENTS_INDEXES_SQL runs only
in migration v1; add a new migration "v4" that executes the missing compound
CREATE INDEX IF NOT EXISTS statements (those added to
CREATE_QUERY_EVENTS_INDEXES_SQL: idx_query_agent_time, idx_query_time_agent,
idx_query_time_project, idx_query_time_source) against the query_events table,
ensure the SQL uses IF NOT EXISTS so the migration is idempotent, and register
the migration in the project's migration registry/runner so it runs for
databases that have already passed v1.
src/renderer/components/UsageDashboard/TasksByHourChart.tsx (1)

70-85: ⚠️ Potential issue | 🟠 Major

Replace console.error with captureException from Sentry.

The catch block silently swallows the error to the console without reporting it to Sentry, which violates the project's error-handling guideline. IPC/network failures here are exactly the kind of unexpected errors Sentry needs to see.

🛠️ Proposed fix
+import { captureException } from '../../../utils/sentry';
 ...
 	} catch (err) {
-		console.error('Failed to fetch Auto Run tasks:', err);
+		captureException(err, { context: 'TasksByHourChart.fetchTasks', timeRange });
 		setError(err instanceof Error ? err.message : 'Failed to load tasks');
 	}

As per coding guidelines: "Do NOT silently swallow exceptions with try-catch-console.error blocks. Let unhandled exceptions bubble up to Sentry for error tracking. Use Sentry utilities (captureException, captureMessage) from 'src/utils/sentry' for explicit error reporting with context."

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

In `@src/renderer/components/UsageDashboard/TasksByHourChart.tsx` around lines 70
- 85, Replace the console.error call in the catch block with Sentry reporting:
import captureException from 'src/utils/sentry' at the top of
TasksByHourChart.tsx, then inside the catch use captureException(err, { extra: {
message: 'Failed to fetch Auto Run tasks', timeRange } }) (or similar context)
to report the error to Sentry, keep setting setError(err instanceof Error ?
err.message : 'Failed to load tasks') and setLoading(false) in finally; remove
the console.error line so errors are reported to Sentry instead of only logged
to the console.
src/renderer/components/UsageDashboard/WeekdayComparisonChart.tsx (1)

272-285: ⚠️ Potential issue | 🟡 Minor

Division by zero when weekend days exist but have zero queries.

The guard on line 267 (weekday.days > 0 && weekend.days > 0) doesn't prevent weekendAvgQueriesPerDay from being 0 — that happens when weekendStats.count === 0 while weekendStats.days > 0 (i.e., weekend days recorded with no queries). The first branch then computes weekdayAvg / 0, producing Infinity, which renders as "Infinity%".

The second branch already carries the correct guard (weekend.avgQueriesPerDay > 0), so the fix is simply to mirror that guard for the first branch.

🐛 Proposed fix
-	{comparisonData.weekday.avgQueriesPerDay > comparisonData.weekend.avgQueriesPerDay ? (
+	{comparisonData.weekend.avgQueriesPerDay > 0 &&
+		comparisonData.weekday.avgQueriesPerDay > comparisonData.weekend.avgQueriesPerDay ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/WeekdayComparisonChart.tsx` around
lines 272 - 285, The JSX branch in WeekdayComparisonChart that currently checks
comparisonData.weekday.avgQueriesPerDay >
comparisonData.weekend.avgQueriesPerDay can divide by zero when
comparisonData.weekend.avgQueriesPerDay is 0; update the condition to require
comparisonData.weekday.avgQueriesPerDay >
comparisonData.weekend.avgQueriesPerDay &&
comparisonData.weekend.avgQueriesPerDay > 0 so the numerator/denominator calc
that uses (comparisonData.weekday.avgQueriesPerDay /
comparisonData.weekend.avgQueriesPerDay) is only executed when the weekend
average is non‑zero; mirror the existing guard logic used in the other branch
(which checks weekend.avgQueriesPerDay > 0) to prevent Infinity% from rendering.
🧹 Nitpick comments (8)
src/renderer/components/UsageDashboard/PeakHoursChart.tsx (1)

106-106: Consider memoizing hasData to avoid redundant iteration on hover state changes.

hasData iterates hourlyData on every render — including the high-frequency re-renders triggered by hoveredHour state updates on mouse-enter/leave. Since hourlyData is already useMemo-stabilized, wrapping hasData is trivial.

♻️ Proposed refactor
-	// Check if there's any data
-	const hasData = hourlyData.some((h) => h.count > 0);
+	// Check if there's any data
+	const hasData = useMemo(() => hourlyData.some((h) => h.count > 0), [hourlyData]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/PeakHoursChart.tsx` at line 106, The
line computing hasData currently re-iterates hourlyData on every render
(including frequent hoveredHour updates); change it to a memoized value by
replacing the direct computation with a useMemo hook (e.g., useMemo(() =>
hourlyData.some(h => h.count > 0), [hourlyData])) so hasData only recalculates
when hourlyData changes; update references to the existing hasData identifier
and ensure useMemo is imported if not already.
src/main/ipc/handlers/git.ts (1)

633-637: Consider using resolved paths (resolvedWorktree / resolvedMainRepo) for consistency.

worktreePath and mainRepoCwd are unresolved here, while the rest of the handler (lines 574–608) consistently uses resolvedWorktree / resolvedMainRepo. The downstream path.resolve(worktreePath, ...) at lines 640–641 is internally consistent, so there's no bug — Node's execFile resolves the cwd internally — but aligning with the pattern used elsewhere in the function would make the intent clearer.

♻️ Suggested alignment
-				const [gitCommonDirResult, mainGitDirResult] = await Promise.all([
-					execFileNoThrow('git', ['rev-parse', '--git-common-dir'], worktreePath),
-					execFileNoThrow('git', ['rev-parse', '--git-dir'], mainRepoCwd),
-				]);
+				const [gitCommonDirResult, mainGitDirResult] = await Promise.all([
+					execFileNoThrow('git', ['rev-parse', '--git-common-dir'], resolvedWorktree),
+					execFileNoThrow('git', ['rev-parse', '--git-dir'], resolvedMainRepo),
+				]);

If adopting this, also update lines 640–641 accordingly:

-					const worktreeCommonDir = path.resolve(worktreePath, gitCommonDirResult.stdout.trim());
-					const mainGitDir = path.resolve(mainRepoCwd, mainGitDirResult.stdout.trim());
+					const worktreeCommonDir = path.resolve(resolvedWorktree, gitCommonDirResult.stdout.trim());
+					const mainGitDir = path.resolve(resolvedMainRepo, mainGitDirResult.stdout.trim());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/git.ts` around lines 633 - 637, The two execFileNoThrow
calls use worktreePath and mainRepoCwd but the rest of the handler uses
resolvedWorktree/resolvedMainRepo; change the Promise.all call to pass
resolvedWorktree and resolvedMainRepo to execFileNoThrow, and also update the
subsequent path.resolve(...) calls (that currently use worktreePath/mainRepoCwd)
to use resolvedWorktree/resolvedMainRepo so the function consistently uses
resolved paths (reference symbols: execFileNoThrow, resolvedWorktree,
resolvedMainRepo, worktreePath, mainRepoCwd, path.resolve).
src/main/stats/schema.ts (1)

58-59: Consider dropping idx_query_agent_time — the reverse-order pair is likely unintentional.

Lines 58 and 59 create compound indexes on the same two columns in opposite orders:

  • idx_query_agent_time (agent_type, start_time)agent_type is the left prefix
  • idx_query_time_agent (start_time, agent_type)start_time is the left prefix

The PR description only calls out (start_time, agent_type). The (agent_type, start_time) index also partially overlaps with the existing idx_query_agent_type single-column index, adding write overhead for every INSERT with minimal extra read benefit. If there is no specific query that benefits from agent_type-first ordering (e.g., WHERE agent_type = ? ORDER BY start_time), idx_query_agent_time can be removed.

🔍 Proposed cleanup (if idx_query_agent_time is unneeded)
  CREATE INDEX IF NOT EXISTS idx_query_project_path ON query_events(project_path);
- CREATE INDEX IF NOT EXISTS idx_query_agent_time ON query_events(agent_type, start_time);
  CREATE INDEX IF NOT EXISTS idx_query_time_agent ON query_events(start_time, agent_type);
  CREATE INDEX IF NOT EXISTS idx_query_time_project ON query_events(start_time, project_path);
  CREATE INDEX IF NOT EXISTS idx_query_time_source ON query_events(start_time, source)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/stats/schema.ts` around lines 58 - 59, The diff creates two compound
indexes on query_events in opposite orders; remove the unnecessary
idx_query_agent_time (agent_type, start_time) to avoid write overhead unless you
have queries relying on agent_type-first ordering. In practice drop the CREATE
INDEX IF NOT EXISTS idx_query_agent_time statement and keep CREATE INDEX IF NOT
EXISTS idx_query_time_agent (start_time, agent_type); verify no queries use the
agent_type-first pattern (e.g., WHERE agent_type = ? ORDER BY start_time) and
note idx_query_agent_type (single-column) already exists and overlaps with the
removed index.
src/renderer/components/UsageDashboard/SummaryCards.tsx (1)

83-117: Consider memoizing the inner MetricCard component.

SummaryCards is now memoized, but MetricCard is a plain function. Any state-driven re-render inside SummaryCards will still re-render all MetricCard instances. Since MetricCard is pure (props-only, no internal state), wrapping it with memo is a one-liner that aligns with the PR's optimization intent.

♻️ Proposed refactor
-function MetricCard({ icon, label, value, theme, animationIndex = 0 }: MetricCardProps) {
+const MetricCard = memo(function MetricCard({ icon, label, value, theme, animationIndex = 0 }: MetricCardProps) {
   return (
     ...
   );
-}
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/SummaryCards.tsx` around lines 83 -
117, MetricCard is a pure, props-only component but not memoized, so it still
re-renders whenever SummaryCards updates; wrap MetricCard with React.memo (or
export default memo(MetricCard)) to prevent unnecessary renders. Import
React/memo if not present, keep the existing MetricCardProps typings, and rely
on React.memo's default shallow prop comparison (or provide a custom areEqual
function if deep comparison is needed).
src/renderer/components/UsageDashboard/DurationTrendsChart.tsx (1)

297-300: Prefer useId() over Math.random() for the SVG gradient ID.

Math.random() inside useMemo([]) is non-deterministic and can generate mismatched IDs under React 18 Strict Mode double-invocation or future concurrent-rendering scenarios. useId() (available since React 18) produces a stable, unique, render-consistent ID without the randomness.

♻️ Proposed refactor
-import React, { memo, useState, useMemo, useCallback } from 'react';
+import React, { memo, useState, useMemo, useCallback, useId } from 'react';
-	// Generate unique ID for gradient
-	const gradientId = useMemo(
-		() => `duration-gradient-${Math.random().toString(36).slice(2, 9)}`,
-		[]
-	);
+	// Stable unique ID for SVG gradient – safe under concurrent rendering
+	const baseId = useId();
+	const gradientId = `duration-gradient-${baseId.replace(/:/g, '')}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/DurationTrendsChart.tsx` around lines
297 - 300, Replace the non-deterministic gradientId created with
useMemo/Math.random in the DurationTrendsChart component by using React's useId
hook: import useId from 'react', call const gradientId = useId() (or
prefix/suffix for readability) where the current useMemo-generated id is
defined, and update any places that reference that gradientId (e.g., SVG
<linearGradient> id and corresponding fill="url(#...)" usages) so the SVG id is
stable and consistent under Strict Mode and concurrent rendering.
src/renderer/components/UsageDashboard/ChartSkeletons.tsx (2)

47-74: columns prop controls grid layout but not item count — a mismatch.

Both SummaryCardsSkeleton (line 56, hardcoded length: 5) and AutoRunStatsSkeleton (line 325, hardcoded length: 6) accept a columns prop that only affects gridTemplateColumns, not the number of rendered items. Callers who pass, say, columns={3} or columns={4} will get the wrong number of skeleton cards. This is pre-existing but becomes more visible now that both are memoized and more likely to receive varying columns at runtime via DashboardSkeleton.

♻️ Proposed fix for SummaryCardsSkeleton
-	{Array.from({ length: 5 }).map((_, i) => (
+	{Array.from({ length: columns }).map((_, i) => (
♻️ Proposed fix for AutoRunStatsSkeleton
-	{Array.from({ length: 6 }).map((_, i) => (
+	{Array.from({ length: columns }).map((_, i) => (

Also applies to: 316-337

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

In `@src/renderer/components/UsageDashboard/ChartSkeletons.tsx` around lines 47 -
74, SummaryCardsSkeleton and AutoRunStatsSkeleton accept a columns prop but
still render a hardcoded number of items (Array.from length 5 and 6); update
both components to use the passed columns value for the rendered item count
(e.g., replace Array.from({ length: 5 }) and Array.from({ length: 6 }) with
Array.from({ length: columns }) or Math.max(1, columns) to guard against
0/undefined), keeping existing default param values and preserving
keys/data-testid and theme styling in the mapped items.

167-246: Math.random() in render produces flickering on theme changes.

Line 218 calls Math.random() directly during render to vary cell opacity. Now that the component is memoized, this only fires when props change — but a theme switch will still cause all heatmap cells to jump to new random opacities. Consider seeding the values once with useMemo so the skeleton pattern is stable across re-renders.

♻️ Proposed refactor
+	const rows = 7;
+	const cols = 12;
+	const cellOpacities = useMemo(
+		() => Array.from({ length: cols }, () =>
+			Array.from({ length: rows }, () => 0.2 + Math.random() * 0.15)
+		),
+		[] // stable for the lifetime of the component
+	);
 	const cellSize = 12;
 	const cellGap = 3;
-	const rows = 7;
-	const cols = 12;

Then replace the inline Math.random() call:

-					opacity: 0.2 + Math.random() * 0.15,
+					opacity: cellOpacities[weekIdx][dayIdx],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/UsageDashboard/ChartSkeletons.tsx` around lines 167 -
246, The render calls to Math.random() inside ActivityHeatmapSkeleton cause
opacity flicker on re-renders (e.g., theme changes); generate a stable matrix of
random opacities once with useMemo (dependent on rows/cols, or empty to be fully
stable) and replace the inline Math.random() in the cell render with a lookup
from that memoized array (use a structure keyed by weekIdx/dayIdx). Add the
useMemo near the top of ActivityHeatmapSkeleton, import React's useMemo if
needed, and use the memoized opacity values when rendering SkeletonBox cells and
the legend so the pattern no longer changes on theme switches.
src/renderer/components/Toast.tsx (1)

313-319: Stabilize onRemove prop so ToastItem memoization can actually skip rerenders.

At Line 318, onRemove={() => removeToast(toast.id)} creates a fresh function every render, defeating shallow prop equality for the memoized ToastItem child. Since removeToast is a stable reference from the Zustand store, pass it directly with an updated prop signature instead.

♻️ Proposed refactor
-	onRemove: () => void;
+	onRemove: (toastId: string) => void;
-	setTimeout(onRemove, 300);
+	setTimeout(() => onRemove(toast.id), 300);
-						onRemove={() => removeToast(toast.id)}
+						onRemove={removeToast}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/Toast.tsx` around lines 313 - 319, The inline arrow
onRemove={() => removeToast(toast.id)} creates a new function each render and
prevents ToastItem memoization; instead pass the stable removeToast function
directly and update ToastItem's prop signature to accept an id (e.g. onRemove:
(id: string) => void) so ToastItem can call onRemove(toast.id) internally.
Locate the map that renders <ToastItem ... onRemove={...}> and change it to
onRemove={removeToast}, then update the ToastItem component (and its prop types)
to accept onRemove and invoke onRemove(toast.id) rather than expecting a
zero-arg callback. Ensure removeToast remains the same Zustand selector so its
reference stays stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main/stats/schema.ts`:
- Around line 52-62: Existing installations won't get the new compound indexes
because CREATE_QUERY_EVENTS_INDEXES_SQL runs only in migration v1; add a new
migration "v4" that executes the missing compound CREATE INDEX IF NOT EXISTS
statements (those added to CREATE_QUERY_EVENTS_INDEXES_SQL:
idx_query_agent_time, idx_query_time_agent, idx_query_time_project,
idx_query_time_source) against the query_events table, ensure the SQL uses IF
NOT EXISTS so the migration is idempotent, and register the migration in the
project's migration registry/runner so it runs for databases that have already
passed v1.

In `@src/renderer/components/QRCode.tsx`:
- Around line 57-61: Replace the console.error call in the QR generation promise
catch block inside the QRCode component with Sentry reporting: import
captureException (and optionally captureMessage) from 'src/utils/sentry', then
call captureException(err, { extra: { component: 'QRCode', operation:
'generateDataUrl' } }) and/or captureMessage('Failed to generate QR code')
before setting state (setError, setDataUrl). Update the catch handler in the
QRCode component (the .catch((err) => { ... }) block) to use these Sentry
utilities so the error is reported rather than only logged to console.

In `@src/renderer/components/UsageDashboard/AutoRunStats.tsx`:
- Around line 413-429: The bar div rendered in the map lacks keyboard activation
— add an onKeyDown handler on the same element (the bar div with data-testid
`task-bar-${day.date}`) that listens for Enter and Space and calls the same
logic as handleMouseEnter/handleMouseLeave (e.g., invoke the tooltip/show
details function used by handleMouseEnter for focus and call handleMouseLeave on
blur or Escape), preventDefault for Space to avoid page scroll, and ensure the
element retains tabIndex={0} and appropriate ARIA label (the existing aria-label
from formatFullDate/day counts is fine); update
handleMouseEnter/handleMouseLeave or extract a shared function if needed so
keyboard and mouse both trigger identical behavior.
- Around line 149-150: In groupSessionsByDate, fix the accumulators so
grouped[date].count accumulates attempts (session.tasksTotal ?? 0) while
grouped[date].successCount continues to accumulate completions
(session.tasksCompleted ?? 0), and adjust the pre-filter that selects sessions
(the filter around session.tasks...) to guard on tasksTotal (e.g., only include
sessions with tasksTotal > 0 or tasksTotal != null) so the computed
successRate/opacity/aria-label/tooltip reflect actual completion rate; look for
the grouped variable and the groupSessionsByDate function to apply this change.
- Around line 175-183: The catch block in AutoRunStats.tsx swallows IPC errors
via console.error; replace that with explicit Sentry reporting: import
captureException (and optionally captureMessage) from src/utils/sentry, and in
the catch for window.maestro.stats.getAutoRunSessions(timeRange) call
captureException(err, {extra: {timeRange}}) (and/or captureMessage with context)
before setting state with setError(err instanceof Error ? err.message : 'Failed
to load Auto Run stats'); keep the existing setLoading(false) in finally and do
not suppress the exception silently—ensure Sentry gets the error and contextual
info while preserving UI error state (symbols: getAutoRunSessions, setSessions,
setError, setLoading, AutoRunStats).

In `@src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx`:
- Around line 130-132: Replace the console.error swallowing in the catch inside
LongestAutoRunsTable (the try/catch around fetching Auto Run sessions) by
importing captureException from 'src/utils/sentry' and calling
captureException(err, { extra: { context: 'fetchAutoRunSessions' } }) (or
captureMessage for non-exceptions) so the error is reported to Sentry, then
rethrow the error (throw err) so unexpected failures bubble up; update the
import list at the top of LongestAutoRunsTable.tsx to include captureException.

In `@src/renderer/components/UsageDashboard/TasksByHourChart.tsx`:
- Around line 70-85: Replace the console.error call in the catch block with
Sentry reporting: import captureException from 'src/utils/sentry' at the top of
TasksByHourChart.tsx, then inside the catch use captureException(err, { extra: {
message: 'Failed to fetch Auto Run tasks', timeRange } }) (or similar context)
to report the error to Sentry, keep setting setError(err instanceof Error ?
err.message : 'Failed to load tasks') and setLoading(false) in finally; remove
the console.error line so errors are reported to Sentry instead of only logged
to the console.

In `@src/renderer/components/UsageDashboard/WeekdayComparisonChart.tsx`:
- Around line 272-285: The JSX branch in WeekdayComparisonChart that currently
checks comparisonData.weekday.avgQueriesPerDay >
comparisonData.weekend.avgQueriesPerDay can divide by zero when
comparisonData.weekend.avgQueriesPerDay is 0; update the condition to require
comparisonData.weekday.avgQueriesPerDay >
comparisonData.weekend.avgQueriesPerDay &&
comparisonData.weekend.avgQueriesPerDay > 0 so the numerator/denominator calc
that uses (comparisonData.weekday.avgQueriesPerDay /
comparisonData.weekend.avgQueriesPerDay) is only executed when the weekend
average is non‑zero; mirror the existing guard logic used in the other branch
(which checks weekend.avgQueriesPerDay > 0) to prevent Infinity% from rendering.

In `@src/renderer/hooks/stats/useStats.ts`:
- Around line 135-153: The effect sets mountedRef.current = true but returns
early when enabled is false, so no cleanup runs; ensure the effect always
returns a cleanup function that sets mountedRef.current = false and cancels
pending debounce work. Modify the effect around mountedRef, enabled, fetchStats,
debouncedUpdate and cancelDebounce so that after setting mountedRef.current =
true you still return a cleanup function even if you skip the initial
fetch/subscription; when you do subscribe via
window.maestro.stats.onStatsUpdate(debouncedUpdate) capture the unsubscribe and
call it inside that same cleanup so the cleanup always calls cancelDebounce(),
sets mountedRef.current = false, and unsubscribes if a subscription was created.

---

Nitpick comments:
In `@src/main/ipc/handlers/git.ts`:
- Around line 633-637: The two execFileNoThrow calls use worktreePath and
mainRepoCwd but the rest of the handler uses resolvedWorktree/resolvedMainRepo;
change the Promise.all call to pass resolvedWorktree and resolvedMainRepo to
execFileNoThrow, and also update the subsequent path.resolve(...) calls (that
currently use worktreePath/mainRepoCwd) to use resolvedWorktree/resolvedMainRepo
so the function consistently uses resolved paths (reference symbols:
execFileNoThrow, resolvedWorktree, resolvedMainRepo, worktreePath, mainRepoCwd,
path.resolve).

In `@src/main/stats/schema.ts`:
- Around line 58-59: The diff creates two compound indexes on query_events in
opposite orders; remove the unnecessary idx_query_agent_time (agent_type,
start_time) to avoid write overhead unless you have queries relying on
agent_type-first ordering. In practice drop the CREATE INDEX IF NOT EXISTS
idx_query_agent_time statement and keep CREATE INDEX IF NOT EXISTS
idx_query_time_agent (start_time, agent_type); verify no queries use the
agent_type-first pattern (e.g., WHERE agent_type = ? ORDER BY start_time) and
note idx_query_agent_type (single-column) already exists and overlaps with the
removed index.

In `@src/renderer/components/Toast.tsx`:
- Around line 313-319: The inline arrow onRemove={() => removeToast(toast.id)}
creates a new function each render and prevents ToastItem memoization; instead
pass the stable removeToast function directly and update ToastItem's prop
signature to accept an id (e.g. onRemove: (id: string) => void) so ToastItem can
call onRemove(toast.id) internally. Locate the map that renders <ToastItem ...
onRemove={...}> and change it to onRemove={removeToast}, then update the
ToastItem component (and its prop types) to accept onRemove and invoke
onRemove(toast.id) rather than expecting a zero-arg callback. Ensure removeToast
remains the same Zustand selector so its reference stays stable.

In `@src/renderer/components/UsageDashboard/ChartSkeletons.tsx`:
- Around line 47-74: SummaryCardsSkeleton and AutoRunStatsSkeleton accept a
columns prop but still render a hardcoded number of items (Array.from length 5
and 6); update both components to use the passed columns value for the rendered
item count (e.g., replace Array.from({ length: 5 }) and Array.from({ length: 6
}) with Array.from({ length: columns }) or Math.max(1, columns) to guard against
0/undefined), keeping existing default param values and preserving
keys/data-testid and theme styling in the mapped items.
- Around line 167-246: The render calls to Math.random() inside
ActivityHeatmapSkeleton cause opacity flicker on re-renders (e.g., theme
changes); generate a stable matrix of random opacities once with useMemo
(dependent on rows/cols, or empty to be fully stable) and replace the inline
Math.random() in the cell render with a lookup from that memoized array (use a
structure keyed by weekIdx/dayIdx). Add the useMemo near the top of
ActivityHeatmapSkeleton, import React's useMemo if needed, and use the memoized
opacity values when rendering SkeletonBox cells and the legend so the pattern no
longer changes on theme switches.

In `@src/renderer/components/UsageDashboard/DurationTrendsChart.tsx`:
- Around line 297-300: Replace the non-deterministic gradientId created with
useMemo/Math.random in the DurationTrendsChart component by using React's useId
hook: import useId from 'react', call const gradientId = useId() (or
prefix/suffix for readability) where the current useMemo-generated id is
defined, and update any places that reference that gradientId (e.g., SVG
<linearGradient> id and corresponding fill="url(#...)" usages) so the SVG id is
stable and consistent under Strict Mode and concurrent rendering.

In `@src/renderer/components/UsageDashboard/PeakHoursChart.tsx`:
- Line 106: The line computing hasData currently re-iterates hourlyData on every
render (including frequent hoveredHour updates); change it to a memoized value
by replacing the direct computation with a useMemo hook (e.g., useMemo(() =>
hourlyData.some(h => h.count > 0), [hourlyData])) so hasData only recalculates
when hourlyData changes; update references to the existing hasData identifier
and ensure useMemo is imported if not already.

In `@src/renderer/components/UsageDashboard/SummaryCards.tsx`:
- Around line 83-117: MetricCard is a pure, props-only component but not
memoized, so it still re-renders whenever SummaryCards updates; wrap MetricCard
with React.memo (or export default memo(MetricCard)) to prevent unnecessary
renders. Import React/memo if not present, keep the existing MetricCardProps
typings, and rely on React.memo's default shallow prop comparison (or provide a
custom areEqual function if deep comparison 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 fec2fa0 and 035ccd4.

📒 Files selected for processing (27)
  • src/main/ipc/handlers/git.ts
  • src/main/stats/schema.ts
  • src/renderer/components/ConfirmModal.tsx
  • src/renderer/components/ContextWarningSash.tsx
  • src/renderer/components/HistoryHelpModal.tsx
  • src/renderer/components/QRCode.tsx
  • src/renderer/components/QuickActionsModal.tsx
  • src/renderer/components/RenameTabModal.tsx
  • src/renderer/components/Toast.tsx
  • src/renderer/components/UsageDashboard/ActivityHeatmap.tsx
  • src/renderer/components/UsageDashboard/AgentComparisonChart.tsx
  • src/renderer/components/UsageDashboard/AgentEfficiencyChart.tsx
  • src/renderer/components/UsageDashboard/AgentUsageChart.tsx
  • src/renderer/components/UsageDashboard/AutoRunStats.tsx
  • src/renderer/components/UsageDashboard/ChartSkeletons.tsx
  • src/renderer/components/UsageDashboard/DurationTrendsChart.tsx
  • src/renderer/components/UsageDashboard/EmptyState.tsx
  • src/renderer/components/UsageDashboard/LocationDistributionChart.tsx
  • src/renderer/components/UsageDashboard/LongestAutoRunsTable.tsx
  • src/renderer/components/UsageDashboard/PeakHoursChart.tsx
  • src/renderer/components/UsageDashboard/SessionStats.tsx
  • src/renderer/components/UsageDashboard/SourceDistributionChart.tsx
  • src/renderer/components/UsageDashboard/SummaryCards.tsx
  • src/renderer/components/UsageDashboard/TasksByHourChart.tsx
  • src/renderer/components/UsageDashboard/WeekdayComparisonChart.tsx
  • src/renderer/hooks/agent/useFilteredAndSortedSessions.ts
  • src/renderer/hooks/stats/useStats.ts

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.
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.

Caution

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

⚠️ Outside diff range comments (2)
src/renderer/components/SymphonyModal.tsx (2)

313-320: ⚠️ Potential issue | 🟡 Minor

Guard parent keyboard activation from nested interactive children.

On Line [314], the parent key handler can still run when focus is inside a child button/link, which may trigger unintended onSelect() on Enter/Space. Add a target guard before handling keys.

Proposed fix
 			onKeyDown={
 				isSelectable
 					? (e) => {
+							if (e.target !== e.currentTarget) return;
 							if (e.key === 'Enter' || e.key === ' ') {
 								e.preventDefault();
 								onSelect();
 							}
 						}
 					: undefined
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/SymphonyModal.tsx` around lines 313 - 320, The parent
onKeyDown handler in the SymphonyModal component is firing even when focus is
inside nested interactive children; update the isSelectable key handler to guard
so it only activates when the event originates on the parent container (e.g.
check that event.target === event.currentTarget or that event.target does not
sit inside a nested interactive element like button/a/[role="button"]). Modify
the onKeyDown callback used when isSelectable is true to return early if the
target is a descendant interactive element, and only call onSelect() when the
guard passes.

311-325: ⚠️ Potential issue | 🟡 Minor

Add explicit focus class for the newly tabbable card.

Line [311] correctly adds keyboard focusability, but Line [323] should also include outline-none (and ideally a visible focus style) to match repo accessibility conventions.

Proposed fix
-			className={`w-full p-3 rounded-lg border text-left transition-all ${
+			className={`w-full p-3 rounded-lg border text-left transition-all outline-none focus-visible:ring-2 ${
 				isBlocked ? 'opacity-75 hover:bg-white/5 cursor-pointer' : !isAvailable ? 'opacity-60' : 'hover:bg-white/5 cursor-pointer'
 			} ${isSelected ? 'ring-2' : ''}`}

As per coding guidelines: `src/renderer/**/*.{tsx,jsx}: Add tabIndex={0} or tabIndex={-1} and outline-none class to ensure focus works correctly. Use ref={(el) => el?.focus()} for auto-focus in React components.

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

In `@src/renderer/components/SymphonyModal.tsx` around lines 311 - 325, The card
element in SymphonyModal.tsx that conditionally sets tabIndex/onClick/onKeyDown
(the JSX block containing tabIndex={isSelectable ? 0 : -1},
onClick={isSelectable ? onSelect : undefined}, onKeyDown=..., and className=...)
needs an explicit focus class: add "outline-none" to the className and include a
visible focus ring style (for example the repo's standard focus utility like
"focus:ring-2 focus:ring-primary" or similar) so keyboard focus is both
accessible and consistent with repo conventions; keep the existing conditional
tabIndex logic and ensure the same className string includes these focus classes
for both selectable and non-selectable states.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/renderer/components/SymphonyModal.tsx`:
- Around line 313-320: The parent onKeyDown handler in the SymphonyModal
component is firing even when focus is inside nested interactive children;
update the isSelectable key handler to guard so it only activates when the event
originates on the parent container (e.g. check that event.target ===
event.currentTarget or that event.target does not sit inside a nested
interactive element like button/a/[role="button"]). Modify the onKeyDown
callback used when isSelectable is true to return early if the target is a
descendant interactive element, and only call onSelect() when the guard passes.
- Around line 311-325: The card element in SymphonyModal.tsx that conditionally
sets tabIndex/onClick/onKeyDown (the JSX block containing tabIndex={isSelectable
? 0 : -1}, onClick={isSelectable ? onSelect : undefined}, onKeyDown=..., and
className=...) needs an explicit focus class: add "outline-none" to the
className and include a visible focus ring style (for example the repo's
standard focus utility like "focus:ring-2 focus:ring-primary" or similar) so
keyboard focus is both accessible and consistent with repo conventions; keep the
existing conditional tabIndex logic and ensure the same className string
includes these focus classes for both selectable and non-selectable states.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 035ccd4 and 84cb95b.

📒 Files selected for processing (1)
  • src/renderer/components/SymphonyModal.tsx

pedramamini and others added 7 commits February 24, 2026 18:55
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.
@pedramamini pedramamini merged commit 1d4dad7 into main Feb 25, 2026
2 checks passed
pedramamini added a commit that referenced this pull request Feb 25, 2026
* perf: remove streaming debug logs and fix index-based React keys

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).

* fix: address PR review feedback from CodeRabbit

- 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

* perf: prevent session bloat by stripping file tree and preview history 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

* feat: honor local .gitignore patterns in file tree indexing

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.

* refactor: clean up file-scale PR with shared gitignore parser, options 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

* fix: enforce read-only env vars for OpenCode to prevent file writes in 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.

* fix: use fixed icon sizes in FilePreview button panel for compact scrolled header

* fix: keep BatchRunner open when navigating to WorktreeConfig modal (#451)

WorktreeConfigModal now layers on top (priority 752 > 720) instead of
replacing the BatchRunner. User returns to their Auto Run setup after
configuring worktrees. Bumped WorktreeConfigModal z-index above
BatchRunner's z-[9999].

* perf: cherry-pick low-risk optimizations from PR #385

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.

* fix: make blocked Symphony issues selectable for document exploration

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.

* feat: add group @mentions in group chat and fix navigation history

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.

* feat: allow changing agent provider in Edit Agent modal

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: archive state for group chat  (#377)

* 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>

* fix: address PR #450 review feedback across stats, UI, and accessibility

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.

* fix: move blocked issue message to footer with greyed-out Start button

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.

* fix: prevent context menus from rendering off-screen

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.

* fix: prevent orphaned file preview when tab bar is hidden

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.

* fix: address PR review — missed onRemoteCommand path and weak test

- 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

* polish: UI refinements for header z-index, status dot opacity, and Director'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

* polish: reduce collapsed sidebar indicator opacity to 0.25 for less visual noise

---------

Co-authored-by: Sam Shpuntoff <82393483+sshpuntoff@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
pedramamini added a commit that referenced this pull request Feb 25, 2026
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.
pedramamini added a commit that referenced this pull request Feb 25, 2026
* perf: remove streaming debug logs and fix index-based React keys

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).

* fix: address PR review feedback from CodeRabbit

- 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

* perf: prevent session bloat by stripping file tree and preview history 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

* feat: honor local .gitignore patterns in file tree indexing

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.

* refactor: clean up file-scale PR with shared gitignore parser, options 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

* fix: enforce read-only env vars for OpenCode to prevent file writes in 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.

* perf: cherry-pick low-risk optimizations from PR #385

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.

* fix: make blocked Symphony issues selectable for document exploration

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.

* feat: add group @mentions in group chat and fix navigation history

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.

* feat: allow changing agent provider in Edit Agent modal

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: archive state for group chat  (#377)

* 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>

* fix: address PR #450 review feedback across stats, UI, and accessibility

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.

* fix: move blocked issue message to footer with greyed-out Start button

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.

* fix: prevent context menus from rendering off-screen

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.

* fix: prevent orphaned file preview when tab bar is hidden

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.

* fix: address PR review — missed onRemoteCommand path and weak test

- 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

* polish: UI refinements for header z-index, status dot opacity, and Director'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

* polish: reduce collapsed sidebar indicator opacity to 0.25 for less visual noise

* polish: Director's Notes keeps old content visible during regeneration

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.

* fix: update prop extraction hooks for rebased branch compatibility

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).

---------

Co-authored-by: Sam Shpuntoff <82393483+sshpuntoff@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
pedramamini added a commit that referenced this pull request Feb 26, 2026
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.
pedramamini added a commit that referenced this pull request Feb 27, 2026
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.
pedramamini added a commit that referenced this pull request Feb 28, 2026
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.
pedramamini added a commit that referenced this pull request Mar 1, 2026
* perf: remove streaming debug logs and fix index-based React keys

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).

* fix: address PR review feedback from CodeRabbit

- 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

* perf: prevent session bloat by stripping file tree and preview history 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

* feat: honor local .gitignore patterns in file tree indexing

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.

* refactor: clean up file-scale PR with shared gitignore parser, options 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

* perf: cherry-pick low-risk optimizations from PR #385

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.

* fix: make blocked Symphony issues selectable for document exploration

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.

* feat: add group @mentions in group chat and fix navigation history

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.

* feat: allow changing agent provider in Edit Agent modal

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: archive state for group chat  (#377)

* 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>

* fix: address PR #450 review feedback across stats, UI, and accessibility

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.

* fix: move blocked issue message to footer with greyed-out Start button

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.

* fix: prevent context menus from rendering off-screen

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.

* fix: prevent orphaned file preview when tab bar is hidden

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.

* fix: address PR review — missed onRemoteCommand path and weak test

- 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

* polish: UI refinements for header z-index, status dot opacity, and Director'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

* polish: Director's Notes keeps old content visible during regeneration

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.

* fix: update prop extraction hooks for rebased branch compatibility

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).

* feat: add layout algorithm options to Document Graph (Mind Map, Radial, Force-Directed)

Adds a dropdown in the Document Graph header to switch between three
layout algorithms. Layout choice persists per-agent (via Session) with
global default fallback (via settingsStore).

- Extract layout code from MindMap.tsx into mindMapLayouts.ts
- Add Radial layout (concentric rings) and Force-Directed layout (d3-force)
- Add layout type dropdown UI with layer stack Escape handling
- Wire persistence through App.tsx (session + global default)
- Add tests for all three algorithms, settingsStore, and props

* polish: add "Load full file" button to truncated file preview banner

* fix: worktree auto-run spin-out creates duplicate agents and fails to start

Two bugs in the worktree "Run in Worktree" flow:

1. Duplicate agents: spawnWorktreeAgentAndDispatch didn't mark the
   worktree path as recently created, so the file watcher in
   useWorktreeHandlers raced and created a second session. Fixed by
   introducing a shared worktreeDedup module checked by both hooks.

2. Auto-run never starts: startBatchRun uses sessionsRef which only
   updates on React re-render, but is called synchronously after
   Zustand store mutation. Added fallback to useSessionStore.getState()
   for immediate access to just-created sessions.

* fix: lint cleanup, auto-scroll empty-tab guard, and worktree batch improvements

- Fix ESLint errors: empty catch block comment, unused eslint-disable directive,
  unused variable prefixing in App.tsx and DirectorNotesModal
- Hide auto-scroll button when tab has no content (filteredLogs.length > 0 guard)
- Fix worktree auto-run: skip setupWorktree when worktreeTarget already created
  the worktree, preventing "belongs to different repository" false positive
- Add "Auto Run Started" toast notification with task/document counts
- Record PR creation results in agent history panel
- Update auto-scroll and batch processor tests for new behavior

* feat: add "Open in Finder/Explorer" button to Storage Location settings

Opens the active storage folder in the native file manager. Adds
getOpenInLabel() to platformUtils for platform-aware button text.

* fix: prefix unused destructured vars to satisfy ESLint after rebase

After rebasing on main, three variables (terminalWidth, setTerminalWidth,
disableConfetti) from useSettings were destructured but not yet consumed
in App.tsx, triggering no-unused-vars warnings.

* feat: add "View history" link to Auto Run batch progress footer

Helps users discover the History tab for viewing completed Auto Run
work entries. Link appears only when the Auto Run tab is active.

* feat: show issue counts on Symphony tiles via GitHub Search API

Uses a single GitHub Search API call to batch-fetch open issue counts
for all registered repos (instead of 2 calls per repo). Tiles now show
"View #N Issues" when issues exist, "No Issues" (ghosted at 45% opacity)
when none, and the original "View Issues" while counts are loading.

- New IPC handler: symphony:getIssueCounts (30-min cache TTL)
- New fetchIssueCounts() using /search/issues with multiple repo: qualifiers
- Counts fetched automatically when registry loads
- Updated RepositoryTile, useSymphony hook, preload bridge, global.d.ts

* fix: show task summary on TodoWrite tool cards in AI terminal

TodoWrite tool cards rendered as bare "TodoWrite ●" with no detail
because the todos array input wasn't handled in the tool detail
extraction chain. Added summarizeTodos helper that shows the
in-progress task's activeForm with completion progress (e.g.,
"Running tests (2/5)").

* fix: remove close button from group chat header, fix agent switching

The X button in the group chat header was unnecessary since users can
switch away by clicking another agent or group chat. SessionList was
bypassing App.tsx's setActiveSessionId wrapper (which dismisses group
chat) by reading directly from the session store, so clicking an agent
in the sidebar didn't exit the group chat view. Now SessionList wraps
the call to also clear activeGroupChatId.

* fix: skip archived group chats during keyboard navigation cycling

Archived group chats were included in the visual order built by
useCycleSession, causing keyboard shortcuts (Cmd+]/Cmd+[) to land
on hidden archived items. Now filters them out before building the
navigation list.

* fix: file explorer dedup, worktree timer race, ENOENT handling, type fixes

- Add dual-layer deduplication to file explorer (readDir + flatten) to
  prevent duplicate entries and React key collisions
- Fix worktree dedup timer race condition by clearing old timers on
  re-mark and on explicit clear
- Return null instead of throwing for ENOENT in fs:readFile IPC handler
  to avoid noisy Electron error logging for expected missing files
- Add isFile field to DirectoryEntry in preload and renderer types to
  match global.d.ts and runtime data
- Extract DocumentGraphLayoutType alias to reduce inline union repetition
- Add targetBranch to PRCreationResult so batch history logs show the
  resolved target branch instead of a hardcoded fallback
- Strengthen tests: mind map side assertion, FilePreview re-query after
  state change, settings fallback persistence, new dedup and timer tests

Claude ID: 174e0697-1313-4c5b-af72-64595f238cd9
Maestro ID: 21845e77-8847-4578-a785-1cd3c3556b6c

---------

Co-authored-by: Sam Shpuntoff <82393483+sshpuntoff@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
pedramamini added a commit that referenced this pull request Mar 1, 2026
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.
pedramamini added a commit that referenced this pull request Mar 1, 2026
* perf: remove streaming debug logs and fix index-based React keys

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).

* fix: address PR review feedback from CodeRabbit

- 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

* perf: prevent session bloat by stripping file tree and preview history 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

* feat: honor local .gitignore patterns in file tree indexing

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.

* refactor: clean up file-scale PR with shared gitignore parser, options 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

* perf: cherry-pick low-risk optimizations from PR #385

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.

* fix: make blocked Symphony issues selectable for document exploration

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.

* feat: add group @mentions in group chat and fix navigation history

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.

* feat: allow changing agent provider in Edit Agent modal

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: archive state for group chat  (#377)

* 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>

* fix: address PR #450 review feedback across stats, UI, and accessibility

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.

* fix: move blocked issue message to footer with greyed-out Start button

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.

* fix: prevent context menus from rendering off-screen

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.

* fix: prevent orphaned file preview when tab bar is hidden

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.

* fix: address PR review — missed onRemoteCommand path and weak test

- 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

* polish: UI refinements for header z-index, status dot opacity, and Director'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

* polish: Director's Notes keeps old content visible during regeneration

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.

* fix: update prop extraction hooks for rebased branch compatibility

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).

* feat: add layout algorithm options to Document Graph (Mind Map, Radial, Force-Directed)

Adds a dropdown in the Document Graph header to switch between three
layout algorithms. Layout choice persists per-agent (via Session) with
global default fallback (via settingsStore).

- Extract layout code from MindMap.tsx into mindMapLayouts.ts
- Add Radial layout (concentric rings) and Force-Directed layout (d3-force)
- Add layout type dropdown UI with layer stack Escape handling
- Wire persistence through App.tsx (session + global default)
- Add tests for all three algorithms, settingsStore, and props

* fix: lint cleanup, auto-scroll empty-tab guard, and worktree batch improvements

- Fix ESLint errors: empty catch block comment, unused eslint-disable directive,
  unused variable prefixing in App.tsx and DirectorNotesModal
- Hide auto-scroll button when tab has no content (filteredLogs.length > 0 guard)
- Fix worktree auto-run: skip setupWorktree when worktreeTarget already created
  the worktree, preventing "belongs to different repository" false positive
- Add "Auto Run Started" toast notification with task/document counts
- Record PR creation results in agent history panel
- Update auto-scroll and batch processor tests for new behavior

* feat: show issue counts on Symphony tiles via GitHub Search API

Uses a single GitHub Search API call to batch-fetch open issue counts
for all registered repos (instead of 2 calls per repo). Tiles now show
"View #N Issues" when issues exist, "No Issues" (ghosted at 45% opacity)
when none, and the original "View Issues" while counts are loading.

- New IPC handler: symphony:getIssueCounts (30-min cache TTL)
- New fetchIssueCounts() using /search/issues with multiple repo: qualifiers
- Counts fetched automatically when registry loads
- Updated RepositoryTile, useSymphony hook, preload bridge, global.d.ts

* fix: address PR #485 review feedback — null safety, cache validation, a11y, formatting

- readFile ENOENT null return: update global.d.ts type to string | null,
  add null guards across 12 call sites (MarkdownRenderer, FilePreview,
  AutoRun, DocumentEditor, DocumentGenerationView, ImageDiffViewer,
  DocumentGraphView, DebugWizardModal, useAutoRunImageHandling,
  useFileExplorerEffects, useTabHandlers, useAppHandlers)
- Symphony issue counts: add pagination (up to 1,000 results), validate
  cache against requested repoSlugs, clear stale counts on failure/empty
- Worktree fixes: clear dedup marker on worktreeSetup throw, propagate
  targetBranch in PR creation catch path
- GroupChatHeader: add keyboard a11y (tabIndex, role, onKeyDown)
- Settings: extract DOCUMENT_GRAPH_LAYOUT_TYPES constant, dedup validation
- DocumentGraphView: sync layoutType state with defaultLayoutType prop
- Tests: fix vi.hoisted() TDZ, proper Shortcut mock shape, clearAllMocks,
  assert console.warn in dedup tests, update symphony cache type test
- Prettier: format all CI-flagged files

---------

Co-authored-by: Sam Shpuntoff <82393483+sshpuntoff@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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