Sticky Settings
A cross-app concept (requested in #322): when a user makes certain choices, the choice is recorded against their profile implicitly — no settings screen, no save button. It is not shown on the profile page and is changed only by revisiting the original area of choice and choosing differently.
Mechanics
- One
jsonbcolumn holds all sticky settings:users.settings(null: false, default: {}). -
Each setting is exposed through
store_accessoronUser:store_accessor :settings, :review_mode - The controller that owns the choice resolves it: an explicit choice in params wins and is persisted (only when it actually changed — no writes on every request); otherwise the stored value applies; otherwise the feature’s default.
- Always validate the stored value against the feature’s whitelist before using it — the column is user data.
Current sticky settings
| Setting | Default | Set by | Used by |
|---|---|---|---|
review_mode |
read |
Tapping a mode tab (Read/Write/Listen/Speak) on the review card | Decks::ReviewsController#show — the Study button goes straight into review in the sticky mode; the mode-chooser page was removed |
document_voices |
{} (the first read-aloud plan for a language picks a random catalog voice and persists it here, #586) |
The voice picker on the document reader toolbar (PATCH /documents/:id/voice), or implicitly by ReadAloudPlanner |
Text-to-speech: Resources::API::AttachAudioService and the read-aloud/dictation planners prefer the stored voice for the language (User#document_voice_for); values validated against VoiceCatalog |
Adding a new sticky setting
- Add the key to the
store_accessor :settingslist onUser. - Resolve it in the owning controller (explicit param → persist if changed; stored value → validate; fallback default).
- Document it in the table above.
No migration is needed for new keys — the jsonb column absorbs them.