# Unified Image Paste UX

CTM treats pasted screenshots as first-class attachments and shows compact prompt tokens by default:

- `Cmd+V` on macOS and `Ctrl+V` elsewhere paste clipboard images into supported CTM inputs.
- Composer-style surfaces show `[Image #n]` tokens, not local file paths or long URLs.
- Attachment thumbnails show the matching `[Image #n]` label.
- Explicit path and URL references remain copyable from attachment UI for workflows that need raw paths.
- Browser uploads are normalized before storage: CTM keeps the aspect ratio and caps the long edge at 2000px so Claude Code does not poison long-running sessions with images that exceed Anthropic's many-image request limit.

## Surfaces

- Terminal sessions: paste events and the macOS `Ctrl+V` compatibility path upload images and insert the compact token, for example `[Image #1]`. The token is what the user sees and edits in both Claude and Codex sessions. `Cmd+Shift+V` on macOS and `Ctrl+Shift+V` elsewhere intentionally paste the raw local path reference. The terminal context menu can copy all pasted image paths or URLs for the active session.
- Terminal sessions backed by Claude Code or Codex use provider-native image handoff. CTM uploads and normalizes the browser clipboard image, then sends the normalized local image path as a bracketed paste to the PTY. The provider creates the real image attachment and renders its own compact `[Image #n]` chip, so the model receives image context while the terminal does not show raw paths.
- Terminal sessions backed by Claude Code or Codex also append durable image metadata when the prompt is submitted. CTM does not change the paste behavior: paste still inserts the compact token and keeps provider-native path handoff intact. At Enter/submit time, any unsent image attachments from the active input are appended once as `[Attached image: "..."]` or `[Attached images: "...", "..."]` before the newline reaches the PTY. This makes future Conversation/Review import able to resolve the image path even if the provider transcript only preserves terminal text.
- Terminal text paste also normalizes CTM image references. If the clipboard contains `[Image #1: "/path/to/file.png"]`, a raw local image path, or an `/api/images/file/...` URL, default paste deduplicates repeated references and hands each local image path to provider-native attachment paste when possible.
- Terminal image paste accepts browser clipboard files by `image/*` MIME type or by common image filename extension when the OS leaves the MIME type empty.
- Wall-E sessions: pasted images become structured message attachments. The composer inserts `[Image #n]`; sending preserves the compact token in the message text and sends image data separately.
- Queue Builder: pasted or uploaded images are stored with queued items and represented by `[Image #n]` in queued prompt text. Wall-E queue sends pass structured image attachments. Terminal queue sends preserve the compact visible text in queue state, then replace image labels with provider-native image-path paste payloads for Claude Code and Codex at delivery time.

## Provider Contract

Compact tokens are UI references, not raw file paths. CTM keeps attachment metadata, normalized uploads, and copyable path/URL references beside the visible token. Provider adapters must not send only `[Image #n]` to a raw PTY. They should either send structured attachments, or, for Claude Code and Codex terminal sessions, trigger native image attachment by bracket-pasting the normalized local image path. Explicit path paste remains available for CLIs that require a visible local filesystem reference in the prompt text.

Submit-time metadata is a transcript durability layer, not a replacement for native provider image attachment. It must not run during paste, path-copy, or path-paste handling, and it must mark attachments as submitted or discarded so later prompts do not inherit stale image references.

## Performance Contract

Image storage must not do large synchronous work on CTM's primary Node.js event loop.

- Browser uploads may arrive as buffers, but hashing must process large buffers in bounded chunks and yield between chunks.
- Screenshot capture must store the captured file through a streaming file path, then rename or copy it into the content-addressed image store. Do not synchronously read the screenshot back into a full buffer after `screencapture` exits.
- The shared image-store helpers are the boundary for this behavior: `saveImage()` handles in-memory uploads, while `saveImageFromFile()` handles captured or already-on-disk images.

This keeps terminal typing, WebSocket output flushes, and screenshot preview rendering responsive while large Retina screenshots are being stored.
