-
Notifications
You must be signed in to change notification settings - Fork 218
fix: always swap conversation data on tab switch #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f5dcbfd
e9f3b36
213ba21
aa1df1e
09fb97f
b315a64
c951a19
c322cc4
fd8ea3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** | ||
| * BashToolViewer | ||
| * | ||
| * Renders Bash tool calls with syntax-highlighted command input | ||
| * via CodeBlockViewer and collapsible output section. | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| import { CodeBlockViewer } from '@renderer/components/chat/viewers'; | ||
|
|
||
| import { type ItemStatus } from '../BaseItem'; | ||
|
|
||
| import { CollapsibleOutputSection } from './CollapsibleOutputSection'; | ||
| import { renderOutput } from './renderHelpers'; | ||
|
|
||
| import type { LinkedToolItem } from '@renderer/types/groups'; | ||
|
|
||
| interface BashToolViewerProps { | ||
| linkedTool: LinkedToolItem; | ||
| status: ItemStatus; | ||
| } | ||
|
|
||
| export const BashToolViewer: React.FC<BashToolViewerProps> = ({ linkedTool, status }) => { | ||
| 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" | ||
| /> | ||
|
|
||
| {/* Output Section — Collapsible */} | ||
| {!linkedTool.isOrphaned && linkedTool.result && ( | ||
| <CollapsibleOutputSection status={status}> | ||
| {renderOutput(linkedTool.result.content)} | ||
| </CollapsibleOutputSection> | ||
| )} | ||
| </> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /** | ||
| * CollapsibleOutputSection | ||
| * | ||
| * Reusable component that wraps tool output in a collapsed-by-default section. | ||
| * Shows a clickable header with label, StatusDot, and chevron toggle. | ||
| */ | ||
|
|
||
| import React, { useState } from 'react'; | ||
|
|
||
| import { ChevronDown, ChevronRight } from 'lucide-react'; | ||
|
|
||
| import { type ItemStatus, StatusDot } from '../BaseItem'; | ||
|
|
||
| interface CollapsibleOutputSectionProps { | ||
| status: ItemStatus; | ||
| children: React.ReactNode; | ||
| /** Label shown in the header (default: "Output") */ | ||
| label?: string; | ||
| } | ||
|
|
||
| export const CollapsibleOutputSection: React.FC<CollapsibleOutputSectionProps> = ({ | ||
| status, | ||
| children, | ||
| label = 'Output', | ||
| }) => { | ||
| const [isExpanded, setIsExpanded] = useState(false); | ||
|
|
||
| return ( | ||
| <div> | ||
| <button | ||
| type="button" | ||
| 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 ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />} | ||
| {label} | ||
| <StatusDot status={status} /> | ||
| </button> | ||
| {isExpanded && ( | ||
| <div | ||
| className="max-h-96 overflow-auto rounded p-3 font-mono text-xs" | ||
| style={{ | ||
| backgroundColor: 'var(--code-bg)', | ||
| border: '1px solid var(--code-border)', | ||
| color: | ||
| status === 'error' | ||
| ? 'var(--tool-result-error-text)' | ||
| : 'var(--color-text-secondary)', | ||
| }} | ||
| > | ||
| {children} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |||||||||||||
|
|
||||||||||||||
| import React from 'react'; | ||||||||||||||
|
|
||||||||||||||
| import { CodeBlockViewer } from '@renderer/components/chat/viewers'; | ||||||||||||||
| import { CodeBlockViewer, MarkdownViewer } from '@renderer/components/chat/viewers'; | ||||||||||||||
|
|
||||||||||||||
| import type { LinkedToolItem } from '@renderer/types/groups'; | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -54,12 +54,49 @@ export const ReadToolViewer: React.FC<ReadToolViewerProps> = ({ linkedTool }) => | |||||||||||||
| ? startLine + limit - 1 | ||||||||||||||
| : undefined; | ||||||||||||||
|
|
||||||||||||||
| const isMarkdownFile = /\.mdx?$/i.test(filePath); | ||||||||||||||
| const [viewMode, setViewMode] = React.useState<'code' | 'preview'>(isMarkdownFile ? 'preview' : 'code'); | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether ReadToolViewer remounts on tool-item switch (key-based mount)
# If not remounted, the state-sync effect above is required.
rg -n --type=tsx -C4 'ReadToolViewer|key=' src/renderer/components/chatRepository: matt1398/claude-devtools Length of output: 95 🏁 Script executed: #!/bin/bash
# Correct the file type and search for ReadToolViewer usage
rg -n --type=typescript -C4 'ReadToolViewer' src/renderer/components/chatRepository: matt1398/claude-devtools Length of output: 102 🏁 Script executed: #!/bin/bash
# Also search for the component definition and its parent usages
fd --type f -e tsx -e ts 'linkedTool|ReadToolViewer' src/renderer/components/chatRepository: matt1398/claude-devtools Length of output: 132 🏁 Script executed: #!/bin/bash
# Find the parent component that uses ReadToolViewer
rg -n '<ReadToolViewer' src/renderer/components --type=typescript -C6Repository: matt1398/claude-devtools Length of output: 102 🏁 Script executed: #!/bin/bash
# Search for ReadToolViewer usage without type flag
rg 'ReadToolViewer' src/renderer/components --context=5Repository: matt1398/claude-devtools Length of output: 4901 🏁 Script executed: #!/bin/bash
# Read the ReadToolViewer component to check dependencies and useState
cat -n src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx | head -100Repository: matt1398/claude-devtools Length of output: 4047 🏁 Script executed: #!/bin/bash
# Find parent components that render ReadToolViewer
rg '<ReadToolViewer' src/renderer/components --context=8Repository: matt1398/claude-devtools Length of output: 3313 🏁 Script executed: #!/bin/bash
# Check LinkedToolItem.tsx to verify the parent rendering context
cat -n src/renderer/components/chat/items/LinkedToolItem.tsx | grep -A 15 -B 5 'ReadToolViewer'Repository: matt1398/claude-devtools Length of output: 1874 Sync Line 58 initializes Proposed fix const isMarkdownFile = /\.mdx?$/i.test(filePath);
const [viewMode, setViewMode] = React.useState<'code' | 'preview'>(isMarkdownFile ? 'preview' : 'code');
+ React.useEffect(() => {
+ setViewMode(isMarkdownFile ? 'preview' : 'code');
+ }, [isMarkdownFile]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <CodeBlockViewer | ||||||||||||||
| fileName={filePath} | ||||||||||||||
| content={content} | ||||||||||||||
| startLine={startLine} | ||||||||||||||
| endLine={endLine} | ||||||||||||||
| /> | ||||||||||||||
| <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> | ||||||||||||||
| )} | ||||||||||||||
| {isMarkdownFile && viewMode === 'preview' ? ( | ||||||||||||||
| <MarkdownViewer content={content} label="Markdown Preview" copyable /> | ||||||||||||||
| ) : ( | ||||||||||||||
| <CodeBlockViewer | ||||||||||||||
| fileName={filePath} | ||||||||||||||
| content={content} | ||||||||||||||
| startLine={startLine} | ||||||||||||||
| endLine={endLine} | ||||||||||||||
| /> | ||||||||||||||
| )} | ||||||||||||||
| </div> | ||||||||||||||
| ); | ||||||||||||||
| }; | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,7 @@ export const WriteToolViewer: React.FC<WriteToolViewerProps> = ({ linkedTool }) | |
| const content = (toolUseResult?.content as string) || (linkedTool.input.content as string) || ''; | ||
| const isCreate = toolUseResult?.type === 'create'; | ||
| const isMarkdownFile = /\.mdx?$/i.test(filePath); | ||
| const [viewMode, setViewMode] = React.useState<'code' | 'preview'>('code'); | ||
| const [viewMode, setViewMode] = React.useState<'code' | 'preview'>(isMarkdownFile ? 'preview' : 'code'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether WriteToolViewer remounts with a distinct key when tool/file changes.
rg -n --type=tsx -C4 'WriteToolViewer|key=' src/renderer/components/chatRepository: matt1398/claude-devtools Length of output: 95 🏁 Script executed: # Search for WriteToolViewer usage to understand remounting behavior
rg -n 'WriteToolViewer' src/renderer/components --type ts --type tsxRepository: matt1398/claude-devtools Length of output: 95 🏁 Script executed: # Also check for key prop patterns in chat component files
rg -n 'key=' src/renderer/components/chat -A 2 -B 2Repository: matt1398/claude-devtools Length of output: 25345 🏁 Script executed: # Look at the parent component to understand when/how filePath changes
fd -e ts -e tsx src/renderer/components/chat | head -20Repository: matt1398/claude-devtools Length of output: 485 🏁 Script executed: # Search directly for WriteToolViewer component
rg -n 'WriteToolViewer' src/rendererRepository: matt1398/claude-devtools Length of output: 727 🏁 Script executed: # Also search in all locations (not just renderer)
rg -n 'WriteToolViewer'Repository: matt1398/claude-devtools Length of output: 727 🏁 Script executed: # Look at the WriteToolViewer file itself to understand its props
cat -n src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx | head -50Repository: matt1398/claude-devtools Length of output: 2316 🏁 Script executed: # Check LinkedToolItem and how it's rendered - look for parent usage and key patterns
rg -n 'LinkedToolItem' src/renderer/components/chat -B 2 -A 2Repository: matt1398/claude-devtools Length of output: 9529 🏁 Script executed: # Check the full LinkedToolItem component around line 184
sed -n '175,195p' src/renderer/components/chat/items/LinkedToolItem.tsxRepository: matt1398/claude-devtools Length of output: 901 🏁 Script executed: # Check if itemId is based on tool.id and if it's stable across updates
sed -n '130,170p' src/renderer/components/chat/items/ExecutionTrace.tsxRepository: matt1398/claude-devtools Length of output: 1629 🏁 Script executed: # Also check DisplayItemList to see how itemKey is constructed for tools
sed -n '145,165p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 838 🏁 Script executed: # Check ReadToolViewer to see if it has similar initialization and if it's been fixed
cat -n src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx | head -40Repository: matt1398/claude-devtools Length of output: 1479 🏁 Script executed: # Check if LinkedToolItem receives key in DisplayItemList - look for the full context
sed -n '148,162p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 698 🏁 Script executed: # Check the structure of linkedTool/item.tool to understand if it's immutable or can change
rg -n 'linkedTool.*filePath|item\.tool' src/renderer/components/chat/items/linkedTool -A 1 | head -30Repository: matt1398/claude-devtools Length of output: 50 🏁 Script executed: # Get full context around LinkedToolItem rendering in DisplayItemList - see if key is assigned
sed -n '145,170p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 962 🏁 Script executed: # Check how items array is constructed and if tool objects are immutable or mutable
rg -n 'case.*tool|linkedTool' src/renderer/components/chat/DisplayItemList.tsx -B 3 -A 8 | head -50Repository: matt1398/claude-devtools Length of output: 780 🏁 Script executed: # Check if itemKey is being used as key prop for LinkedToolItem - search around line 153-165
sed -n '148,175p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 1071 🏁 Script executed: # Check if the items array content is mutable or immutable when tool result updates
rg -n 'items.*=' src/renderer/components/chat/DisplayItemList.tsx | head -10Repository: matt1398/claude-devtools Length of output: 148 🏁 Script executed: # Look at ReadToolViewer to see if it also has the same pattern (useState without useEffect)
sed -n '17,60p' src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsxRepository: matt1398/claude-devtools Length of output: 1598 🏁 Script executed: # Critical check: does LinkedToolItem get a key prop in DisplayItemList?
sed -n '150,180p' src/renderer/components/chat/DisplayItemList.tsx | grep -E 'key=|linkedKey|itemKey.*='Repository: matt1398/claude-devtools Length of output: 185 🏁 Script executed: # Let me see the full map function in DisplayItemList to confirm key prop usage
sed -n '100,120p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 839 🏁 Script executed: # Check if there's a key prop being passed in the rendered element
rg -n 'return.*itemKey' src/renderer/components/chat/DisplayItemList.tsx -A 15 | head -40Repository: matt1398/claude-devtools Length of output: 50 🏁 Script executed: # Check the return statement in the map function - what wraps the element?
sed -n '100,330p' src/renderer/components/chat/DisplayItemList.tsx | tail -50Repository: matt1398/claude-devtools Length of output: 1865 🏁 Script executed: # More focused: look at the end of the switch and what happens to element and itemKey
sed -n '285,310p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 1123 🏁 Script executed: # Perfect! Now let's check if ReadToolViewer also has the same issue - does it have a useEffect?
grep -n "useEffect\|viewMode" src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsxRepository: matt1398/claude-devtools Length of output: 602 🏁 Script executed: # Check WriteToolViewer for any useEffect
grep -n "useEffect\|viewMode" src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsxRepository: matt1398/claude-devtools Length of output: 602 🏁 Script executed: # Look at the full wrapper - see the key being passed
sed -n '325,345p' src/renderer/components/chat/DisplayItemList.tsxRepository: matt1398/claude-devtools Length of output: 558 Add a useEffect to sync Line 24 initializes const [viewMode, setViewMode] = React.useState<'code' | 'preview'>(isMarkdownFile ? 'preview' : 'code');
+ React.useEffect(() => {
+ setViewMode(isMarkdownFile ? 'preview' : 'code');
+ }, [isMarkdownFile]);Note: 🤖 Prompt for AI Agents |
||
|
|
||
| return ( | ||
| <div className="space-y-2"> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: matt1398/claude-devtools
Length of output: 1569
Pass
varianttoBashToolViewerdirectly, not toBaseItem.The
BaseItemPropsinterface does not include avariantproperty. The code passesvariant={toolVariant}toBaseItemon line 175, which will cause a TypeScript error. Move thevariantprop to theBashToolViewercomponent on line 189-190 where it belongs.🤖 Prompt for AI Agents