How pi-web Handles Draft Persistence Using the draft-store Module
The draft-store module provides a lightweight, type-safe persistence layer that saves chat drafts to the browser's localStorage using session-scoped keys, enabling automatic recovery of user input across page reloads.
The agegr/pi-web repository implements draft persistence through a client-side storage strategy that requires no server round-trips. The draft-store module—located at lib/draft-store.ts—offers a minimal API for serializing and deserializing draft content, while components/ChatInput.tsx orchestrates the read and write lifecycle. This approach ensures that unsent messages survive browser refreshes and accidental tab closures.
Core Architecture of the draft-store Module
The draft-store module exports strictly typed interfaces and utility functions that abstract localStorage operations. According to the source code, the module defines the shape of draft data and provides three primary methods for managing persistence.
Data Models: ChatDraft and ChatDraftImage
The module defines two TypeScript interfaces that structure the draft data:
ChatDraftImage: Describes attached images with asrcproperty and an optionalaltattribute.ChatDraft: Contains the textcontentstring and an optional array ofChatDraftImageobjects.
These types ensure that both message text and media attachments persist consistently across sessions.
Storage API: getDraft, setDraft, and clearDraft
The module exposes three functions that handle all interaction with localStorage:
getDraft(key): Retrieves the JSON string fromlocalStorage, parses it, and returns aChatDraftobject. Returnsnullif no entry exists for the provided key.setDraft(key, draft): Accepts a session key andChatDraftobject, serializes the data to JSON, and writes it tolocalStorage.clearDraft(key): Permanently removes the draft entry associated with the session key fromlocalStorage.
Each function uses a unique key per chat session to prevent collision between different conversation contexts.
Integrating Draft Persistence in ChatInput.tsx
The components/ChatInput.tsx file implements the draft lifecycle by invoking the draft-store methods at critical user interaction points. This integration ensures that drafts restore automatically when users return to the chat and clear immediately after successful message delivery.
Restoring Drafts on Component Mount
When the chat input component initializes, a useEffect hook retrieves any previously saved draft for the current sessionId:
// components/ChatInput.tsx – loading a saved draft
useEffect(() => {
const saved = getDraft(sessionId);
if (saved) {
setContent(saved.content);
setImages(saved.images ?? []);
}
}, [sessionId]);
This pattern ensures that users returning to the page see their unsent text and attached images exactly as they left them.
Persisting Drafts in Real-Time
To minimize data loss, the component persists the draft on every keystroke. The onChange handler calls setDraft immediately after updating local state:
// components/ChatInput.tsx – persisting on each change
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newContent = e.target.value;
setContent(newContent);
// Persist the draft immediately
setDraft(sessionId, { content: newContent, images });
};
This synchronous write to localStorage guarantees that even abrupt page closures preserve the latest user input.
Clearing Drafts After Message Send
Once the user successfully sends a message, the component invokes clearDraft to remove the persisted data and reset the input field:
// components/ChatInput.tsx – clearing after send
const sendMessage = async () => {
await postMessage(content, images);
clearDraft(sessionId); // remove persisted draft
setContent(''); // reset UI
setImages([]);
};
As noted in AGENTS.md (around line 70), the draft-store.ts utility is listed among the repository's core modules, confirming its integral role in the application architecture.
Benefits of Client-Side Draft Storage
Using localStorage through the draft-store module offers several architectural benefits:
- Zero Server Overhead: Drafts never traverse the network, reducing database load and eliminating latency for persistence operations.
- Instant Recovery: Data retrieval happens synchronously from the browser store, enabling immediate UI restoration without async loading states.
- Session Isolation: Unique keys per
sessionIdensure drafts remain scoped to specific conversations, preventing cross-contamination between chat contexts.
Summary
- The
draft-storemodule atlib/draft-store.tsprovides type-safelocalStoragewrappers for chat draft persistence. - Three core functions—
getDraft,setDraft, andclearDraft—manage the full lifecycle of draft data using session-scoped keys. components/ChatInput.tsxconsumes these utilities to restore drafts on mount, persist changes in real-time, and clear storage after successful message transmission.- The implementation relies entirely on client-side storage, eliminating server dependencies while surviving page reloads and navigation events.
Frequently Asked Questions
How does pi-web ensure drafts don't leak between different chat sessions?
The draft-store module accepts a unique key parameter (typically the sessionId) for every operation. Each draft writes to a distinct localStorage key scoped to that specific session, ensuring complete isolation between separate conversations.
What happens to drafts if the user disables localStorage in their browser?
If localStorage is unavailable or disabled, the getDraft function returns null, and setDraft/clearDraft operations fail silently. The ChatInput component handles this gracefully by falling back to empty initial states, allowing the chat to function normally without persistence.
Where is the draft-store module located in the pi-web repository?
The implementation resides in lib/draft-store.ts, as confirmed by the repository file structure and referenced in AGENTS.md around line 70. This location follows the project's convention of housing utility modules in the top-level lib directory.
Does the draft persistence support rich media like images?
Yes. The ChatDraft interface includes an optional images array containing ChatDraftImage objects with src and alt properties. The setDraft function serializes the entire object—including image metadata—to localStorage, and getDraft restores it when the component remounts.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →