Conversation
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>
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>
There was a problem hiding this comment.
💡 Codex Review
builderbot/apps/mark/src/lib/features/sessions/SessionModal.svelte
Lines 397 to 398 in 20b3422
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".
| function removeImage(imageId: string) { | ||
| imageIds = imageIds.filter((id) => id !== imageId); | ||
| imagePreviews = new Map(imagePreviews); | ||
| imagePreviews.delete(imageId); |
There was a problem hiding this comment.
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 👍 / 👎.
- 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>
…line Images attached in session compose dialogs (NewSessionModal, SessionModal, ProjectSection) were created with session_id = NULL, which is the same state as branch timeline images. If the user cancelled the dialog or quit the app, these images appeared as orphans in the branch timeline. Fix: create compose-dialog images with session_id = "pending" so they are excluded from the timeline query (WHERE session_id IS NULL). When a session is actually started, the runner overwrites "pending" with the real session ID via set_images_session_id. On app startup, any images still marked "pending" are cleaned up (deleted from DB and disk). Changes: - Image::new accepts a `pending` flag; when true sets session_id to the "pending" sentinel instead of NULL - create_image / create_image_from_data Tauri commands accept optional `pending` parameter (defaults to false for backward compat) - All session-compose callers (ImageAttachment, NewSessionModal, SessionModal, ProjectSection) pass pending: true - Branch card timeline drops continue to use the default (not pending) - Store::cleanup_pending_images deletes orphaned pending images + files - Called on startup alongside cancel_dead_sessions
Summary
Test plan
🤖 Generated with Claude Code