Skip to content

fix: hide empty tool input card and remove user prompt copy button#165

Open
psypeal wants to merge 9 commits intomatt1398:mainfrom
psypeal:fix/ui-cleanup
Open

fix: hide empty tool input card and remove user prompt copy button#165
psypeal wants to merge 9 commits intomatt1398:mainfrom
psypeal:fix/ui-cleanup

Conversation

@psypeal
Copy link
Copy Markdown
Contributor

@psypeal psypeal commented Apr 5, 2026

Summary

  • Hide empty "Input" card for tools with no arguments (e.g. EnterPlanMode renders blank Input section)
  • Remove copy button from user prompt blocks — not useful for user messages
  • Remove unused Rust pipeline badge components from MiddlePanel

Test plan

  • pnpm typecheck passes
  • pnpm test — all tests pass
  • Manual: open session with EnterPlanMode — no blank Input card
  • Manual: user prompts no longer show copy button
  • Manual: MiddlePanel renders without Rust badge

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added enhanced session refresh via Ctrl+R/Cmd+R keyboard shortcut with improved behavior distinction
    • Introduced Bash tool viewer with collapsible output sections
    • Added markdown file preview mode with code/preview toggle
    • Expanded syntax highlighting support for additional languages
  • Improvements

    • Chat now automatically scrolls to latest messages on session refresh
    • Enhanced tool output display with status indicators
  • Removed

    • Removed copy button from user chat messages

psypeal and others added 9 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
# Conflicts:
#	src/renderer/utils/sessionAnalyzer.ts
#	test/renderer/utils/sessionAnalyzer.test.ts
…hlighting

