Skip to content

feat: add image support#351

Open
matt2e wants to merge 29 commits intomainfrom
image-support
Open

feat: add image support#351
matt2e wants to merge 29 commits intomainfrom
image-support

Conversation

@matt2e
Copy link
Contributor

@matt2e matt2e commented Mar 5, 2026

Summary

  • Add image storage layer with SQLite-backed persistence and Tauri commands for storing, retrieving, and deleting images
  • Integrate image attachments into the session system, allowing users to attach images when creating or continuing sessions via a new ImageAttachment component
  • Extend the ACP protocol driver to pass image data (base64 + mime type) alongside text prompts
  • Add image timeline items in the branch timeline with an ImageViewerModal for full-size viewing
  • Include image metadata in branch context so agents are aware of attached images

Test plan

  • Verify images can be attached when creating a new session
  • Verify images can be attached when continuing an existing session
  • Verify attached images appear in the branch timeline
  • Verify clicking a timeline image opens the full-size viewer modal
  • Verify image deletion works from the viewer modal
  • Verify image data is correctly passed through the ACP protocol to agents

🤖 Generated with Claude Code

matt2e and others added 9 commits March 5, 2026 17:24
Phase 1 of image support: Image model, SQLite migration v18,
CRUD operations, filesystem management, and Tauri commands for
create/get/list/delete images. Images are stored in the project
folder for automatic cleanup on project deletion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2 of image support: Extend AgentDriver to accept image content
blocks, add image_ids to SessionConfig, base64-encode images before
sending to agents, and update start_branch_session/resume_session
to accept image IDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3 of image support: Add ImageAttachment component with file
picker and clipboard paste support, integrate into NewSessionModal
for attaching images when starting sessions, and add frontend
command wrappers for image CRUD operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…:run

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
matt2e and others added 15 commits March 6, 2026 11:27
The v18→v19 migration dropped the 4 existing session cleanup triggers
but didn't drop trg_cleanup_session_after_image_delete before creating
it, causing a "trigger already exists" panic on subsequent app launches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add unique_image_filename_for_branch to the Store which checks existing
filenames for a branch and appends a counter suffix before the extension
when a duplicate is found (e.g. 'Screenshot.png' → 'Screenshot 2.png').

Both create_image and create_image_from_data now call this before
constructing the Image record, ensuring every image on a branch has a
distinct display name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Subscribe the NewSessionModal to the shared drag-drop service so
images dragged over the dialog are attached to the session instead
of falling through to the branch card behind it.

Changes:
- NewSessionModal: subscribe modal element to subscribeDragDrop,
  handle image file drops via createImage, add drag-over border
  highlight styling matching the branch card pattern.
- dragDrop.ts: iterate subscribers in reverse order so later
  subscribers (modals layered on top) take priority over earlier
  ones (branch cards) at the same coordinates. Update doc comments
  to reflect the service is no longer BranchCard-specific.
Only show the relative timestamp in the image timeline row subtitle,
removing the file size display.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iewer

Replace the ImageAttachment component in SessionModal with inline
image handling: a paperclip icon button to the left of the text input,
image thumbnails displayed below the divider between messages and
input, clipboard paste support, and drag-and-drop via the shared
dragDrop service (same pattern as NewSessionModal).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add image_ids column to session_messages (schema v20) so user messages
record which images were attached. The session runner now persists
image IDs alongside the prompt text via add_session_message_with_images.

The SessionModal lazily loads image data for any message with imageIds
and renders thumbnails below the user text in chat bubbles.

Previously, images were sent to the agent via ACP content blocks but
the message stored in the DB was text-only, so opening a session dialog
showed no trace of attached images.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Images attached in the new session dialog were appearing in the branch
timeline because they were stored in the images table with session_id
NULL. Now when a session starts with image attachments, the runner sets
session_id on those image records. The list_images_for_branch query
filters to session_id IS NULL, so session-scoped images only appear
in the session message history (via session_messages.image_ids) and
not in the branch timeline or agent context.

Changes:
- images.rs: add set_images_session_id() to associate images with a
  session; update list_images_for_branch to exclude session-scoped
  images (WHERE session_id IS NULL)
- session_runner.rs: call set_images_session_id after persisting the
  user message so attached images are marked as session-scoped

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dals

Add a `blocking` flag to DragDropSubscription that prevents events from
falling through to earlier subscribers when a modal's backdrop covers the
viewport. Both NewSessionModal and SessionModal now set blocking: true,
so drags on the dimmed overlay no longer trigger the branch card beneath.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add paperclip button, drag-and-drop, and clipboard paste support for
attaching images to the project-level session input. Images display as
thumbnails below the prompt and are passed to the agent via image_ids.

