Skip to content

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

Closed
Psypeal wants to merge 8 commits intomatt1398:mainfrom
Psypeal:main
Closed

fix(#85): Ctrl+R session refresh via IPC with scroll-to-bottom#86
Psypeal wants to merge 8 commits intomatt1398:mainfrom
Psypeal:main

Conversation

@Psypeal
Copy link
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

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

Release Notes

  • New Features
    • Session refresh now triggered via keyboard shortcut (Ctrl+R/Cmd+R)
    • Chat automatically scrolls to bottom when session refreshes
    • Improved code syntax highlighting with per-line formatting
    • Extended language support for syntax highlighting: R, Ruby, PHP, and SQL

Psypeal and others added 5 commits February 21, 2026 04:51
# Conflicts:
#	src/renderer/components/chat/ContextBadge.tsx
#	src/renderer/utils/contextTracker.ts
# Conflicts:
#	src/renderer/components/chat/ContextBadge.tsx
sessionAnalyzer now reads pre-computed costUsd from calculateMetrics()
instead of re-computing costs independently. Parent cost uses
detail.metrics.costUsd, subagent cost uses proc.metrics.costUsd.
This ensures the cost analysis panel and chat header always agree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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

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 significantly enhances the user experience for session refreshing within the Electron application. It addresses issues with keyboard shortcuts by routing refresh commands through IPC, ensuring that the application's custom refresh logic is always triggered. Additionally, it unifies refresh paths and introduces an automatic scroll-to-bottom feature, making session refreshes smoother and more predictable. A refactor of the session cost calculation also improves data consistency.

Highlights

  • IPC for Ctrl+R/Cmd+R: Implemented Inter-Process Communication (IPC) to handle Ctrl+R/Cmd+R keyboard shortcuts, preventing Chromium's default reload and allowing the renderer process to refresh the session.
  • Unified Session Refresh: Consolidated all session refresh mechanisms (IPC, refresh button, keyboard shortcut) to use refreshSessionInPlace, ensuring consistent behavior and avoiding UI unmounting.
  • Scroll-to-Bottom on Refresh: Added functionality to automatically scroll the chat history to the bottom after a session refresh, improving user experience.
  • Refactored Cost Calculation: Standardized session cost calculation to use detail.metrics.costUsd as the single source of truth, simplifying logic and improving accuracy.
Changelog
  • src/main/index.ts
    • Intercepted Ctrl+R/Cmd+R to prevent default Chromium reload
    • Sent session:refresh IPC message to the renderer process
    • Blocked Ctrl+Shift+R for hard reload
  • src/preload/constants/ipcChannels.ts
    • Added SESSION_REFRESH constant for the new IPC channel
  • src/preload/index.ts
    • Exposed onSessionRefresh to the renderer process, allowing it to listen for refresh events from the main process
  • src/renderer/api/httpClient.ts
    • Provided a no-op implementation for onSessionRefresh in browser mode, as Ctrl+R refresh is Electron-specific
  • src/renderer/components/chat/ChatHistory.tsx
    • Added an effect to listen for session-refresh-scroll-bottom custom events and trigger a smooth scroll to the bottom
  • src/renderer/components/layout/TabBar.tsx
    • Updated the refresh button handler to use refreshSessionInPlace and dispatch the session-refresh-scroll-bottom event
    • Replaced fetchSessionDetail with refreshSessionInPlace in the useStore selector
  • src/renderer/hooks/useKeyboardShortcuts.ts
    • Modified the keyboard shortcut handler for Ctrl+R/Cmd+R to use refreshSessionInPlace and dispatch the session-refresh-scroll-bottom event
    • Replaced fetchSessionDetail with refreshSessionInPlace in the useStore selector
  • src/renderer/store/index.ts
    • Implemented an IPC listener for session:refresh events, triggering refreshSessionInPlace and the scroll-to-bottom event
  • src/renderer/utils/sessionAnalyzer.ts
    • Updated parentCost to directly use detail.metrics.costUsd
    • Removed manual parentCost accumulation within message processing
    • Changed computedCost for subagents to directly use proc.metrics.costUsd
  • src/shared/types/api.ts
    • Added onSessionRefresh to the ElectronAPI interface
  • test/renderer/utils/sessionAnalyzer.test.ts
    • Updated analyzeSession tests to reflect the new unified cost computation logic, asserting parentCostUsd and totalSessionCostUsd directly from detail.metrics.costUsd
    • Added new test cases for unified cost computation, covering parent and subagent costs
Activity
  • The pull request includes a detailed test plan in its description, outlining steps to verify the new Ctrl+R/Cmd+R behavior, refresh button functionality, and the blocking of Ctrl+Shift+R. No other specific activity like comments or reviews are provided in the context.
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.

Copy link

@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 successfully implements the Ctrl+R session refresh feature by routing the shortcut through IPC to the renderer. It also unifies refresh paths to use refreshSessionInPlace and adds a smooth scroll-to-bottom after refresh. The changes are well-implemented and include necessary updates to the preload script, renderer API, and various components. I've included a couple of suggestions to improve maintainability by refactoring some logic. Additionally, the unrelated but beneficial refactoring of cost calculation in sessionAnalyzer.ts to use a single source of truth is a good improvement.

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;
}

