Skip to content

t3code/support markdown chat attachments#3927

Open
jakeleventhal wants to merge 2 commits into
pingdotgg:mainfrom
jakeleventhal:t3code/support-markdown-chat-attachments
Open

t3code/support markdown chat attachments#3927
jakeleventhal wants to merge 2 commits into
pingdotgg:mainfrom
jakeleventhal:t3code/support-markdown-chat-attachments

Conversation

@jakeleventhal

@jakeleventhal jakeleventhal commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Verification

Screenshot 2026-07-12 at 3 07 25 PM Screenshot 2026-07-12 at 3 09 10 PM [t3code-attachment-tests.zip](https://github.com/user-attachments/files/29944548/t3code-attachment-tests.zip)

Summary

  • allow UTF-8 text files to be dragged into the chat composer alongside images
  • persist text attachments under .t3/attachments and insert standard file-link chips into the prompt
  • avoid collapsing otherwise short user prompts when attachment links contain long hidden paths

Why

The composer previously rejected every dropped file that was not an image. This made it impossible to attach Markdown, source, configuration, or other text files as agent context.

Text attachments now reuse the existing project file-writing and Markdown file-link paths, so they render and behave like other file references. Files are limited to 1 MB and rejected when they contain NUL bytes or invalid UTF-8.

The message collapse heuristic also now measures displayed link labels instead of their backing absolute paths. Without this, prompts containing several attachments could appear hidden even though their visible content was short.

Validation

  • pnpm exec vp test apps/web/src/components/chat/MessagesTimeline.test.tsx
  • pnpm exec vp check
  • pnpm exec vp run typecheck
  • manual drag-and-drop verification with extensionless, Markdown, source, and configuration text files

Note

Medium Risk
New workspace writes under .t3/attachments and broader drop handling change composer behavior; limits and UTF-8 checks reduce risk, but failed writes only surface as thread errors.

Overview
Chat composer now treats non-image drops as text attachments (images still use the existing image path). Valid files are written to .t3/attachments/<uuid>/ via projectEnvironment.writeFile, capped at 1 MB, rejected if they contain NUL bytes or invalid UTF-8, then a standard file-link chip is appended to the prompt using serializeComposerFileLink / resolvePathLinkTarget. Drag-and-drop and the shared attachment handler were renamed from image-only to addComposerAttachments; plan-question blocking copy was broadened to “files.”

Messages timeline fixes “Show full message” so collapse is based on visible link labels, not the full markdown URL—short prompts with many attachment links stay expanded.

A MessagesTimeline test covers prompts with many long-path file links plus short trailing text.

Reviewed by Cursor Bugbot for commit f044e40. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add markdown file attachment support to the chat composer

  • Paste and drop handlers in ChatComposer.tsx now route all files through a unified addComposerAttachments path instead of an image-only handler.
  • Non-image files are handled by addComposerTextAttachment, which validates UTF-8 content, enforces a 1 MB limit, writes the file to .t3/attachments/<uuid>/<safeName> in the workspace, and inserts a markdown link into the prompt.
  • Collapse logic in MessagesTimeline.tsx now strips markdown link destinations before measuring message length, so short prompts with many long file links are not incorrectly collapsed.
📊 Macroscope summarized f044e40. 2 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

- Store supported text files in the project workspace
- Insert attachment links into the chat prompt
- Base user-message collapse thresholds on visible link text
- Add regression coverage for markdown attachment links
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c9f7e4e4-553b-4974-a904-fe5bc992979e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ 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.

@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 13, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new feature (text file attachments with filesystem writes) that adds significant user-facing capability beyond simple changes. New features with file system operations warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f044e40. Configure here.

setIsDragOverComposer(false);
const files = Array.from(event.dataTransfer.files);
addComposerImages(files);
void addComposerAttachments(files);

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.

Send races text attachment

Medium Severity

When text files are dropped or pasted, their asynchronous processing to write the file and add its link to the prompt is not awaited. This allows the composer's prompt to be submitted prematurely, resulting in messages sent without the intended attachment link and potentially leaving orphaned attachment files.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f044e40. Configure here.

currentPrompt.length,
currentPrompt.length,
`${separator}${serializeComposerFileLink(resolvePathLinkTarget(relativePath, gitCwd))} `,
);

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.

Parallel drops corrupt prompt

High Severity

Each text attachment appends using applyPromptReplacement at promptRef.current.length captured after its own async work, with no serialization across overlapping addComposerAttachments calls. Concurrent drops can insert links at the same stale offset and splice into the middle of the prompt instead of the end.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f044e40. Configure here.

const imageFiles = files.filter((file) => file.type.startsWith("image/"));
if (imageFiles.length === 0) return;
event.preventDefault();
addComposerImages(imageFiles);

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.

Mixed batch errors overwritten

Medium Severity

In a mixed drop, text failures use error = (await addComposerTextAttachment(file)) ?? error, but image size and count checks assign error = ... directly. A later image error replaces an earlier text error, and a lingering image error can remain after a later text file attaches successfully.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f044e40. Configure here.

const imageFiles = files.filter((file) => file.type.startsWith("image/"));
if (imageFiles.length === 0) return;
event.preventDefault();
addComposerImages(imageFiles);

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.

Image limit skips text files

Low Severity

When the per-message image cap is reached, addComposerAttachments breaks out of the file loop. Any non-image files later in the same DataTransfer list are never passed to addComposerTextAttachment, so they are dropped silently aside from the image-limit error.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f044e40. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f044e403be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

setIsDragOverComposer(false);
const files = Array.from(event.dataTransfer.files);
addComposerImages(files);
void addComposerAttachments(files);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent sending while dropped text files are still attaching

For dropped markdown/text files this handler now fire-and-forgets an async path: addComposerAttachments waits for file.arrayBuffer() and projects.writeFile before inserting the markdown link. If the user presses Send before that RPC finishes, onSend snapshots the prompt without the file link and clears it; the async callback then appends the link afterward, so the turn is sent without the attachment and a stale attachment link is left in the composer.

Useful? React with 👍 / 👎.

return `'${file.name}' exceeds the 1 MB text attachment limit.`;
}
const safeName = file.name.replace(/[^a-zA-Z0-9._-]+/g, "-") || "context.md";
const relativePath = `.t3/attachments/${randomUUID()}/${safeName}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid storing dropped text attachments in tracked workspaces

This writes every dropped text/markdown attachment under .t3/attachments inside the user's project, but the code does not ensure that .t3/ is ignored for arbitrary workspaces. In projects that have not already added that ignore rule, attaching a file creates untracked files that show up in git status and can be accidentally committed along with the user's changes.

Useful? React with 👍 / 👎.

applyPromptReplacement(
currentPrompt.length,
currentPrompt.length,
`${separator}${serializeComposerFileLink(resolvePathLinkTarget(relativePath, gitCwd))} `,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep text attachments inside the new worktree

When the first message is sent in New worktree mode, gitCwd is still the original project root because the worktree has not been created yet, so this inserts an absolute link to .t3/attachments in the original checkout. The bootstrap path then creates the worktree and starts the provider session there, leaving the model with a prompt that points outside its active workspace/sandbox instead of to a file in the worktree.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant