fix(favorites): include playlist_id in workspace global favorites reorder payload

The workspace global-favorites reorder path still sent updates with only
content_id and position. Since the backend UPDATE is now scoped by
(contentId, playlistId), that payload binds an undefined playlist id and
matches no rows — the DB write silently no-ops (flagged by Greptile P1).

Also scope the prepared-statement example in the sqlite-db-worker gotcha
doc by (contentId, playlistId) so it no longer documents the
cross-playlist rewrite this PR fixes (flagged by Codex P3).

Regression spec asserts the reorder payload carries playlist_id per item
(fails on the old payload shape) and that the appState uid order is
still persisted for non-Xtream items.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-12 11:39:40 +02:00
1 parent 4fa1872964
commit 7ba58592da
3 files changed
+130 -3

No files matched your search

+11 -3
View File
@@ -511,14 +511,22 @@ Always call the synchronous `.run(placeholderValues)` on prepared statements
executed inside a synchronous transaction callback:
```ts
// favorites is playlist-scoped: filter by (contentId, playlistId), otherwise
// a same-contentId favorite in another playlist gets rewritten too.
const stmt = db.update(schema.favorites)
.set({ position: sql<number>`${sql.placeholder('position')}` })
.where(eq(schema.favorites.contentId, sql.placeholder('contentId')))
.where(
and(
eq(schema.favorites.contentId, sql.placeholder('contentId')),
eq(schema.favorites.playlistId, sql.placeholder('playlistId'))
)
)
.prepare();
db.transaction(() => {
for (const { content_id, position } of chunk) {
stmt.run({ position, contentId: content_id }); // NOT .execute()
for (const { content_id, playlist_id, position } of chunk) {
// NOT .execute()
stmt.run({ position, contentId: content_id, playlistId: playlist_id });
}
});
```