fix(settings): persist the "System" theme choice - #97
Merged
Merged
Conversation
The theme picker mapped "system" to nil, and a synthesized Encodable
omits a nil field with encodeIfPresent, so the PATCH body encoded to
`{}`. `/api/user/update` applies `...(theme !== undefined && { theme })`,
so the key never arrived, the stored theme was never changed, and the
response returned the unchanged user — snapping the picker back.
Send "system" verbatim instead. The route performs no validation on
`theme` and the web persists the same literal string, so the value
round-trips; RootView.preferredScheme already treats any non-light/dark
value as "follow the OS", so no change was needed there.
The comment claiming the server rejects "system" was wrong and is gone.
Tests pin the wire format: theme "system" sends {"theme":"system"}, and
a nil theme omits the key entirely (the silent no-op that caused this).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bss5MgZa7Jvj2m9zdaUd1
This was referenced Sep 16, 2026
Adron
added a commit
that referenced
this pull request
Sep 17, 2026
Conflict confined to project.pbxproj, but a plain union of both sides was wrong here: this branch was stacked on #97 and so carries object definitions main had already picked up at a different position in the file. Taking both produced six duplicate UUID definitions — a corrupt project, not a merge. Resolved by unioning the hunks and then dropping any object definition whose UUID was already defined earlier in the file. Verified afterwards that ThemePreference.swift and ThemePreferenceTests.swift each still have exactly one PBXBuildFile, Sources entry, PBXFileReference and group entry, and that the mute files main contributed are likewise intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bss5MgZa7Jvj2m9zdaUd1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #79. Settings → Appearance → Theme → System now sticks.
The picker mapped
"system"tonil, and Swift's synthesizedEncodableomits a nil field(
encodeIfPresent), so thePATCH /api/user/updatebody encoded to{}. The route applies...(theme !== undefined && { theme }), so an absent key is a no-op: the stored theme was neverchanged, the response returned the unchanged user, and
syncFromUsersnapped the picker back tothe old value.
The comment justifying the nil mapping — "Sending the string
"system"is rejected or treated asdefault (light) by the server" — was factually wrong. The route destructures
themeand storeswhatever string arrives with no validation (
app/api/user/update/route.ts:24,102,157), and the webpersists the same literal
"system"(components/layout/ThemeProvider.tsx:11,17), so the valueround-trips with the web Settings page.
What's included
InterlinedList/Views/SettingsView.swift— sendnewValueverbatim from the theme picker;delete the nil mapping and the incorrect comment. The spurious-save guard is unchanged.
InterlinedListTests/APIClientTests/APIClientProfileTests.swift— two tests pinning the wireformat:
theme: "system"sends exactly{"theme":"system"}, and a nil theme omits the keyentirely (the silent no-op this bug rode on, with the route semantics noted as the "why").
RootView.preferredSchemeverified, not changed: it returnsnilfor any non-light/darkvalue, so
"system"already means "follow the OS".No new files, so no
project.pbxprojchange.Testing
** TEST SUCCEEDED **— Executed 1149 tests, with 0 failures, including the two new ones.The encoder behaviour was also confirmed directly against the
updateUserSettingsbody struct:the old
"system" → nilmapping encodes to{}, the fix encodes to{"theme":"system"}.Not verified here (needs a live account): the round-trip through
GET /api/userand an apprelaunch, and parity with the web Settings page.
🤖 Generated with Claude Code