Skip to content

Context attachment fixes - #29

Open
ajs2583 wants to merge 4 commits into
mainfrom
context-attachment-fixes
Open

ajs2583 wants to merge 4 commits into
mainfrom
context-attachment-fixes

Conversation

@ajs2583

@ajs2583 ajs2583 commented Jan 28, 2026

Copy link
Copy Markdown
Collaborator

This pull request introduces a new file attachment feature to the chat UI, allowing users to attach files and context references to their messages. It also improves the error display and integrates context picking (e.g., referencing files or symbols with "#") into the message input. The changes span the frontend React components, chat state management, and styling.

New file attachment and context picking functionality:

  • Added support for file attachments and context references (files or symbols) to user messages. Attachments are managed in chat state and displayed in both the message input and message list. Users can pick files or insert references with "#", and remove attachments before sending. (ui/src/hooks/useChat.ts [1] [2] [3] [4] [5] [6]; ui/src/components/MessageInput.tsx [7] [8] [9] [10]; ui/src/components/MessageList.tsx [11]; ui/src/ChatApp.tsx [12] [13]
  • Integrated context picker: typing "#" in the message input triggers a context picker, allowing insertion of workspace file or symbol references directly into the message. Context references can also be added as attachments. (ui/src/components/MessageInput.tsx [1] [2]; ui/src/hooks/useChat.ts [3]

UI/UX improvements:

  • Enhanced error display with improved styling and clearer visual feedback, using VSCode theme variables for better integration. (ui/src/ChatApp.tsx ui/src/ChatApp.tsxL63-R76)
  • Attachments are now shown above the input area and in sent user messages, with options to remove before sending. (ui/src/components/MessageInput.tsx [1] [2]; ui/src/components/MessageList.tsx [3]

Styling and configuration:

  • Added Tailwind CSS directives to the main stylesheet for utility-first styling. (ui/src/index.css ui/src/index.cssR1)
  • Minor changes to configuration and environment template files. (ui/.env.template [1]; api/__init__.py [2]

Summary by CodeRabbit

  • New Features

    • File attachment support—attach, manage, and send files in chat; attachments persist across messages.
    • VS Code context-picker integration for inserting workspace context into messages.
  • UI/UX Improvements

    • Inline context-search hint and adjusted Enter-key behavior while context search is active.
    • Refreshed error styling with inline indicator; attachments shown above input and with messages.
  • Documentation

    • Expanded README and evaluation docs with architecture, setup, and usage guidance.
  • Chores

    • Minor formatting and template comment updates.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an attachments system to the chat UI (pick/remove attachments, context-picker integration), wires attachments through the useChat hook into MessageInput and MessageList, updates hook and component APIs, and includes substantial documentation and README rewrites plus a few non-functional comment/formatting edits.

Changes

Cohort / File(s) Summary
Comment/formatting edits
api/__init__.py, ui/.env.template, ui/src/index.css
Non-functional formatting and comment changes (string quoting, small comment insertions); no behavioral changes.
Hook: attachments state & API
ui/src/hooks/useChat.ts
Added attachments state, pickFiles, removeAttachment; handle filesPicked and contextPickerResult; ChatMessage now may include attachments; hook return exposes attachments management.
Chat wiring
ui/src/ChatApp.tsx
Consumes new hook fields and passes attachments, pickFiles, removeAttachment into MessageInput; minor error-display styling adjustments.
Message input: UI & behavior
ui/src/components/MessageInput.tsx
Replaced internal file handling with prop-driven attachments, added VS Code context-picker listener and related UI state/placeholder behavior, changed Enter/send semantics, renders attachments with remove controls; function signature expanded.
Message rendering
ui/src/components/MessageList.tsx
Renders "Attached files:" block for user messages with attachments.
Docs & READMEs
README.md, docs/evaluation.md, ui/README.md
Large rewrites and expansions: full project README overhaul, evaluation doc refactor/specification, and updated UI README with architecture, setup, and commands.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant ChatApp
    participant MessageInput
    participant useChat
    participant API
    participant VSCode

    User->>MessageInput: Click "pick files" / trigger context picker
    MessageInput->>useChat: pickFiles()
    useChat->>User: Open file picker (UI / external)
    User->>useChat: Select file(s)
    useChat->>useChat: Add attachment(s) to state
    useChat->>ChatApp: Update attachments prop
    ChatApp->>MessageInput: Pass attachments, pickFiles, removeAttachment

    alt Context picker flow
        VSCode->>MessageInput: contextPickerResult
        MessageInput->>useChat: notify context result
        useChat->>useChat: Add context as attachment
    end

    User->>MessageInput: Send message (with attachments)
    MessageInput->>useChat: sendMessage(text, attachments)
    useChat->>API: POST message payload with attachments
    API-->>useChat: message accepted / response
    useChat->>ChatApp: Append message to list
    ChatApp->>MessageInput: Render updated UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code with shiny files in tow,

Picked and pinned where soft contexts grow,
VS Code nudged, "Attach this one,"
Messages flew, the work now done—
A small rabbit’s dev-time glow. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Context attachment fixes' directly addresses the main feature additions in the changeset: file attachments, context references, and context picker integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

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

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)
ui/src/hooks/useChat.ts (1)

85-90: Clear attachments when history is cleared to prevent stale context leakage.
Right now historyCleared resets messages/feedback but leaves attachments intact, which can unintentionally carry files into a fresh conversation.

🐛 Proposed fix
                 case 'historyCleared':
                     setMessages([]);
                     setFeedbackState(new Map());
                     setPendingFeedback(new Map());
                     pendingFeedbackRef.current = new Map();
+                    setAttachments([]);
                     break;
🤖 Fix all issues with AI agents
In `@ui/.env.template`:
- Around line 1-2: Remove the temporary placeholder comment and add a trailing
newline to satisfy the linter: delete the "#  test" line and ensure the file
ends with a single blank line after the VITE_API_BASE= entry (leave the
VITE_API_BASE variable intact).

In `@ui/src/components/MessageInput.tsx`:
- Around line 98-115: When in the branch handling contextPickerActive, handle
the case where the '#' was deleted by checking hashIndex after computing it; if
hashIndex is -1, call setContextPickerActive(false) and reset
hashPositionRef.current = -1 to fully deactivate the picker and avoid leaving
the UI stuck, otherwise continue extracting textAfterHash and posting the
openContextPicker message via vscodeApi.postMessage as before.
- Around line 29-61: The effect leaks message listeners and can insert
"#undefined"; update setupMessageListener to return a cleanup function that
removes the added window message handler, then call that cleanup from the
useEffect return (where handleMessage is passed) so the listener is removed on
unmount/re-render; also guard the reference construction in handleMessage (where
you build reference from msg.result.reference || `#${msg.result.file ||
msg.result.name}`) by falling back to a safe value or skipping insertion when
neither msg.result.file nor msg.result.name exists (use textareaRef,
hashPositionRef and setMessage logic already present).
🧹 Nitpick comments (3)
api/__init__.py (1)

28-28: Remove the stray test comment before release.
This looks like a leftover placeholder and adds noise to production config.

♻️ Proposed cleanup
-    `#this` is a test
ui/src/components/MessageInput.tsx (2)

63-69: Clarify or remove the misleading comment.

The comment "Don't clear attachments - they should persist for visibility" is confusing because attachments is a prop managed by the parent component, not local state. This component cannot clear them regardless. If the intent is to document expected parent behavior, consider rewording or removing to avoid confusion.

✏️ Suggested fix
     const handleSend = () => {
         if (message.trim() && !disabled) {
             onSend(message, attachments);
             setMessage('');
-            // Don't clear attachments - they should persist for visibility
+            // Note: Parent component manages attachment state and should clear after send
         }
     };

157-163: Redundant null check on required prop.

pickFiles is a required prop in the interface, so the if (pickFiles) check is unnecessary. TypeScript guarantees it will be provided.

✏️ Suggested simplification
                     onClick={(e) => {
                         e.preventDefault();
                         e.stopPropagation();
-                        if (pickFiles) {
-                            pickFiles();
-                        }
+                        pickFiles();
                     }}

Comment thread ui/.env.template
Comment on lines +1 to +2
VITE_API_BASE=
# test No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clean up placeholder comment and add trailing newline (linter warning).
The dotenv linter flags the missing ending blank line, and the # test comment looks like a temporary marker.

✅ Suggested fix
-VITE_API_BASE=
-#  test
+VITE_API_BASE=
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
VITE_API_BASE=
# test
VITE_API_BASE=
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 2-2: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🤖 Prompt for AI Agents
In `@ui/.env.template` around lines 1 - 2, Remove the temporary placeholder
comment and add a trailing newline to satisfy the linter: delete the "#  test"
line and ensure the file ends with a single blank line after the VITE_API_BASE=
entry (leave the VITE_API_BASE variable intact).

Comment on lines +29 to +61
useEffect(() => {
const handleMessage = (msg: VSCodeMessage) => {
if (msg.command === 'contextPickerResult') {
setContextPickerActive(false);

if (msg.result && textareaRef.current) {
// Insert the reference into the input at the hash position
const textarea = textareaRef.current;
const currentValue = textarea.value;
const cursorPos = textarea.selectionStart;

// Find the hash position
if (hashPositionRef.current >= 0) {
const beforeHash = currentValue.substring(0, hashPositionRef.current);
const afterCursor = currentValue.substring(cursorPos);
const reference = msg.result.reference || `#${msg.result.file || msg.result.name}`;

const newValue = beforeHash + reference + ' ' + afterCursor;
setMessage(newValue);

// Set cursor position after the inserted reference
setTimeout(() => {
const newCursorPos = hashPositionRef.current + reference.length + 1;
textarea.setSelectionRange(newCursorPos, newCursorPos);
}, 0);
}

hashPositionRef.current = -1;
}
}
};
setupMessageListener(handleMessage);
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Memory leak: event listener is never cleaned up.

setupMessageListener adds a window.addEventListener but provides no way to remove it. Without a cleanup function, listeners accumulate on each component mount/remount, causing memory leaks and potentially triggering stale handlers.

Additionally, line 44 can produce #undefined if neither msg.result.file nor msg.result.name exists.

🐛 Proposed fix

First, update vscodeApi.ts to return a cleanup function:

export function setupMessageListener(callback: (message: VSCodeMessage) => void): () => void {
    if (typeof window !== 'undefined') {
        const handler = (event: MessageEvent) => callback(event.data);
        window.addEventListener('message', handler);
        return () => window.removeEventListener('message', handler);
    }
    return () => {};
}

Then use the cleanup in the effect:

     useEffect(() => {
         const handleMessage = (msg: VSCodeMessage) => {
             if (msg.command === 'contextPickerResult') {
                 setContextPickerActive(false);
                 
                 if (msg.result && textareaRef.current) {
                     // Insert the reference into the input at the hash position
                     const textarea = textareaRef.current;
                     const currentValue = textarea.value;
                     const cursorPos = textarea.selectionStart;
                     
                     // Find the hash position
                     if (hashPositionRef.current >= 0) {
                         const beforeHash = currentValue.substring(0, hashPositionRef.current);
                         const afterCursor = currentValue.substring(cursorPos);
-                        const reference = msg.result.reference || `#${msg.result.file || msg.result.name}`;
+                        const reference = msg.result.reference || (msg.result.file || msg.result.name ? `#${msg.result.file || msg.result.name}` : '');
                         
+                        if (!reference) return;
+                        
                         const newValue = beforeHash + reference + ' ' + afterCursor;
                         setMessage(newValue);
                         
                         // Set cursor position after the inserted reference
                         setTimeout(() => {
                             const newCursorPos = hashPositionRef.current + reference.length + 1;
                             textarea.setSelectionRange(newCursorPos, newCursorPos);
                         }, 0);
                     }
                     
                     hashPositionRef.current = -1;
                 }
             }
         };
-        setupMessageListener(handleMessage);
+        const cleanup = setupMessageListener(handleMessage);
+        return cleanup;
     }, []);
🤖 Prompt for AI Agents
In `@ui/src/components/MessageInput.tsx` around lines 29 - 61, The effect leaks
message listeners and can insert "#undefined"; update setupMessageListener to
return a cleanup function that removes the added window message handler, then
call that cleanup from the useEffect return (where handleMessage is passed) so
the listener is removed on unmount/re-render; also guard the reference
construction in handleMessage (where you build reference from
msg.result.reference || `#${msg.result.file || msg.result.name}`) by falling
back to a safe value or skipping insertion when neither msg.result.file nor
msg.result.name exists (use textareaRef, hashPositionRef and setMessage logic
already present).

Comment on lines +98 to 115
} else if (contextPickerActive && value.length > 0) {
// Update query as user types after "#"
const hashIndex = value.lastIndexOf('#', cursorPos - 1);
if (hashIndex >= 0) {
const textAfterHash = value.substring(hashIndex + 1, cursorPos);
const spaceIndex = textAfterHash.indexOf(' ');
const query = spaceIndex > 0 ? textAfterHash.substring(0, spaceIndex) : textAfterHash;

// Update context picker query
vscodeApi.postMessage({
command: 'openContextPicker',
query: query
});
}
} else {
setContextPickerActive(false);
hashPositionRef.current = -1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Context picker may not deactivate when "#" is deleted.

If the user is in contextPickerActive mode and deletes the # character while other text remains, the code enters the else if branch (line 98) but hashIndex will be -1. The condition on line 101 prevents entering the inner block, but contextPickerActive remains true, leaving the UI stuck in search mode.

🐛 Proposed fix
         } else if (contextPickerActive && value.length > 0) {
             // Update query as user types after "#"
             const hashIndex = value.lastIndexOf('#', cursorPos - 1);
             if (hashIndex >= 0) {
                 const textAfterHash = value.substring(hashIndex + 1, cursorPos);
                 const spaceIndex = textAfterHash.indexOf(' ');
                 const query = spaceIndex > 0 ? textAfterHash.substring(0, spaceIndex) : textAfterHash;
                 
                 // Update context picker query
                 vscodeApi.postMessage({
                     command: 'openContextPicker',
                     query: query
                 });
+            } else {
+                // Hash was deleted, deactivate context picker
+                setContextPickerActive(false);
+                hashPositionRef.current = -1;
             }
         } else {
             setContextPickerActive(false);
             hashPositionRef.current = -1;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (contextPickerActive && value.length > 0) {
// Update query as user types after "#"
const hashIndex = value.lastIndexOf('#', cursorPos - 1);
if (hashIndex >= 0) {
const textAfterHash = value.substring(hashIndex + 1, cursorPos);
const spaceIndex = textAfterHash.indexOf(' ');
const query = spaceIndex > 0 ? textAfterHash.substring(0, spaceIndex) : textAfterHash;
// Update context picker query
vscodeApi.postMessage({
command: 'openContextPicker',
query: query
});
}
} else {
setContextPickerActive(false);
hashPositionRef.current = -1;
}
} else if (contextPickerActive && value.length > 0) {
// Update query as user types after "#"
const hashIndex = value.lastIndexOf('#', cursorPos - 1);
if (hashIndex >= 0) {
const textAfterHash = value.substring(hashIndex + 1, cursorPos);
const spaceIndex = textAfterHash.indexOf(' ');
const query = spaceIndex > 0 ? textAfterHash.substring(0, spaceIndex) : textAfterHash;
// Update context picker query
vscodeApi.postMessage({
command: 'openContextPicker',
query: query
});
} else {
// Hash was deleted, deactivate context picker
setContextPickerActive(false);
hashPositionRef.current = -1;
}
} else {
setContextPickerActive(false);
hashPositionRef.current = -1;
}
🤖 Prompt for AI Agents
In `@ui/src/components/MessageInput.tsx` around lines 98 - 115, When in the branch
handling contextPickerActive, handle the case where the '#' was deleted by
checking hashIndex after computing it; if hashIndex is -1, call
setContextPickerActive(false) and reset hashPositionRef.current = -1 to fully
deactivate the picker and avoid leaving the UI stuck, otherwise continue
extracting textAfterHash and posting the openContextPicker message via
vscodeApi.postMessage as before.

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/evaluation.md`:
- Line 144: Replace the generic markdown image reference "! [alt
text](image.png)" with a descriptive alt text and a clear path; specifically
update the alt text to briefly describe the image (e.g., "Evaluation output
structure diagram") and rename the file/path to a meaningful name (e.g.,
assets/images/output-structure.png) or, if the content is better represented as
text, convert the image to a code block or inline markdown diagram; locate the
markdown image line in the document and make the replacement so screen readers
and future maintainers understand the image's purpose.

Comment thread docs/evaluation.md
The output structure is similar to:

![alt text](image.png) No newline at end of file
![alt text](image.png)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Improve image reference for accessibility and clarity.

The image reference uses generic placeholder text "alt text" and an unclear path "image.png". This impacts both accessibility (screen readers need descriptive alt text) and maintainability (unclear where the image is located or what it shows).

📝 Suggested improvement

Replace with a descriptive reference, for example:

-![alt text](image.png)
+![Example evaluation output JSON structure showing per-item scores and metric averages](images/evaluation-output-example.png)

Or if the image shows the output structure, consider whether a code block might be clearer than an image.

🤖 Prompt for AI Agents
In `@docs/evaluation.md` at line 144, Replace the generic markdown image reference
"! [alt text](image.png)" with a descriptive alt text and a clear path;
specifically update the alt text to briefly describe the image (e.g.,
"Evaluation output structure diagram") and rename the file/path to a meaningful
name (e.g., assets/images/output-structure.png) or, if the content is better
represented as text, convert the image to a code block or inline markdown
diagram; locate the markdown image line in the document and make the replacement
so screen readers and future maintainers understand the image's purpose.

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.

1 participant