Skip to content

perf(app): fix composer lag via buffered blob draft storage - #40207

Merged
Hona merged 2 commits into
anomalyco:devfrom
Hona:draft-persistence
Aug 3, 2026
Merged

perf(app): fix composer lag via buffered blob draft storage#40207
Hona merged 2 commits into
anomalyco:devfrom
Hona:draft-persistence

Conversation

@Hona

@Hona Hona commented Aug 3, 2026

Copy link
Copy Markdown
Member

User-Facing Impact

  • No composer typing lag: Replaces synchronous per-keystroke JSON writes with debounced persistence.
  • Lower disk footprint: Content-addressed image blobs replace duplicated inline base64 strings across drafts and history.

Implementation & Code References

  • Targeted routing: Routes only prompt drafts and history through DraftStore in packages/app/src/utils/persist.ts:634; all other persisted() consumers remain unchanged.
  • Buffered SQLite WAL / IndexedDB: Desktop writes are debounced by 500ms to SQLite WAL (packages/desktop/src/main/draft-store.ts:56) with SHA-256 BLOB storage (packages/desktop/src/main/draft-store.ts:68). Browser uses IndexedDB (packages/app/src/utils/draft-store.ts:114).
  • Late base64 encoding: Attachments store { blob: { id, url } } references in prompt state; images are encoded to data URLs only at submission (packages/app/src/components/prompt-input/submit.ts:530).
  • Startup GC & shutdown flush: Sweeps unreferenced blobs during initialization (packages/desktop/src/main/draft-store.ts:36, packages/app/src/utils/draft-store.ts:128) and flushes pending desktop writes on quit/relaunch (packages/desktop/src/main/ipc.ts:131).

@Hona
Hona requested a review from Brendonovich as a code owner August 3, 2026 03:05
Copilot AI review requested due to automatic review settings August 3, 2026 03:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors prompt draft + history persistence to avoid storing base64 image payloads in persisted state by introducing a dedicated draft store with content-addressed blobs (SQLite WAL on desktop, IndexedDB on web), and only encoding images to data URLs at API request time.

Changes:

  • Replaces persisted dataUrl image attachments with { blob: { id, url } } references across app + session-ui prompt inputs.
  • Adds a cross-platform draft store (browser IndexedDB + desktop SQLite/Drizzle) and lazy migration from legacy persisted drafts/history.
  • Updates prompt submission to base64-encode images on-demand via blobDataUrl(...), plus adds tests for migration + desktop draft storage.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/session-ui/src/v2/components/prompt-input/types.ts Updates attachment typing from dataUrl to blob reference.
packages/session-ui/src/v2/components/prompt-input/store.test.ts Updates prompt-input store tests to use blob references.
packages/session-ui/src/v2/components/prompt-input/prompt-input.stories.tsx Updates Storybook examples to use blob references.
packages/session-ui/src/v2/components/prompt-input/index.tsx Renders attachment previews from attachment.blob.url.
packages/session-ui/src/v2/components/prompt-input/attachments.ts Switches attachment ingestion from data URLs to blob references (SHA-256 id + object URL).
packages/desktop/src/renderer/index.tsx Wires desktop draftStore into the platform layer using preload IPC APIs.
packages/desktop/src/preload/types.ts Adds draft store IPC methods to the preload API typing.
packages/desktop/src/preload/index.ts Exposes draft store IPC methods via ipcRenderer.invoke.
packages/desktop/src/main/ipc.ts Registers IPC handlers for draft documents + blob storage.
packages/desktop/src/main/index.ts Uses app.quit() instead of app.exit(0) for shutdown paths.
packages/desktop/src/main/draft-store.ts Implements a buffered SQLite WAL draft store + content-addressed blob table.
packages/desktop/src/main/draft-store.test.ts Adds a test covering draft buffering flush + blob storage.
packages/desktop/package.json Adds drizzle-orm dependency for the desktop draft store.
packages/app/test-browser/prompt-persistence.test.ts Adds browser tests for legacy image migration + non-overwrite behavior.
packages/app/src/utils/prompt.ts Converts extracted SDK file parts to legacy blob references.
packages/app/src/utils/prompt.test.ts Updates tests to assert blob references instead of inline data URLs.
packages/app/src/utils/persist.ts Adds draft targeting + draft-store routing and migration race avoidance.
packages/app/src/utils/draft-store.ts Introduces DraftStore abstraction with blob migration + encode/decode.
packages/app/src/index.ts Re-exports createDraftStore / DraftStore.
packages/app/src/entry.tsx Installs browser draft store on the web platform.
packages/app/src/context/tabs.tsx Ensures prompt drafts are removed via draft persistence target.
packages/app/src/context/prompt-state.ts Updates image attachment parts to store blob references.
packages/app/src/context/platform.tsx Adds draftStore?: DraftStore to the platform contract.
packages/app/src/components/prompt-input/submit.ts Encodes blob references into data URLs at submit/send time.
packages/app/src/components/prompt-input/image-attachments.tsx Uses blob URLs for image preview rendering.
packages/app/src/components/prompt-input/history.test.ts Updates history cloning test fixtures for blob references.
packages/app/src/components/prompt-input/history-store.ts Persists history via draft store and defers writes until init completes.
packages/app/src/components/prompt-input/build-request-parts.ts Adjusts request builder input typing to require encoded dataUrl images.
packages/app/src/components/prompt-input/attachments.ts Stores new attachments as blobs when draft store is available.
packages/app/src/components/prompt-input.tsx Uses blob URLs for image preview dialog rendering.
packages/app/src/components/prompt-input-v2.tsx Routes v2 attachment storage through platform.draftStore.putBlob.
bun.lock Locks the added drizzle-orm dependency.
Suppressed comments (1)

packages/app/src/utils/persist.ts:668

  • draftLatest is only updated on setItem. If removeItem happens while a legacy migration is in-flight, getItem can still call current.setItem(key, draftLatest) later and resurrect the deleted draft. Track deletions and force-remove after migration completes when needed.
        if (draftLatest === undefined) {
          if (draft && migrated !== null) return (await current.getItem(key)) ?? migrated
          return migrated
        }
        await current.setItem(key, draftLatest)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +138
ipcMain.handle("draft-blob-get", (_event, id: string) => {
const data = drafts.getBlob(id)
return data ? Uint8Array.from(data).buffer : null
})
Comment on lines +14 to +18
const urls = new Map<string, string>()

function blobUrl(id: string, blob: Blob) {
const existing = urls.get(id)
if (existing) return existing
Comment on lines +224 to +228
async function blobReference(file: File) {
const id = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", await file.arrayBuffer())))
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
return { id, url: URL.createObjectURL(file) }
]
.filter((x) => !!x)
.map(toAsyncStorage)
let draftLatest: string | undefined
@Hona Hona changed the title fix(app): persist prompt drafts without base64 perf(app): fix composer lag via buffered blob draft storage Aug 3, 2026
@Hona Hona added the beta label Aug 3, 2026
opencode-agent Bot added a commit that referenced this pull request Aug 3, 2026
opencode-agent Bot added a commit that referenced this pull request Aug 3, 2026
opencode-agent Bot added a commit that referenced this pull request Aug 3, 2026
opencode-agent Bot added a commit that referenced this pull request Aug 3, 2026
@Hona
Hona merged commit 6ff0ade into anomalyco:dev Aug 3, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants