End-to-end attachment support across CLI, desktop, and sync — local-first asset storage with content-addressed naming, logical URI references in markdown, and out-of-band binary sync for linked projects.
Insert and display image attachments inside issue descriptions and comments from both CLI and desktop.
Assets stored on the local filesystem by default under .fp/assets/ with content-addressed SHA-256 naming.
Sync binary assets to remote object storage independently of Yjs issue sync frames — no binary blobs in WebSocket protocol.
Use fp-asset://<id> logical URIs in markdown. Shared resolver renders consistently across all surfaces.
Binary files stored under .fp/assets/ with SHA-256 hash filenames and MIME/size metadata in SQLite.
fp-asset://<asset-id> replaces raw file:// URLs in markdown for portable, resolver-agnostic references.
Local and remote tables track which issues/comments reference which assets — driven from Y.Doc state, not command handlers.
Project-scoped upload/download via Worker routes backed by R2 object storage. Separate from WebSocket Yjs protocol.
Asset reconcile pass runs after issue sync converges. Upload/download queue handles races and retries independently.
One resolver pipeline for fp-asset:// refs across desktop, fp-ui review, and console — no per-surface forks.
graph TD
subgraph LOCAL["Local Storage"]
ASSETS[".fp/assets/\nContent-Addressed Files"]
SQLITE["SQLite\nMetadata + Ref Indexes"]
end
subgraph MARKDOWN["Markdown Layer"]
URI["fp-asset:// URIs\nin Issue/Comment Markdown"]
end
subgraph RESOLVER["Shared Resolver"]
PIPE["Attachment-Aware\nMarkdown Pipeline"]
end
subgraph SURFACES["Rendering Surfaces"]
CLI["CLI"]
DESKTOP["Desktop"]
CONSOLE["Console Web"]
end
subgraph SYNC["Asset Sync Layer"]
QUEUE["Reconcile Queue"]
API["Worker Asset API\nAuthenticated Routes"]
R2["R2 Object Storage"]
end
ASSETS --> URI
SQLITE --> URI
URI --> PIPE
PIPE --> CLI
PIPE --> DESKTOP
PIPE --> CONSOLE
ASSETS -.->|upload| QUEUE
QUEUE -->|HTTP| API
API --> R2
R2 -->|download| QUEUE
QUEUE -.->|hydrate| ASSETS
classDef localStyle fill:#22d3ee11,stroke:#22d3ee44,stroke-width:2px
classDef mdStyle fill:#34d39911,stroke:#34d39944,stroke-width:2px
classDef resolverStyle fill:#fbbf2411,stroke:#fbbf2444,stroke-width:2px
classDef surfaceStyle fill:#a78bfa11,stroke:#a78bfa44,stroke-width:2px
classDef syncStyle fill:#f4728011,stroke:#f4728044,stroke-width:2px
class ASSETS,SQLITE localStyle
class URI mdStyle
class PIPE resolverStyle
class CLI,DESKTOP,CONSOLE surfaceStyle
class QUEUE,API,R2 syncStyle
Lock down asset identity, local storage layout, and logical markdown URI rules. Build shared rendering and derived-data machinery once.
.fp/assets/ layout with content-addressed namingfp-asset:// URI syntax definitionfp-asset:// refshttp(s) images pass through unchangedLet users ingest local images and place logical attachment refs into issue/comment markdown. Local-only projects work without sync.
fp attach <file> command — ingest, hash, print markdown reference--attach flag on issue create/update/commentfp-asset:// URIsfp-asset:// and http(s) refsfp attachKeep binary asset movement outside Yjs frames. Make linked projects converge attachment bytes independently of markdown state.
fp project syncClose the loop on correctness and operability. Enforce validation, define GC, add recovery, run final regression.
packages/qa-scripts/results/