feat: sync UI preferences across devices via UserSettings #79

Merged
nalum merged 5 commits from feat/sync-ui-preferences into main 2026-08-16 12:37:55 +00:00
Owner

Closes #60.

Text scale and reduce-motion synced only half the display preferences — they lived device-local while theme, locale and tour version already rode UserSettings. This brings them into the synced model and makes the server record the source of truth, with localStorage demoted to a device cache.

What changed

  • ADR-0029 records the schema addition, server-as-source-of-truth, localStorage-as-cache, the migration, and last-write-wins.
  • proto: TextScale enum (UNSPECIFIED=1.0 / LARGE=1.15 / EXTRA_LARGE=1.33) plus text_scale, reduce_motion, flatten_cards on UserSettings. Every field defaults to today's behavior when unset. No new RPC, no scrub-map change.
  • web (reference): reads the three from the member record, writes through to server + cache, and runs a one-time local→server migration so an existing text-size/motion choice is not reset when the server record predates the fields.
  • android: syncs reduce_motion (the motion chip now pushes through and pull applies it); whole-message writes preserve text_scale and flatten_cards untouched.
  • docs: AGENTS.md now states the server record is the source of truth and localStorage is the device cache.

Deliberate gaps (rule 1)

  • flatten_cards is plumbed but drives no render and has no toggle yet — that is #48, which stacks on this branch.
  • Android renders no text-scale control or card tilt, so it has no UI for those two; it syncs reduce_motion and preserves the rest. Recorded in ADR-0029.

Verification

  • make check green (Go + web build + i18n).
  • Android module compiles.
  • Live-deploy API round-trip: Update→Read persists all three fields, siblings preserved, last-write-wins confirmed.

Two-device visual UI check still owed.

🤖 Generated with Claude Code

Closes #60. Text scale and reduce-motion synced only half the display preferences — they lived device-local while theme, locale and tour version already rode `UserSettings`. This brings them into the synced model and makes the server record the source of truth, with localStorage demoted to a device cache. ## What changed - **ADR-0029** records the schema addition, server-as-source-of-truth, localStorage-as-cache, the migration, and last-write-wins. - **proto**: `TextScale` enum (UNSPECIFIED=1.0 / LARGE=1.15 / EXTRA_LARGE=1.33) plus `text_scale`, `reduce_motion`, `flatten_cards` on `UserSettings`. Every field defaults to today's behavior when unset. No new RPC, no scrub-map change. - **web** (reference): reads the three from the member record, writes through to server + cache, and runs a one-time local→server migration so an existing text-size/motion choice is not reset when the server record predates the fields. - **android**: syncs `reduce_motion` (the motion chip now pushes through and pull applies it); whole-message writes preserve `text_scale` and `flatten_cards` untouched. - **docs**: AGENTS.md now states the server record is the source of truth and localStorage is the device cache. ## Deliberate gaps (rule 1) - `flatten_cards` is plumbed but drives no render and has no toggle yet — that is **#48**, which stacks on this branch. - Android renders no text-scale control or card tilt, so it has no UI for those two; it syncs `reduce_motion` and preserves the rest. Recorded in ADR-0029. ## Verification - `make check` green (Go + web build + i18n). - Android module compiles. - Live-deploy API round-trip: `Update`→`Read` persists all three fields, siblings preserved, last-write-wins confirmed. Two-device visual UI check still owed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ADR-0029. Text scale and reduce-motion synced only half the display
preferences; they lived in the device-local prefs bag while theme,
locale and tour version already synced through UserSettings. The ADR
records bringing them into the synced model, adding flatten_cards for
the card-tilt work (#48) to build on, and making the server record the
source of truth with localStorage demoted to a device cache. The Android
text-scale UI gap is recorded as deliberate (issue #60, rule 1).
Text scale and reduce-motion synced only half the display preferences —
they lived device-local while theme, locale and tour version already rode
UserSettings. Add text_scale (a TextScale enum pinning the web's three
sizes), reduce_motion and flatten_cards to the synced settings so every
display preference follows the member across devices. flatten_cards is the
knob issue #48 wires to the card tilt; it is defined here so the tilt work
rides the synced model instead of a fourth device-local flag. Every field
defaults to today's behavior when unset. ADR-0029.
Read the three display preferences from the member record, not from
localStorage alone, and write them through to both server and cache on
change — closing the split where theme synced but text size and motion
reset per device (ADR-0029). Add flattenCards to the prefs bag and set its
data attribute so issue #48 only adds the toggle and the tilt render. A
one-time migration keeps this device's text size and motion choice when the
server record predates the synced fields, pushing them up once instead of
letting server defaults reset them.
The in-app motion chip changed only this device's store; the web's
reduce-motion synced through UserSettings but Android's never round-tripped.
Push the motion choice through pushTheme and apply the server's reduce_motion
on pull, so the two are one preference now (ADR-0029). The whole-message
write leaves text_scale and flatten_cards untouched, so a value the web set
survives an Android write even though this app renders neither a text-size
control nor card tilt yet — a deliberate parity gap (rule 1).
docs: UI preferences sync via UserSettings, localStorage is a cache
All checks were successful
check / commits (pull_request) Successful in 10s
check / web (pull_request) Successful in 1m30s
check / go (pull_request) Successful in 2m42s
check / report (pull_request) Successful in 3s
android / build (pull_request) Successful in 5m24s
android / report (pull_request) Successful in 3s
6e18fd5fdf
The frontend note said scale and motion persist in namespaced localStorage.
Since ADR-0029 the server record is the source of truth for every UI
preference and localStorage is the device cache. Correct the invariant
(rule 5).

Test report

Suite Tests Result Skipped
Unit 1330 ✅ pass 1
Integration 83 ✅ pass —

Coverage: 27.7%

Updated by the check workflow · commit c656b3bbca

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1330 | ✅ pass | 1 | | Integration | 83 | ✅ pass | — | **Coverage:** 27.7% <sub>Updated by the check workflow · commit c656b3bbca139be06f27abe58d516af4b5d789b0</sub>

Android test report

Suite Tests Result Skipped
Unit (debug) 31 ✅ pass 0

Updated by the android workflow · commit c656b3bbca

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | 31 | ✅ pass | 0 | <sub>Updated by the android workflow · commit c656b3bbca139be06f27abe58d516af4b5d789b0</sub>
nalum force-pushed feat/sync-ui-preferences from 6e18fd5fdf
All checks were successful
check / commits (pull_request) Successful in 10s
check / web (pull_request) Successful in 1m30s
check / go (pull_request) Successful in 2m42s
check / report (pull_request) Successful in 3s
android / build (pull_request) Successful in 5m24s
android / report (pull_request) Successful in 3s
to c656b3bbca
Some checks failed
check / commits (pull_request) Successful in 7s
check / web (pull_request) Successful in 1m36s
check / go (pull_request) Successful in 2m43s
check / report (pull_request) Successful in 3s
android / build (pull_request) Successful in 5m20s
android / report (pull_request) Successful in 4s
android / report (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
check / commits (push) Has been cancelled
check / go (push) Has been cancelled
android / build (push) Has been cancelled
2026-08-14 23:12:48 +00:00
Compare
nalum changed target branch from main to fix/events-update-registry 2026-08-14 23:12:55 +00:00
nalum changed target branch from fix/events-update-registry to main 2026-08-16 12:37:48 +00:00
nalum merged commit c656b3bbca into main 2026-08-16 12:37:55 +00:00
nalum deleted branch feat/sync-ui-preferences 2026-08-16 12:37:55 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!79
No description provided.