Arkadiusz Fal b67b3472f6 Let a purge be answered once and a re-seed be answered until it sticks
The two answers to a deleted zone were sharing one lifecycle, and only one of
them deserved it. A re-seed only adds, so it is held open until the engine has
actually serialized the work and redone for as long as it has not. A purge
removes, and replaying it removes more than it was asked to — the account added
while the app kept running, the category switched back on, the reset that runs
its own upload afterwards. It is done the moment its durable half is, and the
comment that called both idempotent now says which is which.

The note itself was never scoped to an identity. A marker written under one
Apple account replayed against the next one's namespace, answering a wipe that
never happened there, and a purge would have taken that account's staged records
and its outbox with it. The account change forgets it now, the way it forgets
everything else it holds.

Retiring the marker trusted whichever state update arrived next, which is not
the same as one that carries the work: an update handed over before the recovery
registered anything can still reach the main actor after it, describing a state
in which none of it exists. Deliveries are counted where the engine hands them
over, and only a later one can retire what was armed.

A refused re-seed waited for the next launch. The accounts it could not speak
for announce themselves when they load, and the recovery is answered then —
outside the callback tree that announcement comes from.

A purge left two ways back for what it dropped: a pending zone save would have
recreated the zone on the next send, and the pre-engine pending-change keys
would have been rediscovered on a later launch. It clears both, and it now runs
after everything that puts work into the engine rather than before.

The account half fences before it destroys, like its upload twin, so a purge
arriving during the identity reload gap answers with nothing rather than with
somebody else's records.

And the reset that promises a re-upload is refused while sync is off, where
nothing would have performed one.

Re-entering the engine from inside its own callback is what started all of this,
and nothing in the code said so. A task-local marks the callback tree and the
two entry points assert on it in debug builds — a plain Task inherits it, which
is the mistake; Task.detached escapes it, which is the fix.
2026-09-09 23:59:59 +02:00
2026-04-18 20:37:24 +02:00
2026-08-23 11:04:21 +00:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:37:24 +02:00
2026-08-23 12:46:11 +02:00
2026-04-23 15:45:49 +02:00
2026-04-18 20:37:24 +02:00
2026-04-18 20:38:00 +02:00
2021-11-10 00:25:38 +01:00
2026-07-18 17:38:43 +02:00

Yattee logo

Yattee

Privacy oriented video player for iOS, tvOS and macOS

AGPL v3 GitHub issues GitHub pull requests

Discord

Yattee 2 is in the works! A new version of the app is being built with a refreshed experience. It pairs with the new Yattee Server — a self-hosted backend powered by yt-dlp that supports 1000+ sites. Join the TestFlight beta to try early builds, and check the new documentation site at docs.yattee.stream for guides, roadmap and changelog.

Features

  • Native user interface built with SwiftUI with customization settings
  • Player queue and history
  • Player component with custom controls, gestures and support for 4K playback
  • Fullscreen, Picture in Picture and background audio playback
  • SponsorBlock, configurable categories to skip

License

Yattee and its components is shared on AGPL v3 license.

Description
Privacy oriented video player for iOS, tvOS and macOS
Readme AGPL-3.0 344 MiB
Languages
Swift 96.9%
Ruby 2.7%
C 0.3%