Choose a reason for hiding this comment

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

medium

The logic for handling Ctrl+R and Ctrl+Shift+R can be combined to be more concise and reduce duplication of the condition check. This would improve readability and maintainability.

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

Comment on lines +278 to +283
void Promise.all([
state.refreshSessionInPlace(activeTab.projectId, activeTab.sessionId),
state.fetchSessions(activeTab.projectId),
]).then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
});

Choose a reason for hiding this comment

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

medium

This logic for refreshing a session and then dispatching a scroll event is duplicated in a few places. It's also present in src/renderer/components/layout/TabBar.tsx and src/renderer/hooks/useKeyboardShortcuts.ts. To improve maintainability and reduce duplication, consider extracting this logic into a new action in one of your Zustand slices (e.g., sessionSlice or sessionDetailSlice). For example, you could create a refreshActiveSessionAndScroll() action.

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

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

The pull request implements an IPC-driven session refresh flow for Ctrl+R / Cmd+R hotkeys. The main process intercepts the hotkey, prevents Chromium's default reload behavior, and sends a session:refresh IPC message to the renderer. The renderer listens for this event and refreshes the active session. Additionally, syntax highlighting for code blocks is enhanced with per-line highlighting support for multiple languages including R, Ruby, PHP, and SQL.

Changes

Cohort / File(s) Summary
IPC Session Refresh Infrastructure
src/main/index.ts, src/preload/constants/ipcChannels.ts, src/preload/index.ts, src/renderer/api/httpClient.ts, src/shared/types/api.ts
Main process intercepts Ctrl+R / Cmd+R and sends session:refresh IPC message. New IPC channel constant SESSION_REFRESH defined. Preload exposes onSessionRefresh API method; browser mode fallback includes no-op implementation. ElectronAPI type extended with onSessionRefresh callback.
Renderer Session Refresh Handling
src/renderer/components/layout/TabBar.tsx, src/renderer/hooks/useKeyboardShortcuts.ts, src/renderer/store/index.ts, src/renderer/components/chat/ChatHistory.tsx
TabBar and useKeyboardShortcuts now use refreshSessionInPlace instead of fetchSessionDetail. Store listens to onSessionRefresh and refreshes active session and sessions list. ChatHistory listens to session-refresh-scroll-bottom custom event and scrolls to bottom. Cmd+R flow now dispatches scroll event after refresh completes.
Syntax Highlighting Enhancements
src/renderer/components/chat/viewers/MarkdownViewer.tsx, src/renderer/components/chat/viewers/syntaxHighlighter.ts
MarkdownViewer applies per-line syntax highlighting to code blocks. syntaxHighlighter expanded with language keyword sets for R, Ruby, PHP, SQL; added comment detection for multiple languages; introduced case-insensitive SQL keyword matching.

Possibly related PRs

  • PR #68: Also modifies before-input-event Ctrl/Cmd+R handling in src/main/index.ts for reload prevention and IPC messaging logic.
  • PR #66: Adds initial Ctrl/Cmd+R page reload prevention in src/main/index.ts before-input-event handler, forming the foundation for this session refresh IPC flow.
🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes are mostly within scope, but PR includes additional enhancements beyond the core Ctrl+R fix: syntax highlighting per-line rendering [syntaxHighlighter.ts, MarkdownViewer.tsx] and markdown viewer improvements that are not directly related to the issue #85 objectives. Consider separating syntax highlighting changes (syntaxHighlighter.ts, MarkdownViewer.tsx) into a separate PR to keep the scope focused on the Ctrl+R/session refresh fix specified in issue #85.
✅ Passed checks (1 passed)
Check name Status Explanation
Linked Issues check ✅ Passed All primary objectives from issue #85 are implemented: Ctrl+R/Cmd+R is restored via IPC interception [main/index.ts], keyboard-triggered refresh mirrors button functionality using refreshSessionInPlace [useKeyboardShortcuts.ts, TabBar.tsx], and scroll flicker is eliminated by using in-place session refresh instead of full unmounting [store/index.ts].