- Add BashToolViewer with syntax-highlighted command input via CodeBlockViewer
- Add CollapsibleOutputSection (collapsed by default) for tool output
- Apply collapsible output to DefaultToolViewer and BashToolViewer
- Add bash/zsh/fish keyword sets for syntax highlighting
- Add keyword sets for c, cpp, java, kotlin, swift, lua, html, yaml
- Fix markdown/html false type-coloring on capitalized words
- Add markdown preview toggle to ReadToolViewer (defaults to preview)
- Default WriteToolViewer to preview mode for markdown files
- Extend comment detection for zsh, fish, yaml (#) and lua (--)
- Skip rendering Input section for tools with no arguments (e.g. EnterPlanMode)
- Remove copy button from user prompt blocks
- Remove unused Rust pipeline badge components from MiddlePanel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai coderabbitai bot added the bug Something isn't working label Apr 5, 2026
Copy link
Copy Markdown
Contributor

@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 implements a custom session refresh mechanism that intercepts Ctrl+R/Cmd+R to update session state via IPC, avoiding full page reloads. It introduces a BashToolViewer, a reusable CollapsibleOutputSection, and significantly expands syntax highlighting support for multiple languages including Bash, C, Java, and Swift. Markdown files in tool viewers now feature a preview mode. Review feedback highlights a potential regression in Markdown highlighting logic and suggests a safety check for null/undefined tool inputs.


// If no highlighting support, return plain text as single-element array
if (keywords.size === 0 && !['json', 'css', 'html', 'bash', 'markdown'].includes(language)) {
if (keywords.size === 0 && !['json', 'css', 'bash'].includes(language)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The removal of markdown from the exclusion list causes the function to return early for Markdown content, as markdown is not present in the KEYWORDS map. This prevents any generic syntax highlighting (such as for strings or comments) from being applied to Markdown. If this was not intentional, markdown should be added back to the list of languages that bypass the early return.

}

export const DefaultToolViewer: React.FC<DefaultToolViewerProps> = ({ linkedTool, status }) => {
const hasInput = Object.keys(linkedTool.input).length > 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Accessing Object.keys(linkedTool.input) directly is risky if linkedTool.input could be null or undefined. Given that other parts of the codebase (like hasBashContent) use optional chaining for input, it's safer to verify its existence before calling Object.keys to avoid potential runtime errors.

Suggested change
const hasInput = Object.keys(linkedTool.input).length > 0;
const hasInput = linkedTool.input && Object.keys(linkedTool.input).length > 0;

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

The PR implements session refresh triggered by Ctrl+R keyboard shortcut via IPC communication, updates tool viewers with collapsible output sections and markdown previews, adds Bash tool support, removes CopyButton from user chat, and extends syntax highlighting for multiple languages.

Changes

Cohort / File(s) Summary
IPC and API layer for session refresh
src/preload/constants/ipcChannels.ts, src/preload/index.ts, src/renderer/api/httpClient.ts, src/shared/types/api.ts
Added SESSION_REFRESH IPC channel constant and onSessionRefresh API method to subscribe to session refresh events; includes no-op implementation in browser mode (HttpAPIClient).
Main process keyboard handling
src/main/index.ts
Updated Ctrl+R / Cmd+R handling to distinguish between standard refresh (without Shift) and hard reload (with Shift); calls preventDefault() and sends session:refresh IPC for standard refresh only.
Renderer store and refresh handlers
src/renderer/store/index.ts, src/renderer/hooks/useKeyboardShortcuts.ts, src/renderer/components/layout/TabBar.tsx
Added IPC listener for session refresh events; updated refresh action handlers to use refreshSessionInPlace instead of fetchSessionDetail and dispatch session-refresh-scroll-bottom event on completion.
Chat components
src/renderer/components/chat/ChatHistory.tsx, src/renderer/components/chat/UserChatGroup.tsx
Added window event listener for session-refresh-scroll-bottom to trigger smooth scroll-to-bottom; removed CopyButton integration from user chat bubble.
Linked tool viewer refactoring
src/renderer/components/chat/items/LinkedToolItem.tsx, src/renderer/components/chat/items/linkedTool/{BashToolViewer,CollapsibleOutputSection,DefaultToolViewer,ReadToolViewer,WriteToolViewer}.tsx, src/renderer/components/chat/items/linkedTool/index.ts
Added BashToolViewer and CollapsibleOutputSection components; refactored output rendering to use collapsible sections with status indicators; added markdown preview toggle for ReadToolViewer and WriteToolViewer; updated LinkedToolItem to detect and route Bash tools.
Syntax highlighting extensions
src/renderer/components/chat/viewers/syntaxHighlighter.ts
Expanded keyword sets for bash, c, java, kotlin, swift, lua, html, yaml; added keyword reuse for zsh, fish, cpp, hpp; extended comment detection for multiple languages including yaml, lua, and sql.
Tool rendering utilities
src/renderer/utils/toolRendering/toolContentChecks.ts, src/renderer/utils/toolRendering/index.ts
Added hasBashContent helper function to detect Bash tool items with command input; exported new helper in barrel exports.

Possibly related PRs

Suggested labels

bug

✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/ui-cleanup

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.

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

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/chat/items/LinkedToolItem.tsx (1)

149-175: ⚠️ Potential issue | 🔴 Critical

variant prop cannot be passed to BaseItem and import of StepVariant is broken.

Line 44 imports StepVariant from a non-existent file: @renderer/constants/stepVariants does not exist. Additionally, line 175 passes variant={toolVariant} to BaseItem, but the component's BaseItemProps interface (lines 15-44) does not declare a variant prop. Running pnpm typecheck will fail on both the missing import and the undeclared prop. Either remove the variant logic entirely or create src/renderer/constants/stepVariants.ts to export StepVariant type and wire it through BaseItemProps.

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

In `@src/renderer/components/chat/items/LinkedToolItem.tsx` around lines 149 -
175, The code currently imports StepVariant and uses variant={toolVariant} but
StepVariant is missing and BaseItemProps doesn't accept variant; either remove
the variant logic or add the type and prop: create
src/renderer/constants/stepVariants.ts exporting export type StepVariant =
'tool' | 'tool-error' (or the actual variants you need), update the import in
LinkedToolItem to point to that file, add variant?: StepVariant to the
BaseItemProps interface and update the BaseItem component to accept and apply
the variant (className/visual handling) so passing variant={toolVariant} is
type-safe; alternatively, if you prefer removal, delete the StepVariant import,
the toolVariant constant, and the variant={toolVariant} prop from the BaseItem
usage in LinkedToolItem.
🧹 Nitpick comments (3)
src/renderer/hooks/useKeyboardShortcuts.ts (1)

259-271: Verify: This handler is reachable only in browser mode.

In Electron mode, the main process intercepts Cmd+R via before-input-event and calls preventDefault() before the keydown reaches the renderer (see src/main/index.ts:492-496). The IPC-based refresh path handles it instead.

However, in browser mode (using HttpAPIClient), there's no main process to intercept the shortcut, so this handler IS needed. The implementation correctly mirrors the IPC handler's behavior.

Consider adding a brief comment noting this is the browser-mode fallback:

       // Cmd+R: Refresh current session and sidebar session list
+      // Note: In Electron mode, main process intercepts this via IPC (session:refresh).
+      // This handler is the fallback for browser mode.
       if (event.key === 'r') {
🤖 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 259 - 271, Add a
brief clarifying comment above the Cmd+R keyboard handler (the block checking
event.key === 'r' in useKeyboardShortcuts.ts) that this branch is the
browser-mode fallback for refresh (used when running with HttpAPIClient) because
in Electron the main process intercepts Cmd+R via before-input-event and the
IPC-based refresh path is used; mention the functions involved
(refreshSessionInPlace, fetchSessions) and the dispatched CustomEvent
'session-refresh-scroll-bottom' so future readers understand why this handler
exists.
src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx (1)

22-27: Keep the empty-input predicate in toolContentChecks.ts.

hasInput is another tool-content validation rule, but it now lives inside the viewer. Moving it next to the other has*Content helpers keeps the "should this section render?" logic centralized instead of drifting across viewers.

Based on learnings: Applies to src/renderer/utils/toolRendering/toolContentChecks.ts : Implement tool content validation in utils/toolRendering/toolContentChecks.ts

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

In `@src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx` around
lines 22 - 27, The viewer currently computes hasInput locally; move this
predicate into the centralized helper module and consume it from there: add and
export a function (e.g. hasInput or hasToolInput) in
utils/toolRendering/toolContentChecks.ts that accepts the linkedTool (or its
input object) and returns whether Object.keys(input).length > 0, then replace
the local hasInput in DefaultToolViewer with an import call to that helper so
all has*Content rules live in toolContentChecks.ts (update any imports/usages
accordingly).
src/renderer/components/chat/items/linkedTool/CollapsibleOutputSection.tsx (1)

30-35: Expose expand/collapse state to assistive tech.

Add aria-expanded (and optionally aria-controls) on the toggle button so screen readers can announce the current state.

Suggested patch
 export const CollapsibleOutputSection: React.FC<CollapsibleOutputSectionProps> = ({
   status,
   children,
   label = 'Output',
 }) => {
   const [isExpanded, setIsExpanded] = useState(false);
+  const contentId = 'tool-output-section';
 
   return (
     <div>
       <button
         type="button"
+        aria-expanded={isExpanded}
+        aria-controls={contentId}
         className="mb-1 flex items-center gap-2 text-xs"
         style={{ color: 'var(--tool-item-muted)', background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}
         onClick={() => setIsExpanded((prev) => !prev)}
       >
@@
       {isExpanded && (
         <div
+          id={contentId}
           className="max-h-96 overflow-auto rounded p-3 font-mono text-xs"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/chat/items/linkedTool/CollapsibleOutputSection.tsx`
around lines 30 - 35, The toggle button in CollapsibleOutputSection doesn't
expose its state to assistive tech; update the button element that calls
setIsExpanded to include aria-expanded={isExpanded} and add an aria-controls
attribute pointing to the id of the collapsible content (create or reuse a
unique id on the content container) so screen readers can announce
expand/collapse state and relationship between the button and the content.
🤖 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/chat/items/linkedTool/BashToolViewer.tsx`:
- Around line 12-17: The import ordering in BashToolViewer.tsx is wrong: move
the path-alias import "import type { LinkedToolItem } from
'@renderer/types/groups';" above the relative imports (e.g., above "import {
type ItemStatus } from '../BaseItem';", "import { CollapsibleOutputSection }
from './CollapsibleOutputSection';", and "import { renderOutput } from
'./renderHelpers';") so imports follow the project rule (external packages, then
path aliases, then relative imports); update the file to place the
LinkedToolItem import in the correct group.
- Around line 25-42: BashToolViewer currently casts
linkedTool.input.command/description unsafely and always renders
CodeBlockViewer; add a runtime type guard isBashInput(input: unknown): input is
{ command: string; description?: string } that verifies command is a non-empty
string and description (if present) is a string, then use this guard in
BashToolViewer to only render CodeBlockViewer when isBashInput(linkedTool.input)
is true; compute fileName from the validated description (truncate to 60 chars)
or fall back to "bash" and remove the unsafe `as string`/`as string | undefined`
casts.

In `@src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx`:
- Around line 61-89: The Code/Preview toggle uses color-only selection; update
the two buttons in ReadToolViewer (the elements that call setViewMode and read
viewMode) to include aria-pressed={viewMode === 'code'} for the "Code" button
and aria-pressed={viewMode === 'preview'} for the "Preview" button so assistive
tech can detect the active state, and apply the identical change in
WriteToolViewer for its corresponding toggle buttons to keep behavior
consistent.

In `@src/renderer/components/chat/viewers/syntaxHighlighter.ts`:
- Around line 924-929: The current comment-detection if-block (checking language
=== 'bash'|'zsh' etc. and remaining.startsWith('#')) incorrectly treats every
'#' as a comment start; update the condition in the if-block that checks
remaining and language so that for shell languages (bash/zsh/fish) you only
treat '#' as a comment when it is a real comment boundary (e.g., at line start
or preceded by whitespace) and not when it is inside a parameter expansion;
implement this by detecting whether the current '#' is within an open parameter
expansion (scan backwards from the current position in remaining for an
unmatched '${' without a closing '}' and skip comment handling if found) and/or
require the character before '#' to be whitespace or start-of-line for shell
languages, leaving the existing behavior unchanged for non-shell languages;
adjust the if-block that references remaining and the language list accordingly.

---

Outside diff comments:
In `@src/renderer/components/chat/items/LinkedToolItem.tsx`:
- Around line 149-175: The code currently imports StepVariant and uses
variant={toolVariant} but StepVariant is missing and BaseItemProps doesn't
accept variant; either remove the variant logic or add the type and prop: create
src/renderer/constants/stepVariants.ts exporting export type StepVariant =
'tool' | 'tool-error' (or the actual variants you need), update the import in
LinkedToolItem to point to that file, add variant?: StepVariant to the
BaseItemProps interface and update the BaseItem component to accept and apply
the variant (className/visual handling) so passing variant={toolVariant} is
type-safe; alternatively, if you prefer removal, delete the StepVariant import,
the toolVariant constant, and the variant={toolVariant} prop from the BaseItem
usage in LinkedToolItem.

---

Nitpick comments:
In `@src/renderer/components/chat/items/linkedTool/CollapsibleOutputSection.tsx`:
- Around line 30-35: The toggle button in CollapsibleOutputSection doesn't
expose its state to assistive tech; update the button element that calls
setIsExpanded to include aria-expanded={isExpanded} and add an aria-controls
attribute pointing to the id of the collapsible content (create or reuse a
unique id on the content container) so screen readers can announce
expand/collapse state and relationship between the button and the content.

In `@src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx`:
- Around line 22-27: The viewer currently computes hasInput locally; move this
predicate into the centralized helper module and consume it from there: add and
export a function (e.g. hasInput or hasToolInput) in
utils/toolRendering/toolContentChecks.ts that accepts the linkedTool (or its
input object) and returns whether Object.keys(input).length > 0, then replace
the local hasInput in DefaultToolViewer with an import call to that helper so
all has*Content rules live in toolContentChecks.ts (update any imports/usages
accordingly).

In `@src/renderer/hooks/useKeyboardShortcuts.ts`:
- Around line 259-271: Add a brief clarifying comment above the Cmd+R keyboard
handler (the block checking event.key === 'r' in useKeyboardShortcuts.ts) that
this branch is the browser-mode fallback for refresh (used when running with
HttpAPIClient) because in Electron the main process intercepts Cmd+R via
before-input-event and the IPC-based refresh path is used; mention the functions
involved (refreshSessionInPlace, fetchSessions) and the dispatched CustomEvent
'session-refresh-scroll-bottom' so future readers understand why this handler
exists.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51f6643a-558b-40ef-9590-20799334213c

📥 Commits

Reviewing files that changed from the base of the PR and between c0708bb and c3cb448.

📒 Files selected for processing (20)
  • 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/chat/UserChatGroup.tsx
  • src/renderer/components/chat/items/LinkedToolItem.tsx
  • src/renderer/components/chat/items/linkedTool/BashToolViewer.tsx
  • src/renderer/components/chat/items/linkedTool/CollapsibleOutputSection.tsx
  • src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx
  • src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx
  • src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx
  • src/renderer/components/chat/items/linkedTool/index.ts
  • src/renderer/components/chat/viewers/syntaxHighlighter.ts
  • src/renderer/components/layout/TabBar.tsx
  • src/renderer/hooks/useKeyboardShortcuts.ts
  • src/renderer/store/index.ts
  • src/renderer/utils/toolRendering/index.ts
  • src/renderer/utils/toolRendering/toolContentChecks.ts
  • src/shared/types/api.ts
💤 Files with no reviewable changes (1)
  • src/renderer/components/chat/UserChatGroup.tsx

Comment on lines +12 to +17
import { type ItemStatus } from '../BaseItem';

import { CollapsibleOutputSection } from './CollapsibleOutputSection';
import { renderOutput } from './renderHelpers';

import type { LinkedToolItem } from '@renderer/types/groups';
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

Reorder imports to match the project’s import grouping rule.

Line 17 imports a path alias after relative imports. Move import type { LinkedToolItem } from '@renderer/types/groups'; above the ../ and ./ imports.

As per coding guidelines, **/*.{ts,tsx} requires import order: external packages, path aliases, then relative imports.

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

In `@src/renderer/components/chat/items/linkedTool/BashToolViewer.tsx` around
lines 12 - 17, The import ordering in BashToolViewer.tsx is wrong: move the
path-alias import "import type { LinkedToolItem } from
'@renderer/types/groups';" above the relative imports (e.g., above "import {
type ItemStatus } from '../BaseItem';", "import { CollapsibleOutputSection }
from './CollapsibleOutputSection';", and "import { renderOutput } from
'./renderHelpers';") so imports follow the project rule (external packages, then
path aliases, then relative imports); update the file to place the
LinkedToolItem import in the correct group.

Comment on lines +25 to +42
const command = linkedTool.input.command as string;
const description = linkedTool.input.description as string | undefined;

// Use the description (truncated) as the file name label, or fallback to "bash"
const fileName = description
? description.length > 60
? description.slice(0, 57) + '...'
: description
: 'bash';

return (
<>
{/* Input Section — Syntax-highlighted command */}
<CodeBlockViewer
fileName={fileName}
content={command}
language="bash"
/>
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

🧩 Analysis chain

🏁 Script executed:

fd -t f "BashToolViewer.tsx"

Repository: matt1398/claude-devtools

Length of output: 132


🏁 Script executed:

fd -t f "LinkedToolItem" --type f

Repository: matt1398/claude-devtools

Length of output: 121


🏁 Script executed:

fd -t f "toolContentChecks.ts"

Repository: matt1398/claude-devtools

Length of output: 121


🏁 Script executed:

cat -n src/renderer/components/chat/items/linkedTool/BashToolViewer.tsx

Repository: matt1398/claude-devtools

Length of output: 1886


🏁 Script executed:

cat -n src/renderer/components/chat/items/LinkedToolItem.tsx | head -80

Repository: matt1398/claude-devtools

Length of output: 3032


🏁 Script executed:

cat -n src/renderer/utils/toolRendering/toolContentChecks.ts

Repository: matt1398/claude-devtools

Length of output: 2530


Guard tool input shape before rendering command content.

Lines 25-26 use unchecked casts (as string), and CodeBlockViewer is always rendered without validating that the command is a non-empty string. This can produce invalid input cards if the payload shape drifts. Add an isBashInput type guard and only render CodeBlockViewer when the command is a non-empty string.

Suggested patch
 interface BashToolViewerProps {
   linkedTool: LinkedToolItem;
   status: ItemStatus;
 }
 
+interface BashInput {
+  command: string;
+  description?: string;
+}
+
+function isBashInput(input: Record<string, unknown>): input is BashInput {
+  return (
+    typeof input.command === 'string' &&
+    (input.description === undefined || typeof input.description === 'string')
+  );
+}
+
 export const BashToolViewer: React.FC<BashToolViewerProps> = ({ linkedTool, status }) => {
-  const command = linkedTool.input.command as string;
-  const description = linkedTool.input.description as string | undefined;
+  if (!isBashInput(linkedTool.input) || linkedTool.input.command.trim().length === 0) {
+    return null;
+  }
+
+  const { command, description } = linkedTool.input;
 
   // Use the description (truncated) as the file name label, or fallback to "bash"
   const fileName = description

Per coding guidelines, **/*.{ts,tsx} requires: "Use TypeScript type guards with isXxx naming convention for runtime type checking".

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

In `@src/renderer/components/chat/items/linkedTool/BashToolViewer.tsx` around
lines 25 - 42, BashToolViewer currently casts
linkedTool.input.command/description unsafely and always renders
CodeBlockViewer; add a runtime type guard isBashInput(input: unknown): input is
{ command: string; description?: string } that verifies command is a non-empty
string and description (if present) is a string, then use this guard in
BashToolViewer to only render CodeBlockViewer when isBashInput(linkedTool.input)
is true; compute fileName from the validated description (truncate to 60 chars)
or fall back to "bash" and remove the unsafe `as string`/`as string | undefined`
casts.

Comment on lines +61 to +89
<div className="space-y-2">
{isMarkdownFile && (
<div className="flex items-center justify-end gap-1">
<button
type="button"
onClick={() => setViewMode('code')}
className="rounded px-2 py-1 text-xs transition-colors"
style={{
backgroundColor: viewMode === 'code' ? 'var(--tag-bg)' : 'transparent',
color: viewMode === 'code' ? 'var(--tag-text)' : 'var(--color-text-muted)',
border: '1px solid var(--tag-border)',
}}
>
Code
</button>
<button
type="button"
onClick={() => setViewMode('preview')}
className="rounded px-2 py-1 text-xs transition-colors"
style={{
backgroundColor: viewMode === 'preview' ? 'var(--tag-bg)' : 'transparent',
color: viewMode === 'preview' ? 'var(--tag-text)' : 'var(--color-text-muted)',
border: '1px solid var(--tag-border)',
}}
>
Preview
</button>
</div>
)}
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

Expose the active mode to assistive tech.

The selected state here is color-only, so screen readers cannot tell whether "Code" or "Preview" is active. Add aria-pressed to both buttons, and mirror the same tweak in WriteToolViewer.

Suggested accessibility fix
         <div className="flex items-center justify-end gap-1">
           <button
             type="button"
             onClick={() => setViewMode('code')}
+            aria-pressed={viewMode === 'code'}
             className="rounded px-2 py-1 text-xs transition-colors"
             style={{
               backgroundColor: viewMode === 'code' ? 'var(--tag-bg)' : 'transparent',
@@
           <button
             type="button"
             onClick={() => setViewMode('preview')}
+            aria-pressed={viewMode === 'preview'}
             className="rounded px-2 py-1 text-xs transition-colors"
             style={{
               backgroundColor: viewMode === 'preview' ? 'var(--tag-bg)' : 'transparent',
📝 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
<div className="space-y-2">
{isMarkdownFile && (
<div className="flex items-center justify-end gap-1">
<button
type="button"
onClick={() => setViewMode('code')}
className="rounded px-2 py-1 text-xs transition-colors"
style={{
backgroundColor: viewMode === 'code' ? 'var(--tag-bg)' : 'transparent',
color: viewMode === 'code' ? 'var(--tag-text)' : 'var(--color-text-muted)',
border: '1px solid var(--tag-border)',
}}
>
Code
</button>
<button
type="button"
onClick={() => setViewMode('preview')}
className="rounded px-2 py-1 text-xs transition-colors"
style={{
backgroundColor: viewMode === 'preview' ? 'var(--tag-bg)' : 'transparent',
color: viewMode === 'preview' ? 'var(--tag-text)' : 'var(--color-text-muted)',
border: '1px solid var(--tag-border)',
}}
>
Preview
</button>
</div>
)}
<div className="space-y-2">
{isMarkdownFile && (
<div className="flex items-center justify-end gap-1">
<button
type="button"
onClick={() => setViewMode('code')}
aria-pressed={viewMode === 'code'}
className="rounded px-2 py-1 text-xs transition-colors"
style={{
backgroundColor: viewMode === 'code' ? 'var(--tag-bg)' : 'transparent',
color: viewMode === 'code' ? 'var(--tag-text)' : 'var(--color-text-muted)',
border: '1px solid var(--tag-border)',
}}
>
Code
</button>
<button
type="button"
onClick={() => setViewMode('preview')}
aria-pressed={viewMode === 'preview'}
className="rounded px-2 py-1 text-xs transition-colors"
style={{
backgroundColor: viewMode === 'preview' ? 'var(--tag-bg)' : 'transparent',
color: viewMode === 'preview' ? 'var(--tag-text)' : 'var(--color-text-muted)',
border: '1px solid var(--tag-border)',
}}
>
Preview
</button>
</div>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx` around
lines 61 - 89, The Code/Preview toggle uses color-only selection; update the two
buttons in ReadToolViewer (the elements that call setViewMode and read viewMode)
to include aria-pressed={viewMode === 'code'} for the "Code" button and
aria-pressed={viewMode === 'preview'} for the "Preview" button so assistive tech
can detect the active state, and apply the identical change in WriteToolViewer
for its corresponding toggle buttons to keep behavior consistent.

Comment on lines +924 to 929
// Check for comment (# style for Python/Shell/R/Ruby/PHP/YAML)
if (
(language === 'python' || language === 'bash' || language === 'r' || language === 'ruby' || language === 'php') &&
(language === 'python' || language === 'bash' || language === 'zsh' || language === 'fish' ||
language === 'r' || language === 'ruby' || language === 'php' || language === 'yaml') &&
remaining.startsWith('#')
) {
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

🧩 Analysis chain

🌐 Web query:

In Bash and Zsh, does #inside${#var}or${var#prefix} start a comment, or is it part of parameter expansion syntax?

💡 Result:

In both Bash and Zsh, the # inside ${#var} or ${var#prefix} is part of the parameter expansion syntax and does not start a comment. The shell recognizes ${...} as a parameter expansion construct during parsing, and content within the braces is treated as syntax until the closing }, not as a comment. Bash official documentation explicitly defines ${#parameter} as substituting the length of the parameter's value and ${parameter#word} / ${parameter##word} as removing the shortest/longest match of word from the beginning of parameter's value. Comments begin with # only as the start of a word (beginning of line, after whitespace, or after an operator), not inside recognized expansions. Zsh documentation similarly describes parameter expansion with ${name#pattern} for removing the shortest prefix match and ${#name} for length, with # as part of the syntax. Zsh parameter expansion occurs before comment processing in the expansion order. Examples from sources confirm functionality: - Bash: echo ${string:7} and ${var#pattern} work as expected. - Zsh: ${var#*/} extracts directory parts. No sources indicate # inside ${...} triggers commenting; it would break documented syntax if so.

Citations:


Shell # handling breaks valid parameter expansion.

For bash/zsh, this treats any raw # as a comment start, so commands like ${#files[@]} or ${path#"$HOME"/} will render the rest of the line as a comment. In Bash and Zsh, # inside parameter expansion constructs (e.g., ${#var} for length, ${var#prefix} for prefix removal) is syntax, not a comment marker. Please gate shell comments on real comment boundaries instead of any # token.

🤖 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 924 -
929, The current comment-detection if-block (checking language === 'bash'|'zsh'
etc. and remaining.startsWith('#')) incorrectly treats every '#' as a comment
start; update the condition in the if-block that checks remaining and language
so that for shell languages (bash/zsh/fish) you only treat '#' as a comment when
it is a real comment boundary (e.g., at line start or preceded by whitespace)
and not when it is inside a parameter expansion; implement this by detecting
whether the current '#' is within an open parameter expansion (scan backwards
from the current position in remaining for an unmatched '${' without a closing
'}' and skip comment handling if found) and/or require the character before '#'
to be whitespace or start-of-line for shell languages, leaving the existing
behavior unchanged for non-shell languages; adjust the if-block that references
remaining and the language list accordingly.

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.

1 participant