# Scratchpad — Ticket Comment Clipboard Image Attachments - Plan slug: `ticket-comment-clipboard-image-attachments` - Created: `2026-03-01` ## What This Is Working notes for implementing clipboard image paste support in ticket comments, with attachment-backed rendering and outbound email inline-image support. ## Decisions - (2026-03-01) Paste behavior is immediate upload, not deferred-until-submit. - (2026-03-01) Uploaded clipboard images are persisted as ticket documents/files. - (2026-03-01) Render saved comment images from attachment-serving URLs, not embedded `data:image` payloads. - (2026-03-01) Outbound ticket-comment emails should render pasted images inline via CID attachments. - (2026-03-01) Cancel flow must offer keep/delete choice when pasted images were uploaded. - (2026-03-01) Delete choice on cancel is hard delete (remove metadata + stored object), not soft detach. ## Discoveries / Constraints - (2026-03-01) Existing inbound email work already converts `data:image`/CID content into ticket document attachments; this plan should reuse those attachment rendering and storage patterns where possible. - (2026-03-01) Clipboard image flows are user-driven and must avoid storing raw base64 image strings in persisted comment payloads. - (2026-03-01) Cancel-delete safety must prevent removing artifacts that are already referenced by submitted comments. ## Commands / Runbooks - (2026-03-01) Scaffolded plan folder with: - `python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/scaffold_plan.py "Ticket Comment Clipboard Image Attachments" --slug ticket-comment-clipboard-image-attachments` ## Links / References - Related plan: `ee/docs/plans/2026-02-27-inbound-email-embedded-images-and-original-eml/` - Related plan: `ee/docs/plans/2026-02-27-inbound-email-inapp-artifact-persistence-remaining-work/` - Related plan: `ee/docs/plans/2026-03-01-unified-inbound-email-pointer-queue/` ## Open Questions - Confirm final max size/count limits for clipboard image uploads, or reuse existing ticket attachment limits exactly. - Confirm whether cancel-delete prompt supports per-image selection or all-or-nothing delete in first version. - (2026-03-01) F001 implemented: ticket comment editor now supports clipboard image paste handling via BlockNote `uploadFile` wiring in `TextEditor`, enabled from `TicketConversation` when the new feature flag is on. - (2026-03-01) Implementation notes: introduced `packages/tickets/src/lib/clipboardImageUtils.ts` for MIME detection, deterministic naming, and pre-upload validation; this utility underpins paste-image handling. - (2026-03-01) Commands: `npx vitest run src/lib/clipboardImageUtils.test.ts` (packages/tickets), `npx vitest run src/test/unit/notifications/ticketCommentInlineImageEmail.test.ts` (server), `npx vitest run src/blocknoteUtils.image.test.ts` (packages/formatting), `npx tsc -p packages/tickets/tsconfig.json --noEmit`, `npx tsc -p server/tsconfig.json --noEmit`. - (2026-03-01) F002 implemented: ticket comment image uploads now start immediately on paste via `TextEditor` `uploadFile` wiring and `TicketConversation.handleClipboardImageUpload`, without waiting for comment submit. - (2026-03-01) F003 implemented: Persist pasted images through ticket document/file pipeline via `uploadDocument` and returned document/file ids in comment image URLs. - (2026-03-01) F004 implemented: Deterministic clipboard image naming added through `createClipboardImageFilename` and upload-time file renaming (`clipboard-image-YYYYMMDD-HHMMSS-SEQ.ext`). - (2026-03-01) F005 implemented: BlockNote image upload placeholder path is enabled by providing `uploadFile` to editor configuration for pasted images. - (2026-03-01) F006 implemented: Successful upload resolves placeholder to attachment-backed image node using `/api/documents/view/` URL payload from upload handler. - (2026-03-01) F007 implemented: Upload failure path now propagates upload/validation errors through editor upload API so users can retry/remove via BlockNote image error controls. - (2026-03-01) F008 implemented: Comment content now persists attachment-backed image URLs (document view endpoints) instead of raw `data:image` bodies. - (2026-03-01) F009 implemented: Ticket comment rendering path now uses saved attachment-serving URLs for inline images in BlockNote-rendered comment content. - (2026-03-01) F010 implemented: Attachment image serving auth strengthened in `/api/documents/view/[fileId]` for ticket-associated document access checks. - (2026-03-01) F011 implemented: Outbound ticket-comment email flow now maps comment image URLs into ticket document lookup + inline image processing model. - (2026-03-01) F012 implemented: Outbound email now builds CID inline attachments for eligible ticket-comment images via storage-backed attachment generation. - (2026-03-01) F013 implemented: Outbound comment HTML image `src` values are rewritten to `cid:` references matching generated inline attachments. - (2026-03-01) F014 implemented: Outbound processing now falls back to original attachment URL when CID conversion/download fails, preserving renderability. - (2026-03-01) F015 implemented: Ticket compose state now tracks uploaded clipboard draft images (document/file/url identity) for subsequent cancel handling. - (2026-03-01) F016 implemented: Canceling a draft with uploaded clipboard images now prompts keep-vs-delete through confirmation dialog copy and actions. - (2026-03-01) F017 implemented: Delete option on cancel now hard-deletes selected draft clipboard images through server action + document deletion pipeline. - (2026-03-01) F018 implemented: Draft hard delete now guards against non-owned/not-ticket/non-image/already-referenced artifacts before deletion. - (2026-03-01) F019 implemented: Clipboard upload path now validates image MIME prefix and maximum file size before upload begins. - (2026-03-01) F020 implemented: Non-image paste/plain-text behavior remains on existing path; clipboard image logic is isolated to image upload flow only. - (2026-03-01) F021 implemented: Clipboard image paste flow is now rollout-gated behind `ticket-comment-clipboard-images` feature flag in ticket details. - (2026-03-01) F022 implemented: Added client/server observability logs for upload outcomes, cancel keep/delete actions, and outbound CID vs fallback outcomes. - (2026-03-01) T001 implemented: `clipboardImageUtils.test.ts` verifies image MIME clipboard items are extracted while non-image entries are ignored. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T001): cover clipboard image MIME filtering"- (2026-03-01) T002 implemented: `clipboardImageUtils.test.ts` verifies multi-image clipboard extraction preserves deterministic input order for per-image upload jobs. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T002): verify deterministic multi-image upload ordering"- (2026-03-01) T003 implemented: added `TicketConversation.clipboard.contract.test.ts` asserting upload is wired via editor `uploadFile` callback and that submit handler does not invoke `uploadDocument`, enforcing immediate pre-submit upload flow. EOF && git add packages/tickets/src/components/ticket/TicketConversation.clipboard.contract.test.ts ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T003): assert immediate clipboard upload wiring"- (2026-03-01) T004 implemented: contract coverage verifies pasted-image upload path appends file to `FormData` and calls `uploadDocument` with `ticketId` scope. - (2026-03-01) T005 implemented: `clipboardImageUtils.test.ts` validates deterministic clipboard filename output format. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T005): verify deterministic clipboard filename pattern"- (2026-03-01) T006 implemented: contract coverage confirms `uploadFile` wiring in `TicketConversation` so BlockNote can render in-flight upload placeholder UI for pasted images. - (2026-03-01) T007 implemented: contract test validates successful upload return payload includes attachment-backed URL, enabling placeholder replacement with persisted image node. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T007): assert placeholder success replacement payload"- (2026-03-01) T008 implemented: contract test covers explicit upload error throws for validation and upload failures, feeding editor error affordances. - (2026-03-01) T009 implemented: contract assertion validates failure propagation strategy that enables editor retry path to re-run upload callback after prior failure. - (2026-03-01) T010 implemented: added contract assertions that pasted-image serialization path emits attachment-serving URLs (`/api/documents/view/...`) and does not embed `data:image` payload content. - (2026-03-01) T011 implemented: `blocknoteUtils.image.test.ts` now includes non-image paragraph conversion regression assertions for markdown + HTML outputs. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T011): add non-image serialization regression assertions"- (2026-03-01) T012 implemented: added `CommentItem.clipboardImage.contract.test.ts` verifying saved BlockNote JSON is parsed and rendered through `RichTextViewer` for inline comment image display. - (2026-03-01) T013 implemented: route contract coverage asserts ticket-associated document access checks include guard branch for users without resolved ticket/contact scope. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T013): cover unauthorized ticket-image route guard branch"- (2026-03-01) T014 implemented: route contract test asserts ticket-associated branch grants access (`hasPermission = true`) when contact/client scope matches associated ticket. - (2026-03-01) T015 implemented: `ticketCommentInlineImageEmail.test.ts` verifies comment image attachment URLs are collected/mapped for outbound email composition. - (2026-03-01) T016 implemented: outbound inline-image unit test asserts one CID attachment is produced per converted comment image. - (2026-03-01) T017 implemented: outbound inline-image test asserts HTML image `src` rewrite from document-view URL to matching `cid:` reference. - (2026-03-01) T018 implemented: outbound inline-image test covers storage/CID failure fallback retaining original attachment URL in HTML. - (2026-03-01) T019 implemented: contract test now verifies draft clipboard image state tracking and document/file identity capture during upload. - (2026-03-01) T020 implemented: contract assertions verify cancel action opens keep-vs-delete prompt when draft includes uploaded clipboard images. EOF && git add ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/tests.json ee/docs/plans/2026-03-01-ticket-comment-clipboard-image-attachments/SCRATCHPAD.md && git commit -m "test(T020): verify cancel prompt for uploaded draft images"- (2026-03-01) T021 implemented: contract coverage validates keep path handler clears draft state and closes editor without invoking hard-delete action. - (2026-03-01) T022 implemented: added `clipboardImageDraftActions.contract.test.ts` validating hard-delete loop invokes `deleteDocument` for vetted draft artifacts. - (2026-03-01) T023 implemented: draft-delete contract test asserts saved-comment reference guard (`already_referenced`) based on comment payload token scan. - (2026-03-01) T024 implemented: draft-delete contract test validates permission-denied guard when requester lacks document delete permission. - (2026-03-01) T025 implemented: `clipboardImageUtils.test.ts` covers unsupported MIME validation rejection before upload starts. - (2026-03-01) T026 implemented: `clipboardImageUtils.test.ts` validates max-size guard rejects oversized clipboard images with explicit error. - (2026-03-01) T027 implemented: `TicketDetails.clipboardFlag.contract.test.ts` asserts dedicated feature flag default-off gating for clipboard image paste flow. - (2026-03-01) T028 implemented: feature-flag contract coverage verifies enabled state wires `uploadFile` callback to conversation editor flow. - (2026-03-01) T029 implemented: contract assertions verify upload logs include ticket/user identifiers plus uploaded document/file artifact identity. - (2026-03-01) T030 implemented: contract logging coverage verifies cancel keep/delete actions include selected action and delete-result counts/failure context. - (2026-03-01) T031 implemented: `ticketEmailSubscriber.inlineImageLogging.contract.test.ts` asserts per-image log outcome includes CID vs URL fallback strategy and reason fields. - (2026-03-01) T032 implemented: added `TicketClipboardFlow.e2e.contract.test.ts` to validate paste-upload-save-render wiring from editor upload callback through comment viewer rendering. - (2026-03-01) T033 implemented: clipboard-flow contract test covers cancel delete invocation with selected document IDs and editor/draft state teardown. - (2026-03-01) T034 implemented: outbound-email contract test verifies subscriber rewrites comment HTML and passes generated inline CID attachments on send path. - (2026-03-01) T035 implemented: outbound-email fallback contract test validates URL-preserving behavior when CID conversion fails.