Replace direct file picker trigger with a modal offering two import methods: file upload and paste JSON content. Uses a textarea instead of navigator.clipboard.readText() to avoid browser permission prompts. Also centers both import dialogs and updates spec for clipboard import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.1 KiB
Feature Specification: JSON Import/Export
Feature Branch: 007-json-import-export
Created: 2026-03-27
Status: Draft
Input: Gitea issue #17 — JSON import/export for full encounter state
User Scenarios & Testing (mandatory)
Story IE-1 — Export encounter to file (Priority: P1)
A DM has set up an encounter (combatants, HP, initiative, conditions) and wants to save it as a file for backup or to share with another DM. They click an export button, and the browser downloads a .json file containing the full application state.
Why this priority: Export is the foundation — without it, import has nothing to work with. It also delivers standalone value as a backup mechanism.
Independent Test: Can be fully tested by creating an encounter, exporting, and verifying the downloaded file contains all encounter data, undo/redo history, and player character templates.
Acceptance Scenarios:
- Given an encounter with combatants (some with HP, AC, conditions, initiative), When the user clicks the export button, Then a
.jsonfile is downloaded containing the encounter state, undo/redo stacks, and player character templates. - Given an empty encounter with no combatants, When the user clicks the export button, Then a
.jsonfile is downloaded containing the empty encounter state and any existing player character templates. - Given an encounter with player character combatants (color, icon, linked template), When the user exports, Then the exported file preserves all player character visual properties and template links.
Story IE-2 — Import encounter from file (Priority: P1)
A DM receives a .json file from another DM (or from their own earlier export) and wants to load it. They click an import button, choose an import method (file upload or clipboard paste), and the application replaces the current state with the imported data.
Why this priority: Import completes the core value proposition — without it, export is just a read-only backup. Both are needed for the feature to be useful.
Independent Test: Can be tested by importing a valid .json file (or pasting valid JSON from the clipboard) and verifying the encounter, undo/redo history, and player characters are replaced with the imported data.
Acceptance Scenarios:
- Given the user clicks the import action, Then a dialog appears offering two import methods: file upload and clipboard paste.
- Given the current encounter is empty, When the user imports valid encounter data (via file or clipboard), Then the application loads the imported encounter, undo/redo history, and player characters without any confirmation prompt.
- Given the current encounter has combatants, When the user imports valid encounter data, Then a confirmation dialog appears warning that the current encounter will be replaced.
- Given the confirmation dialog is shown, When the user confirms, Then the current encounter, undo/redo history, and player characters are replaced with the imported data.
- Given the confirmation dialog is shown, When the user cancels, Then the current state remains unchanged.
Story IE-3 — Reject invalid import files (Priority: P2)
A DM accidentally selects a wrong file (a non-JSON file, a corrupted export, or a JSON file with the wrong structure). The application rejects it and shows a clear error message without losing the current state.
Why this priority: Error handling is essential for a good user experience but secondary to the core import/export flow.
Independent Test: Can be tested by attempting to import various invalid files and verifying appropriate error messages appear while the current state is preserved.
Acceptance Scenarios:
- Given the user selects a non-JSON file (e.g., an image), When the import attempts to parse it, Then a user-facing error message is shown and the current state is unchanged.
- Given the user selects a JSON file with missing required fields, When the import validates it, Then a user-facing error message is shown and the current state is unchanged.
- Given the user selects a JSON file with a valid top-level structure but individual combatants with invalid fields (e.g., negative HP, unknown condition IDs), When the import validates it, Then invalid fields on otherwise valid combatants are dropped/defaulted (same as localStorage rehydration), but if the top-level structure is malformed (missing
encounterkey, wrong types), the file is rejected with an error message. - Given the user chooses clipboard import but the clipboard is empty or contains non-JSON text, Then a user-facing error message is shown and the current state is unchanged.
Edge Cases
- What happens when the exported file is from a newer version of the application with unknown fields? The import ignores unknown fields and loads what it can validate.
- What happens when the user imports a file with player characters that conflict with existing player character IDs? The imported player characters replace the existing ones entirely (full state replacement, not merge).
- What happens when the undo/redo stacks in the imported file are empty or missing? The system loads with empty undo/redo stacks (same as a fresh session).
- What happens when the browser blocks the file download (e.g., popup blocker)? The export uses a direct download mechanism (anchor element with download attribute) that is not subject to popup blocking.
Requirements (mandatory)
Functional Requirements
- FR-001: The system MUST provide an export action accessible from the UI that downloads the full application state as a
.jsonfile. - FR-002: The exported file MUST contain the current encounter (combatants and turn/round state), undo/redo stacks, and player character templates.
- FR-003: The exported file MUST use a human-readable filename that includes context (e.g., date or encounter info).
- FR-004: The system MUST provide an import action accessible from the UI that lets the user choose between uploading a
.jsonfile or pasting from the clipboard. - FR-005: On import, the system MUST replace the current encounter, undo/redo history, and player characters with the imported data.
- FR-006: The system MUST show a confirmation dialog before importing if the current encounter is non-empty (has at least one combatant).
- FR-007: The system MUST validate imported data using the same rules applied when loading from localStorage — invalid fields are cleaned or dropped, structurally malformed files are rejected entirely.
- FR-008: The system MUST show a user-facing error message when an imported file is rejected as invalid.
- FR-009: A failed or cancelled import MUST NOT alter the current application state.
- FR-010: Export and import actions MUST be accessible from the same location in the UI.
Key Entities
- Export Bundle: A single JSON structure containing the encounter snapshot, undo stack, redo stack, and player character list. Represents the full application state at the time of export.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: Users can export the current state to a downloadable file in one click.
- SC-002: Users can import a previously exported file and see the full encounter restored, including combatant stats, turn tracking, and player characters.
- SC-003: Importing an invalid file shows an error message within 1 second without affecting the current state.
- SC-004: A round-trip (export then import) produces an encounter identical to the original.
Assumptions
- The export format does not need to be backwards-compatible across application versions at this stage. Future format versioning is not included in MVP baseline.
- Export/import covers the three main state stores: encounter, undo/redo, and player characters. Bestiary cache and user settings (theme, rules edition) are excluded.
- The import is a full state replacement, not a merge. There is no selective import of individual pieces.
Dependencies
- Undo/redo system (spec 006) must be implemented so that undo/redo stacks can be included in the export.