✏️ 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

@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

🤖 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/hooks/useKeyboardShortcuts.ts`:
- Around line 263-268: The Promise.all call that runs
refreshSessionInPlace(selectedProjectId, selectedSessionId) and
fetchSessions(selectedProjectId) must have a rejection handler to avoid
unhandled promise rejections; update the Promise.all(...).then(...) chain (the
block that dispatches the 'session-refresh-scroll-bottom' CustomEvent) to append
a .catch(...) that handles/logs errors (e.g., using console.error or your app
logger) and ensures any necessary cleanup, so failures in refreshSessionInPlace
or fetchSessions are handled explicitly.

In `@test/renderer/utils/sessionAnalyzer.test.ts`:
- Line 1560: Replace exact equality assertions for computed decimal totals with
approximate equality checks: change
expect(report.costAnalysis.subagentCostUsd).toBe(0.25) and the similar assertion
for totalSessionCostUsd to use toBeCloseTo, e.g.
expect(report.costAnalysis.subagentCostUsd).toBeCloseTo(0.25, 2) and
expect(report.costAnalysis.totalSessionCostUsd).toBeCloseTo(<expected>, 2), so
floating-point rounding won't cause flaky failures; update the test in
sessionAnalyzer.test.ts where these properties are asserted.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3c4fa6 and 09fb97f.

📒 Files selected for processing (11)
  • 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/renderer/utils/sessionAnalyzer.ts
  • src/shared/types/api.ts
  • test/renderer/utils/sessionAnalyzer.test.ts

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

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/renderer/hooks/useKeyboardShortcuts.ts | sed -n '250,280p'

Repository: matt1398/claude-devtools

Length of output: 1207


🏁 Script executed:

# Get context about the imports and Zustand store usage
head -50 src/renderer/hooks/useKeyboardShortcuts.ts

Repository: matt1398/claude-devtools

Length of output: 1370


🏁 Script executed:

# Check if there's error handling in the Zustand store being used
rg -A 5 -B 5 "useSessionStore\|useProjectStore" src/renderer/hooks/useKeyboardShortcuts.ts | head -100

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Find the store file
find src -name "store*" -type f | head -20

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Check the actual store implementation to understand error handling
head -100 src/renderer/store/index.ts

Repository: matt1398/claude-devtools

Length of output: 4342


🏁 Script executed:

# Look for how other parts of the renderer handle errors with the store
rg "catch\|setError\|error" src/renderer/store --type ts --type tsx -A 2 -B 2 | head -100

Repository: matt1398/claude-devtools

Length of output: 95


🏁 Script executed:

# Search for error handling patterns in the codebase, checking for notification or error state
rg "error|Error|catch" src/renderer/store/slices --type ts -A 2 -B 2 | head -150

Repository: matt1398/claude-devtools

Length of output: 9676


🏁 Script executed:

# Look at the sessionSlice to see how refreshSessionInPlace is implemented
cat src/renderer/store/slices/sessionSlice.ts | head -200

Repository: matt1398/claude-devtools

Length of output: 7181


🏁 Script executed:

# Check if there's a notificationSlice and how it handles errors
cat src/renderer/store/slices/notificationSlice.ts | head -100

Repository: matt1398/claude-devtools

Length of output: 3415


🏁 Script executed:

# Find refreshSessionInPlace implementation in the store
rg -A 20 "refreshSessionInPlace" src/renderer/store/slices/sessionSlice.ts | head -60

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Also check sessionDetailSlice since it might have the implementation
rg -A 20 "refreshSessionInPlace" src/renderer/store/slices/sessionDetailSlice.ts | head -60

Repository: matt1398/claude-devtools

Length of output: 2156


Add rejection handler to prevent unhandled promise rejections.

The Promise.all(...).then(...) chain at line 263 lacks a .catch() handler. While the underlying functions (refreshSessionInPlace and fetchSessions) have internal error handling, the promise chain itself can still surface as an unhandled rejection without explicit rejection handling.

🔧 Suggested fix
         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 via keyboard shortcut', 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
void Promise.all([
fetchSessionDetail(selectedProjectId, selectedSessionId),
refreshSessionInPlace(selectedProjectId, selectedSessionId),
fetchSessions(selectedProjectId),
]);
]).then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
});
void Promise.all([
refreshSessionInPlace(selectedProjectId, selectedSessionId),
fetchSessions(selectedProjectId),
])
.then(() => {
window.dispatchEvent(new CustomEvent('session-refresh-scroll-bottom'));
})
.catch((error) => {
console.error('Failed to refresh session via keyboard shortcut', error);
});
🤖 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 263 - 268, The
Promise.all call that runs refreshSessionInPlace(selectedProjectId,
selectedSessionId) and fetchSessions(selectedProjectId) must have a rejection
handler to avoid unhandled promise rejections; update the
Promise.all(...).then(...) chain (the block that dispatches the
'session-refresh-scroll-bottom' CustomEvent) to append a .catch(...) that
handles/logs errors (e.g., using console.error or your app logger) and ensures
any necessary cleanup, so failures in refreshSessionInPlace or fetchSessions are
handled explicitly.

const report = analyzeSession(createMockDetail({ processes }));
expect(report.subagentMetrics.byAgent[0].costUsd).toBe(0.15);
expect(report.subagentMetrics.byAgent[1].costUsd).toBe(0.10);
expect(report.costAnalysis.subagentCostUsd).toBe(0.25);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its size
wc -l test/renderer/utils/sessionAnalyzer.test.ts

Repository: matt1398/claude-devtools

Length of output: 116


🏁 Script executed:

# Read the specific lines mentioned in the review comment (around lines 1560 and 1587)
sed -n '1550,1600p' test/renderer/utils/sessionAnalyzer.test.ts | cat -n

Repository: matt1398/claude-devtools

Length of output: 1732


Use toBeCloseTo for computed decimal totals.

Assertions for subagentCostUsd and totalSessionCostUsd are computed sums and should use toBeCloseTo() instead of toBe() to be resilient to floating-point precision issues.

🔧 Suggested fix
-      expect(report.costAnalysis.subagentCostUsd).toBe(0.25);
+      expect(report.costAnalysis.subagentCostUsd).toBeCloseTo(0.25, 8);
@@
-      expect(report.costAnalysis.totalSessionCostUsd).toBe(0.80);
+      expect(report.costAnalysis.totalSessionCostUsd).toBeCloseTo(0.80, 8);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/renderer/utils/sessionAnalyzer.test.ts` at line 1560, Replace exact
equality assertions for computed decimal totals with approximate equality
checks: change expect(report.costAnalysis.subagentCostUsd).toBe(0.25) and the
similar assertion for totalSessionCostUsd to use toBeCloseTo, e.g.
expect(report.costAnalysis.subagentCostUsd).toBeCloseTo(0.25, 2) and
expect(report.costAnalysis.totalSessionCostUsd).toBeCloseTo(<expected>, 2), so
floating-point rounding won't cause flaky failures; update the test in
sessionAnalyzer.test.ts where these properties are asserted.

