Preferences are per-user state the API keeps on the profile, things like which field a list sorts on or whether bookmarks render condensed. Settings covers config the app owns, preferences cover the choices a user makes and expects to find again on their next visit, on any device.
None of this is a form. There is no preferences screen to submit. Changing a sort or flipping a view writes the preference as a side effect of the control that changed, and the request goes out in the background.
Every preference is a writable computed in usePreferences, one file per app. Reading falls back through the stored preference to the app's config default, so a user who has never set one still gets something sensible and a newly added preference needs no migration. Writing updates the store and fires the request.
That is the only place the preferences on the auth store get written. Components bind to the computed and never reach for the store or the endpoint themselves, which is what keeps the local write and the request from drifting apart.
The UI renders off the store, not off the response. If the write waited on the server, every sort change and view toggle would sit for a round trip before anything moved. So the store takes the new value immediately and the request follows behind it. A failure is left silent, since it is a preference and the next change re-persists it anyway.
This is the part that changed when sorting moved out of the route query. Query params update synchronously and the list refetches on the spot, so nothing optimistic was needed. Reading from a store brings that back as something you have to do on purpose.
Sorting used to live in the route query. It does not anymore, partly because a URL param that always mirrors a stored preference is noise, and partly because the watcher persisting it fired on back and forward navigation too, saving preferences the user never picked.
The resource params composables still hand sorting back out, since the list's query key wants every server param in one object. They wrap it to also reset the page, which Query Params does for free and preferences do not.
The view toggle deliberately does not go through the params composable. That object is the query key, so a preference sitting in it would refetch the entire list every time someone flipped between condensed and expanded.
Add the key to both sides of the preferences mapping on the auth model, give it a default in the app's config, and declare a computed for it in usePreferences. A tags view or a default page size is one more computed in the same file rather than a new composable, which is the whole reason preferences are one file per app instead of one file each.
Settings for the config layer preferences fall back to.
Query Params for the server params that do stay in the route.