Commit Graph

6 Commits

Author SHA1 Message Date
Arkadiusz Fal
1c0a3e8e9a Keep one login's verdicts and content off another's
A feed failure recorded its verdict before checking whether anyone was
still waiting for it, and the verdict resolves the *currently* selected
account — so a 401 from a credential the app had already moved off could
mark its replacement, or a different account, as rejected indefinitely.

The instance browser drops its feed, subscriptions and playlists when
the selected login changes. Nothing else there would notice: the
instance, the tab and the "already has data" shortcut are all unchanged
between two accounts on the same server.

Caches are no longer discarded while the identity is unresolved. With
iCloud Drive off the scope drops to unresolved on every activation and
comes back the same, and the context reads as local in between — which
looked like a move and threw the caches away several times a day.

Local is always in the account picker again. Its feed needs a Yattee
Server and reports that itself; with correction no longer moving away
from local, leaving it out of the list made it unreachable once left.
And the subscriptions view reloads when an instance is switched off,
which moves reads to local without touching the context or any status.
2026-08-28 23:24:06 +02:00
Arkadiusz Fal
81aa47196f Stop the legacy validator from overruling a local selection
Local data is always a valid context. The legacy validator predates
that: it treats local as unavailable without a Yattee Server, which is a
feed concern the feed reports itself — and since the correction now
moves the data context with it, that would have quietly put users on
somebody's backend account, overwriting a local choice made on another
device.

Stale feed responses no longer touch anything. Rejecting only the videos
still let a request from a login the cache had moved off overwrite the
new one's loading state, error state and pagination — replacement
requests are allowed to overlap, so both were in flight.

The unreachable retry timers are cancelled when the namespace reloads. A
migrated account reuses its instance's UUID, so the same ID can exist
under the next Apple account, and an old timer would declare that
account reachable on the strength of a verdict about someone else's.

Rewriting a credential's sync flag is skipped when it already matches
and restores the original if the re-add fails. Changing that flag means
delete-then-add, and the delete propagates to every device — turning
sync on should never be able to log the user out.
2026-08-27 23:57:28 +02:00
Arkadiusz Fal
b07af6c153 Separate account status from the account list, and stop stranding accounts
Recomputing statuses reads the Keychain, which cannot know that a server
rejected a credential or failed to answer — and it ran in reaction to
the same notification those verdicts were announced with, so it erased
them as they were recorded. Status changes now have their own
notification. With that separation `unreachable` no longer has to be
preserved to survive: it is transient, and preserving it stopped every
read from reaching the server, so nothing would ever discover that the
network had come back.

A fetch already running when its provider is discarded now has its
result dropped. Signing in again — or switching Apple account — changes
neither the context nor the account ID, so the previous login's channels
would otherwise arrive as the current account's.

Caches with no recorded owner are discarded rather than adopted. The
first launch after upgrading finds feeds written before anyone recorded
whose they were, and the Apple account may have changed since.

The legacy validator's auto-correction moves the data context with it,
and Manage Channels reloads on the context rather than the legacy value
— it could list one account's channels while unsubscribes went to
another.
2026-08-27 18:19:29 +02:00
Arkadiusz Fal
f3e03e5790 Stop automatic context repairs from outranking real choices
Correcting away from a deleted account was stamped with the current time
and published. A device hearing about the deletion late would therefore
sink an explicit choice another device made in between, and push its own
automatic fallback over it. The repair now keeps the timestamp of the
selection it replaces and is never uploaded: every device performs the
same repair when it sees the deletion, so they converge without anyone
writing anything.

The tombstone is queued before the removal can persist. Deleting the
selected account also corrects the context, and that wrote storage first
— a crash in the gap left the account gone here and alive everywhere
else, with nothing owed to the server.

An identity reload replaces the context too, so it announces one. The
legacy selection and the feed cache, which is not keyed by account,
would otherwise keep serving the previous Apple account's data; the
cache is now cleared unconditionally on an identity change, and the
legacy selection falls back to local when the new namespace's context
cannot be mapped yet.

The legacy validator no longer corrects a selection whose account still
exists. It predates account-scoped credentials and reads "not signed in"
as "not available", which is now routinely temporary — an unresolved
identity scope, or a credential iCloud Keychain has yet to deliver — and
correcting on that desynchronized it from the data context, which is
preserved.

Background refresh routes on the effective context, so a disabled
instance no longer receives background requests.
2026-08-25 23:57:45 +02:00
Arkadiusz Fal
defa6cf084 Make the account picker and feed route see account logins
Round three of review.

The subscription picker is fed by SubscriptionAccountValidator, which still
decided availability from the instance-keyed credential managers. A login
created after the revamp writes only an account-scoped key, so it never
appeared in the picker at all - which meant the account resolver added last
round could not even be reached. Availability now comes from AccountsManager
and account status.

SubscriptionFeedCache dispatched on the legacy setting and picked its
instance from it, so an account waiting on its credential produced
subscriptions from local data while the feed reported notAuthenticated, and
deleting the selected account could leave the route pointing at its server.
Both the route and the credential now come from the effective context, with
the legacy lookup kept only as a fallback for state that predates accounts.

Foreground activation re-resolved the identity scope but only recomputed
statuses when the scope actually moved. An iCloud Keychain credential
arriving after launch does not move it, so the account stayed
credentialMissing and reads stayed on the local fallback until relaunch.
Statuses are now recomputed on every activation in both scene-phase
branches.

Providers are bound to an account but never told it anything: a revoked SID
left the account signedIn, so the validator kept treating it as valid and
neither the banner nor the read fallback engaged. Server calls now record
their outcome. Conservatively - a raw 401 marks the credential rejected only
when no reverse-proxy basic auth is configured, since otherwise the 401 may
have come from the proxy. The two-stage probe that disambiguates the rest is
Phase 2; until then an ambiguous failure leaves the status alone rather than
wrongly telling the user to sign in again.
2026-08-25 22:40:47 +02:00
Arkadiusz Fal
100df744d9 Yattee v2 rewrite 2026-04-18 20:37:24 +02:00