feat(playlists): two-way (3-way) merge YouTube sync + mandatory preview (S7)
"Sync to YouTube" was a one-directional destructive push (local wins, deletes from YouTube every item not in the local list), so items added on YouTube were lost and a dirty pull was skipped entirely. Replace it with a real 3-way merge: each item is classified against the last-synced baseline as a local-add, a YouTube-add, or a genuine delete, so an add or delete on either side survives. - Persist the ordered baseline id set (new playlists.synced_video_ids, migration 0075); the fingerprint hash alone couldn't drive a merge. NULL baseline (fresh export / pre-S7 link) unions losslessly on the first sync and records one. - Pure merge_plan(baseline, local, youtube) -> add/remove per side + merged order (local order, YouTube-only additions appended). merge_playlist applies the local side directly and the YouTube side through the failure breaker; the baseline only advances on full success so a partial failure re-converges. - plan_merge/push-plan now report what each side gains and loses; the confirm dialog shows the two-way plan before applying (pushPlanMerge, HU+EN). - Tests: 10 new (pure classification incl. delete-vs-stale-copy; DB apply proving a YouTube-add and a Siftlode-add both survive on both sides, a genuine delete propagates without collateral loss, no-baseline never deletes, create path).
This commit is contained in:
@@ -362,7 +362,15 @@ export default function Playlists() {
|
||||
setPushing(true);
|
||||
try {
|
||||
const plan = await api.playlistPushPlan(selectedId);
|
||||
if (plan.action === "update" && !plan.to_insert && !plan.to_delete && !plan.to_reorder) {
|
||||
const nothingToDo =
|
||||
plan.action === "update" &&
|
||||
!plan.add_yt &&
|
||||
!plan.add_local &&
|
||||
!plan.remove_yt &&
|
||||
!plan.remove_local &&
|
||||
!plan.to_reorder &&
|
||||
!plan.rename;
|
||||
if (nothingToDo) {
|
||||
notify({ level: "info", message: t("playlists.pushUpToDate", { name: plName(detail) }) });
|
||||
return;
|
||||
}
|
||||
@@ -376,21 +384,21 @@ export default function Playlists() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
let message =
|
||||
const message =
|
||||
plan.action === "create"
|
||||
? t("playlists.pushPlanCreate", {
|
||||
count: plan.to_insert,
|
||||
count: plan.add_yt,
|
||||
units: plan.units_estimate,
|
||||
left: plan.remaining_today,
|
||||
})
|
||||
: t("playlists.pushPlanUpdate", {
|
||||
insert: plan.to_insert,
|
||||
del: plan.to_delete,
|
||||
reorder: plan.to_reorder,
|
||||
: t("playlists.pushPlanMerge", {
|
||||
addYt: plan.add_yt,
|
||||
addLocal: plan.add_local,
|
||||
removeYt: plan.remove_yt,
|
||||
removeLocal: plan.remove_local,
|
||||
units: plan.units_estimate,
|
||||
left: plan.remaining_today,
|
||||
});
|
||||
if (plan.yt_extra > 0) message += " " + t("playlists.pushDiverged", { count: plan.yt_extra });
|
||||
const ok = await confirm({
|
||||
title: t("playlists.pushTitle"),
|
||||
message,
|
||||
|
||||
Reference in New Issue
Block a user