Add keyword sets and comment detection for four new languages in the token-based syntax highlighter. Wire highlightLine into MarkdownViewer so fenced code blocks in assistant text also receive coloring. - R: keywords, # comments - Ruby: keywords, # comments - PHP: keywords, # comments - SQL: keywords: (case-insensitive), -- comments
# Conflicts:
#	src/renderer/utils/sessionAnalyzer.ts
#	test/renderer/utils/sessionAnalyzer.test.ts
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.

🧹 Nitpick comments (3)
src/renderer/components/chat/viewers/syntaxHighlighter.ts (1)

236-279: block_given? will never match due to word regex pattern.

The word match regex on line 535 (/^([a-zA-Z_$][a-zA-Z0-9_$]*)/) does not include ?, so block_given? will be tokenized as block_given (identifier) followed by ? (operator). Consider removing it from the keyword set or adjusting the regex to handle Ruby's method naming conventions.

Option 1: Remove the unmatchable keyword
     'yield',
-    'block_given?',
     'require',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/chat/viewers/syntaxHighlighter.ts` around lines 236 -
279, The keyword "block_given?" in the ruby Set will never match because the
identifier regex used in the tokenizer (/^([a-zA-Z_$][a-zA-Z0-9_$]*)/) does not
allow a trailing '?'—either remove "block_given?" from the ruby keywords Set or
update the tokenizer's identifier regex (used where the word match is performed)
to accept Ruby method name suffixes like '?' (e.g., allow an optional trailing
'?' in the pattern) so "block_given?" is recognized as a single keyword/token;
update references to the ruby Set and the tokenizer function that uses the
word-regex (the identifier matching logic) accordingly.
src/renderer/components/chat/viewers/MarkdownViewer.tsx (1)

158-171: Consider handling non-string children gracefully.

If children is already a React element (not a string), raw will be empty and lines will contain a single empty string, resulting in no visible code. This could happen if react-markdown pre-processes the content in certain edge cases.

Proposed defensive fallback
       if (isBlock) {
         const lang = codeClassName?.replace('language-', '') ?? '';
         const raw = typeof children === 'string' ? children : '';
+        // Fallback to original children if not a string
+        if (!raw && children) {
+          return (
+            <code className="font-mono text-xs" style={{ color: COLOR_TEXT }}>
+              {children}
+            </code>
+          );
+        }
         const text = raw.replace(/\n$/, '');
         const lines = text.split('\n');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/chat/viewers/MarkdownViewer.tsx` around lines 158 -
