Skip to content

refactor: consolidate modal layer and group chat spawn (Phase 10)#824

Merged
reachrazamair merged 5 commits intoRunMaestro:rcfrom
jSydorowicz21:dedup/phase-10-modal-spawn-consolidation
Apr 16, 2026
Merged

refactor: consolidate modal layer and group chat spawn (Phase 10)#824
reachrazamair merged 5 commits intoRunMaestro:rcfrom
jSydorowicz21:dedup/phase-10-modal-spawn-consolidation

Conversation

@jSydorowicz21
Copy link
Copy Markdown
Contributor

@jSydorowicz21 jSydorowicz21 commented Apr 13, 2026

Summary

Two structural consolidations:

  • 10A: migrate 47 modals to the canonical useModalLayer hook, replacing manual registerLayer/unregisterLayer boilerplate and hand-rolled Escape handlers
  • 10B: extract spawnGroupChatAgent helper that encapsulates SSH wrap + Windows shell/stdin handling + process spawn, consolidating 4 near-identical spawn sites

Net: 58 files, -954 lines (exceeds the ~328-line playbook target)

10A - Modal layer migration

Hook enhancement (src/renderer/hooks/ui/useModalLayer.ts):

  • Added optional enabled flag
  • Switched to a stable ref-based escape closure so re-registration is no longer needed when onEscape callback identity changes

Migrated 47 call sites across 46 files to useModalLayer(priority, ariaLabel, onEscape, options?):
AgentCreationDialog, AgentPromptComposerModal, AgentSessionsBrowser, AgentSessionsModal, AutoRunExpandedModal, AutoRunLightbox, AutoRunSearchBar, BatchRunnerModal, CreatePRModal, CreateWorktreeModal, CueModal, DirectorNotesModal, DocumentGraphView, DocumentsPanel, ExecutionQueueBrowser, FileSearchModal, FirstRunCelebration, GitDiffViewer, GitLogViewer, HistoryDetailModal, WizardExitConfirmDialog, KeyboardMasteryCelebration, LeaderboardRegistrationModal, LogViewer, MarketplaceModal, MergeProgressModal, MergeSessionModal, PlaygroundPanel, ProcessMonitor, PromptComposerModal, QuickActionsModal, QuitConfirmModal, SendToAgentModal, SettingsModal, StandingOvationOverlay, SummarizeProgressModal, SymphonyModal, TabSwitcherModal, TransferProgressModal, UsageDashboardModal, ExistingAutoRunDocsModal, ExistingDocsModal, MaestroWizard, WizardExitConfirmModal, WizardResumeModal, TourOverlay.

Sites skipped (9): overlay-type registrations (type: 'overlay' / allowClickOutside) in DocumentGraph dropdowns, FileExplorerPanel, FilePreview, LightboxModal, TerminalOutput, TerminalSearchBar. The canonical hook hardcodes type: 'modal'; these need a separate variant.

Test mock updates: 9 test files had their useLayerStack mock extended with updateLayerHandler; one NewInstanceModal assertion was rewritten to verify behavior (escape routes to latest onClose) instead of handler identity.

10B - Group chat spawn helper

New: src/main/group-chat/spawnGroupChatAgent.ts (~155 lines) encapsulates:

  • SSH remote config wrapping
  • Windows shell + stdin command building
  • processManager.spawn invocation

Consolidated 4 call sites in group-chat-router.ts: moderator spawn, participant spawn, synthesis spawn, recovery spawn. Each collapsed ~60 lines of imperative code to a single helper call.

Store resolve() consolidation: not applicable - no widespread duplication exists.

Test plan

  • npm run lint passes (all 3 tsconfigs)
  • npx prettier --check . passes
  • 366 targeted tests pass (useModalLayer, AppModals, group-chat)
  • 540+ migrated-modal tests pass
  • Press Escape in any migrated modal - it closes (layer stack routes escape correctly)
  • Open nested modals - layer priority still respected
  • Spawn a group chat moderator and a participant - both start correctly on local and SSH remote

Risk

Medium. Modal layer touches keyboard routing across the app. 540+ tests cover the migrated paths and all are green.

Summary by CodeRabbit

  • Tests

    • Improved modal-layer test mocks to reflect updated modal handler behavior for more reliable modal-related tests.
  • Refactor

    • Consolidated modal lifecycle, Escape and focus handling into a unified hook for more consistent, predictable modal behavior across the UI.
  • Chores

    • Introduced a centralized agent process spawn helper to streamline and harden agent startup and recovery.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 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

