Skip to content

fix(#85): Ctrl+R session refresh via IPC with scroll-to-bottom#89

Merged
matt1398 merged 1 commit intomatt1398:mainfrom
Psypeal:fix/ctrl-r-session-refresh
Mar 4, 2026
Merged

fix(#85): Ctrl+R session refresh via IPC with scroll-to-bottom#89
matt1398 merged 1 commit intomatt1398:mainfrom
Psypeal:fix/ctrl-r-session-refresh

Conversation

@Psypeal
Copy link
Copy Markdown
Contributor

@Psypeal Psypeal commented Feb 26, 2026

Summary

  • Route Ctrl+R through IPC (main → preload → renderer) since event.preventDefault() in before-input-event blocks both Chromium reload and keydown propagation
  • Switch all refresh paths (IPC listener, refresh button, keyboard shortcut fallback) to refreshSessionInPlace to avoid unmounting the scroll container
  • Dispatch session-refresh-scroll-bottom custom event to smoothly scroll to bottom after refresh

Rebased onto latest main (after #87 revert) from a dedicated feature branch.

Test plan

  • Press Ctrl+R (or Cmd+R on macOS) — session refreshes and scrolls to bottom without flicker
  • Click the refresh button in the tab bar — same behavior
  • Ctrl+Shift+R is blocked (no hard reload)
  • Non-Electron (browser) mode: no errors from missing onSessionRefresh

Fixes #85

Summary by CodeRabbit

  • New Features
    • Implemented a session refresh shortcut (Ctrl/Cmd+R) that updates your active session in-place without requiring a full page reload. The chat automatically scrolls to the bottom when refresh completes.
    • Hard reload functionality (Ctrl/Cmd+Shift+R) has been disabled to prevent unintended interruptions.

Electron's before-input-event preventDefault blocks both Chromium's built-in reload AND keydown propagation to the renderer. Route Ctrl+R through IPC instead: main process intercepts and sends session:refresh, preload bridges to renderer, store listener refreshes in place.

Switch all refresh paths (IPC, refresh button, keyboard shortcut) to refreshSessionInPlace to avoid unmounting the scroll container, then dispatch a custom event to smoothly scroll to the bottom.

Fixes matt1398#85
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @Psypeal, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where the Ctrl+R shortcut for refreshing a session was not functioning as intended due to conflicts with Electron's default behavior. It introduces a robust IPC mechanism to correctly capture and process the refresh command, standardizes the refresh logic across all entry points, and enhances the user experience by automatically scrolling the chat view to the bottom after a refresh. This ensures consistent and smooth session refreshing without visual disruptions.

Highlights

  • IPC-based Ctrl+R Refresh: Implemented a new Inter-Process Communication (IPC) route for Ctrl+R/Cmd+R to trigger a session refresh, bypassing Chromium's default reload behavior and ensuring the keydown event is properly handled by the renderer process.
  • Standardized Session Refresh Logic: Unified all session refresh triggers (IPC, refresh button, keyboard shortcut) to use the refreshSessionInPlace function, which prevents the scroll container from unmounting and improves user experience.
  • Scroll-to-Bottom on Refresh: Added functionality to smoothly scroll the chat history to the bottom after any session refresh, ensuring the latest content is immediately visible.
Changelog
  • src/main/index.ts
    • Intercepted Ctrl+R/Cmd+R keydown events to prevent Chromium's default page reload and sent a session:refresh IPC message to the renderer process.
    • Blocked Ctrl+Shift+R/Cmd+Shift+R to prevent hard reloads.
  • src/preload/constants/ipcChannels.ts
    • Added a new SESSION_REFRESH constant for the IPC channel used for session refresh events.
  • src/preload/index.ts
    • Imported the SESSION_REFRESH constant.
    • Exposed an onSessionRefresh method via the electronAPI to allow the renderer process to listen for session refresh events from the main process.
  • src/renderer/api/httpClient.ts
    • Provided a no-operation (no-op) implementation for onSessionRefresh in non-Electron (browser) environments to prevent errors.
  • src/renderer/components/chat/ChatHistory.tsx
    • Added a useEffect hook to listen for the custom session-refresh-scroll-bottom event and trigger a smooth scroll to the bottom of the chat history.
  • src/renderer/components/layout/TabBar.tsx
    • Updated the handleRefresh function to use refreshSessionInPlace instead of fetchSessionDetail.
    • Dispatched a session-refresh-scroll-bottom custom event after a refresh initiated by the refresh button.
  • src/renderer/hooks/useKeyboardShortcuts.ts
    • Modified the Ctrl+R/Cmd+R keyboard shortcut handler to use refreshSessionInPlace instead of fetchSessionDetail.
    • Dispatched a session-refresh-scroll-bottom custom event after a refresh initiated by the keyboard shortcut.
  • src/renderer/store/index.ts
    • Implemented an IPC listener for the session:refresh event to trigger refreshSessionInPlace and dispatch the session-refresh-scroll-bottom event when a refresh is initiated from the main process (e.g., via Ctrl+R).
  • src/shared/types/api.ts
    • Added the onSessionRefresh method to the ElectronAPI interface definition.
Activity
  • The pull request was rebased onto the latest main branch after a previous revert.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai coderabbitai bot added the bug Something isn't working label Feb 26, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This PR implements keyboard-driven session refresh for both Electron and browser environments by intercepting Ctrl/Cmd+R in the main process, using IPC to communicate with the renderer, and dispatching a custom event to scroll the chat to the bottom after refresh.

Changes

Cohort / File(s) Summary
Main Process IPC Bridge
src/main/index.ts, src/preload/constants/ipcChannels.ts
Differentiate Ctrl/Cmd+R (regular reload) from Ctrl/Cmd+Shift+R (hard reload) and send IPC message session:refresh to renderer instead of allowing default browser reload behavior.
Preload API
src/preload/index.ts, src/shared/types/api.ts
Expose onSessionRefresh method in ElectronAPI interface to allow renderer process to listen for session refresh events via IPC.
Renderer API & State Management
src/renderer/api/httpClient.ts, src/renderer/store/index.ts
Add no-op onSessionRefresh stub for browser mode and register listener in store to handle refresh from main process by calling refreshSessionInPlace and dispatching custom scroll event.
Renderer Components & Hooks
src/renderer/components/chat/ChatHistory.tsx, src/renderer/components/layout/TabBar.tsx, src/renderer/hooks/useKeyboardShortcuts.ts
Replace fetchSessionDetail with refreshSessionInPlace for session refresh operations and add listener for session-refresh-scroll-bottom custom event to auto-scroll chat to bottom.

Possibly related PRs

Suggested labels

bug

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed All coding requirements from issue #85 are addressed: Ctrl+R/Cmd+R refresh via IPC, scroll-to-bottom, Ctrl+Shift+R blocking, browser mode support.
Out of Scope Changes check ✅ Passed All changes directly support the PR objectives of enabling Ctrl+R refresh via IPC with scroll-to-bottom behavior. No extraneous modifications detected.

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


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

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

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/hooks/useKeyboardShortcuts.ts (1)

260-270: ⚠️ Potential issue | 🟡 Minor

Add error handling for the session refresh operation.

Similar to the store listener, this Promise.all lacks error handling. If either operation fails, the error will be silently swallowed and the scroll event won't fire.

🛡️ Proposed fix to add error handling
       // Cmd+R: Refresh current session and sidebar session list
       if (event.key === 'r') {
         event.preventDefault();
         if (selectedProjectId && selectedSessionId) {
           void Promise.all([
             refreshSessionInPlace(selectedProjectId, selectedSessionId),
             fetchSessions(selectedProjectId),
-          ]).then(() => {
-            window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
-          });
+          ])
+            .then(() => {
+              window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
+            })
+            .catch((error) => {
+              console.error('Failed to refresh session:', error);
+            });
         }
         return;
       }

As per coding guidelines: "Implement error handling with try/catch in main process and console.error for debugging."

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

In `@src/renderer/hooks/useKeyboardShortcuts.ts` around lines 260 - 270, The
Promise.all call that runs refreshSessionInPlace(selectedProjectId,
selectedSessionId) and fetchSessions(selectedProjectId) needs error handling so
failures aren't swallowed; wrap the async work in a try/catch (or append a
.catch) that logs the error with console.error and prevents silent failures, and
only dispatch the 'session-refresh-scroll-bottom' CustomEvent on success. Locate
the block using refreshSessionInPlace, fetchSessions and window.dispatchEvent
and add the try/catch (or Promise.catch) to log errors and avoid firing the
scroll event when an error occurs.
🧹 Nitpick comments (1)
src/main/index.ts (1)

