Skip to content

fix(wework): align codex message attachments and selection - #1716

Merged
qdaxb merged 1 commit into
wecode-ai:mainfrom
icycrystal4:codex/wework-codex-message-polish
Jul 3, 2026
Merged

qdaxb merged 1 commit into
wecode-ai:mainfrom
icycrystal4:codex/wework-codex-message-polish

Conversation

@icycrystal4

@icycrystal4 icycrystal4 commented Jul 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Align Wework local text attachment chips with Codex-style rendering, including text preview metadata and Tauri file opening.
  • Improve assistant message file-change blocks: compact height, left-side diff stats, copy action, and stable expanded selection behavior.
  • Fix Tauri message text selection and double-click behavior by removing interfering containment/compositor/selection handling in the chat transcript path.
  • Document local-first text attachment metadata and open_local_file behavior 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.ts
  • pnpm --dir wework exec tsc -b
  • pnpm --dir wework exec eslint .
  • cargo check --manifest-path wework/src-tauri/Cargo.toml
  • cargo clippy --manifest-path executor/Cargo.toml --all-features -- -D warnings
  • cargo test --manifest-path executor/Cargo.toml --all-features
  • Pre-push quality gate passed with AI_VERIFIED=1 after 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

  • Rebased on latest origin/main before publishing; no conflicts remained.
  • Final Tauri behavior was manually confirmed in the running Wework app before publishing.

Summary by CodeRabbit

  • New Features

    • Added support for text attachments in chat, including compact previews and a “show in text box” action.
    • Added the ability to open attached local files directly from the app.
    • Added copy-to-clipboard for file diff previews.
  • Bug Fixes

    • Improved attachment previews and persistence after refresh.
    • Refined message and diff preview behavior so content stays visible during selection and hover interactions.
    • Adjusted chat layout and scrolling to better handle long content and wrapped text.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

Text Attachment Preview and Local File Opening

Layer / File(s) Summary
Attachment data model and text preview utilities
wework/src/types/api.ts, wework/src/lib/attachments.ts, wework/src/lib/runtime-attachments.ts, wework/src/api/local/localServices.ts, executor/src/runtime_work/handler.rs, docs/en/..., docs/zh/...
Attachment gains text_preview/text_content; new isTextAttachment, getAttachmentTextPreview, readTextAttachmentMetadata helpers; local runtime payload strips text_content and adds text_preview; executor normalizer copies text_length/text_preview; docs updated.
Local file opening command
wework/src-tauri/src/lib.rs, wework/src/lib/local-terminal.ts, wework/src/lib/local-terminal.test.ts
New open_local_file Tauri command (macOS-only helper), registered in invoke handler; JS openLocalFile wrapper with path validation and tests.
Attachment upload metadata enrichment
wework/src/features/workbench/useWorkbenchAttachments.ts, wework/src/features/workbench/workbenchProjectChatHooks.test.tsx
Uploaded attachments are enriched with text metadata read from the source file, verified by a new test.
Composer text attachment display and show-in-composer
wework/src/components/chat/composer/AttachmentBadges.tsx, CompactChatComposer.tsx, ProjectChatComposer.tsx, wework/src/components/chat/ChatInput.test.tsx, i18n files
New TextAttachmentCard with preview and show-in-composer button; composer handlers move text_content into input and remove the attachment; new locale strings.
Message rendering and click-to-open text attachments
wework/src/components/chat/MessageList.tsx, MessageList.test.tsx
Document/text attachment rendering refactored to support click-to-open via local file path with fallback callback.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Message Layout Overflow and Tauri Selection Handling

Layer / File(s) Summary
Overflow class removal across layout components
AssistantMarkdown.tsx, MessageList.tsx, ScrollableMessageArea.tsx, DesktopWorkbenchMain.tsx, DesktopWorkbenchLayout.test.tsx, related tests
Removes overflow-x-hidden/will-change classes from message containers and layout wrappers, with updated test assertions.
Tauri-specific selection handling
MessageList.tsx, MessageList.test.tsx
Disables content-visibility toggling and selection-state effects under Tauri runtime.
Hover action selection styling
MessageList.tsx, MessageList.test.tsx
Switches hover timestamp/action classes from select-text to select-none.

Estimated code review effort: 2 (Simple) | ~15 minutes

Diff Preview Interaction and Copy-to-Clipboard

