mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
* feat(chat): workspace-scoped attachment binding + fire-and-forget send Uploads are now workspace-scoped: the chat session is created and attachments are bound to the message at send time, so a paste/drop no longer creates an empty session the user never sends. - LinkAttachmentsToChatMessage returns the ids it actually bound; the client diffs requested-vs-bound and warns on partial bind, replacing an extra listChatMessagesPage fetch. - Cancelling an empty chat task detaches attachments before deleting the user message (attachment FK is ON DELETE CASCADE) and returns them via cancelled_chat_message.attachments, so a restored draft can re-bind. - SendChatMessageResponse.attachment_ids has no omitempty: "requested but bound zero" serializes [] so the client can tell it apart from an older server and still warn. - Send is fire-and-forget: it no longer steals focus when the user has navigated to another session (guarded on the live store + new-chat agent id); the reply surfaces via the unread dot. commitInput gets clearEditor so a navigated-away commit doesn't wipe the editor now showing another session, while still clearing the sent draft's data. - Draft restore is session-aware so a failed fire-and-forget send restores into the session it was sent from, never the one the user moved to. - Removed the now-unreferenced migrateInputDraft store action. Verified: core/views typecheck, chat-input (15) / store (3) / api client (24) unit tests, go build + vet, handler SendChatMessage + CancelTaskByUser DB tests. Full make check / E2E left to CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(chat): guard attachment survival on empty-chat cancel Cancelling an empty chat task deletes the user message, and attachment.chat_message_id is ON DELETE CASCADE (migration 083), so the detach-before-delete in finalizeCancelledChatMessage is the only thing keeping the user's attachment from being silently destroyed. Nothing covered it. Add a DB regression test that binds an attachment to the cancelled user message and asserts: the row survives the cascade (chat_message_id NULL, chat_session_id retained), the cancel response returns it via cancelled_chat_message.attachments, and a resend re-binds it to the new message. Verified red when the detach step is removed. Related issue: MUL-3364 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(comment): pessimistic submit for comment/reply composers The comment and reply composers cleared the editor after `await onSubmit` returned, with no in-flight lock. On a slow send the WS `comment:created` event already dropped the real comment into the timeline while the box still held the same text + spinner, so it read as two comments. And because `submitComment`/`submitReply` swallow errors (toast, no rethrow), a failed send still reached `clearContent` and silently discarded the user's draft. Recover the comment/reply portion of the closed #4236: make the submit callback resolve a success boolean (true on success, false on the caught failure), lock the editor while in flight (pointer-events-none + dimmed wrapper + aria-busy, since ContentEditor can't toggle Tiptap `editable` post-mount), keep the button spinning, and clear only on success — a failed send keeps the draft. Chat composer is out of scope (already reworked on this branch); attachment binding is untouched. Adds two view tests (in-flight lock then clear-on-success; failed send keeps the draft); both verified red against the un-fixed code. Related issue: MUL-3364 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>