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

> Learn how the pi-web draft-store maintains local chat drafts across sessions. Discover its in-memory Map mechanism and how data clears upon page reload or navigation.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-07-24

---

**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`](https://github.com/agegr/pi-web/blob/main/lib/draft-store.ts), this utility allows components like [`ChatInput.tsx`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/draft-store.ts):

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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.