Repository navigation
fix(wework): align codex message attachments and selection - #1716
Conversation
📝 WalkthroughWalkthroughThis PR adds text attachment support (detection, preview extraction, metadata enrichment, composer/message UI, and Tauri local file opening), removes overflow-x clipping across message layout components with Tauri-specific selection handling, and adds a copy-to-clipboard control plus pointer-stability fix to the diff preview. ChangesText Attachment Preview and Local File Opening
Estimated code review effort: 3 (Moderate) | ~30 minutes Message Layout Overflow and Tauri Selection Handling
Estimated code review effort: 2 (Simple) | ~15 minutes Diff Preview Interaction and Copy-to-Clipboard
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant MessageList
participant openLocalAttachmentPath
participant openLocalFile
participant TauriBackend
User->>MessageList: click text/document attachment
MessageList->>openLocalAttachmentPath: attempt open(local_path)
openLocalAttachmentPath->>openLocalFile: invoke path
openLocalFile->>TauriBackend: invoke("open_local_file", path)
TauriBackend-->>openLocalFile: success or error
alt open fails or no local path
openLocalAttachmentPath->>MessageList: fallback onOpenFile()
end
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
wework/src/features/workbench/useWorkbenchAttachments.ts (1)
63-92: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winUnhandled rejection risk if upload fails.
textMetadataPromiseis created (line 63) but only awaited on the success path (line 74). IfuploadAttachmentthrows and execution jumps to thecatchblock,textMetadataPromiseis left unawaited. ShouldreadTextAttachmentMetadatareject (e.g., aFileReadererror), this becomes an unhandled promise rejection.🔧 Suggested fix
- const textMetadataPromise = readTextAttachmentMetadata(file) + const textMetadataPromise = readTextAttachmentMetadata(file).catch(() => undefined)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/features/workbench/useWorkbenchAttachments.ts` around lines 63 - 92, The attachment upload flow in useWorkbenchAttachments leaves readTextAttachmentMetadata unawaited if uploadAttachment throws, which can trigger an unhandled rejection. Update the async upload handler to always settle textMetadataPromise, even on the error path, by awaiting it in the catch/finally logic and handling its rejection before exiting. Keep the fix near uploadAttachment, readTextAttachmentMetadata, and the surrounding try/catch so the metadata promise is consumed regardless of whether the upload succeeds.
🧹 Nitpick comments (4)
wework/src/lib/attachments.ts (1)
52-69: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDetection mismatch between
isTextAttachmentandreadTextAttachmentMetadata's gating logic.
isTextAttachment(lines 56-62) treats a file as text when its MIME type includesjson/xml/yamlsubstrings, in addition totext/*and the extension set.readTextAttachmentMetadata's gate (lines 76-81) only checkstext/*prefix and the extension set — it omits the json/xml/yaml substring checks.Concretely, a file with
mime_typelikeapplication/xmland an extension outsideTEXT_ATTACHMENT_EXTENSIONS(e.g..rss) would be classified as text byisTextAttachmentat render time but skipped byreadTextAttachmentMetadataat upload time, sotext_preview/text_contentare never populated and the UI silently falls back to filename display. Consider extracting a single shared predicate used by both functions to keep detection consistent.♻️ Suggested consolidation
+function isTextLikeMimeType(mimeType: string): boolean { + const normalized = mimeType.toLowerCase() + return ( + normalized.startsWith('text/') || + normalized.includes('json') || + normalized.includes('xml') || + normalized.includes('yaml') + ) +} + export function isTextAttachment( attachment: Pick<Attachment, 'mime_type' | 'file_extension'> ): boolean { - const mimeType = attachment.mime_type.toLowerCase() - return ( - mimeType.startsWith('text/') || - mimeType.includes('json') || - mimeType.includes('xml') || - mimeType.includes('yaml') || - TEXT_ATTACHMENT_EXTENSIONS.has(attachment.file_extension.toLowerCase()) - ) + return ( + isTextLikeMimeType(attachment.mime_type) || + TEXT_ATTACHMENT_EXTENSIONS.has(attachment.file_extension.toLowerCase()) + ) }const fileExtension = normalizedFileExtension(file.name) const mimeType = file.type || 'application/octet-stream' - if ( - !mimeType.toLowerCase().startsWith('text/') && - !TEXT_ATTACHMENT_EXTENSIONS.has(fileExtension) - ) { + if (!isTextLikeMimeType(mimeType) && !TEXT_ATTACHMENT_EXTENSIONS.has(fileExtension)) { return null }Also applies to: 71-81
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/lib/attachments.ts` around lines 52 - 69, `isTextAttachment` and `readTextAttachmentMetadata` use different text-detection rules, so attachments like XML/JSON/YAML can be classified as text in one place but skipped in the other. Unify the gating logic by extracting a shared predicate (or reusing `isTextAttachment`) for both `getAttachmentTextPreview`/render-time checks and `readTextAttachmentMetadata` so MIME substring handling and extension checks always match.wework/src/components/chat/composer/CompactChatComposer.tsx (1)
94-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated "show text attachment" handler logic.
This handler (extract text, append with
\n, remove attachment, refocus viarequestAnimationFrame) is duplicated nearly verbatim inProjectChatComposer.tsx(lines 118-125). Consider extracting a shared hook, e.g.useShowTextAttachment(value, onChange, onRemoveAttachment, textareaRef), to avoid future divergence between the two composers.♻️ Suggested shared hook
// wework/src/components/chat/composer/useShowTextAttachment.ts import type { RefObject } from 'react' import type { Attachment } from '`@/types/api`' export function useShowTextAttachment( value: string, onChange: (value: string) => void, onRemoveAttachment: (attachmentId: number) => void, textareaRef: RefObject<HTMLTextAreaElement | null> ) { return (attachment: Attachment) => { const text = attachment.text_content if (!text) return onChange(value ? `${value}\n${text}` : text) onRemoveAttachment(attachment.id) window.requestAnimationFrame(() => textareaRef.current?.focus()) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/components/chat/composer/CompactChatComposer.tsx` around lines 94 - 101, The show-text-attachment logic in CompactChatComposer is duplicated in ProjectChatComposer and should be centralized. Extract the shared behavior into a reusable hook such as useShowTextAttachment(value, onChange, onRemoveAttachment, textareaRef), and have both composer components call that hook instead of keeping their own inline handlers. Keep the existing behavior intact: read attachment.text_content, append with a newline when value is present, remove the attachment, and refocus the textarea via requestAnimationFrame.wework/src/components/chat/MessageList.tsx (2)
793-895: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor: redundant
clickableboolean and silent open failures.
clickableduplicates theattachmentPathtruthiness already checked in the branch below it — could just checkattachmentPathdirectly. Also, whenopenLocalFilefails and noonOpenFilefallback is supplied, the failure is only logged toconsole.errorwith no user-facing feedback.Neither is blocking; flagging for polish.
♻️ Optional cleanup
- const attachmentPath = openableAttachmentPath(attachment) - const clickable = Boolean(attachmentPath) + const attachmentPath = openableAttachmentPath(attachment) @@ - if (clickable && attachmentPath) { + if (attachmentPath) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/components/chat/MessageList.tsx` around lines 793 - 895, In MessageTextAttachment, remove the redundant clickable boolean and branch directly on attachmentPath since the truthiness check already covers it. In openLocalAttachmentPath, keep the onOpenFile fallback but add user-facing failure handling for the openLocalFile error when no fallback is provided, using the existing openLocalAttachmentPath and MessageTextAttachment flow to surface the failure instead of only logging to console.error.
130-136: 🚀 Performance & Scalability | 🔵 TrivialContent-visibility optimization is now fully disabled in Tauri, not just during selection.
disableMessageContentVisibilityincludesisTauriunconditionally, so[content-visibility:auto]containment is off for the whole session in the desktop app rather than only while text selection is active. This is consistent with the PR's goal of fixing selection/double-click behavior, but it removes a rendering perf optimization for very long transcripts in Tauri specifically.Worth confirming this trade-off has been validated against long conversation performance in the Tauri app (the PR description mentions manual confirmation of behavior, but doesn't call out long-transcript scroll perf specifically).
Also applies to: 186-189, 223-223
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/components/chat/MessageList.tsx` around lines 130 - 136, The `MessageList` content-visibility change is disabling `[content-visibility:auto]` for the entire Tauri session via `disableMessageContentVisibility`, not just during text selection. Review the `isTauriRuntime()` gating in `MessageList` and the related selection-handling spots in the same component so `isTauri` only affects the temporary double-click/selection workaround, while long-transcript rendering keeps the optimization when safe; validate the behavior in the Tauri app before keeping the broader disablement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@wework/src-tauri/src/lib.rs`:
- Around line 657-667: The open_local_file command currently accepts any
existing path and passes it to open_local_file_with_default_app, so tighten it
to an approved workspace/attachment root. In open_local_file, canonicalize the
input path first, verify the resolved path stays under the allowed root, and
reject anything outside it before calling open_local_file_with_default_app; this
also avoids relying on Path::is_file for security-sensitive validation and
closes the symlink/TOCTOU gap.
In `@wework/src/components/chat/blocks/ToolBlockItem.tsx`:
- Around line 249-253: The copy handlers are awaiting clipboard writes without
handling failures, so a rejected write can leave the UI stuck and trigger
unhandled rejections. Update handleCopy in ToolBlockItem and the matching copy
handler in MarkdownCodeBlock to wrap copyCodeText in error handling, only
setCopied(true) on success, and ensure the fallback state is reset on failure so
both copy buttons behave safely at runtime.
In `@wework/src/components/chat/blocks/ToolBlocksDisplay.test.tsx`:
- Around line 389-395: The clipboard mock in ToolBlocksDisplay.test.tsx is
leaking between tests because navigator.clipboard is overridden inside the test
but never restored. Update the test around the writes to navigator.clipboard and
the surrounding afterEach to save the original clipboard descriptor/value and
restore it after the test, so later tests in the same suite are isolated and do
not inherit the mocked writeText.
---
Outside diff comments:
In `@wework/src/features/workbench/useWorkbenchAttachments.ts`:
- Around line 63-92: The attachment upload flow in useWorkbenchAttachments
leaves readTextAttachmentMetadata unawaited if uploadAttachment throws, which
can trigger an unhandled rejection. Update the async upload handler to always
settle textMetadataPromise, even on the error path, by awaiting it in the
catch/finally logic and handling its rejection before exiting. Keep the fix near
uploadAttachment, readTextAttachmentMetadata, and the surrounding try/catch so
the metadata promise is consumed regardless of whether the upload succeeds.
---
Nitpick comments:
In `@wework/src/components/chat/composer/CompactChatComposer.tsx`:
- Around line 94-101: The show-text-attachment logic in CompactChatComposer is
duplicated in ProjectChatComposer and should be centralized. Extract the shared
behavior into a reusable hook such as useShowTextAttachment(value, onChange,
onRemoveAttachment, textareaRef), and have both composer components call that
hook instead of keeping their own inline handlers. Keep the existing behavior
intact: read attachment.text_content, append with a newline when value is
present, remove the attachment, and refocus the textarea via
requestAnimationFrame.
In `@wework/src/components/chat/MessageList.tsx`:
- Around line 793-895: In MessageTextAttachment, remove the redundant clickable
boolean and branch directly on attachmentPath since the truthiness check already
covers it. In openLocalAttachmentPath, keep the onOpenFile fallback but add
user-facing failure handling for the openLocalFile error when no fallback is
provided, using the existing openLocalAttachmentPath and MessageTextAttachment
flow to surface the failure instead of only logging to console.error.
- Around line 130-136: The `MessageList` content-visibility change is disabling
`[content-visibility:auto]` for the entire Tauri session via
`disableMessageContentVisibility`, not just during text selection. Review the
`isTauriRuntime()` gating in `MessageList` and the related selection-handling
spots in the same component so `isTauri` only affects the temporary
double-click/selection workaround, while long-transcript rendering keeps the
optimization when safe; validate the behavior in the Tauri app before keeping
the broader disablement.
In `@wework/src/lib/attachments.ts`:
- Around line 52-69: `isTextAttachment` and `readTextAttachmentMetadata` use
different text-detection rules, so attachments like XML/JSON/YAML can be
classified as text in one place but skipped in the other. Unify the gating logic
by extracting a shared predicate (or reusing `isTextAttachment`) for both
`getAttachmentTextPreview`/render-time checks and `readTextAttachmentMetadata`
so MIME substring handling and extension checks always match.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5f4dab31-b673-4ec5-844b-caa923035d30
📒 Files selected for processing (31)
docs/en/developer-guide/runtime-local-work.mddocs/zh/developer-guide/runtime-local-work.mdexecutor/src/runtime_work/handler.rswework/src-tauri/src/lib.rswework/src/api/local/localServices.tswework/src/components/chat/AssistantMarkdown.tsxwework/src/components/chat/ChatInput.test.tsxwework/src/components/chat/FileChangesCard.test.tsxwework/src/components/chat/FileChangesCard.tsxwework/src/components/chat/MessageList.test.tsxwework/src/components/chat/MessageList.tsxwework/src/components/chat/ScrollableMessageArea.test.tsxwework/src/components/chat/ScrollableMessageArea.tsxwework/src/components/chat/blocks/ToolBlockItem.tsxwework/src/components/chat/blocks/ToolBlocksDisplay.test.tsxwework/src/components/chat/composer/AttachmentBadges.tsxwework/src/components/chat/composer/CompactChatComposer.tsxwework/src/components/chat/composer/ProjectChatComposer.tsxwework/src/components/layout/DesktopWorkbenchLayout.test.tsxwework/src/components/layout/DesktopWorkbenchMain.tsxwework/src/features/workbench/useWorkbenchAttachments.tswework/src/features/workbench/workbenchProjectChatHooks.test.tsxwework/src/i18n/locales/en/chat.jsonwework/src/i18n/locales/en/common.jsonwework/src/i18n/locales/zh-CN/chat.jsonwework/src/i18n/locales/zh-CN/common.jsonwework/src/lib/attachments.tswework/src/lib/local-terminal.test.tswework/src/lib/local-terminal.tswework/src/lib/runtime-attachments.tswework/src/types/api.ts
| #[tauri::command] | ||
| fn open_local_file(path: String) -> Result<(), String> { | ||
| let path = normalized_non_empty(path).ok_or_else(|| "Local file path is empty".to_string())?; | ||
|
|
||
| if !std::path::Path::new(&path).is_file() { | ||
| return Err("Local file does not exist".to_string()); | ||
| } | ||
|
|
||
| open_local_file_with_default_app(&path) | ||
| } | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether sibling Tauri commands scope paths to a workspace root
rg -n -B3 -A25 'fn open_local_workspace' wework/src-tauri/src
rg -n -B3 -A25 'fn save_local_attachment_file' wework/src-tauri/src
rg -n -B3 -A15 'fn local_path_exists' wework/src-tauri/srcRepository: wecode-ai/Wegent
Length of output: 8189
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find all Rust/TS call sites and path sources for the local file opener
rg -n -B2 -A6 'open_local_file\(' wework/src-tauri/src wework/src -g '!**/target/**'
rg -n -B2 -A6 'open_local_file_with_default_app|local_attachment_root|normalized_non_empty|local_path_exists' wework/src-tauri/src wework/src -g '!**/target/**'
# Inspect the surrounding code where attachment/local file paths are produced or persisted
rg -n -B4 -A12 'save_local_attachment_file|local_path|attachment.*path|workspace_path' wework/src-tauri/src wework/src -g '!**/target/**'Repository: wecode-ai/Wegent
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant Rust functions before reading them
ast-grep outline wework/src-tauri/src/lib.rs --view expanded
# Read the helper that defines attachment roots and any path scoping helpers
rg -n -A80 -B20 'fn local_attachment_root|fn unique_attachment_directory|fn unique_download_path|fn sanitized_download_filename' wework/src-tauri/src/lib.rs
# Read the command and its nearby helpers with line numbers
sed -n '520,930p' wework/src-tauri/src/lib.rs | cat -nRepository: wecode-ai/Wegent
Length of output: 32281
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate every call site for open_local_file and every place local_path is constructed or persisted
rg -n -B3 -A6 'open_local_file\(' wework/src-tauri/src wework/src -g '!**/target/**'
rg -n -B4 -A10 'local_path\s*[:=]|local_path' wework/src-tauri/src wework/src -g '!**/target/**'
rg -n -B4 -A10 'message\.attachments|attachments.*local_path|local_path.*attachments' wework/src-tauri/src wework/src -g '!**/target/**'Repository: wecode-ai/Wegent
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Narrowly inspect the frontend call sites that trigger the native file opener
rg -n -B4 -A8 "open_local_file|invoke<.*open_local_file|invoke\\('open_local_file'|invoke\\(\"open_local_file\"" wework/src -g '!**/target/**' -g '!**/*.snap'
# Inspect any helper that derives a local path for opening attachments
rg -n -B4 -A10 "local_path|local_preview_url|getDownloadableLocalPath|open.*attachment|attachment.*open" wework/src/components wework/src/lib -g '!**/target/**'Repository: wecode-ai/Wegent
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Where are attachment paths created/populated?
rg -n -B3 -A8 "local_path:|local_preview_url:|local_path\\s*[:=]|local_preview_url\\s*[:=]" wework/src -g '!**/target/**' -g '!**/*.snap' | head -n 220
# Find every direct consumer of the native file opener helper
rg -n -B3 -A8 "openLocalFile\\(|openLocalAttachmentPath\\(|openableAttachmentPath\\(" wework/src -g '!**/target/**' -g '!**/*.snap'Repository: wecode-ai/Wegent
Length of output: 22185
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant frontend helper and attachment click path with line numbers
sed -n '1,130p' wework/src/lib/local-terminal.ts | cat -n
sed -n '785,890p' wework/src/components/chat/MessageList.tsx | cat -nRepository: wecode-ai/Wegent
Length of output: 8250
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Identify how attachment local paths are sourced in the runtime data model
rg -n -B4 -A10 "interface Attachment|local_path\\?:|local_preview_url\\?:" wework/src/types/api.ts
rg -n -B4 -A12 "localRuntimeAttachments\\(|getAttachmentTextPreview\\(|createImageAttachment\\(" wework/src -g '!**/target/**' -g '!**/*.snap'Repository: wecode-ai/Wegent
Length of output: 20612
Constrain open_local_file to an allowed root
It accepts any existing path and hands it to macOS open. If this is only meant for workspace/attachment files, canonicalize first and reject paths outside the allowed root; that also closes the symlink/TOCTOU gap around is_file().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src-tauri/src/lib.rs` around lines 657 - 667, The open_local_file
command currently accepts any existing path and passes it to
open_local_file_with_default_app, so tighten it to an approved
workspace/attachment root. In open_local_file, canonicalize the input path
first, verify the resolved path stays under the allowed root, and reject
anything outside it before calling open_local_file_with_default_app; this also
avoids relying on Path::is_file for security-sensitive validation and closes the
symlink/TOCTOU gap.
| const handleCopy = async () => { | ||
| await copyCodeText(copyText) | ||
| setCopied(true) | ||
| window.setTimeout(() => setCopied(false), 1500) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Relevant source =="
git ls-files 'wework/src/components/chat/blocks/ToolBlockItem.tsx' 'wework/src/**' | sed -n '1,120p'
echo
echo "== ToolBlockItem.tsx outline =="
ast-grep outline wework/src/components/chat/blocks/ToolBlockItem.tsx --view expanded || true
echo
echo "== Copy-related code in ToolBlockItem.tsx =="
rg -n "handleCopy|copyCodeText|navigator\.clipboard|execCommand|setCopied|void handleCopy" wework/src/components/chat/blocks/ToolBlockItem.tsx
echo
echo "== copyCodeText definition and usages =="
rg -n "function copyCodeText|const copyCodeText|copyCodeText\\(" wework/src -g '*.ts' -g '*.tsx'
echo
echo "== Tauri capability / permission config mentions =="
rg -n "clipboard-manager:allow-write-text|clipboard|capability|permissions|allow-write-text" wework -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'
echo
echo "== Package / Tauri config files =="
git ls-files 'wework/**tauri*' 'wework/**capabilit*' 'wework/**permission*' 'wework/**src-tauri/**' 'wework/**src-tauri/**/**' | sed -n '1,200p'Repository: wecode-ai/Wegent
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== ToolBlockItem.tsx copy section =="
sed -n '240,395p' wework/src/components/chat/blocks/ToolBlockItem.tsx | cat -n
echo
echo "== MarkdownCodeBlock.tsx copy helper =="
sed -n '100,290p' wework/src/components/chat/MarkdownCodeBlock.tsx | cat -n
echo
echo "== Tauri default capabilities =="
cat -n wework/src-tauri/capabilities/default.json
echo
echo "== Search for clipboard capability config =="
rg -n '"clipboard|allow-write-text|allow-read-text|permissions"' wework/src-tauri -g '*.json' -g '*.toml' -g '*.yaml' -g '*.yml'Repository: wecode-ai/Wegent
Length of output: 15096
Handle clipboard failures in both copy buttons
handleCopy awaits copyCodeText without a catch, so a rejected clipboard write leaves the button stuck and can surface as an unhandled rejection. The checked-in Tauri capability config doesn’t grant clipboard write access, so this can fail at runtime in the desktop build. Same fix applies to MarkdownCodeBlock.tsx:158-172.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src/components/chat/blocks/ToolBlockItem.tsx` around lines 249 - 253,
The copy handlers are awaiting clipboard writes without handling failures, so a
rejected write can leave the UI stuck and trigger unhandled rejections. Update
handleCopy in ToolBlockItem and the matching copy handler in MarkdownCodeBlock
to wrap copyCodeText in error handling, only setCopied(true) on success, and
ensure the fallback state is reset on failure so both copy buttons behave safely
at runtime.
| test('renders file changes inside completed processing details', async () => { | ||
| const writeText = vi.fn().mockResolvedValue(undefined) | ||
| Object.defineProperty(navigator, 'clipboard', { | ||
| value: { writeText }, | ||
| configurable: true, | ||
| }) | ||
|
|
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Restore navigator.clipboard after the test.
The clipboard mock is defined via Object.defineProperty but never restored, unlike the file's afterEach which resets fake timers. This can leak the mocked writeText into later tests in this suite. As per coding guidelines, tests should "keep tests isolated."
🧹 Proposed fix
test('renders file changes inside completed processing details', async () => {
const writeText = vi.fn().mockResolvedValue(undefined)
+ const originalClipboard = navigator.clipboard
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
configurable: true,
})
+ try {
...
+ } finally {
+ Object.defineProperty(navigator, 'clipboard', {
+ value: originalClipboard,
+ configurable: true,
+ })
+ }
})🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src/components/chat/blocks/ToolBlocksDisplay.test.tsx` around lines
389 - 395, The clipboard mock in ToolBlocksDisplay.test.tsx is leaking between
tests because navigator.clipboard is overridden inside the test but never
restored. Update the test around the writes to navigator.clipboard and the
surrounding afterEach to save the original clipboard descriptor/value and
restore it after the test, so later tests in the same suite are isolated and do
not inherit the mocked writeText.
Source: Coding guidelines
Summary
open_local_filebehavior in the runtime local work developer guide.Testing
pnpm --dir wework exec vitest run src/components/chat/MessageList.test.tsx src/components/chat/ScrollableMessageArea.test.tsx src/components/layout/DesktopWorkbenchLayout.test.tsx src/components/chat/ChatInput.test.tsx src/components/chat/FileChangesCard.test.tsx src/components/chat/blocks/ToolBlocksDisplay.test.tsx src/components/chat/blocks/ToolBlockItem.test.tsx src/features/workbench/workbenchProjectChatHooks.test.tsx src/lib/local-terminal.test.tspnpm --dir wework exec tsc -bpnpm --dir wework exec eslint .cargo check --manifest-path wework/src-tauri/Cargo.tomlcargo clippy --manifest-path executor/Cargo.toml --all-features -- -D warningscargo test --manifest-path executor/Cargo.toml --all-featuresAI_VERIFIED=1after docs were updated; the hook reran frontend, Wework, backend syntax, knowledge syntax, executor cargo test/clippy, executor-manager syntax, and Alembic multi-head checks.Validation
origin/mainbefore publishing; no conflicts remained.Summary by CodeRabbit
New Features
Bug Fixes