171, The current MarkdownViewer code assumes children is a string and turns
non-string children into an empty code block; update the render to handle
non-string children by short-circuiting: if typeof children !== 'string' (or
React.isValidElement(children)), return the <code className="font-mono text-xs"
style={{ color: COLOR_TEXT }}>{children}</code> directly instead of running it
through raw/lines/hl/highlightLine; otherwise keep the existing logic that
computes raw/text/lines and maps lines with hl(highlightLine(...)). This
preserves existing highlighting for string input while gracefully rendering
pre-processed React nodes.
src/renderer/components/layout/TabBar.tsx (1)

215-223: Add error handling for graceful degradation.

The async handleRefresh function lacks try/catch. If either refreshSessionInPlace or fetchSessions rejects, the error is silently swallowed (since the onClick handler doesn't await). Users receive no feedback on failure.

♻️ Proposed fix to add error handling
   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);
+      }
     }
   };

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/components/layout/TabBar.tsx` around lines 215 - 223, The
handleRefresh function should guard the Promise.all call with a try/catch so
failures in refreshSessionInPlace or fetchSessions are handled: wrap the
existing await Promise.all([...]) and the subsequent window.dispatchEvent call
in a try block and in the catch log the error via console.error (and optionally
trigger any UI fallback/error event), ensuring handleRefresh still returns
gracefully when an error occurs; update references to handleRefresh,
refreshSessionInPlace, fetchSessions, and the session-refresh-scroll-bottom
dispatch accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/renderer/components/chat/viewers/MarkdownViewer.tsx`:
- Around line 158-171: The current MarkdownViewer code assumes children is a
string and turns non-string children into an empty code block; update the render
to handle non-string children by short-circuiting: if typeof children !==
'string' (or React.isValidElement(children)), return the <code
className="font-mono text-xs" style={{ color: COLOR_TEXT }}>{children}</code>
directly instead of running it through raw/lines/hl/highlightLine; otherwise
keep the existing logic that computes raw/text/lines and maps lines with
hl(highlightLine(...)). This preserves existing highlighting for string input
while gracefully rendering pre-processed React nodes.

In `@src/renderer/components/chat/viewers/syntaxHighlighter.ts`:
- Around line 236-279: The keyword "block_given?" in the ruby Set will never
match because the identifier regex used in the tokenizer
(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/) does not allow a trailing '?'—either remove
"block_given?" from the ruby keywords Set or update the tokenizer's identifier
regex (used where the word match is performed) to accept Ruby method name
suffixes like '?' (e.g., allow an optional trailing '?' in the pattern) so
"block_given?" is recognized as a single keyword/token; update references to the
ruby Set and the tokenizer function that uses the word-regex (the identifier
matching logic) accordingly.

In `@src/renderer/components/layout/TabBar.tsx`:
- Around line 215-223: The handleRefresh function should guard the Promise.all
call with a try/catch so failures in refreshSessionInPlace or fetchSessions are
handled: wrap the existing await Promise.all([...]) and the subsequent
window.dispatchEvent call in a try block and in the catch log the error via
console.error (and optionally trigger any UI fallback/error event), ensuring
handleRefresh still returns gracefully when an error occurs; update references
to handleRefresh, refreshSessionInPlace, fetchSessions, and the
session-refresh-scroll-bottom dispatch accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09fb97f and c951a19.

📒 Files selected for processing (5)
  • src/renderer/components/chat/ChatHistory.tsx
  • src/renderer/components/chat/viewers/MarkdownViewer.tsx
  • src/renderer/components/chat/viewers/syntaxHighlighter.ts
  • src/renderer/components/layout/TabBar.tsx
  • src/shared/types/api.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/shared/types/api.ts
  • src/renderer/components/chat/ChatHistory.tsx

@Psypeal
Copy link
Contributor Author

Psypeal commented Feb 26, 2026

Superseded by #89 — rebased onto a dedicated feature branch to resolve conflicts.

@Psypeal Psypeal closed this Feb 26, 2026
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

1 participant