Nightly cloud-backup syncs kept retrying Google connections whose
refresh token is permanently dead (Google returns 400 invalid_grant;
3 of 12 prod connections are in this state), and the settings card
showed the raw English error string while presenting the account as
connected.
- refreshAccessToken now throws a typed GoogleTokenRefreshError
carrying status + body, with an isInvalidGrant discriminator.
- performSync catches the invalid_grant case, persists
status: 'needs_reauth' (+ needs_reauth_at) on the connection JSON in
extension_data (no migration needed), and returns a needs_reauth
failure instead of throwing. Transient failures (5xx, network, other
400s) still throw and stay retried.
- The nightly cron loads connections for due companies and skips
needs_reauth ones (reported as skipped in the summary) instead of
retrying the dead token every night. A successful refresh clears a
stale flag; reconnecting via OAuth writes a fresh connection.
- CloudBackupCard shows a reconnect callout (Swedish-first, sv+en
strings) wired to the existing connect action, and replaces the raw
error string on the schedule row with a short reconnect notice.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>