492-496: Consider using a shared constant for the IPC channel name.

The channel name 'session:refresh' is hardcoded here while SESSION_REFRESH is defined in src/preload/constants/ipcChannels.ts. I see the comment at lines 46-51 explains this avoids a boundary violation between main and preload. However, if the constant value changes in the future, this hardcoded string could diverge.

Consider either:

  1. Moving shared IPC channel constants to @shared/constants (accessible to both main and preload)
  2. Documenting the coupling in both locations

This is a minor maintainability concern since the current approach is functionally correct.

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

In `@src/main/index.ts` around lines 492 - 496, The hardcoded IPC channel string
in mainWindow.webContents.send('session:refresh') should be replaced with a
shared constant to avoid divergence; either move SESSION_REFRESH from
src/preload/constants/ipcChannels.ts into a module accessible to both main and
preload (e.g., `@shared/constants/ipcChannels`) or create a small shared constant
export and import it into src/main/index.ts, then change the send call to use
that constant (SESSION_REFRESH) so both sender (main) and receiver (preload)
reference the same identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/components/layout/TabBar.tsx`:
- Around line 215-223: The handleRefresh function currently awaits
refreshSessionInPlace and fetchSessions with no error handling; wrap the async
work in a try/catch inside handleRefresh, call await Promise.all(...) in the try
block, and in the catch set a component-level error state (e.g., using useState
like [refreshError, setRefreshError]) or call an existing error handler so
failures in refreshSessionInPlace and fetchSessions produce visible renderer
error state; also ensure the catch logs the error and does not suppress the
window.dispatchEvent call when appropriate.

In `@src/renderer/store/index.ts`:
- Around line 271-289: Wrap the session refresh Promise.all invocation in
explicit error handling so rejections are not swallowed: when handling the
api.onSessionRefresh callback (inside the block using useStore.getState and
activeTab), replace the bare void Promise.all([...]).then(...) with an
async/try-catch (or add a .catch) around state.refreshSessionInPlace and
state.fetchSessions so any error is caught; log the error with console.error
including contextual info (e.g., projectId/sessionId and the error) and still
avoid leaving unhandled rejections—ensure
window.dispatchEvent('session-refresh-scroll-bottom') remains in the success
path only.

---

Outside diff comments:
In `@src/renderer/hooks/useKeyboardShortcuts.ts`:
- Around line 260-270: The Promise.all call that runs
refreshSessionInPlace(selectedProjectId, selectedSessionId) and
fetchSessions(selectedProjectId) needs error handling so failures aren't
swallowed; wrap the async work in a try/catch (or append a .catch) that logs the
error with console.error and prevents silent failures, and only dispatch the
'session-refresh-scroll-bottom' CustomEvent on success. Locate the block using
refreshSessionInPlace, fetchSessions and window.dispatchEvent and add the
try/catch (or Promise.catch) to log errors and avoid firing the scroll event
when an error occurs.

---

Nitpick comments:
In `@src/main/index.ts`:
- Around line 492-496: The hardcoded IPC channel string in
mainWindow.webContents.send('session:refresh') should be replaced with a shared
constant to avoid divergence; either move SESSION_REFRESH from
src/preload/constants/ipcChannels.ts into a module accessible to both main and
preload (e.g., `@shared/constants/ipcChannels`) or create a small shared constant
export and import it into src/main/index.ts, then change the send call to use
that constant (SESSION_REFRESH) so both sender (main) and receiver (preload)
reference the same identifier.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d86dab7 and f972d22.

📒 Files selected for processing (9)
  • src/main/index.ts
  • src/preload/constants/ipcChannels.ts
  • src/preload/index.ts
  • src/renderer/api/httpClient.ts
  • src/renderer/components/chat/ChatHistory.tsx
  • src/renderer/components/layout/TabBar.tsx
  • src/renderer/hooks/useKeyboardShortcuts.ts
  • src/renderer/store/index.ts
  • src/shared/types/api.ts

Comment on lines 215 to 223
const handleRefresh = async (): Promise<void> => {
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
await Promise.all([
fetchSessionDetail(activeTab.projectId, activeTab.sessionId, activeTabId ?? undefined),
refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
fetchSessions(activeTab.projectId),
]);
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
}
};
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 | 🟡 Minor

Add error handling for the async refresh operations.

The handleRefresh function lacks error handling. If refreshSessionInPlace or fetchSessions fails, the error propagates silently with no user feedback. As per coding guidelines, renderer components should use error state for error handling.

🛡️ Proposed fix to add error handling
 const handleRefresh = async (): Promise<void> => {
   if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
-    await Promise.all([
-      refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
-      fetchSessions(activeTab.projectId),
-    ]);
-    window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
+    try {
+      await Promise.all([
+        refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
+        fetchSessions(activeTab.projectId),
+      ]);
+      window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
+    } catch (error) {
+      console.error('Failed to refresh session:', error);
+    }
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleRefresh = async (): Promise<void> => {
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
await Promise.all([
fetchSessionDetail(activeTab.projectId, activeTab.sessionId, activeTabId ?? undefined),
refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
fetchSessions(activeTab.projectId),
]);
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
}
};
const handleRefresh = async (): Promise<void> => {
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
try {
await Promise.all([
refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
fetchSessions(activeTab.projectId),
]);
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
} catch (error) {
console.error('Failed to refresh session:', error);
}
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/layout/TabBar.tsx` around lines 215 - 223, The
handleRefresh function currently awaits refreshSessionInPlace and fetchSessions
with no error handling; wrap the async work in a try/catch inside handleRefresh,
call await Promise.all(...) in the try block, and in the catch set a
component-level error state (e.g., using useState like [refreshError,
setRefreshError]) or call an existing error handler so failures in
refreshSessionInPlace and fetchSessions produce visible renderer error state;
also ensure the catch logs the error and does not suppress the
window.dispatchEvent call when appropriate.

