fix(settings): fall back to the profile endpoint when the settings API is absent - #153
Conversation
…I is absent
Every profile setting is silently discarded on servers that predate the
canonical settings API. Confirmed against production:
PUT /api/v1/settings/values/playback.subtitle_language -> 404
GET /api/v1/settings/values/effective -> 404
GET /api/v1/settings/contract/capabilities -> 404
GET /api/v1/settings/overlay-config -> 200
The screen applies the change optimistically, the write 404s, and the
ViewModel rolls back — so the row snaps straight back to its old value with
no error shown. It affects subtitle language, subtitle mode, forced
subtitles and metadata language equally; subtitles are just where it gets
noticed. The controller's own doc asserted Android "no longer depends on the
profile endpoint accepting subtitle_language", and that assumption does not
hold for these servers.
The legacy columns on PUT /profiles/{id} still carry all four preferences
there, and the server mirrors them into canonical rows once upgraded, so the
fallback loses nothing.
- Reads: load() returns the profile's real values instead of a null snapshot.
That mattered more than it looks — a null snapshot left the screen on
defaults, and the next edit wrote those defaults over preferences the
viewer had never touched.
- Writes: the canonical path is tried whenever the contract is believed
present, so a modern server is never pushed onto the legacy columns. Only
a 404 triggers the fallback, and it flips the belief so later writes skip
the dead route. Any other failure is reported as a failure — retrying a
rejected value on the legacy path would store what the contract refused.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 57 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Closing because supported clients require the canonical settings API and server upgrades are mandatory. We will keep the canonical-only write path and fix the unsupported-server UX instead: show the existing upgrade-required notice, prevent edits to unavailable profile settings, and leave device-local subtitle controls usable. |
Every profile setting is silently discarded on servers without the settings API
Reported as "I can't set Dutch as my default subtitle language — it snaps back to Off." It turned out to have nothing to do with subtitles, or with which languages the picker offers. Captured from a production server's own logs:
The server predates the canonical settings API and answers 404 to the whole family. The settings screen applies the change optimistically, the write 404s, and the ViewModel rolls back — so the row snaps straight back to its previous value with no error shown.
The diagnostic that isolates it: does English fail too? It does. That separates a picker problem from a write-path problem in one step.
Scope
Not subtitles — everything
ProfileSettingsControllerowns:playback.subtitle_languageplayback.subtitle_modeplayback.show_forced_subtitlescatalog.metadata_languageSubtitles are simply where a user notices first.
Availability.SERVER_UPGRADE_REQUIREDalready exists for exactly this case — the screen is supposed to explain, "instead of rendering rows whose edits would silently go nowhere." The probe fires and returns that value, but the setters never consulted it, so the rows rendered as editable regardless.The fix
These preferences rode
PUT /profiles/{id}as named columns before the contract landed, and the class doc records the assumption that replaced it:That does not hold for servers without the contract. Those columns still work there, and the server mirrors them into canonical rows once upgraded, so the fallback loses nothing.
Writes — the canonical path is tried whenever the contract is believed present, so a modern server is never pushed onto legacy columns. Only a 404 triggers the fallback, and it flips the belief so subsequent writes skip the dead route rather than re-probing per keystroke. Any other failure is reported as a failure: retrying a rejected value on the legacy path would store precisely what the contract refused.
Reads — this mattered more than it first appears.
load()returned a null snapshot on these servers, so the screen rendered defaults, and the next edit would write those defaults over preferences the viewer had never touched. It now reads the profile's real values.Verification
Confirmed end-to-end against the same production server that produced the 404s, from a device build:
The UI renders the profile returned by that PUT (no re-read, by design), so the value persisting is the confirmation the response carried it.
Seven unit tests, covering the two ways this is easy to get wrong:
plus: the contract is abandoned once rather than re-probed per write; the load returns real values instead of defaults; all four settings fall back, not just subtitle language; and with no fallback configured the failure still surfaces.
./gradlew testgreen.Notes for review
ProfileSettingsController's companion moves fromprivatetointernalso the legacy implementation can sharenormalizeSubtitleMode. Same normalisation on both paths, rather than a second copy that drifts.nulldisables it, which is what the existing canonical-path tests want.