[User storage] New browser API#277600
Open
tsullivan wants to merge 10 commits into
Open
Conversation
- `isAvailable()`/`isAvailable$()` and `canWrite()`/`canWrite$()` — seeded from a new server-injected `available` boolean (computed from `profile_uid` presence in the rendering service), so consumers stop making a separate user-profile network request. - `get()` reshaped to **async** — resolves once the value is truly known (awaits lazy hydration), rejects on fetch failure. `peek()` is unchanged and remains the sanctioned synchronous, cache-only read. - `getState$()` — new load-state observable (`loading`/`resolved`/`error`). - `update(key, default, updater)` — safe resolved read-modify-write; no-ops (skips the write) when the updater returns the same reference. Consumers migrated: - `date_range_picker_presets_service.ts` — `savePreset`/`deletePreset` now use `update()` (fixing the actual data-loss race), `getCanWrite$` delegates to `userStorage.canWrite$()`, `userProfile` dependency dropped entirely. - `navigation_customization_service.ts` — the only code broken by the async `get()` reshape; its two calls switched to `peek()` (its key is `preload:true`, so behavior is unchanged).
tsullivan
force-pushed
the
user-storage/new-browser-api
branch
from
July 24, 2026 20:45
df64072 to
83a494e
Compare
tsullivan
marked this pull request as ready for review
July 24, 2026 20:45
davismcphee
approved these changes
Jul 24, 2026
davismcphee
left a comment
Contributor
There was a problem hiding this comment.
Code-only review, Data Discovery changes LGTM 👍 It wasn't clear to me if there was something I needed to manually test here.
Co-authored-by: Davis McPhee <davismcphee@hotmail.com>
Contributor
💛 Build succeeded, but was flaky
Failed CI StepsTest Failures
Metrics [docs]Page load bundle
History
cc @tsullivan |
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 #276110
Extends the browser User Storage client so consumers no longer reimplement availability checks or lazy-cache readiness themselves, then migrates the two in-repo consumers onto the new APIs.
New client APIs:
isAvailable()synchronous signal to flag if consumers can read/write to storageget()changed to an async, resolved readgetState$()load-state observableupdate()read-modify-write helperAPI additions
isAvailable()availableflag (falsefor anonymous / noprofile_uid/ authz failure at inject time). A single condition gates both read-preload and writes.get()→ async resolvedPromisethat resolves once the value is known (awaiting a lazy fetch) and rejects on fetch failure.peek()remains the synchronous cache-only read.get()callers mustawait; read-modify-write no longer builds on unhydrated cache state.getState$()load-state observable{ status: 'loading' | 'resolved' | 'error', value }.update()read-modify-write helperServer plumbing
The
availableflag is computed in the rendering service (fromprofile_uidpresence, defaulting tofalseon missing service / anonymous / authz failure) and threaded to the browser through the shared injected-metadata types.Consumer migrations
savePreset/deletePresetuseupdate();getCanWrite$()wrapsisAvailable()as a one-shot observable;userProfiledependency removed.get()calls switched topeek().preload: true, sopeek()returns the same cached value.useUserStoragehook[value, setter, { status }](backed bygetState$) instead of[value, setter].Identify risks
get()is now async (breaking browser JS API). A caller treating it as synchronous would get aPromise. Mitigation: audited and migrated every in-repo consumer; full type-check passes, so no caller compiles against the old signature.update()is last-write-wins. Concurrent writers (e.g. multiple tabs) can still clobber each other. Mitigation: documented in the API; true optimistic concurrency is out of scope (no version/etag in the stack today) and noted as a follow-up.