Comment on lines +271 to +289
// Listen for Ctrl+R / Cmd+R session refresh from main process (fixes #85)
if (api.onSessionRefresh) {
const cleanup = api.onSessionRefresh(() => {
const state = useStore.getState();
const activeTabId = state.activeTabId;
const activeTab = activeTabId ? state.openTabs.find((t) => t.id === activeTabId) : null;
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
void Promise.all([
state.refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
state.fetchSessions(activeTab.projectId),
]).then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
});
}
});
if (typeof cleanup === 'function') {
cleanupFns.push(cleanup);
}
}
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 | 🟡 Minor

Add error handling for the session refresh operation.

The Promise.all call lacks error handling. If either refreshSessionInPlace or fetchSessions rejects, the error will be silently swallowed (unhandled promise rejection), and the scroll event won't be dispatched.

🛡️ Proposed fix to add error handling
   if (api.onSessionRefresh) {
     const cleanup = api.onSessionRefresh(() => {
       const state = useStore.getState();
       const activeTabId = state.activeTabId;
       const activeTab = activeTabId ? state.openTabs.find((t) => t.id === activeTabId) : null;
       if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
         void Promise.all([
           state.refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
           state.fetchSessions(activeTab.projectId),
-        ]).then(() => {
-          window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
-        });
+        ])
+          .then(() => {
+            window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
+          })
+          .catch((error) => {
+            console.error('Failed to refresh session:', error);
+          });
       }
     });

