Skip to content

Settings: edit every server-side preference, and honor theme for real (#46) - #182

Merged
Adron merged 2 commits into
mainfrom
issue-46-honor-preferences
Sep 24, 2026
Merged

Adron merged 2 commits into
mainfrom
issue-46-honor-preferences

Conversation

@Adron

@Adron Adron commented Sep 24, 2026

Copy link
Copy Markdown
Member

Closes #46

Recreates #167. That PR was auto-closed by GitHub when its base branch issue-127-wire-types was deleted on merge of #131 — my mistake for merging with --delete-branch while dependents still pointed at it. Same branch, same commits, now based on main (which contains the CurrentUser fields it needed). The original discussion, including my review, is on #167.

What this does

#131 modelled the 14 missing preference fields; this makes them editable and effective. All 15 PATCH /api/user/update field names verified live and round-tripping: displayName, bio, avatar, isPrivateAccount, githubDefaultRepo, theme, maxMessageLength, messagesPerPage, viewingPreference, showPreviews, notificationTrayLimit, defaultPubliclyVisible, showAdvancedPostSettings, latitude, longitude.

It also fixes a live bug — see #171

"Save profile" has never worked. UpdateProfileAsync built its body from an anonymous object, and JsonSerializerDefaults.Web doesn't drop nulls, so every save shipped "theme": null:

A) what Save profile sent:  {"displayName":"…","bio":"…","isPrivateAccount":false,"theme":null}
   -> HTTP 500 {"error":"Internal server error"}
B) same body, null removed
   -> HTTP 200 {"message":"User updated successfully"}

Both writers now build a sparse dictionary. The generalisation is on #171: any PATCH body built from an anonymous object with optional parameters is a latent 500 on this API.

Four findings that shaped the implementation

  1. Native JSON numbers/booleans are correct — the OpenAPI request schema is wrong, typing every field as "string". (The response schema uses native types.)
  2. viewingPreference is enum-validated, and the server named its own values in a 400: my_messages, all_messages, followers_only, following_only.
  3. theme is not validated — it accepted and stored "zzz_bogus". So ResolveDark treats anything unrecognized as "follow the OS", and the OS listener is retained.
  4. The PATCH 200's user is narrower than GET /api/user (no accountStatus/cleared/pendingEmail/isAdministrator), so deserializing it would blank Settings: account-status banner and gated-feature messaging #50's banner fields — callers re-fetch.

Live ranges from 400s, now validated client-side: maxMessageLength 1–10000, messagesPerPage 10–30, notificationTrayLimit 10–40.

Consumers still needing wiring

Six consumption points sit in files held by other PRs, each spelled out with its exact one-line change: messagesPerPage, notificationTrayLimit, maxMessageLength, showAdvancedPostSettings, showPreviews, viewingPreference. defaultPubliclyVisible was already honored; theme is done here.

Two of those are not one-liners: viewingPreference needs a product decision (followers_only/following_only have no server-side equivalent — see #36), and showPreviews has nothing to gate yet since nothing calls the metadata endpoint.

Shared-account hygiene

Nine fields were probed off their real values and all nine restored, confirmed by a GET /api/user that diffed byte-identical to the pre-probe capture. No identifying field touched.

Builds green in Debug and Release.

🤖 Generated with Claude Code

…for real

#131 modelled the 14 missing `GET /api/user` fields; this makes them
editable and makes the one consumption point that isn't in an in-flight
file actually obey its value.

Theme precedence (#46's acceptance criterion) now works: an explicit
server `theme` of light/dark wins over the OS, and `system` — or null,
or anything else — falls back to the existing HKCU/`UserPreferenceChanged`
behaviour, which stays wired. `App` re-resolves on every `CurrentUser`
change, so login, session restore and a settings save all take effect
without a restart. The fallback is not defensive padding: the server does
NOT validate `theme` (it accepted and stored `"zzz_bogus"` in a live
probe), so an unrecognized value has to degrade to the OS setting.

Fixes a live 500 along the way. `UpdateProfileAsync` built its body from
an anonymous object, and `JsonSerializerDefaults.Web` does not drop
nulls, so every "Save profile" shipped `"theme": null` — which the server
answers with `500 internal_error`, writing nothing. Both writers now
build a sparse dictionary and omit untouched fields, which also stops a
profile save from stomping preferences set elsewhere.

Verified live 2026-09-16 against the shared test account with a
throwaway `issue-46-probe` sync-token. All 15 `PATCH /api/user/update`
field names accept native JSON numbers/booleans (the OpenAPI *request*
schema types them as `"string"` — that's wrong). The server named the
`viewingPreference` enum itself in a 400, and enforces
maxMessageLength 1–10000, messagesPerPage 10–30, notificationTrayLimit
10–40; those ranges are now validated client-side so a typo is an inline
message rather than a raw 400. Every value changed was captured first and
restored; a follow-up `GET /api/user` diffed byte-identical.

The PATCH 200 does return a `user`, but a narrower one than `GET
/api/user` (no accountStatus/cleared/pendingEmail/isAdministrator), so
callers re-fetch rather than trust it — house read-after-write rule.

33 assertions covering the theme truth table, the normalizers, the
sparse-body serialization and the captured live payload were run in a
throwaway net10.0 console project outside the repo; all pass. Debug and
Release both build clean.

Closes #46

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Single trivial conflict in SettingsView.xaml's resource dictionary: this branch
adds StringEqualsToBoolConverter (for the viewingPreference radio group) and
main has InverseBooleanConverter (from #132's notification-channel toggles).
Different converters, both wanted — kept both.

SettingsViewModel.cs auto-merged.

Debug and Release both build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Adron
Adron merged commit 6b5061f into main Sep 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Settings: model and honor every server-side preference on GET /api/user

1 participant