Centralized modal layer management by replacing manual useLayerStack usage with useModalLayer across many renderer components; added a new async spawnGroupChatAgent helper and replaced direct processManager.spawn calls in the group-chat router; updated tests to include updateLayerHandler in LayerStackContext mocks.

Changes

Cohort / File(s) Summary
Tests: LayerStackContext mocks
src/__tests__/renderer/components/CueModal.test.tsx, src/__tests__/renderer/components/DocumentGraph/DocumentGraphView.test.tsx, src/__tests__/renderer/components/ExecutionQueueBrowser.test.tsx, src/__tests__/renderer/components/LeaderboardRegistrationModal.test.tsx, src/__tests__/renderer/components/UsageDashboard/responsive-layout.test.tsx, src/__tests__/renderer/components/UsageDashboard/state-transition-animations.test.tsx, src/__tests__/renderer/components/UsageDashboardModal.test.tsx
Added updateLayerHandler: vi.fn() to the mocked useLayerStack return shape in multiple tests.
Test adjustments (handler capture/invocation)
src/__tests__/renderer/components/NewInstanceModal.test.tsx, src/__tests__/renderer/components/SettingsModal.test.tsx
Relaxed assertions to allow expect.any(Function) for captured handlers and invoked the captured function in one test; included updateLayerHandler in mock returns where needed.
New helper: spawnGroupChatAgent
src/main/group-chat/spawnGroupChatAgent.ts
Added exported async helper and types to centralize agent spawning: command selection, optional SSH wrapping (throws if enabled but no store), Windows spawn config, env/prompt injection, and final processManager.spawn; returns { pid, success }.
Group chat router refactor
src/main/group-chat/group-chat-router.ts
Replaced inline processManager.spawn(...) usages with await spawnGroupChatAgent(...) for multiple agent flows; removed local SSH/win-spawn wiring and updated imports.
Hook: useModalLayer
src/renderer/hooks/ui/useModalLayer.ts
Made ariaLabel optional, added enabled?: boolean option to gate registration, introduced onEscapeRef to call latest handler, and updated registration/unregistration logic to respect enabled.
Modal components: useModalLayer adoption
src/renderer/components/... (examples: AgentCreationDialog.tsx, AgentPromptComposerModal.tsx, AgentSessionsBrowser.tsx, CueModal/CueModal.tsx, DocumentGraph/DocumentGraphView.tsx, ExecutionQueueBrowser.tsx, FileSearchModal.tsx, FirstRunCelebration.tsx, GitDiffViewer.tsx, LeaderboardRegistrationModal.tsx, MarketplaceModal.tsx, PromptComposerModal.tsx, QuickActionsModal.tsx, Settings/SettingsModal.tsx, UsageDashboard/..., Wizard/..., etc.)
Replaced manual useLayerStack() register/unregister/update flows with useModalLayer() calls; removed layerIdRef and associated effect lifecycles, moving escape/focus logic into the hook or simplified mount-only focus effects.

Sequence Diagram(s)

sequenceDiagram
  participant Router as GroupChat Router
  participant Helper as spawnGroupChatAgent
  participant SSH as SSH Wrapper (sshStore)
  participant WinCfg as Windows Spawn Config
  participant PM as ProcessManager

  Router->>Helper: await spawnGroupChatAgent(config)
  alt ssh enabled
    Helper->>SSH: wrapSpawnWithSsh(command,args,cwd,prompt,env)
    SSH-->>Helper: { command, args, cwd, sshStdinScript }
  end
  Helper->>WinCfg: getWindowsSpawnConfig(agentId, sshRemoteConfig?)
  WinCfg-->>Helper: windows spawn options
  Helper->>PM: processManager.spawn({ sessionId, command, args, cwd, env, stdinScript, readOnlyMode, ... })
  PM-->>Helper: { pid, success }
  Helper-->>Router: return { pid, success }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hopping through hooks with glee,
Layers now managed clean and free,
Agents spawn in tidy rows,
Tests learn where the handler goes,
A tiny refactor — carrot for me.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: consolidating modal layer registration and group chat spawn logic across the codebase in Phase 10.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@jSydorowicz21 jSydorowicz21 self-assigned this Apr 14, 2026
@jSydorowicz21 jSydorowicz21 marked this pull request as ready for review April 14, 2026 04:35
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 14, 2026

Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance.

@jSydorowicz21 jSydorowicz21 force-pushed the dedup/phase-10-modal-spawn-consolidation branch from f361226 to 4af7a74 Compare April 15, 2026 05:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

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

