Repository navigation
Receive capture attachments on the desktop Inbox - #698
Merged
Merged
Conversation
A phone capture that names files (local ADR 0014) now lands as an Inbox
item that carries them. The intake records each file's metadata and saves
the item before fetching any byte, then downloads each blob through
GET /api/sync/attachments/{id} into _inbox/attachments/<itemId>/,
sha256-verified. A failed fetch or save is recorded on that file and the
capture is still received, so a file never stalls a sync page; a replay
fetches only files never tried, and a failed one waits for Retry.
- InboxAttachment value object owned by InboxItem, persisted in the new
additive inbox_item_attachments table (local ADR 0006)
- Ports IInboxAttachmentSource (sync GET) and IInboxAttachmentFiles
(workspace folder), answered in Infrastructure.Sync / .FileSystem
- ContentKindDetector: pictures only is image, any other file is document
- Detail pane: thumbnails above the body, file rows with Open / Retry
- Routing hands the item's folder to the task as its AttachmentPath
- Devbook: Attachment in the inbox domain chapter, domain.invariants.md,
requirements.md and the Capture attachments feature
Backlog plan item desktop-attachment-intake (+mobile-inbox-slice).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the two conflicts with the Inbox multi-select work (#701): InboxErrors keeps both the attachment errors and ItemAlreadyDecided, and requirements.md keeps both chapters (Act on several at once, then Capture attachments) under one header. BatchTests constructs InboxItems by hand, so it now names the three attachment handlers as unwired, the way it already leaves the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#694 moved the Inbox aggregates' invariant tables into a new domain.invariants.md, the same file this branch created for the attachment invariants. The merged page keeps main's header and its 22 invariants, and adds the 7 attachment invariants to the Inbox Item chapter. domain.md keeps this branch's related and tests lists, which include main's, and no longer says the older rules are still in a table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JSdotNet
enabled auto-merge
September 26, 2026 23:15
…ment-intake-214d32 # Conflicts: # src/Modules/Inbox/Backlog.Modules.Inbox.UI/InboxDesktopState.cs # src/Modules/Inbox/Backlog.Modules.Inbox/Extensions/InboxModuleRegistration.cs # src/Modules/Inbox/Backlog.Modules.Inbox/Services/InboxItems.cs
…ment-intake-214d32 # Conflicts: # src/Modules/Inbox/Backlog.Modules.Inbox.Abstractions/DataTransferObjects/InboxCaptureDto.cs # src/Modules/Inbox/Backlog.Modules.Inbox/Features/ReceiveCapture/ReceiveCaptureCommand.cs
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A phone capture that names files (local ADR 0014) now arrives on the desktop Inbox as one item that carries them. The item records each file's details the moment the capture lands. It then downloads the bytes through
GET /api/sync/attachments/{id}into<workspace>/_inbox/attachments/<itemId>/and checks each one's sha256. Pictures show as thumbnails above the body, and every other file gets a row with Open. A file that failed shows its reason and a Retry button.A failed download is recorded on that file, and the capture still counts as received. A file can never stall a sync page. A replayed page downloads nothing twice: it fetches only files never tried, and a failed file waits for Retry.
Changes
InboxAttachmentvalue object, owned byInboxItem.RecordAttachmentsadds by id only.MarkAttachmentDownloaded/MarkAttachmentFailedkeep a file either on disk or failed, never both.AttachmentFileNamegives each file a stable, file-system-safe name.IInboxAttachmentSourceandIInboxAttachmentFiles. Both are optional: a head without them records the files and leaves them waiting.ReceiveCaptureCommandrecords the files, saves the item, then downloads throughInboxAttachmentDownloads. That step never throws, and it skips a file already on disk with the right sha256.RetryAttachment,ReadAttachmentandOpenAttachmentslices, exposed onIInboxItems.ContentKindDetector: a capture that is only pictures isimage; any other file makes itdocument.RouteToBackloghands the item's folder to the task as itsAttachmentPath.inbox_item_attachmentstable, additive per ADR 0006. It is written in the same transaction as the item row.InboxAttachmentSyncSource: a typed client on the token pipeline, registered byAddTaskSyncClient.SyncHttpgains an overload for non-JSON bodies.WorkspaceInboxAttachmentFiles, registered byAddInboxCrossContextAdapters..partialfile and a move, and cleans the.partialup on failure.CreateNoWindow.TaskReplicaMerge.ToCapturepasses the attachment details through to the intake.InboxBacklogTargetwrites the attachment withEntryTextParser.WithAttachment.InboxItemDetail.razorshows the thumbnail strip and the file rows (Open / Retry and reason / "Waiting to download"). The styles inapp.cssuse tokens only..devbook/domain/inbox/)Attachmentchapter and the term attachment folder indomain.md.domain.invariants.mdwith 7 invariants.requirements.mdwith 8 requirements.features.md.Testing
dotnet build Backlog.slnis green, mobile head included.dotnet testflaps in this repo.Alert, and a missingCreateNoWindow.AttachmentIntakeTests, 9 invariant tests inInboxAttachmentTests, and 4 kind-detection tests.InboxPaneTests.node .devbook/_tools/devbook-meta/build.mjs --checkandnode tools/devbook/build-database.mjs --checkboth pass.Verification (
devbook:verify-change, after opening)All 7 acceptance criteria and 21 of 24 chapter rows are aligned. Nothing is spec-ahead, in conflict or unresolved. Every chapter is
status: draft.InboxDesktopState.ThumbnailMaxBytes). A larger one becomes a file row. The chapter doesn't mention the capcapture-specscapture-specstestslinkscapture-specsName/Sha256Related
desktop-attachment-intakeof plan+mobile-inbox-slice, afterattachment-store-service(Add the attachment store to the sync service and the AppHost (ADR 0014) #668). No GitHub issue.Follow-ups, left out on purpose:
model.mdandflow.mddiagrams don't show attachments.domain.md.Checklist
build\stop-aspire-before-pr.ps1)🤖 Generated with Claude Code