Schema migration v21 makes images.branch_id nullable so project-level
images (no branch) can be stored. The Image model, Tauri commands, and
frontend wrappers are updated to accept optional branchId throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The v20→v21 migration didn't drop images_new before creating it,
causing a "table already exists" panic on subsequent app launches
(same pattern as the v18→v19 fix in 21cb83f).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The v21 migration (make branch_id nullable) was not safe to re-run:
DROP TABLE images also implicitly dropped all triggers referencing it,
and if the migration then failed, subsequent launches panicked with
"no such table: main.images" from the orphaned trigger definitions.

Now the migration:
- Drops ALL 5 session-cleanup triggers upfront (not just the image one)
- Uses DROP TABLE IF EXISTS / INSERT OR IGNORE for the partial-run case
- Recreates all 5 triggers after the table rename

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Image thumbnails attached to the project session prompt were rendering
below the white border of the input wrapper. Restructure the prompt
layout so the input row (paperclip, textarea, actions) and the image
thumbnails are both children of the bordered wrapper.

Changes:
- Wrap the input controls in a new .prompt-input-row flex container
- Move the reply-images block inside .prompt-input-wrapper
- Change .prompt-input-wrapper from row flex to column flex
- Adjust reply-images padding from top-only to bottom+sides (inside border)
- Update responsive media query to target .prompt-input-row

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The plus button in the attachments row already serves as the add-image
affordance, so the paperclip is redundant when images are attached.
Conditionally render it only when imageIds is empty.
…edundant paperclip

Two fixes:
- Pass projectId to SessionModal when opened from ProjectSection so the
  paperclip button and image attachment appear in project session chat
  history. Previously canAttachImages required both branchId and projectId,
  but project sessions have no branch.
- Hide the paperclip button in SessionModal when images are already
  attached, since the + button in the image row is redundant (matching
  the existing ProjectSection behavior).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The image support branch introduced three incremental migrations
(v19: images table, v20: image_ids column, v21: nullable branch_id)
with several resilience fixes between them. Since no released DB
will be at the intermediate versions, consolidate into a single
v18→v21 migration that creates the final schema directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@matt2e matt2e marked this pull request as ready for review March 6, 2026 02:22
@matt2e matt2e requested review from baxen and wesbillman as code owners March 6, 2026 02:22
@matt2e matt2e changed the title feat: add image support for sessions and branches feat: add image support Mar 6, 2026
Copy link

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

Choose a reason for hiding this comment

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

💡 Codex Review

messageQueue = [...messageQueue, text];
return;

P2 Badge Preserve image IDs when queuing a follow-up message

When the session is running, handleSend snapshots imageIdsToSend but queues only the text payload; later processQueue calls sendMessage(next) without any image IDs. Any queued turn that includes attachments (for example via drag-drop while live) will silently drop the images from the eventual request and leave uploaded image records/files unattached.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +248 to +251
function removeImage(imageId: string) {
imageIds = imageIds.filter((id) => id !== imageId);
imagePreviews = new Map(imagePreviews);
imagePreviews.delete(imageId);

Choose a reason for hiding this comment

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

P1 Badge Delete removed project prompt images from storage

Project images are persisted as soon as they are attached (createImageFromData), but this removal path only updates local UI state. If a user removes an attachment (or closes the composer without sending), the image row/file is left behind with branch_id = NULL and no session_id, which means it is not shown in branch timelines and is effectively orphaned from the UI while still consuming disk/database space.

Useful? React with 👍 / 👎.

matt2e and others added 5 commits March 6, 2026 13:33
- Remove SVG from allowed image extensions (unsupported by LLM APIs)
- Clean up orphan files on disk when DB insert fails in create_image
  and create_image_from_data
- Validate MIME type parameter against whitelist of image types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Call deleteImage on backend when removing image from attachment list
- Use chunked base64 encoding to avoid O(n²) string concatenation
- Add .catch() to getImageData effect to prevent infinite retry loops
- Prevent duplicate paste handling across multiple components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log warnings for all silent image failure paths in session runner
- Add foreign key constraint on images.session_id for proper cascade
- Log when SimpleDriverWrapper discards image attachments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix concurrent image drop race conditions in all three modal components
  by processing drops sequentially and batching state updates
- Call deleteImage on backend when removing images in ProjectSection
- Add .catch() to getImageData effects to prevent infinite retry loops
- Use chunked base64 encoding in SessionModal to avoid O(n²) overhead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ete orphaned reply images

- Remove dead 'svg' arm from mime_type_for_extension (SVG was already
  excluded from ALLOWED_IMAGE_EXTENSIONS)
- Remove .svg from frontend IMAGE_EXTENSIONS to match backend whitelist
- Remove image/svg+xml from ImageAttachment file picker accept attribute
- Add sentinel value in SessionModal messageImageCache .catch() to
  prevent infinite retry loop when image data fetch fails
- Call deleteImage in SessionModal removeReplyImage to clean up orphaned
  images on the backend when removed from the reply attachment list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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