Skip to content

feat(platform): improve chat UX with persisted drafts and error handling#502

Merged
Israeltheminer merged 9 commits into
mainfrom
feat/chat-improvements
Feb 20, 2026
Merged

feat(platform): improve chat UX with persisted drafts and error handling#502
Israeltheminer merged 9 commits into
mainfrom
feat/chat-improvements

Conversation

@Israeltheminer
Copy link
Copy Markdown
Collaborator

@Israeltheminer Israeltheminer commented Feb 20, 2026

Summary

  • Persisted drafts & attachments: Chat message drafts and file attachments are now saved to localStorage per thread, surviving thread switches and page reloads
  • Failed agent response handling: When the AI agent throws an error or returns empty, a "failed" message is now saved to break the UI out of the stuck "thinking" state
  • File display improvements: Images render as thumbnails in markdown, loading skeletons for files, dark mode color fixes, pan/drag support in image preview dialog
  • Message processing cleanup: Internal file reference metadata ([ATTACHED FILES...], fileId: ...) is stripped from displayed messages
  • Optimistic pending messages: File attachments are now included in optimistic pending messages
  • Message info dialog refactor: Token/performance stats refactored from copy-pasted JSX into data-driven StatItem component

Test plan

  • Verify drafts persist when switching between chat threads
  • Verify file attachments persist when switching threads
  • Verify failed AI responses show an error message instead of infinite loading
  • Verify images display as thumbnails in message markdown
  • Verify image preview dialog supports pan/drag when zoomed in
  • Verify internal file references are not visible in message text
  • Run existing and new tests to confirm all pass

Summary by CodeRabbit

Release Notes

  • New Features

    • Chat drafts now automatically persist across sessions
    • Image previews can be panned and dragged when zoomed
    • File attachments persist and are automatically restored per conversation
  • Bug Fixes

    • Missing file previews now display graceful placeholders instead of blank spaces
    • Failed agent responses now appear in chat history for better transparency
    • Internal file references are stripped from displayed messages
  • Style

    • Enhanced markdown image rendering with improved sizing and grouping
    • Updated chat message styling for better visual consistency
    • Improved file attachment display formatting

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 20, 2026

Greptile Summary

This PR significantly improves chat UX by implementing persistent drafts and attachments, fixing the stuck loading state issue, and enhancing file display capabilities.

Key improvements:

  • Drafts and attachments now persist across thread switches via localStorage, improving user experience when navigating between conversations
  • Failed AI responses now save a "failed" status message, preventing the UI from being stuck in infinite "thinking" state
  • Images render as compact thumbnails in markdown messages with an improved preview dialog that supports pan/drag when zoomed
  • Internal file reference metadata is stripped from displayed messages for cleaner UX
  • Message info dialog refactored to use data-driven components instead of repetitive JSX

Implementation quality:

  • Comprehensive test coverage for new hooks and error handling logic
  • Proper SSR-safe implementation with typeof window checks
  • Graceful error handling with try-catch blocks around localStorage operations
  • Batch file URL fetching optimized with a 10-file limit

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The changes are well-tested, follow existing patterns, handle edge cases properly, and include comprehensive error handling. The implementation is defensive with proper SSR checks, error boundaries, and graceful degradation.
  • No files require special attention

Important Files Changed