198-209: ⚠️ Potential issue | 🟠 Major

Remove local Escape handling now that the modal is layer-registered.

After adopting useModalLayer, handling Escape again in the window listener (Line 201) creates competing close paths and can break top-layer Escape routing.

Suggested fix
	useEffect(() => {
		const handleKeyDown = (e: KeyboardEvent) => {
-			if (e.key === 'Enter' || e.key === 'Escape') {
+			if (e.key === 'Enter') {
				e.preventDefault();
				handleClose();
			}
		};

		window.addEventListener('keydown', handleKeyDown);
		return () => window.removeEventListener('keydown', handleKeyDown);
	}, [handleClose]);

As per coding guidelines "For modal escape not working, register with layer stack (don't handle Escape locally) and check priority is set correctly in src/renderer/constants/modalPriorities.ts."

Also applies to: 212-216

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

In `@src/renderer/components/FirstRunCelebration.tsx` around lines 198 - 209, The
window keydown effect currently intercepts Escape and conflicts with the modal
layer; remove local Escape handling by either deleting the keydown listener
entirely or changing the handleKeyDown callback to only handle Enter (leave
Escape handling to useModalLayer), and remove any duplicate listeners elsewhere
(the other effect at the 212-216 region). Update the useEffect that defines
handleKeyDown (and any matching cleanup) so it no longer calls handleClose on
Escape; ensure the modal is registered via useModalLayer and that its priority
in modalPriorities.ts is correct.
🧹 Nitpick comments (2)
src/renderer/components/KeyboardMasteryCelebration.tsx (1)

168-183: Redundant Escape handling between window listener and modal layer.

The component handles Escape in two places:

  1. The useModalLayer hook (line 181-183)
  2. The window keydown listener (lines 168-178)

While the isClosingRef guard prevents double-execution, the Escape handling in the window listener is now redundant. Consider removing e.key === 'Escape' from the window listener since useModalLayer already handles it through the layer stack.

♻️ Suggested simplification
 	// Handle keyboard events - use ref to avoid stale closure
 	useEffect(() => {
 		const handleKeyDown = (e: KeyboardEvent) => {
-			if (e.key === 'Enter' || e.key === 'Escape') {
+			if (e.key === 'Enter') {
 				e.preventDefault();
 				handleCloseRef.current();
 			}
 		};

 		window.addEventListener('keydown', handleKeyDown);
 		return () => window.removeEventListener('keydown', handleKeyDown);
 	}, []); // Empty deps - handler reads from ref
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/KeyboardMasteryCelebration.tsx` around lines 168 -
183, The window-level keydown handler in useEffect redundantly checks for
'Escape' even though useModalLayer already handles Escape via the layer stack;
update the handleKeyDown function (inside the useEffect) to only handle 'Enter'
(remove the e.key === 'Escape' branch), keep e.preventDefault() and
handleCloseRef.current() for Enter, and leave useModalLayer(...) and its Escape
handling untouched so Escape is only handled by useModalLayer.
src/renderer/components/FirstRunCelebration.tsx (1)

218-221: Consider dropping manual mount-focus to avoid focus ownership conflicts.

useModalLayer already registers this as a focus-capturing modal; this extra mount focus can be redundant and occasionally race with stack-managed focus.

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

In `@src/renderer/components/FirstRunCelebration.tsx` around lines 218 - 221,
Remove the manual mount-focus useEffect in FirstRunCelebration: delete the block
that calls containerRef.current?.focus() inside useEffect and rely on
useModalLayer's focus management instead; ensure containerRef remains attached
to the modal container (and not removed) so useModalLayer can manage focus
without the redundant focus call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/group-chat/spawnGroupChatAgent.ts`:
- Around line 97-130: When sshRemoteConfig?.enabled is true but sshStore is null
you must not silently fall back to a local spawn; detect that condition and fail
fast instead of continuing. In spawnGroupChatAgent.ts, add a check after
evaluating sshRemoteConfig: if (sshRemoteConfig?.enabled && !sshStore) throw a
descriptive error (include debugLabel/agentId) so the error bubbles to Sentry;
otherwise continue to call wrapSpawnWithSsh(...) when sshStore exists and call
getWindowsSpawnConfig(agentId, undefined) only when SSH is not enabled or has
been wrapped. This ensures wrapSpawnWithSsh, sshRemoteConfig, sshStore,
getWindowsSpawnConfig and the spawn* variables behave correctly.

In `@src/renderer/components/AutoRun/AutoRunLightbox.tsx`:
- Around line 63-70: The modal layer is being enabled even when the lightbox
cannot render (preview URL missing), which blocks shortcuts/focus; update the
useModalLayer call in the AutoRunLightbox component to only enable when the
component will actually render (e.g. change the enabled prop from isVisible /
lightboxFilename check to include a truthy preview URL/previewReady flag), keep
the same priority (MODAL_PRIORITIES.AUTORUN_LIGHTBOX) and continue to rely on
the layer stack for Escape handling (do not handle Escape locally).

In `@src/renderer/components/HistoryDetailModal.tsx`:
- Around line 100-102: The parent HistoryDetailModal registers an Escape layer
with useModalLayer(MODAL_PRIORITIES.CONFIRM, ..., onCloseRef) which closes the
whole modal even when the nested delete confirmation (showDeleteConfirm) is
open; update the logic so the parent only reacts to Escape when the nested
confirm is not visible (e.g., guard the onCloseRef call with !showDeleteConfirm)
and ensure the nested DeleteConfirmModal itself registers its own layer using
useModalLayer(MODAL_PRIORITIES.CONFIRM, undefined, onNestedClose) so the
confirmation becomes the top layer and handles Escape instead of the parent.

In `@src/renderer/components/SymphonyModal.tsx`:
- Around line 1415-1428: The modal-layer Escape handler registered via
useModalLayer(MODAL_PRIORITIES.SYMPHONY, ...) is closing the modal even when the
project-search input is focused; change the handler to ignore Escape if the
focused element is an input/textarea or the project-search element (e.g., check
document.activeElement and return early when it's an input, textarea, or matches
the project search's selector or has a distinguishing attribute/class), so that
Escape only triggers the layer close/back (showHelpRef, showDetailViewRef,
handleBackRef, onCloseRef) when focus is not inside a focused text input;
alternatively, consume the Escape in the project-search key handler before it
bubbles to useModalLayer.

In `@src/renderer/components/Wizard/ExistingDocsModal.tsx`:
- Around line 53-55: The root onKeyDown handler in ExistingDocsModal is
preventing Escape from reaching the modal layer stack—change the handler so it
does not stop propagation for the Escape key (allow it to bubble) and let
useModalLayer(MODAL_PRIORITIES.EXISTING_AUTORUN_DOCS, 'Existing Playbook
Documents Found', () => onCancelRef.current()) handle close-on-Escape;
specifically, update the component's onKeyDown logic to only stop propagation
for keys that must be blocked (e.g., Tab if needed) and never call
stopPropagation for 'Escape', ensuring the modal priority constant remains the
correct MODAL_PRIORITIES.EXISTING_AUTORUN_DOCS.

In `@src/renderer/components/Wizard/tour/TourOverlay.tsx`:
- Around line 245-248: The window keydown handler still calls skipTour() and
Escape is also wired via useModalLayer(MODAL_PRIORITIES.TOUR...), which can
double-fire skipTour; remove the Escape handling from the local window keydown
listener (the handler that invokes skipTour) so Escape is handled only by the
layer stack, or alternatively have the local handler call
event.stopImmediatePropagation()/preventDefault() before calling skipTour to
prevent the layer from also receiving it; ensure useModalLayer is used with the
correct MODAL_PRIORITIES.TOUR priority and that only one path invokes
skipTour/onClose to avoid duplicate analytics and races.

---

Outside diff comments:
In `@src/renderer/components/FirstRunCelebration.tsx`:
- Around line 198-209: The window keydown effect currently intercepts Escape and
conflicts with the modal layer; remove local Escape handling by either deleting
the keydown listener entirely or changing the handleKeyDown callback to only
handle Enter (leave Escape handling to useModalLayer), and remove any duplicate
listeners elsewhere (the other effect at the 212-216 region). Update the
useEffect that defines handleKeyDown (and any matching cleanup) so it no longer
calls handleClose on Escape; ensure the modal is registered via useModalLayer
and that its priority in modalPriorities.ts is correct.

---

Nitpick comments:
In `@src/renderer/components/FirstRunCelebration.tsx`:
- Around line 218-221: Remove the manual mount-focus useEffect in
FirstRunCelebration: delete the block that calls containerRef.current?.focus()
inside useEffect and rely on useModalLayer's focus management instead; ensure
containerRef remains attached to the modal container (and not removed) so
useModalLayer can manage focus without the redundant focus call.

In `@src/renderer/components/KeyboardMasteryCelebration.tsx`:
- Around line 168-183: The window-level keydown handler in useEffect redundantly
checks for 'Escape' even though useModalLayer already handles Escape via the
layer stack; update the handleKeyDown function (inside the useEffect) to only
handle 'Enter' (remove the e.key === 'Escape' branch), keep e.preventDefault()
and handleCloseRef.current() for Enter, and leave useModalLayer(...) and its
Escape handling untouched so Escape is only handled by useModalLayer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d320c11-8b0a-41cc-bd10-9e0217839265

📥 Commits

Reviewing files that changed from the base of the PR and between 7e23db7 and 4af7a74.

📒 Files selected for processing (58)
  • src/__tests__/renderer/components/CueModal.test.tsx
  • src/__tests__/renderer/components/DocumentGraph/DocumentGraphView.test.tsx
  • src/__tests__/renderer/components/ExecutionQueueBrowser.test.tsx
  • src/__tests__/renderer/components/LeaderboardRegistrationModal.test.tsx
  • src/__tests__/renderer/components/NewInstanceModal.test.tsx
  • src/__tests__/renderer/components/SettingsModal.test.tsx
  • src/__tests__/renderer/components/UsageDashboard/responsive-layout.test.tsx
  • src/__tests__/renderer/components/UsageDashboard/state-transition-animations.test.tsx
  • src/__tests__/renderer/components/UsageDashboardModal.test.tsx
  • src/main/group-chat/group-chat-router.ts
  • src/main/group-chat/spawnGroupChatAgent.ts
  • src/renderer/components/AgentCreationDialog.tsx
  • src/renderer/components/AgentPromptComposerModal.tsx
  • src/renderer/components/AgentSessionsBrowser.tsx
  • src/renderer/components/AgentSessionsModal.tsx
  • src/renderer/components/AutoRun/AutoRunExpandedModal.tsx
  • src/renderer/components/AutoRun/AutoRunLightbox.tsx
  • src/renderer/components/AutoRun/AutoRunSearchBar.tsx
  • src/renderer/components/BatchRunnerModal.tsx
  • src/renderer/components/CreatePRModal.tsx
  • src/renderer/components/CreateWorktreeModal.tsx
  • src/renderer/components/CueModal/CueModal.tsx
  • src/renderer/components/DirectorNotes/DirectorNotesModal.tsx
  • src/renderer/components/DocumentGraph/DocumentGraphView.tsx
  • src/renderer/components/DocumentsPanel.tsx
  • src/renderer/components/ExecutionQueueBrowser.tsx
  • src/renderer/components/FileSearchModal.tsx
  • src/renderer/components/FirstRunCelebration.tsx
  • src/renderer/components/GitDiffViewer.tsx
  • src/renderer/components/GitLogViewer.tsx
  • src/renderer/components/HistoryDetailModal.tsx
  • src/renderer/components/InlineWizard/WizardExitConfirmDialog.tsx
  • src/renderer/components/KeyboardMasteryCelebration.tsx
  • src/renderer/components/LeaderboardRegistrationModal.tsx
  • src/renderer/components/LogViewer.tsx
  • src/renderer/components/MarketplaceModal.tsx
  • src/renderer/components/MergeProgressModal.tsx
  • src/renderer/components/MergeSessionModal.tsx
  • src/renderer/components/PlaygroundPanel.tsx
  • src/renderer/components/ProcessMonitor.tsx
  • src/renderer/components/PromptComposerModal.tsx
  • src/renderer/components/QuickActionsModal.tsx
  • src/renderer/components/QuitConfirmModal.tsx
  • src/renderer/components/SendToAgentModal.tsx
  • src/renderer/components/Settings/SettingsModal.tsx
  • src/renderer/components/StandingOvationOverlay.tsx
  • src/renderer/components/SummarizeProgressModal.tsx
  • src/renderer/components/SymphonyModal.tsx
  • src/renderer/components/TabSwitcherModal.tsx
  • src/renderer/components/TransferProgressModal.tsx
  • src/renderer/components/UsageDashboard/UsageDashboardModal.tsx
  • src/renderer/components/Wizard/ExistingAutoRunDocsModal.tsx
  • src/renderer/components/Wizard/ExistingDocsModal.tsx
  • src/renderer/components/Wizard/MaestroWizard.tsx
  • src/renderer/components/Wizard/WizardExitConfirmModal.tsx
  • src/renderer/components/Wizard/WizardResumeModal.tsx
  • src/renderer/components/Wizard/tour/TourOverlay.tsx
  • src/renderer/hooks/ui/useModalLayer.ts

Comment thread src/main/group-chat/spawnGroupChatAgent.ts
Comment thread src/renderer/components/AutoRun/AutoRunLightbox.tsx
Comment thread src/renderer/components/HistoryDetailModal.tsx
Comment thread src/renderer/components/SymphonyModal.tsx
Comment thread src/renderer/components/Wizard/ExistingDocsModal.tsx
Comment thread src/renderer/components/Wizard/tour/TourOverlay.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/renderer/components/AutoRun/AutoRunLightbox.tsx (1)

58-63: Consider reusing one derived image URL to avoid divergence.

lightboxImageUrl is computed at Line 59-61, then similar logic is recomputed at Line 200-202. Reusing the same value for the render guard and <img src> reduces drift risk.

♻️ Proposed refactor
-		// Don't render if no image is selected
-		const imageUrl =
-			lightboxExternalUrl ||
-			(lightboxFilename ? attachmentPreviews.get(lightboxFilename) : undefined);
-		if (!lightboxFilename || !imageUrl) {
+		// Don't render if no image is selected
+		if (!isVisible || !lightboxImageUrl) {
 			return null;
 		}
@@
-				<img
-					src={imageUrl}
+				<img
+					src={lightboxImageUrl}
 					alt={lightboxFilename}
 					className="max-w-[90%] max-h-[90%] rounded shadow-2xl"
 					onClick={(e) => e.stopPropagation()}
 				/>

Also applies to: 200-205

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

In `@src/renderer/components/AutoRun/AutoRunLightbox.tsx` around lines 58 - 63,
The code computes lightboxImageUrl in the top block but recomputes the same
logic later; consolidate by deriving lightboxImageUrl once (using
lightboxExternalUrl || (lightboxFilename ?
attachmentPreviews.get(lightboxFilename) : undefined)) and reuse that single
variable for both the visibility guard (isVisible) and the <img> src so the
render guard and image source cannot diverge; update uses of isVisible,
lightboxImageUrl, and any later inline recomputation to reference the single
derived variable instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/group-chat/spawnGroupChatAgent.ts`:
- Around line 105-133: The Windows spawn config is currently determined from
sshRemoteConfig even when wrapSpawnWithSsh fell back to local execution; change
the gate to use the actual wrapSpawnWithSsh result (sshWrapped.sshRemoteUsed)
instead of just sshRemoteConfig. Specifically, after calling
wrapSpawnWithSsh(...), use the sshWrapped.sshRemoteUsed truthiness to decide
what to pass into getWindowsSpawnConfig (e.g. getWindowsSpawnConfig(agentId,
sshWrapped.sshRemoteUsed ? sshRemoteConfig : undefined)), so Windows shell/stdin
behavior is enabled for true local execution and skipped only when an SSH remote
is actually used.

---

Nitpick comments:
In `@src/renderer/components/AutoRun/AutoRunLightbox.tsx`:
- Around line 58-63: The code computes lightboxImageUrl in the top block but
recomputes the same logic later; consolidate by deriving lightboxImageUrl once
(using lightboxExternalUrl || (lightboxFilename ?
attachmentPreviews.get(lightboxFilename) : undefined)) and reuse that single
variable for both the visibility guard (isVisible) and the <img> src so the
render guard and image source cannot diverge; update uses of isVisible,
lightboxImageUrl, and any later inline recomputation to reference the single
derived variable instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c8dced7-0d00-4389-b288-c2b5f2b00d53

📥 Commits

Reviewing files that changed from the base of the PR and between c92f274 and aa7fcec.

📒 Files selected for processing (6)
  • src/main/group-chat/spawnGroupChatAgent.ts
  • src/renderer/components/AutoRun/AutoRunLightbox.tsx
  • src/renderer/components/HistoryDetailModal.tsx
  • src/renderer/components/SymphonyModal.tsx
  • src/renderer/components/Wizard/ExistingDocsModal.tsx
  • src/renderer/components/Wizard/tour/TourOverlay.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/components/Wizard/ExistingDocsModal.tsx
  • src/renderer/components/HistoryDetailModal.tsx
  • src/renderer/components/SymphonyModal.tsx

Comment on lines +105 to +133
const sshWrapped = await wrapSpawnWithSsh(
{
command: baseCommand,
args,
cwd,
prompt,
customEnvVars,
promptArgs: agent.promptArgs,
noPromptSeparator: agent.noPromptSeparator,
agentBinaryName: agent.binaryName,
},
sshRemoteConfig,
sshStore
);
spawnCommand = sshWrapped.command;
spawnArgs = sshWrapped.args;
spawnCwd = sshWrapped.cwd;
spawnPrompt = sshWrapped.prompt;
spawnEnvVars = sshWrapped.customEnvVars;
spawnSshStdinScript = sshWrapped.sshStdinScript;
if (sshWrapped.sshRemoteUsed && debugLabel) {
console.log(
`[GroupChat:Debug] SSH remote used for ${debugLabel}: ${sshWrapped.sshRemoteUsed.name}`
);
}
}

// Get Windows-specific spawn config (shell, stdin mode) - skipped for SSH
const winConfig = getWindowsSpawnConfig(agentId, sshRemoteConfig ?? undefined);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use actual SSH usage result (not only config) to gate Windows spawn behavior.

At Line 133, getWindowsSpawnConfig(...) is keyed off sshRemoteConfig.enabled, but wrapSpawnWithSsh(...) can still fall back to local execution (sshRemoteUsed: null). In that fallback case, Windows shell/stdin config stays disabled even though execution is local.

Proposed fix
 let spawnSshStdinScript: string | undefined;
+let sshRemoteUsed = false;

 // Apply SSH wrapping if configured
 if (sshRemoteConfig?.enabled && !sshStore) {
 	throw new Error(`SSH remote is enabled but sshStore is not available for ${debugLabel ?? sessionId}`);
 }
 if (sshStore && sshRemoteConfig?.enabled) {
@@
 	spawnEnvVars = sshWrapped.customEnvVars;
 	spawnSshStdinScript = sshWrapped.sshStdinScript;
+	sshRemoteUsed = !!sshWrapped.sshRemoteUsed;
 	if (sshWrapped.sshRemoteUsed && debugLabel) {
@@
-const winConfig = getWindowsSpawnConfig(agentId, sshRemoteConfig ?? undefined);
+const winConfig = getWindowsSpawnConfig(
+	agentId,
+	sshRemoteUsed ? (sshRemoteConfig ?? undefined) : undefined
+);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/spawnGroupChatAgent.ts` around lines 105 - 133, The
Windows spawn config is currently determined from sshRemoteConfig even when
wrapSpawnWithSsh fell back to local execution; change the gate to use the actual
wrapSpawnWithSsh result (sshWrapped.sshRemoteUsed) instead of just
sshRemoteConfig. Specifically, after calling wrapSpawnWithSsh(...), use the
sshWrapped.sshRemoteUsed truthiness to decide what to pass into
getWindowsSpawnConfig (e.g. getWindowsSpawnConfig(agentId,
sshWrapped.sshRemoteUsed ? sshRemoteConfig : undefined)), so Windows shell/stdin
behavior is enabled for true local execution and skipped only when an SSH remote
is actually used.

Copy link
Copy Markdown

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

♻️ Duplicate comments (1)
src/main/group-chat/spawnGroupChatAgent.ts (1)

134-135: ⚠️ Potential issue | 🟠 Major

Gate Windows spawn config by actual SSH usage, not SSH enablement flag.

At Line 135, getWindowsSpawnConfig(...) is keyed off sshRemoteConfig, but wrapSpawnWithSsh(...) can fall back to local (sshRemoteUsed: null). That leaves local fallback executions without required Windows shell/stdin behavior.

Proposed fix
 	let spawnPrompt: string | undefined = prompt;
 	let spawnEnvVars = customEnvVars;
 	let spawnSshStdinScript: string | undefined;
+	let sshRemoteUsed = false;
@@
 		spawnPrompt = sshWrapped.prompt;
 		spawnEnvVars = sshWrapped.customEnvVars;
 		spawnSshStdinScript = sshWrapped.sshStdinScript;
+		sshRemoteUsed = !!sshWrapped.sshRemoteUsed;
 		if (sshWrapped.sshRemoteUsed && debugLabel) {
@@
-	const winConfig = getWindowsSpawnConfig(agentId, sshRemoteConfig ?? undefined);
+	const winConfig = getWindowsSpawnConfig(
+		agentId,
+		sshRemoteUsed ? (sshRemoteConfig ?? undefined) : undefined
+	);
As per coding guidelines, "When implementing features that spawn agent processes ... support SSH remote execution by checking for `sshRemoteConfig` and using `wrapSpawnWithSsh()`..."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/spawnGroupChatAgent.ts` around lines 134 - 135, The code
calls getWindowsSpawnConfig(agentId, sshRemoteConfig ?? undefined) based only on
sshRemoteConfig, but wrapSpawnWithSsh(...) can decide sshRemoteUsed is null
(local fallback); change the logic to gate Windows-specific config on the actual
sshRemoteUsed returned by wrapSpawnWithSsh (or obtain sshRemoteUsed first) so
you only apply getWindowsSpawnConfig when sshRemoteUsed is non-null; update
references to getWindowsSpawnConfig, wrapSpawnWithSsh, sshRemoteConfig, and
sshRemoteUsed in spawnGroupChatAgent accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/group-chat/spawnGroupChatAgent.ts`:
- Around line 134-135: The code calls getWindowsSpawnConfig(agentId,
sshRemoteConfig ?? undefined) based only on sshRemoteConfig, but
wrapSpawnWithSsh(...) can decide sshRemoteUsed is null (local fallback); change
the logic to gate Windows-specific config on the actual sshRemoteUsed returned
by wrapSpawnWithSsh (or obtain sshRemoteUsed first) so you only apply
getWindowsSpawnConfig when sshRemoteUsed is non-null; update references to
getWindowsSpawnConfig, wrapSpawnWithSsh, sshRemoteConfig, and sshRemoteUsed in
spawnGroupChatAgent accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 761dcc3e-3eb5-4467-9c08-372626ad2906

📥 Commits

Reviewing files that changed from the base of the PR and between aa7fcec and 35f0c63.

📒 Files selected for processing (1)
  • src/main/group-chat/spawnGroupChatAgent.ts

@jSydorowicz21 jSydorowicz21 force-pushed the dedup/phase-10-modal-spawn-consolidation branch from 662362e to 28cbb0b Compare April 16, 2026 14:51
jSydorowicz21 and others added 5 commits April 16, 2026 09:57
Sub-phase 10A: Modal layer migration
- Extend `useModalLayer` hook with optional `enabled` flag for conditional
  registration; switch the underlying registration to a stable ref-based
  escape closure so the layer no longer needs to be re-registered or have
  its handler manually re-pushed when the parent's onEscape callback
  identity changes.
- Migrate 47 modal call sites across 46 files from manual
  `useLayerStack().registerLayer/unregisterLayer/updateLayerHandler`
  boilerplate to `useModalLayer(priority, ariaLabel, onEscape, options?)`.
- Update 9 tests whose `useLayerStack` mocks needed `updateLayerHandler`
  (the migrated hook always uses it). Update one assertion in
  `NewInstanceModal.test.tsx` to verify behavior (escape routes to latest
  onClose) rather than implementation detail (literal handler identity).

Sub-phase 10B: Group chat spawn helper
- Extract `spawnGroupChatAgent()` helper at
  `src/main/group-chat/spawnGroupChatAgent.ts` that encapsulates SSH
  wrapping + Windows shell config + processManager.spawn into a single
  call.
- Replace 4 near-identical inline spawn sequences in
  `group-chat-router.ts` (moderator, participant, synthesis, recovery)
  with calls to the helper. Each site collapses ~60 lines of imperative
  spawn/wrap/Windows code to a single configuration object.

Sites skipped (10A):
- 9 `type: 'overlay'` / `allowClickOutside` registrations in
  DocumentGraphView, FileExplorerPanel, FilePreview, LightboxModal,
  TerminalOutput, TerminalSearchBar (the hook hardcodes `type: 'modal'`).
- These keep their existing `useLayerStack()` calls.

Net change: 57 files, +445 / -1554 (~1109 lines removed).
- Fail fast when SSH enabled but sshStore unavailable (spawnGroupChatAgent)
- Remove duplicate Escape handler from TourOverlay window listener
- Route Escape to delete confirmation before closing HistoryDetailModal
- Only enable AutoRunLightbox layer when preview URL is available
- Let Escape propagate through ExistingDocsModal to layer stack
- Stop Escape propagation in SymphonyModal search to prevent modal close
Wraps long error message line in spawnGroupChatAgent.ts to satisfy
prettier's print-width rule. CI lint-and-format was failing on this.
Retriggers CI after flaky prettier failure on identical content.
@jSydorowicz21 jSydorowicz21 force-pushed the dedup/phase-10-modal-spawn-consolidation branch from 28cbb0b to 954a9f4 Compare April 16, 2026 14:58
@reachrazamair reachrazamair merged commit 98c4ced into RunMaestro:rc Apr 16, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 29, 2026
6 tasks
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