Portal Community

How It Works

  1. On every field change, the FormEngine fires a change event.
  2. The draft system listens to this event with a 500ms debounce.
  3. After 500ms of inactivity, it serialises the current form values to JSON.
  4. The JSON is saved via StorageManager which uses a fallback chain: localStorage → IndexedDB → memory.
  5. The storage key is atlas-forms-draft:{draftKey} where draftKey defaults to the form's ID.
  6. On form mount, if a draft exists for this key, the player shows a "Restore draft?" prompt.
  7. On successful submit, the draft is deleted from storage.

StorageManager Fallback Chain

// packages/storage-js/src/StorageManager.ts

class StorageManager {
  async save(key: string, value: any): Promise<void> {
    const json = JSON.stringify(value);
    try {
      localStorage.setItem(key, json);
    } catch (localStorageError) {
      // localStorage full or unavailable — try IndexedDB
      try {
        await this.idb.put(key, json);
      } catch (idbError) {
        // IndexedDB unavailable — fall back to in-memory map
        this.memoryStore.set(key, json);
      }
    }
  }
}

Draft Restore Prompt

When a draft is found on form mount, FormPlayerPage and FormRenderer show a non-intrusive banner at the top of the form:

// Conceptual prompt behaviour (handled internally)
"You have unsaved changes from [timestamp]. Would you like to restore them?"
[Restore Draft]   [Start Fresh]

// "Restore Draft" → initialValues are replaced with the saved draft values
// "Start Fresh" → draft is deleted; form initialises with the provided initialValues

Configuring Draft Behaviour

// Disable draft saving entirely
<FormRenderer draftEnabled={false} />

// Custom draft key — use when multiple form instances exist on the same page
<FormRenderer
  draftKey={`onboarding-${userId}-step-${stepNumber}`}
/>

// For single-submission forms where draft restore would be confusing,
// clear any existing draft on mount:
useEffect(() => {
  storageManager.delete(`atlas-forms-draft:${formId}`);
}, [formId]);

Draft Storage Size

Draft data is the serialised form values — typically a few KB for most forms. Forms with large binary fields (file uploads, json-editor with large objects) should use custom draftKey strategies and may need to exclude large fields from draft saving:

// Exclude file upload fields from draft by using a custom serialiser
// (advanced — requires a custom FormStateProvider setup)
const draftSerializer = (values: Record<string, any>) => {
  const { 'file-upload-field': _, ...saveable } = values;
  return saveable;
};

Draft Lifecycle Events

EventWhen Fired
draft:savedAfter each debounced auto-save completes
draft:restoredWhen the user accepts the restore prompt
draft:discardedWhen the user declines the restore prompt
draft:clearedAfter successful form submission