# Research: JSON Import/Export **Feature**: 007-json-import-export **Date**: 2026-03-27 ## Decision 1: Export Bundle Contents **Decision**: Export includes encounter, undo/redo stacks, and player characters. Excludes bestiary cache, theme, and rules edition. **Rationale**: The spec explicitly includes undo/redo history and player characters. Theme and rules edition are user preferences that should not transfer between DMs. Bestiary cache is large and can be rebuilt from sources. **Alternatives considered**: - Include theme/edition settings — rejected because these are personal preferences, not encounter data. - Exclude undo/redo — rejected because the spec explicitly requires it and it enables full session restore. - Include bestiary cache — rejected because it's large, device-specific, and reconstructable from source URLs. ## Decision 2: Import Strategy — Full Replacement vs Merge **Decision**: Full state replacement. Import replaces encounter, undo/redo, and player characters entirely. **Rationale**: The spec states "Import replaces all existing state." Merging would require conflict resolution (duplicate IDs, name collisions) which adds significant complexity for unclear benefit. **Alternatives considered**: - Merge player characters — rejected because ID conflicts between different sessions would be complex to resolve and the spec doesn't call for it. - Selective import (pick which parts to load) — rejected as out of MVP scope. ## Decision 3: Validation Approach **Decision**: Reuse existing `rehydrateEncounter()` and player character validation from the persistence layer. These functions already handle all field validation, type checking, and graceful degradation for invalid fields. **Rationale**: The spec explicitly states "validated using the same rules as localStorage loading." The existing `rehydrateEncounter()` function already validates every combatant field, filters invalid conditions, clamps HP values, and rejects structurally malformed data. Reusing it ensures consistency and avoids duplication. **Alternatives considered**: - Write separate import validation — rejected because it would duplicate existing validation logic and risk divergence. - Stricter validation (reject files with any invalid field) — rejected because the existing approach gracefully degrades (strips invalid optional fields) which is more user-friendly. ## Decision 4: File Download Mechanism **Decision**: Use `URL.createObjectURL()` with an anchor element's `download` attribute for triggering the file download. **Rationale**: This is the standard browser-native approach that works across all modern browsers without popup blockers interfering. No server-side component needed. **Alternatives considered**: - `window.open()` with data URI — rejected because popup blockers can interfere. - FileSaver.js library — rejected because the native approach is sufficient and avoids an additional dependency. ## Decision 5: File Upload Mechanism **Decision**: Use an `` element, consistent with the existing pattern in `source-fetch-prompt.tsx` which uses `file.text()` + `JSON.parse()`. **Rationale**: The codebase already has this pattern for bestiary source uploads. Reusing the same approach keeps the UX consistent. ## Decision 6: UI Placement **Decision**: Place export and import actions in the action bar's overflow menu, alongside existing items like "Players", "Manage Sources", and "Settings". **Rationale**: The overflow menu already groups secondary actions. Import/export are infrequent operations that don't need primary button placement. The action bar's `buildOverflowItems()` function makes this straightforward to add. **Alternatives considered**: - Settings modal — rejected because import/export are actions, not settings. - Dedicated toolbar buttons — rejected because import/export are infrequent and would clutter the primary UI. ## Decision 7: Export File Naming **Decision**: Use a filename pattern like `initiative-export-YYYY-MM-DD.json` with the current date. **Rationale**: The date provides context for when the export was created. Including "initiative" in the name makes the file's purpose clear when browsing a downloads folder. ## Decision 8: State Restoration After Import **Decision**: Import must update both React state and localStorage in one operation. The encounter hook's `setEncounter()` triggers a `useEffect` that auto-saves to localStorage, and `setUndoRedoState()` similarly auto-saves. For player characters, the same auto-save pattern applies. **Rationale**: Following the existing state flow ensures consistency. Setting React state triggers the existing persistence effects, so no manual localStorage writes are needed for the import path. ## Decision 9: Export Format Versioning **Decision**: Include a `version` field in the export format (e.g., `1`) but do not implement migration logic in MVP. **Rationale**: The spec's assumptions state "Future format versioning is not included in MVP baseline." Including the version field costs nothing and enables future migration logic without breaking existing exports.