跪拜 Guibai
← All articles
Frontend · AIGC

How IndexedDB Gives an AI Chat App Offline Memory Without Breaking Server Authority

By 倾颜 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Rich AI interfaces that display tool outputs, agent reasoning, and generated artifacts are becoming common, but they break the simple "save text to a database" model. This architecture shows how to add local-first persistence that feels instant without letting the cache silently become the AI's context source or bypass server ownership checks.

Summary

AI Mind, a Next.js chat application that visualizes tool calls, Agent steps, and artifacts, solved the problem of losing complex chat UI state on page refresh. The solution uses browser IndexedDB to store complete local snapshots of recent conversations, enabling instant UI restoration before the server even responds. A strict three-layer data model keeps local display snapshots, server-side conversation identity (Registry), and AI short-term context (ThreadState) from interfering with each other.

A stable snapshot projection filters out incomplete streaming data, pending Agent interrupts, and control signals, ensuring only fully rendered content is persisted. The recovery flow is local-first: the UI renders from IndexedDB immediately, then the server's response performs an authoritative calibration of the conversation list without touching local message snapshots. When the server is unavailable, the app enters a read-only cache state where history is viewable but all mutations are blocked.

Concurrent writes across tabs use revision-based optimistic locking, and a unified deletion flow cleans up the server Registry, ThreadState, and local snapshot together. The implementation required no new server-side database tables and only two minimal API adjustments: an explicit 503 error code for unavailable ThreadState and a new DELETE endpoint.

Takeaways
IndexedDB stores complete UI snapshots of the 10 most recent conversations, enabling instant restoration on page refresh before any server request completes.
Three separate data layers—local snapshot, server Conversation Registry, and server ThreadState—each have one job and never merge or substitute for each other.
A stable snapshot projection filters out streaming half-output, failed requests, pending Agent interrupts, and control signals, so only fully rendered content is ever persisted.
Recovery follows a local-first pattern: render from IndexedDB immediately, then let the server response calibrate the conversation list without touching local message data.
Server unavailability triggers a read-only cache state where history is viewable but sending, switching, and creating are all blocked.
Concurrent writes to the same conversation use revision-based optimistic locking; older revisions cannot overwrite newer ones.
Deleting a conversation cleans up the server Registry, ThreadState, and local IndexedDB snapshot together, but local data is never purged if the server operation fails.
Single snapshots cap at 120 messages, trimming oldest complete messages first; storage quota exhaustion triggers silent degradation without blocking the chat flow.
Conclusions

Treating the local cache as a UI-only concern—never letting it feed into the AI's context—is a deliberate boundary that prevents a common architectural leak in AI applications.

Returning an explicit 503 error code for unavailable ThreadState, rather than a generic empty success response, lets the frontend choose between two different degradation paths instead of guessing.

The decision to allow inconsistency between local snapshots and server ThreadState, rather than blocking sends until they match, prioritizes chat availability over perfect sync—a pragmatic tradeoff for a local-first experience.

Deleting ThreadState before the Registry entry on the server, even without a cross-table transaction, minimizes the worst-case residual state: orphaned AI context data is worse than a missing registry entry.

Concepts & terms
Stable Snapshot Projection
A filtering layer that extracts only fully rendered, completed messages from a live chat stream for persistence, excluding streaming half-output, failed requests, and control signals like AgentInterrupt.
Bounded Hydration
A server response that returns only a limited window of recent plain-text conversation turns for AI context restoration, deliberately excluding rich UI components like tool call results and agent execution traces.
Conversation Registry
A server-side table that tracks conversation identity, ownership, and metadata for the 10 most recent conversations, serving as the authoritative source for which conversations exist and belong to a user.
ThreadState
Server-side short-term memory for the AI runtime, containing bounded recent text, summaries, and pinned decisions, but not the complete chat history or rich UI display components.
Revision-based Optimistic Locking
A concurrency control where each IndexedDB write carries a monotonically increasing revision number; a write is rejected if its revision is lower than the currently stored version, preventing stale data from overwriting newer state.
From the discussion
Featured comments
Orion淇淋

Thanks for the article, host. I followed your approach and built something similar: https://github.com/webos2019/ai-pi-agten. If you have time, please review it.

倾颜

The approach looks fine, but your project architecture could be optimized. If you're separating frontend and backend, tidy up the architecture and layer the file structure. You can have AI do the architecture design for you. If the article project helped, feel free to drop a star [rose].

Orion淇淋  → 倾颜

No problem, thanks. I'll refactor it into an event-driven agent later.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