···287287 invalidTextFormat: Use the expected format before continuing.
288288 agree: Agree
289289 doNotAgree: Do not agree
290290+ progressRestored: Saved progress restored.
291291+ savedProgressDiscarded: Saved progress could not be restored.
290292 submitError: Could not submit response.
291293 back: Back
292294 continue: Continue
+2
locales/ru.yml
···287287 invalidTextFormat: Используйте ожидаемый формат ответа перед продолжением.
288288 agree: Согласен
289289 doNotAgree: Не согласен
290290+ progressRestored: Сохранённый прогресс восстановлен.
291291+ savedProgressDiscarded: Сохранённый прогресс не удалось восстановить.
290292 submitError: Не удалось отправить ответ.
291293 back: Назад
292294 continue: Далее
···11+## Context
22+33+The public runner currently keeps its respondent state entirely in React component memory inside `components/public-form-runner.tsx`. That state already includes the pieces needed to resume a session—`answers`, visited `history`, and `cursor`—but a refresh or closed tab recreates the component and drops all progress.
44+55+This change is intentionally scoped to anonymous public forms. There is no respondent account system, no server-side draft model, and no need to synchronize draft state across devices. The existing runner already has branch-aware navigation, client-side validation, and submission cleanup paths, so the main design problem is how to persist and safely restore the runner state without letting stale drafts corrupt a changed published form.
66+77+Key constraints:
88+- Persistence must remain optional and best-effort; the runner must still work when browser storage is unavailable.
99+- Restored progress must respect the current branching model and not trust corrupted route history blindly.
1010+- The public form payload does not currently expose a dedicated published-version field for draft invalidation.
1111+- The feature should not require database, API, or authentication changes for v1.
1212+1313+## Goals / Non-Goals
1414+1515+**Goals:**
1616+- Preserve in-progress anonymous runner answers and visited position across refreshes and tab closes in the same browser.
1717+- Restore only drafts that still match the current published form structure.
1818+- Keep branching, validation, progress, and submission behavior consistent after restoration.
1919+- Clear persisted draft data after successful submission.
2020+- Provide lightweight respondent feedback when saved progress is restored or discarded.
2121+2222+**Non-Goals:**
2323+- Cross-device or cross-browser draft sync.
2424+- Server-side draft persistence or respondent identity.
2525+- Multiple named drafts for the same form.
2626+- A new creator-facing configuration switch for draft persistence.
2727+- Full privacy hardening such as encryption-at-rest in the browser.
2828+2929+## Decisions
3030+3131+### 1. Use browser `localStorage` as the only persistence layer
3232+Persist runner drafts locally in the browser under a form-scoped storage key. This keeps the feature aligned with the anonymous public runner: no login, no backend writes before submission, and no schema changes.
3333+3434+Draft persistence will be best-effort. All storage reads/writes will be wrapped in `try/catch`, matching the existing theme-preference pattern, so blocked storage or quota failures degrade gracefully to the current non-persistent behavior.
3535+3636+Why this approach:
3737+- Matches the user's requested behavior for surviving refreshes.
3838+- Avoids introducing draft APIs, abuse controls, or cleanup jobs.
3939+- Keeps the implementation isolated to the public runner.
4040+4141+Alternatives considered:
4242+- `sessionStorage`: survives refreshes but not closed tabs, which is weaker than the requested persistence.
4343+- Server-side anonymous drafts: more durable, but adds backend complexity and draft identity problems that are out of scope.
4444+4545+### 2. Persist a validated draft payload instead of raw component state
4646+Store a small JSON payload containing:
4747+- `version`
4848+- `formId` or equivalent form-scoped identity
4949+- `fingerprint`
5050+- `answers`
5151+- `history`
5252+- `cursor`
5353+- `savedAt`
5454+5555+The restore path should validate this payload shape before using it. Invalid or partially corrupted JSON should be ignored and removed.
5656+5757+Why this approach:
5858+- Keeps the payload explicit and migration-friendly.
5959+- Avoids coupling storage directly to transient React implementation details.
6060+- Makes it straightforward to add a future format bump if needed.
6161+6262+Alternatives considered:
6363+- Store only `answers`: simpler, but loses the respondent's current step and weakens restoration for branched flows.
6464+- Store the entire component state object: faster initially, but brittle if unrelated UI state changes.
6565+6666+### 3. Invalidate drafts using a deterministic form fingerprint derived from the public form payload
6767+Because the public payload does not expose a published revision identifier, compute a deterministic fingerprint from the ordered serialized public blocks that drive routing and validation. The fingerprint should include enough structure to detect changes that could make a saved draft unsafe to reuse, such as block order, block IDs, block types, required flags, and block config.
6868+6969+On load, compare the stored fingerprint with the fingerprint computed from the current form. A mismatch means the draft is stale and must be discarded.
7070+7171+Why this approach:
7272+- Prevents restoring answers onto a materially different published form.
7373+- Avoids expanding the server payload just to support draft invalidation.
7474+- Keeps all invalidation logic local to the runner.
7575+7676+Alternatives considered:
7777+- Add `updatedAt` or a version field to `PublicForm`: workable, but introduces API/type changes for a feature that can be implemented client-side.
7878+- Ignore form changes and always restore: unsafe for branching and validation changes.
7979+8080+### 4. Reconstruct restoration safety by replaying the saved route prefix
8181+Do not trust stored `history` and `cursor` blindly. After reading a valid payload and matching fingerprint, replay the saved route prefix from the first block using the saved answers and the existing `resolveNextBlockId` helper. Restore only if the saved visited path remains valid for the current form and the saved cursor points at a block that is still reachable.
8282+8383+This means the restore flow should:
8484+1. confirm the first saved block matches the form start
8585+2. step through the saved visited prefix using current branching rules
8686+3. stop and discard the draft if any next block does not match the replayed route
8787+4. restore `answers`, `history`, and `cursor` only after validation succeeds
8888+8989+Why this approach:
9090+- Keeps restored state aligned with the same routing logic used during live navigation.
9191+- Protects against corrupted or manually edited local storage.
9292+- Preserves the respondent's exact place in branched flows when the draft is valid.
9393+9494+Alternatives considered:
9595+- Trust the stored route directly: simpler, but unsafe when branching rules or stored payloads drift.
9696+- Recompute only from answers and ignore saved cursor: safer, but can move respondents away from the step they were actively on.
9797+9898+### 5. Persist with a short debounce after runner state changes
9999+Save draft updates whenever `answers` or route state changes, but debounce writes slightly so long-text typing does not hammer synchronous `localStorage` writes on every keystroke. The persist effect should also skip writes until the initial restore attempt has completed, avoiding accidental overwrites during hydration.
100100+101101+Why this approach:
102102+- Keeps the draft reasonably current without excessive write churn.
103103+- Preserves refresh resilience even if a respondent has not clicked Continue yet.
104104+- Minimizes the chance of writing a default empty draft before restore logic runs.
105105+106106+Alternatives considered:
107107+- Write on every keystroke: simplest, but noisier and less efficient.
108108+- Write only on Continue/Back: misses unsaved typing in the active step.
109109+110110+### 6. Clear drafts on successful submission and surface toast-level feedback
111111+When submission succeeds, remove the stored draft before switching to the completion state. Also show lightweight localized feedback when a draft is restored successfully or when a stale/corrupted draft is discarded.
112112+113113+A toast-level message fits the existing runner UX and avoids blocking the respondent with extra prompts. The initial implementation does not add a separate “resume vs start over” decision dialog.
114114+115115+Why this approach:
116116+- Prevents old answers from reappearing after a completed response.
117117+- Reuses the runner's existing toast system and localization flow.
118118+- Keeps the UX simple for the first release.
119119+120120+Alternatives considered:
121121+- Silent restore/discard: lower UI overhead, but more surprising to respondents.
122122+- Modal restore prompt: more explicit, but adds friction and extra state management.
123123+124124+## Risks / Trade-offs
125125+126126+- **Local browser storage can retain sensitive draft answers on shared devices** → Mitigation: scope drafts per form, clear on successful submission, and consider a manual discard control or TTL later.
127127+- **Fingerprinting the full block structure may invalidate drafts after minor copy-only edits** → Mitigation: prefer safe invalidation over restoring against a changed form; losing a stale draft is better than resuming on the wrong route.
128128+- **Storage may be unavailable in some browsers or privacy modes** → Mitigation: treat persistence as best-effort and fall back to current in-memory behavior without breaking the runner.
129129+- **Replay validation may discard some drafts that could theoretically be partially recovered** → Mitigation: keep restoration strict in v1 so resumed sessions are predictable and branch-safe.
130130+131131+## Migration Plan
132132+133133+1. Add a small public-runner draft helper module for storage keys, payload parsing, fingerprinting, and remove/read/write helpers.
134134+2. Integrate restore-on-mount logic into `components/public-form-runner.tsx` using the existing branch resolution utilities.
135135+3. Add debounced persistence and successful-submission cleanup in the runner.
136136+4. Add localized public-runner feedback strings for restored and discarded drafts.
137137+5. Verify refresh, close/reopen, stale draft invalidation, branched-path restore, and post-submit cleanup behavior manually and/or with regression tests.
138138+139139+Rollback strategy:
140140+- Stop reading and writing the draft helper from the public runner. Because the feature is browser-local only, no database or API rollback is needed.
141141+142142+## Open Questions
143143+144144+- Do we want a visible “Start over / clear saved progress” action in the runner after the basic restore flow ships?
145145+- Should browser-local drafts expire automatically after a fixed age, such as 7 or 30 days?
···11+## Why
22+33+Refreshing a published form currently resets the public runner back to the start and discards any answers the respondent has already entered. Adding browser-local draft persistence now makes long or interruption-prone forms more forgiving without requiring respondent accounts or backend draft storage.
44+55+## What Changes
66+77+- Persist in-progress public form answers and visited runner position in browser local storage so respondents can recover from refreshes, tab closes, or accidental navigation away.
88+- Restore a saved in-progress draft when the same respondent reopens the same published form in the same browser and continue from the last valid visited step.
99+- Invalidate saved runner drafts when the published form structure no longer matches the saved draft, and clear the saved draft after successful submission.
1010+- Surface lightweight runner feedback when progress is restored or discarded so respondents are not surprised by persisted state.
1111+1212+## Capabilities
1313+1414+### New Capabilities
1515+- None.
1616+1717+### Modified Capabilities
1818+- `anonymous-form-runner`: respondents can resume an in-progress published form from browser-local saved progress instead of losing answers on refresh.
1919+2020+## Impact
2121+2222+- `components/public-form-runner.tsx` runner state management, restoration flow, and submission cleanup
2323+- Public form payload shaping and/or client-side draft fingerprinting used to detect stale saved progress
2424+- Localized public runner copy for restore/discard feedback
2525+- Browser storage behavior for anonymous respondent drafts with no server-side persistence changes
···11+## ADDED Requirements
22+33+### Requirement: Runner preserves in-progress responses in the same browser
44+The system SHALL save a respondent's in-progress answers and current visited step for a published form in browser-local storage while the respondent is completing the form. When the same published form is reopened in the same browser before successful submission, the system SHALL restore the saved draft and resume from the last valid visited step instead of starting over.
55+66+#### Scenario: Respondent refreshes a form after answering several questions
77+- **WHEN** a respondent has entered answers on a published form and refreshes the page before submitting
88+- **THEN** the system restores the saved answers and reopens the runner at the last visited step for that saved draft
99+1010+#### Scenario: Respondent reopens the same form in the same browser
1111+- **WHEN** a respondent leaves a published form before submitting and later opens the same form again in the same browser
1212+- **THEN** the system resumes the saved in-progress draft rather than presenting a blank new run
1313+1414+#### Scenario: Respondent is informed when progress is restored
1515+- **WHEN** the runner successfully restores a saved draft
1616+- **THEN** the system indicates that previously saved progress has been restored
1717+1818+### Requirement: Runner discards stale or completed local drafts safely
1919+The system SHALL ignore and clear a saved local draft when that draft no longer matches the current published form structure, and SHALL clear the saved local draft after a successful submission so later visits begin as a new response.
2020+2121+#### Scenario: Saved draft no longer matches the published form
2222+- **WHEN** a respondent opens a published form and a saved local draft exists for that form but its saved structure fingerprint does not match the current published form
2323+- **THEN** the system discards the saved local draft, starts a fresh runner session, and indicates that saved progress could not be restored
2424+2525+#### Scenario: Respondent submits a form successfully
2626+- **WHEN** a respondent completes and successfully submits a published form that has a saved local draft
2727+- **THEN** the system clears the saved local draft for that form so reopening the form starts a new response
···11+## 1. Draft persistence foundation
22+33+- [x] 1.1 Add a public runner draft helper module for storage keys, payload parsing, fingerprint generation, and safe localStorage read/write/remove operations
44+- [x] 1.2 Add restore validation that replays saved route history against the current form blocks and rejects stale or corrupted drafts
55+66+## 2. Public runner integration
77+88+- [x] 2.1 Integrate restore-on-load logic into `components/public-form-runner.tsx` so valid drafts repopulate answers, history, and cursor before interaction begins
99+- [x] 2.2 Add debounced draft persistence for answer and route changes, plus cleanup on successful submission or stale-draft discard
1010+- [x] 2.3 Add localized public runner feedback for restored progress and discarded saved drafts
1111+1212+## 3. Verification
1313+1414+- [x] 3.1 Verify refresh and close/reopen flows restore the same in-progress answers and visited step in the same browser
1515+- [x] 3.2 Verify branched-path restoration, stale-form invalidation, storage-failure fallback, and post-submit draft cleanup behavior