How the Draft-Store in Pi-Web Persists Local Drafts Across Sessions

The draft-store in pi-web relies on an in-memory JavaScript Map that temporarily holds chat drafts during the current browser session, automatically clearing all data when the page reloads or the user navigates away.

The draft-store module in the agegr/pi-web repository provides lightweight, transient storage for unsent chat messages. Located in lib/draft-store.ts, this utility allows components like ChatInput.tsx to preserve text and image attachments while the user composes messages. However, unlike traditional persistence mechanisms, this store does not utilize localStorage, IndexedDB, or server-side databases to retain data across separate browser sessions.

How the Draft-Store Implements Session-Scoped Storage

The In-Memory Map Architecture

At the core of the persistence layer lies a simple JavaScript Map instantiated at the module level in lib/draft-store.ts:

const drafts = new Map<string, ChatDraft>();

This drafts object lives in the JavaScript heap for the lifetime of the page context. The store exports three primary functions—getDraft, setDraft, and clearDraft—that interact with this private map. No serialization to disk or browser storage APIs occurs during these operations.

Draft Retrieval and Cloning

The getDraft function retrieves stored drafts by key while preventing accidental mutations through the internal cloneDraft utility:

const saved = getDraft(draftKey) ?? { value: "", images: [] };

When getDraft(key) is called, it returns a clone of the stored ChatDraft object (or null if no draft exists). This defensive copying ensures that modifications to the returned object do not affect the internal map state until explicitly saved via setDraft.

Why Drafts Don't Persist Across Page Reloads

Because the storage mechanism relies entirely on in-memory data structures, all draft data is lost when the browser context terminates. When a user refreshes the page or closes the tab, the JavaScript execution environment destroys the drafts map. Upon reloading, the module re-initializes an empty Map, resulting in no restoration of previous session data. This design prioritizes privacy and performance for temporary composition states rather than long-term storage.

Working with the Draft-Store API

Reading Drafts with getDraft

Components access existing drafts using unique keys, typically constructed from session identifiers:

import { getDraft } from "@/lib/draft-store";

const draftKey = `chat-${sessionId}`;
const draft = getDraft(draftKey);

if (draft) {
  console.log("Restored draft:", draft.value);
}

Saving Drafts with setDraft

To persist changes during the active session, components pass the key and a ChatDraft object containing text and optional images:

import { setDraft, type ChatDraftImage } from "@/lib/draft-store";

setDraft(draftKey, {
  value: textareaValue,
  images: attachedImages as ChatDraftImage[],
});

Automatic Cleanup of Empty Drafts

The setDraft function includes intelligent garbage collection. When called with an empty draft—where both value is an empty string and images is an empty array—it automatically removes the entry from the internal map rather than storing unnecessary data. For explicit removal, the clearDraft function deletes the entry immediately:

import { clearDraft } from "@/lib/draft-store";

// Remove draft manually after successful message send
clearDraft(draftKey);

Real-World Usage in ChatInput.tsx

The components/ChatInput.tsx file demonstrates practical integration with the draft-store. It constructs a unique key based on the current session ID and synchronizes the textarea state with the in-memory store:

// Key construction pattern from ChatInput.tsx
const draftKey = `chat-${sessionId}`;
const saved = getDraft(draftKey) ?? { value: "", images: [] };

// During user input
setDraft(draftKey, { value: newText, images: newImages });

// After message submission
clearDraft(draftKey);

This pattern ensures that if the user navigates between chat views within the same page load, their partial message remains available. However, refreshing the browser resets the composition area completely.

Summary

  • In-Memory Only: The draft-store uses a module-level Map<string, ChatDraft> that exists only in JavaScript memory during the page lifecycle.
  • No Cross-Session Persistence: Drafts are destroyed when the page reloads, closes, or navigates away because the store does not write to localStorage, cookies, or disk.
  • Defensive Cloning: The API returns cloned objects via getDraft and the cloneDraft helper to prevent accidental mutations of internal state.
  • Auto-Cleanup: The setDraft function automatically removes entries when saving empty drafts, preventing memory leaks without manual intervention.
  • Session-Keyed: Components like ChatInput.tsx use unique keys (e.g., chat-${sessionId}) to isolate drafts between different chat contexts within the same browser session.

Frequently Asked Questions

Does the pi-web draft-store use localStorage or IndexedDB?

No. The draft-store deliberately avoids browser storage APIs. According to the implementation in lib/draft-store.ts, all data resides in a private Map object that lives only in the current JavaScript execution context. When the user refreshes the page, the map is reinitialized empty and previous drafts cannot be recovered.

What happens to my draft if I refresh the browser page?

All draft data is permanently lost. Because the store maintains drafts exclusively in memory without serialization to disk or browser storage, a page reload creates a fresh Map instance with no saved history. The user must retype their message after refreshing.

How does the draft-store handle multiple concurrent chat sessions?

The store supports multiple simultaneous drafts through unique string keys. In components/ChatInput.tsx, each chat session receives a distinct key formatted as `chat-${sessionId}`. This allows the same drafts map to maintain separate entries for different conversations without cross-contamination during the active session.

Is draft data shared between different browser tabs?

No. Each browser tab runs an isolated JavaScript environment with its own instance of the draft-store module. Consequently, the Map in one tab cannot access draft data from another tab. Drafts are strictly scoped to the individual tab and session in which they were created, providing natural isolation between browsing contexts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →