Text/code attachments (markdown, JSON, .ts, .log, …) need an attachment id
to render through `/api/attachments/{id}/content`. The composer pipeline
was dropping that id at the upload-hook boundary, so the Eye preview gate
only fired for media (PDF / video / audio via filename fallback).
- `useFileUpload` now returns the full `Attachment` (with `link` kept as a
`url` alias) so editor providers can resolve content-type and id.
- New-comment and reply composers hold a `pendingAttachments` state and
feed it to `ContentEditor`; the active subset (those still referenced in
the markdown) is sent on submit as before.
- Comment edit modes (CommentRow + CommentCardImpl) merge pending uploads
with `entry.attachments` for the editor and pipe `attachment_ids` into
`onEdit` so newly uploaded files actually bind to the comment.
- Issue description editor pushes pending `attachment_ids` on every
debounced save and invalidates `issueKeys.attachments` so the preview
Eye survives a refresh.
- `UpdateComment` and `UpdateIssue` handlers accept `attachment_ids` and
call the existing `linkAttachmentsByIDs` / `linkAttachmentsByIssueIDs`
helpers; the bind is idempotent so re-sending an existing id is safe.
Closes MUL-2153.
Co-authored-by: multica-agent <github@multica.ai>
* docs(plans): chat attachment & image support implementation plan
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(db): add chat_session_id/chat_message_id to attachment
Co-authored-by: multica-agent <github@multica.ai>
* feat(db): sqlc — chat_session_id on CreateAttachment + LinkAttachmentsToChatMessage
Co-authored-by: multica-agent <github@multica.ai>
* feat(file): upload-file accepts chat_session_id form field
Co-authored-by: multica-agent <github@multica.ai>
* feat(chat): SendChatMessage links uploaded attachments to the new message
Co-authored-by: multica-agent <github@multica.ai>
* feat(api): uploadFile accepts chatSessionId; sendChatMessage accepts attachmentIds
Co-authored-by: multica-agent <github@multica.ai>
* feat(core): useFileUpload supports chatSessionId context
Co-authored-by: multica-agent <github@multica.ai>
* feat(chat): support paste/drag/upload attachments in chat input
Co-authored-by: multica-agent <github@multica.ai>
* test(e2e): chat input attachment upload + send round-trip
Co-authored-by: multica-agent <github@multica.ai>
* chore(chat): keep lazy-created session title empty so untitled fallback localizes
Co-authored-by: multica-agent <github@multica.ai>
* fix(chat): address review — dedupe ensureSession + parse upload response
- chat-window: cache in-flight createSession promise in a ref so a file drop
followed by a quick send no longer spawns two sessions (and orphans the
attachment on the losing one).
- Attachment type + EMPTY_ATTACHMENT + AttachmentResponseSchema: include the
new chat_session_id / chat_message_id fields the server now returns.
- uploadFile: route the response through parseWithFallback so a malformed
body returns EMPTY_ATTACHMENT instead of an undefined-keyed Attachment,
matching the API boundary rule.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(chat): address PR #2445 review — test ctx, send gating, attachment surface
1. Backend test was 400ing because the handler reads workspace from
middleware-injected ctx, and `newRequest` only sets the header. Helper
`withChatTestWorkspaceCtx` mirrors the agent-access-test pattern and
loads the member row + SetMemberContext before invoking the handler.
2. Attachment metadata now flows end-to-end:
- new sqlc `ListAttachmentsByChatMessageIDs` (batch lookup, mirrors the
comment-side query)
- `chatMessageToResponse` takes `attachments` and `ChatMessageResponse`
surfaces them — same shape as CommentResponse
- `ListChatMessages` loads them via a new `groupChatMessageAttachments`
helper so the chat bubble can render file cards
- daemon claim path pulls `ListAttachmentsByChatMessage` for the latest
user message and ships `ChatMessageAttachments` to the daemon
- `buildChatPrompt` lists id+filename+content_type and instructs the
agent to `multica attachment download <id>` — fixes the private-CDN
expiring-URL problem where the markdown URL would have expired by
the time the agent acts
- TS `ChatMessage` gains an optional `attachments` field
3. Chat composer now blocks send while uploads are in flight:
- `pendingUploads` counter increments in handleUpload, SubmitButton
uses it to disable
- handleSend also gates on `editorRef.current.hasActiveUploads()` to
catch the Mod+Enter path that bypasses the button
- new vitest covers the "drop large file → immediate send" scenario
where attachment id would otherwise be silently dropped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* chore: drop implementation plan doc
Process artefact, not something the repo needs to keep.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>