As per coding guidelines: "Implement error handling with try/catch in main process and console.error for debugging."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Listen for Ctrl+R / Cmd+R session refresh from main process (fixes #85)
if (api.onSessionRefresh) {
const cleanup = api.onSessionRefresh(() => {
const state = useStore.getState();
const activeTabId = state.activeTabId;
const activeTab = activeTabId ? state.openTabs.find((t) => t.id === activeTabId) : null;
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
void Promise.all([
state.refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
state.fetchSessions(activeTab.projectId),
]).then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
});
}
});
if (typeof cleanup === 'function') {
cleanupFns.push(cleanup);
}
}
// Listen for Ctrl+R / Cmd+R session refresh from main process (fixes `#85`)
if (api.onSessionRefresh) {
const cleanup = api.onSessionRefresh(() => {
const state = useStore.getState();
const activeTabId = state.activeTabId;
const activeTab = activeTabId ? state.openTabs.find((t) => t.id === activeTabId) : null;
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
void Promise.all([
state.refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
state.fetchSessions(activeTab.projectId),
])
.then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
})
.catch((error) => {
console.error('Failed to refresh session:', error);
});
}
});
if (typeof cleanup === 'function') {
cleanupFns.push(cleanup);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/store/index.ts` around lines 271 - 289, Wrap the session refresh
Promise.all invocation in explicit error handling so rejections are not
swallowed: when handling the api.onSessionRefresh callback (inside the block
using useStore.getState and activeTab), replace the bare void
Promise.all([...]).then(...) with an async/try-catch (or add a .catch) around
state.refreshSessionInPlace and state.fetchSessions so any error is caught; log
the error with console.error including contextual info (e.g.,
projectId/sessionId and the error) and still avoid leaving unhandled
rejections—ensure window.dispatchEvent('session-refresh-scroll-bottom') remains
in the success path only.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly implements session refresh via IPC to handle Ctrl+R, which is a solid improvement. The changes are consistent across the main, preload, and renderer processes, and the use of a custom event to trigger scrolling is a good approach. My review includes a few suggestions to enhance maintainability by reducing code duplication and extracting magic strings into constants.

Comment on lines +492 to 501
if ((input.control || input.meta) && !input.shift && input.key.toLowerCase() === 'r') {
event.preventDefault();
mainWindow.webContents.send('session:refresh');
return;
}
// Also block Ctrl+Shift+R (hard reload)
if ((input.control || input.meta) && input.shift && input.key.toLowerCase() === 'r') {
event.preventDefault();
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The if conditions for handling Ctrl+R (with and without Shift) are very similar and can be combined to reduce code duplication and improve readability. You can use a single if to check for Ctrl+R and then an inner if to handle the case without the Shift key.

    if ((input.control || input.meta) && input.key.toLowerCase() === 'r') {
      event.preventDefault();
      if (!input.shift) {
        mainWindow.webContents.send('session:refresh');
      }
      return;
    }

Comment on lines +385 to +386
window.addEventListener('session-refresh-scroll-bottom', handler);
return () => window.removeEventListener('session-refresh-scroll-bottom', handler);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The event name session-refresh-scroll-bottom is used as a "magic string" in multiple files (ChatHistory.tsx, TabBar.tsx, useKeyboardShortcuts.ts, store/index.ts). To improve maintainability and prevent potential typos, it would be better to define this as a constant in a shared file (e.g., src/renderer/constants/events.ts) and import it where needed.

Comment on lines 215 to 223
const handleRefresh = async (): Promise<void> => {
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
await Promise.all([
fetchSessionDetail(activeTab.projectId, activeTab.sessionId, activeTabId ?? undefined),
refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
fetchSessions(activeTab.projectId),
]);
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This session refresh logic is duplicated in a few places. Please see my comment on src/renderer/store/index.ts for a suggestion to centralize it in a store action.

Also, the event name session-refresh-scroll-bottom is a magic string. Please see my comment on src/renderer/components/chat/ChatHistory.tsx for a suggestion to define it as a constant.

Comment on lines 262 to 269
if (selectedProjectId && selectedSessionId) {
void Promise.all([
fetchSessionDetail(selectedProjectId, selectedSessionId),
refreshSessionInPlace(selectedProjectId, selectedSessionId),
fetchSessions(selectedProjectId),
]);
]).then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This session refresh logic is duplicated in a few places. Please see my comment on src/renderer/store/index.ts for a suggestion to centralize it in a store action.

Also, the event name session-refresh-scroll-bottom is a magic string. Please see my comment on src/renderer/components/chat/ChatHistory.tsx for a suggestion to define it as a constant.

Comment on lines +272 to +289
if (api.onSessionRefresh) {
const cleanup = api.onSessionRefresh(() => {
const state = useStore.getState();
const activeTabId = state.activeTabId;
const activeTab = activeTabId ? state.openTabs.find((t) => t.id === activeTabId) : null;
if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) {
void Promise.all([
state.refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
state.fetchSessions(activeTab.projectId),
]).then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
});
}
});
if (typeof cleanup === 'function') {
cleanupFns.push(cleanup);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic for refreshing a session and dispatching the scroll event is duplicated in store/index.ts, TabBar.tsx, and useKeyboardShortcuts.ts. To improve maintainability and ensure consistency, this logic could be extracted into a single action within your Zustand store.

For example, you could create an action like refreshActiveSessionAndScroll in one of your store slices:

// In a relevant store slice
refreshActiveSessionAndScroll: async () => {
  const { selectedProjectId, selectedSessionId, refreshSessionInPlace, fetchSessions } = get();
  if (selectedProjectId && selectedSessionId) {
    await Promise.all([
      refreshSessionInPlace(selectedProjectId, selectedSessionId),
      fetchSessions(selectedProjectId),
    ]);
    window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom')); // Consider using a constant for the event name
  }
}

Then, all three locations could simply call useStore.getState().refreshActiveSessionAndScroll(). This would centralize the refresh logic.

@matt1398 matt1398 merged commit 0f58214 into matt1398:main Mar 4, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] CTRL+R does nothing on Windows

2 participants