Repository navigation
Settings: "Save profile" has never worked — it sends theme: null and gets HTTP 500 #171
Description
The Save Profile button returns 500 every time
Found while reviewing #46's work (fixed there — filed for visibility, since a shipped-broken button deserves a tracked identity).
SettingsViewModel calls:
await _session.Api.UpdateProfileAsync(DisplayName, Bio, IsPrivateAccount);UpdateProfileAsync has a 4th parameter defaulting to null:
public Task UpdateProfileAsync(string? displayName, string? bio,
bool? isPrivateAccount = null, string? theme = null, …)
=> SendVoidAsync(HttpMethod.Patch, "api/user/update",
new { displayName, bio, isPrivateAccount, theme }, ct);JsonSerializerDefaults.Web does not drop nulls, so the body always carries "theme": null — and the server rejects an explicit null.
Reproduced live (2026-09-16)
A) exactly what Save profile sends today:
{"displayName":"…","bio":"…","isPrivateAccount":false,"theme":null}
-> HTTP 500 {"error":"Internal server error","code":"internal_error"}
B) identical body with the null key removed:
{"displayName":"…","bio":"…","isPrivateAccount":false}
-> HTTP 200 {"message":"User updated successfully","user":{…}}
So editing your display name, bio or private-account flag from this app has never worked. The catch (InterlinedApiException) shows the message, so it isn't silent — it just always fails.
Root cause worth generalising
Any writer that builds a PATCH body from an anonymous object with optional parameters will ship nulls for the ones the caller omitted. On this API an explicit null is a 500, not a no-op, so every such writer is a latent 500. PATCH bodies must be built sparsely — only the fields actually being changed.
Fix
Already implemented in #167 (issue-46-honor-preferences): both profile writers build a sparse dictionary instead of an anonymous object.
Acceptance criteria
-
UpdateProfileAsyncomits unset fields rather than sending null — Settings: edit every server-side preference, and honorthemefor real (#46) #167 - Any other
PATCH/PUTwriter built from an anonymous object with optional params audited for the same defect - Save profile verified working end to end against a live account
Related live findings from the same work
- The OpenAPI request schema for
/api/user/updatetypes every field as"string". That's wrong — native JSON numbers and booleans are correct (the response schema uses native types). viewingPreferenceis enum-validated; the server names the values in its 400:my_messages,all_messages,followers_only,following_only.themeis not validated — it accepted and stored"zzz_bogus".- Live ranges from 400s:
maxMessageLength1–10000,messagesPerPage10–30,notificationTrayLimit10–40. - The PATCH 200's
useris narrower thanGET /api/user(noaccountStatus/cleared/pendingEmail/isAdministrator) — deserializing it intoCurrentUserwould blank those fields, so callers must re-fetch.
Activity
Adron commentedon Sep 16, 2026
Fixed in #167 (
issue-46-honor-preferences), which builds both profile writers as sparse dictionaries.Filed for visibility rather than as new work — a button that has never worked deserves a tracked identity, and the root cause generalises: any PATCH body built from an anonymous object with optional parameters ships nulls for the omitted ones, and on this API an explicit
nullis a 500, not a no-op. The second acceptance criterion (auditing other writers for the same defect) is genuinely open.