Repository navigation
Batch 2026-09-30: clock skew, vault restore, connect dialog, twin agent, Google sign-in - #1946
Merged
Merged
Conversation
When the account service offers assisted recovery, the backup gets an atomic-assisted wrapper whose key the service hands to a recently signed-in account (the DEK and the secret stay in the browser). So: - A new account's backup is wrapped by the account alone: onboarding asks for no passkey and shows no recovery code. - The sign-in and restore steps unlock it by themselves after a recent sign-in; an old sign-in is asked to sign in again. - Backups made before this get the wrapper the next time a passkey or recovery code opens them. - Both steps offer "Continue with Google", straight back to the app. Without the endpoints (a standalone server), nothing changes.
The app's sign-in step offered Google and an agent secret, the portal offered Google, passkey and an email link, and its homepage panel had no passkey. Every account sign-in now renders AccountSignIn from @tomic/service-ui, so the options and their order are the same everywhere. The app does the passkey and email-link sign-in itself against the account service; builds without an account service show none of it and send no requests for it. Also names assisted recovery on the account page instead of falling back to "your recovery password".
An app that cannot hold the account cookie showed a device-link code instead of the sign-in options. It now shows the same AccountSignIn; each option opens the portal in the system browser at /link with that option (via=google, passkey or email) and this device's code, and the app continues once the code is approved there.
AccountSignIn gets a "How safe is each option?" link that opens a dialog: what a passkey, Google and an email link each trust, who learns what, and what signing in opens. With assisted recovery on it says plainly that Atomic keeps a key that, with its database, can open the identity backup.
Account recovery gets a 'Let Atomic help me recover' switch. Off removes the assisted wrapper, so only a passkey or recovery code opens the identity, and the app adds none in the background. A new assisted wrapper records the service key id; when an unlock says the key was rotated, the app re-wraps with the current key. The app shows an account's address and keeps the account id for backups and bindings.
…count opens With account ids the app shows the address from /api/me in the profile, the share dialog and onboarding. A backup that only signing in opens can now get a passkey from the recovery card: a recent sign-in reaches the DEK, as it does for unlocking.
The onboarding flow now shows the account's address; without it in the session mock the invite test fell through to signed out.
The sign-in step now shows the same account options as the portal, each finished in the system browser, so the separate "Forgot it? Restore from" button is gone there. The spec checks for the options instead.
…roviders Provider buttons now read just the provider's name, side by side in one row, instead of "Continue with ..." stacked on top.
Side by side they need about 330px; on a phone the names would clip. Icons keep their size inside a narrow button.
download-urls signs at most 64 objects per request, and restoreDrive asked for every object at once, so a drive with a longer history could not be restored on a new device.
An app that calls store.proxy.connect used to get a bar above it that read "This app wants to use your Pets account through <proxy origin>. The proxy keeps the connection under your account; this app may use it until you revoke that." It now gets a dialog titled "Connect <platform>" that says which app wants which account, that you approve it on the next page (naming the proxy's host, which is the address to expect there), and that the app never sees your password. When you already connected that platform, the dialog lists each such connection with when it was connected and which apps use it, each with "Use this account"; "Connect another account" still goes to the proxy. The lookup settles either way, so the dialog shows a placeholder while it runs instead of changing its buttons under your cursor. A failure (no app identity, proxy unreachable) is shown in the dialog so you can retry; the app hears `cancelled` if you close it. Closing the dialog while "Connect" is still preparing no longer navigates away afterwards. Dialog gains an optional `labelledBy`, so the dialog is named by its title for screen readers and tests.
…its on
Seen in screenshots of the dialog against the Pets demo:
- On a 390 px phone "Cancel" broke into "Cance" / "l" next to "Connect
another account". That button is now "Use another account", which sits
beside "Use this account" and fits, and the footer wraps its buttons
onto a second line instead of squeezing them.
- The placeholder shown while looking for existing connections was 0 px
high, so the dialog just had a gap. It is now a line-sized pulse.
- A proxy error ran out of its box, because it names the request path.
It now wraps, and leads with what failed ("Could not use this
account." or "Could not start connecting.") above the proxy's words.
The dialog's content already pads its bottom, so the list's own margin left an empty band above the buttons.
The same public key has been written padded, unpadded, standard and URL-safe. A profile stored under one spelling was invisible to the canonical unpadded subject, which got an empty synthesized agent instead, so one person showed up as two resources.
2 of 3 tasks
The React Compiler test refused GettingStartedFlow.tsx with 'PruneHoistedContexts: Unexpected hoisted function' once the assisted-unlock effect called handleSignInWithSecret, declared further down. The function now sits above the effect, next to accountSignIn, and moves nothing else.
joepio
marked this pull request as ready for review
October 1, 2026 03:09
This was referenced Oct 1, 2026
joepio
added a commit
that referenced
this pull request
Oct 1, 2026
atomic-saas CI builds the atomic-server branch of the same name. This branch was stuck before the batch and the audit fixes, so saas #164 ran against an app that reads /api/me far more than develop's does. Same tree as develop.
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.
Before: #1935, #1936, #1944, #1945 and #1856 each waited for their own full Mancave run, several hours in a row.
After: one run tests them together, and squash-merging this batch lands them all.
Includes #1935 (server clock skew), #1936 (vault restore over 64 objects), #1944 (connect dialog), #1945 (twin agent resource) and #1856 (sign in to open the identity, pairs with atomic-saas #164). #1866 had catalog conflicts and stays out.
How: each PR head merged onto develop 5ae2786; CHANGELOG conflict between #1944 and #1856 resolved by keeping both entries.
Generated by Claude Code