Skip to content

fix(settings): reduce settings desync — server-sourced defaults + atomic save - #519

Open
pnewsam wants to merge 2 commits into
stagingfrom
paul/eng-941-settings-desync-client
Open

fix(settings): reduce settings desync — server-sourced defaults + atomic save#519
pnewsam wants to merge 2 commits into
stagingfrom
paul/eng-941-settings-desync-client

Conversation

@pnewsam

@pnewsam pnewsam commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Saving user settings has been a source of many bugs. We've applied many patches but there are some root causes that have gone unaddressed, as described in ENG-941. This makes a first pass at improving some of the settings code design.

This PR is paired with the backend PR. It includes two changes:

  • Read defaults from backend (single source of truth): App.jsx seeded settings from a hard-coded object that had drifted from the server. This removes that object and replaces it with a fetch of the server's GET /settings/, which already returns defaults.
  • Combines many update requests to one: updateSettings sent one PUT /settings/{key} per changed key so any failure resulted in a partial write. It now sends a single all-or-nothing update request to a new bulk update endpoint.

References

pnewsam and others added 2 commits July 28, 2026 12:40
…the server (ENG-1125)

Client half of the ENG-941 settings-CRUD refactor (PR1). App.jsx seeded its
settings state from a literal object of defaults, a second copy of a contract
the server already owns. It had drifted: showDots was False here while the
server default is True (the server value already won post-fetch, so this only
ever produced a wrong-value flash on first paint), and defaultModel hard-coded
a model name.

The server (GET /settings/) is the single source of truth and returns every
field's resolved default, so the client doesn't need its own. This removes the
literal and seeds first paint from a read-through cache of the last successful
fetch instead:

- Add lib/settingsCache.{js,test.js}: load/persist the last settings blob in
  localStorage. A cache of the server, never a competing default — written only
  by fetchSettings, read only to seed useState before the boot fetch lands.
- App.jsx: useState(loadCachedSettings) in place of the hard-coded object.
- api.js: fetchSettings caches its result on success.

On the very first launch the cache is empty and the app renders the server's
values within one fetch (getAgentLabel and the accent/dots reads are all
null-safe on a bare object). No new endpoint, no storage or auth change.

Verified: typecheck, cowork purity, and the full unit suite (603) pass.
Needs an app first-paint smoke in QA (cold start with an empty cache).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…loop (ENG-1126)

Client half of the ENG-941 settings-CRUD refactor (PR2). updateSettings sent one
PUT /settings/{key} per changed key, so a failure partway through left the DB
half-saved (earlier keys committed, later ones not) and threw with a partial
`updated` list. It now sends a single PUT /settings with all changed keys, which
the server applies in one transaction — all-or-nothing.

Pairs with cowork-server #248 (the bulk endpoint). No storage or auth change.
Verified: typecheck, purity, full unit suite pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pnewsam
pnewsam marked this pull request as ready for review July 29, 2026 00:27
@pnewsam pnewsam changed the title fix(settings): reduce settings desync — server-sourced defaults + atomic save [client] (ENG-941) fix(settings): reduce settings desync — server-sourced defaults + atomic save Jul 29, 2026
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.

1 participant