feat(comments): files on a plan's comments, kept in the shared attachment table
Merged
DA
5d ago+16367+16367+01234567890123456789012345678901234567890123456789-74-74-01234567890123456789
Asked for: attachment support in plan comments, kept in a shared attachment database.
What changes
- Storage.
attachment(the shared table that chat, inbox, plan, and conversation files already live in) gains one more owner: a nullableplan_comment_idcolumn that referencesplan_commentwithON DELETE CASCADE. It is added toownerColumns, soattachment_one_owner,ownedByTaskAlone, and theattachment_unsavedindex all count it. A comment's file therefore never counts as a plan picture, toward the plan's picture bound, or in the unsaved-upload sweep.- A new check,
attachment_plan_comment_file, requires each comment file to name its task and workspace, a name, and a position, and holds it to a file's bound (3 MB). - The migration
20261020224552_attachment_add_plan_commentis idempotent. Its lock order and the reasoning behind it are commented at the top.
- A new check,
- Wire.
- A start and a reply can carry
attachmentsin the coding agent's file shape (PNG, JPEG, WebP, PDF, or text), held to the same bounds as a message to an agent. The field is optional, so older clients send what they always did. - Body caps grow by the files' bound.
- Each entry names its files as
{ attachmentId, name, mediaType }.
- A start and a reply can carry
- Store. A comment's files are written in the same transaction as the comment, carrying the plan's id and workspace, so they move with the task the way inbox files do.
readPlanCommentFilereads one back for a member of the workspace. - Serving.
/api/plans/{id}/attachments/{attachment}first looks for a plan picture and, if none has that id, serves a comment file of the same plan. Membership is gated the same way and the bytes headers are the same. Nothing new is routed. The host's plan-attachment read now accepts any type a coding agent's file may be. - Window.
- A comment or reply field has a + picker, takes pasted pictures, and accepts dropped files. Held files show as chips above the words.
- A sent comment draws its files under its words as chips: pictures as thumbnails, other files by type icon. Each chip opens in the viewer.
- Agents.
read_commentsgives the names and types of each comment's files.
Words are still required on a comment that carries files.
Screenshot
A comment with a picture and a PDF under its words, and a reply field holding a text file. Rendered from the panel's own component tree with the panel's stylesheet:
Tests
packages/database/.../plan-comments.test.ts: a comment's files are named on its entry in order, read by members only, are not plan pictures, and go with the comment.apps/web/tests/plan-attachments.test.ts: a comment's file is served at the plan's attachment path, with its type and the sandboxing headers, to members only.packages/panel/.../plan-comments.test.ts: a picked file is sent with the reply, and a comment's files are drawn and read through the plan's attachment read.apps/web/tests/plan-comments.test.ts: the reply-cap test was raised to match the new cap.
Not in this PR
- An agent sees a comment file's name, not its contents.
- A thread handed to an agent by an @mention carries the comment's words, not its files.
Made with Luke