mirror of
https://github.com/yattee/yattee.git
synced 2026-10-31 17:07:18 +00:00
Aim a removal at the row the server counts
Remote playlist entries carried the display row number as their server position, so a row the app could not parse shifted every removal after it by one, and rows were paired back to entries by video id, which cannot tell two copies of the same video apart. - Piped entry positions are now the raw stream index; entries carry a separate displayRow for the row they are drawn as, filled in by both snapshot builders and used by the detail view's row map - an emptied description clears a server-side playlist instead of being read as "leave it alone" - a delete that reported failure forgets a playlist the refetch 404s on, and drops it from the account's cached list - the browse view discards account-scoped content when a status change moves which account the instance resolves to - every mutation re-reads the credential immediately before it is sent, which matters most after Piped's verify-before-remove fetch - Handoff carries the owning account id, so a Piped playlist keeps its ownership on the receiving device
This commit is contained in:
@@ -21,7 +21,9 @@ import Foundation
|
||||
/// - ``invidious(playlistID:indexID:)`` wraps the server's `indexId` token,
|
||||
/// which names the same row across refetches even as videos above it are
|
||||
/// removed.
|
||||
/// - ``piped(playlistID:position:)`` is **snapshot-scoped only**. Piped
|
||||
/// - ``piped(playlistID:position:)`` is **snapshot-scoped only**, and the
|
||||
/// position is the server's own index over every row it sent — not the row
|
||||
/// number the app draws. Piped
|
||||
/// removes by position, so an id minted from one fetch is meaningless once
|
||||
/// the playlist changes. This is safe because the caching layer refetches
|
||||
/// the whole playlist after every mutation — never persist these, and never
|
||||
@@ -47,13 +49,26 @@ struct RemotePlaylistEntry: Identifiable, Hashable, Sendable {
|
||||
/// The video shown for this row.
|
||||
let video: Video
|
||||
|
||||
/// Zero-based position within the playlist as the snapshot saw it.
|
||||
/// Zero-based position within the playlist **as the server counts it** —
|
||||
/// over every row it sent, including the ones the app could not parse and
|
||||
/// does not display. This is the number a removal is addressed by, so it
|
||||
/// must never be renumbered over the filtered display list.
|
||||
let position: Int
|
||||
|
||||
init(id: RemotePlaylistEntryID, video: Video, position: Int) {
|
||||
/// Index of this entry in ``RemotePlaylistSnapshot/playlist``'s `videos`.
|
||||
///
|
||||
/// Separate from ``position`` on purpose: rows the app cannot display are
|
||||
/// dropped from `playlist.videos` but still occupy a server position, so the
|
||||
/// two numbers diverge the moment a playlist holds one. Carried explicitly
|
||||
/// rather than recovered by matching video ids, which cannot tell two rows
|
||||
/// of the same video apart.
|
||||
let displayRow: Int
|
||||
|
||||
init(id: RemotePlaylistEntryID, video: Video, position: Int, displayRow: Int) {
|
||||
self.id = id
|
||||
self.video = video
|
||||
self.position = position
|
||||
self.displayRow = displayRow
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +76,10 @@ struct RemotePlaylistEntry: Identifiable, Hashable, Sendable {
|
||||
///
|
||||
/// `entries` may be shorter than `playlist.videos`: a video the server did not
|
||||
/// hand an identity to is still displayed, it just cannot be a mutation target.
|
||||
/// It can also be shorter than what the server sent: a row that failed to parse
|
||||
/// is neither displayed nor addressable, but it still holds a server position,
|
||||
/// which is why ``RemotePlaylistEntry/position`` and
|
||||
/// ``RemotePlaylistEntry/displayRow`` are two different numbers.
|
||||
struct RemotePlaylistSnapshot: Hashable, Sendable {
|
||||
/// The playlist for display.
|
||||
let playlist: Playlist
|
||||
|
||||
Reference in New Issue
Block a user