Layer / File(s) Summary
Diff preview pointer-inside stability fix
FileChangesCard.tsx, FileChangesCard.test.tsx
Tracks pointer position inside the preview to avoid unintended closing during hover.
Diff copy-to-clipboard control
ToolBlockItem.tsx, ToolBlocksDisplay.test.tsx, en/chat.json, zh-CN/chat.json
Adds a copy button using clipboard API with execCommand fallback and a visibility-lock hook, plus new copy_code locale string.

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
Loading

Possibly related PRs

  • wecode-ai/Wegent#1328: Both PRs modify MessageList.tsx container spacing and hover-action styling classes.
  • wecode-ai/Wegent#1680: Both PRs modify MessageList.tsx content-visibility and selection handling logic.

Suggested reviewers: qdaxb, feifei325

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: aligning message attachments and selection behavior in Wework.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

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 win

Unhandled rejection risk if upload fails.

textMetadataPromise is created (line 63) but only awaited on the success path (line 74). If uploadAttachment throws and execution jumps to the catch block, textMetadataPromise is left unawaited. Should readTextAttachmentMetadata reject (e.g., a FileReader error), 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 win

Detection mismatch between isTextAttachment and readTextAttachmentMetadata's gating logic.

isTextAttachment (lines 56-62) treats a file as text when its MIME type includes json/xml/yaml substrings, in addition to text/* and the extension set. readTextAttachmentMetadata's gate (lines 76-81) only checks text/* prefix and the extension set — it omits the json/xml/yaml substring checks.

Concretely, a file with mime_type like application/xml and an extension outside TEXT_ATTACHMENT_EXTENSIONS (e.g. .rss) would be classified as text by isTextAttachment at render time but skipped by readTextAttachmentMetadata at upload time, so text_preview/text_content are 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 win

Duplicated "show text attachment" handler logic.

This handler (extract text, append with \n, remove attachment, refocus via requestAnimationFrame) is duplicated nearly verbatim in ProjectChatComposer.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 value

Minor: redundant clickable boolean and silent open failures.

clickable duplicates the attachmentPath truthiness already checked in the branch below it — could just check attachmentPath directly. Also, when openLocalFile fails and no onOpenFile fallback is supplied, the failure is only logged to console.error with 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 | 🔵 Trivial

Content-visibility optimization is now fully disabled in Tauri, not just during selection.

disableMessageContentVisibility includes isTauri unconditionally, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4018630 and 8310cec.

📒 Files selected for processing (31)
  • docs/en/developer-guide/runtime-local-work.md
  • docs/zh/developer-guide/runtime-local-work.md
  • executor/src/runtime_work/handler.rs
  • wework/src-tauri/src/lib.rs
  • wework/src/api/local/localServices.ts
  • wework/src/components/chat/AssistantMarkdown.tsx
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/FileChangesCard.test.tsx
  • wework/src/components/chat/FileChangesCard.tsx
  • wework/src/components/chat/MessageList.test.tsx
  • wework/src/components/chat/MessageList.tsx
  • wework/src/components/chat/ScrollableMessageArea.test.tsx
  • wework/src/components/chat/ScrollableMessageArea.tsx
  • wework/src/components/chat/blocks/ToolBlockItem.tsx
  • wework/src/components/chat/blocks/ToolBlocksDisplay.test.tsx
  • wework/src/components/chat/composer/AttachmentBadges.tsx
  • wework/src/components/chat/composer/CompactChatComposer.tsx
  • wework/src/components/chat/composer/ProjectChatComposer.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/features/workbench/useWorkbenchAttachments.ts
  • wework/src/features/workbench/workbenchProjectChatHooks.test.tsx
  • wework/src/i18n/locales/en/chat.json
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/chat.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/lib/attachments.ts
  • wework/src/lib/local-terminal.test.ts
  • wework/src/lib/local-terminal.ts
  • wework/src/lib/runtime-attachments.ts
  • wework/src/types/api.ts

Comment on lines +657 to +667
#[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)
}

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.

🔒 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/src

Repository: 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 -n

Repository: 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 -n

Repository: 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.

Comment on lines +249 to +253
const handleCopy = async () => {
await copyCodeText(copyText)
setCopied(true)
window.setTimeout(() => setCopied(false), 1500)
}

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.

🩺 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.

Comment on lines +389 to +395
test('renders file changes inside completed processing details', async () => {
const writeText = vi.fn().mockResolvedValue(undefined)
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
configurable: true,
})

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.

📐 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

@qdaxb
qdaxb merged commit bf3a94d into wecode-ai:main Jul 3, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants