Phase 6: Drafts, Settings, and Timer
Status: Complete Spec ID prefix:
DRFTPhase: 6 Completed: 2026-02-20
Overview
This phase adds persistence and personalization to the editor experience. Draft storage automatically saves in-progress work to localStorage so users can navigate away and return without losing code. The settings system provides a dialog for configuring feedback levels, keybindings (default/vim/emacs), autocomplete, and formatter preferences, with local persistence and optional server sync for authenticated users. The timer tracks elapsed time from first keystroke, displayed in the status bar.
Dependencies
- [EDIT-01] (EditorStore)
- [EDIT-07] (setFileContent for triggering draft saves)
- [AUTH-07] (Spring Boot UserSettings JPA entity)
- [AUTH-12] (SettingsRepository interface)
- [DSST-07] (UserSettings type, FeedbackConfig, EditorKeybindings)
User Flows
Draft Auto-Save
- User opens a challenge and starts typing
- After 500ms of inactivity, the current file state is saved to localStorage
- User navigates away (to another challenge, pack, or page)
- User returns to the same challenge
- EditorStore detects a saved draft and restores file contents and hint count
Settings Configuration
- User opens the settings dialog
- User toggles feedback checkboxes (pass/fail badges, hints, assertion details, diff, solution)
- User selects keybinding mode (default, vim, or emacs)
- User configures formatter preferences (tab size, use tabs, trigger)
- Settings persist to localStorage immediately
- If authenticated, settings sync to the server with debounced writes
Timer
- User opens a challenge -- timer is at 0:00
- User makes their first edit -- timer starts
- Timer increments every second while editing
- Timer value is displayed in the status bar
- On submit, the elapsed time is included in the attempt record
Acceptance Criteria
Draft Storage
- [ ] DRFT-01 -- Drafts are saved to localStorage with key pattern
nthtime:draft:{challengeId}containing file contents and hints revealed. - [ ] DRFT-02 -- When opening a challenge with an existing draft, the editor restores the draft's file contents instead of the default file stubs.
- [ ] DRFT-03 --
clearDraft()removes the draft for a specific challenge.clearAllDrafts()removes all nthtime draft entries. - [ ] DRFT-04 -- Corrupt or malformed draft JSON is handled gracefully (returns null, does not crash).
Settings
- [ ] DRFT-05 -- Settings dialog allows toggling individual feedback flags: showPassFail, showHints, showAssertionDetails, showDiff, showSolution.
- [ ] DRFT-06 -- Settings dialog allows selecting keybinding mode (default, vim, emacs) and toggling autocomplete.
- [ ] DRFT-07 -- Settings persist to localStorage and are restored on page load.
- [ ] DRFT-08 -- Settings store starts with DEFAULT_FEEDBACK defaults when no saved settings exist.
- [ ] DRFT-09 --
setFeedback()merges partial feedback updates (does not overwrite unset flags). - [ ] DRFT-10 -- When authenticated,
useSettingsSyncfetches settings from the server and merges them with local state. Saves are debounced at 1000ms. - [ ] DRFT-11 -- Legacy
feedbackLevelfrom localStorage is migrated to the new FeedbackConfig format on first load.
Timer
- [ ] DRFT-12 -- Timer starts at 0:00 and begins counting when the user makes their first edit.
- [ ] DRFT-13 -- Elapsed time (in seconds) is included when creating an attempt record.
Technical Context
Key Files
| File | Role |
|---|---|
libs/editor/src/lib/draft-storage.ts | saveDraft, loadDraft, clearDraft, clearAllDrafts |
libs/editor/src/lib/editor-store.ts | Draft integration in initFromChallenge, saveDraft, loadDraft actions |
apps/web/src/lib/settings-store.ts | Singleton Zustand vanilla store for user settings |
apps/web/src/hooks/use-settings-query.ts | useSettingsSync hook for server sync |
apps/web/src/components/challenge/challenge-view.tsx | Debounced draft save subscription (500ms) |
apps/web/src/components/challenge/status-bar.tsx | Timer display and run state |
Patterns and Decisions
- localStorage for drafts -- chosen for simplicity and offline support. Drafts are per-challenge, keyed by challenge ID. No server sync for drafts.
- Singleton settings store -- a single Zustand vanilla store instance manages all settings. It hydrates from localStorage on creation and syncs to the server when authenticated.
- Debounced persistence -- draft saves are debounced at 500ms (triggered by file content changes); settings saves to the server are debounced at 1000ms.
- Migration path -- the old
feedbackLevel(integer 0-4) in localStorage is automatically migrated to the newFeedbackConfig(individual boolean flags) on first load.
API Routes
| Route | Method | Purpose |
|---|---|---|
/api/v1/settings | GET | Read user settings from the server |
/api/v1/settings | PATCH | Update user settings on the server |
Test Coverage
Unit Tests
| Criterion | Test File | Test Description |
|---|---|---|
| DRFT-01 | libs/editor/src/lib/draft-storage.spec.ts | generates deterministic keys; saves and loads a draft |
| DRFT-02 | libs/editor/src/lib/draft-storage.spec.ts | isolates drafts by challenge ID |
| DRFT-03 | libs/editor/src/lib/draft-storage.spec.ts | clears a single draft; clears all drafts |
| DRFT-04 | libs/editor/src/lib/draft-storage.spec.ts | handles corrupt JSON gracefully; handles malformed draft objects |
| DRFT-08 | apps/web/src/lib/settings-store.spec.ts | starts with default settings; hydrate from empty localStorage |
| DRFT-05 | apps/web/src/lib/settings-store.spec.ts | setFeedback updates individual flags and persists |
| DRFT-09 | apps/web/src/lib/settings-store.spec.ts | setFeedback merges multiple flags |
| DRFT-06 | apps/web/src/lib/settings-store.spec.ts | setKeybindings, setAutocomplete updates and persists |
| DRFT-07 | apps/web/src/lib/settings-store.spec.ts | round-trip: mutate -> hydrate fresh store -> state matches |
| DRFT-10 | apps/web/src/lib/settings-store.spec.ts | syncFromServer overrides local state; preserves unincluded settings |
| DRFT-11 | apps/web/src/lib/settings-store.spec.ts | migrates old feedbackLevel from localStorage |
E2E Tests
| Criterion | Test File | Test Description |
|---|---|---|
| DRFT-01, DRFT-02 | apps/web/e2e/drafts.spec.ts | typed content is restored after navigating away and back |
| DRFT-05, DRFT-06 | apps/web/e2e/settings.spec.ts | can open settings dialog and toggle feedback checkboxes |
| DRFT-07 | apps/web/e2e/settings.spec.ts | keybindings selection persists |
Spring Boot Tests
| Criterion | Test Location | Test Description |
|---|---|---|
| DRFT-08 | services/api/src/test/java/... | SettingsController integration test: get returns defaults for new user |
| DRFT-10 | services/api/src/test/java/... | SettingsController integration test: update creates/patches settings |
Open Questions
- None at this time.