Filename Overview
services/platform/app/features/chat/hooks/use-persisted-attachments.ts New hook to persist chat attachments across thread switches using localStorage with URL fetching logic
services/platform/app/features/chat/components/chat-interface.tsx Integrates persisted draft and attachment hooks, removes key prop that was preventing draft persistence
services/platform/convex/lib/agent_response/generate_response.ts Adds failed message saving in error handler to break UI loading state
services/platform/convex/lib/agent_chat/internal_actions.ts Adds failed message saving for empty responses to prevent stuck loading state
services/platform/app/features/chat/hooks/use-message-processing.ts Strips internal file reference markers from displayed messages using regex patterns
services/platform/app/features/chat/components/message-bubble/image-preview-dialog.tsx Adds pan/drag support for zoomed images with pointer events and dark mode fixes

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User enters chat message] --> B{Has attachments?}
    B -->|Yes| C[Save to localStorage]
    B -->|No| D[Save draft to localStorage]
    C --> D
    D --> E[User sends message]
    E --> F[Clear draft & attachments]
    F --> G[Create optimistic pending message]
    G --> H[Call generateAgentResponse]
    H --> I{Agent execution}
    I -->|Success| J[Save assistant message with content]
    I -->|Error thrown| K[Save failed message with status: 'failed']
    I -->|Empty response| L[Save failed message for empty response]
    K --> M[UI exits loading state]
    L --> M
    J --> M
    
    N[User switches threads] --> O[Save current attachments to localStorage]
    O --> P[Load new thread's attachments from localStorage]
    P --> Q{Attachments have image preview URLs?}
    Q -->|No| R[Fetch URLs via useFileUrls batch query]
    Q -->|Yes| S[Render immediately]
    R --> S
Loading

Last reviewed commit: 28565da

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

This PR implements file attachment persistence and lift attachment state management from the ChatInput component to its parent ChatInterface component. It introduces new hooks for persisting attachments to localStorage per chat thread and batch retrieving file URLs. The changes include enhanced error handling in the agent response generation to save failed messages, improved image preview functionality with pan/zoom capabilities, and refined markdown image rendering with better styling defaults. Message bubble components now handle missing file display URLs gracefully with placeholder rendering. Backend APIs are extended to support batch file URL retrieval.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 accurately and concisely describes the main changes: persisting chat drafts and adding error handling for failed AI responses, which align with the PR's core objectives.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/chat-improvements

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@services/platform/app/features/chat/components/message-bubble/markdown-renderer.tsx`:
- Line 46: The CSS selector '[&_image-group]:mt-2' is targeting a non-existent
element named image-group; change it to target the class applied on the div by
replacing the selector with '[&_.image-group]:mt-2' so the rule applies to
descendants with className="image-group" in markdown-renderer.tsx (look for the
array entry string '[&_image-group]:mt-2' and update it to
'[&_.image-group]:mt-2').

In
`@services/platform/app/features/chat/hooks/__tests__/use-persisted-attachments.test.ts`:
- Around line 50-190: Tests currently use non-null assertions (stored!) when
reading localStorage and parsing JSON in use-persisted-attachments.test (e.g.,
the calls around JSON.parse(stored!)). Replace those assertions with explicit
guards: check that localStorage.getItem('chat-attachments-thread-1') returns a
non-null value (throw or fail the test with a clear message or use expect to
assert non-null) before calling JSON.parse, and similarly guard any other stored
variable usages (the three occurrences flagged) so no `!` is used; update the
assertions to operate on the parsed value only after the guard.

In `@services/platform/app/features/chat/hooks/use-message-processing.ts`:
- Around line 134-138: The code passes a possibly undefined m.text to
stripInternalFileReferences when building the message object; update the
construction in the use-message-processing hook so you guard m.text (e.g., use a
default empty string or conditional) before calling stripInternalFileReferences
for the message's content to avoid a runtime error—identify the object creation
where id/key/content are returned and change the content assignment involving
stripInternalFileReferences(m.text) to call stripInternalFileReferences(m.text
?? '') or only call it when m.text is defined.

In `@services/platform/app/features/chat/hooks/use-persisted-attachments.ts`:
- Around line 93-114: The restore/save logic is running during render and calls
setAttachments/savePersisted which causes React warnings; move both the initial
restore (the hasRestoredRef branch using loadPersisted, isRestoringRef,
setAttachments, toFileAttachment) and the thread-switch logic (checking
prevThreadIdRef, calling savePersisted with attachmentsRef, updating
prevThreadIdRef, then loading via loadPersisted and setAttachments) into
useEffect hooks that depend on threadId (and run once on mount for the initial
restore), so state updates happen after render; ensure the effects update
hasRestoredRef and prevThreadIdRef and preserve the existing conversions with
toFileAttachment and the isRestoringRef flag.

Comment thread services/platform/app/features/chat/hooks/use-message-processing.ts
Comment thread services/platform/app/features/chat/hooks/use-persisted-attachments.ts Outdated
Israeltheminer and others added 5 commits February 20, 2026 19:32
Fix [&_image-group] selector to [&_.image-group] to correctly target
elements with className="image-group". Run oxfmt on all branch files.
Previous format pass used oxfmt@0.34.0 which produced different output
than the project's pinned oxfmt@0.32.0 used in CI.
Avoid indented if-conditions in FileAttachmentDisplay. Extract
MAX_BATCH_FILE_IDS to lib/shared/file-types so both backend and
frontend enforce the same batch limit.
The displayUrl variable could be string | null | undefined since
serverFileUrl returns string | null. Adding || undefined coerces
null to undefined, satisfying the img src prop type.
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