v4: a playlist's dump count is of the dumps the viewer may see — it counted every dump in the playlist, so a card said "3 dumps" over a page listing two, and told everyone how many private dumps a playlist held. The lists (/playlists, a profile's playlists and followed ones, the search, a dump's playlists, the add-to-playlist panel) now count by isMemberVisibleTo's rule, in SQL as VISIBLE_DUMP_COUNT (lib/playlist-members.ts): the public dumps, and for the owner their own private ones. GET /api/playlists and /api/users/:key/follow/playlists read the optional sign-in for it. The community page and the digests count the public dumps, their figures being the same for every reader. Live, playlist_created and playlist_updated carry each socket's own count: broadcastChange builds the owner's message and the others' apart. playlist_dumps_updated loses its dumpCount, a client counting the dumps it is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-10 08:25:24 +00:00
parent ffc071af4c
commit 28f910b00e
18 changed files with 273 additions and 94 deletions

View File

@@ -50,8 +50,6 @@ export interface PlaylistEvent {
* order.
*/
dumpIds?: string[];
/** Set when the dumps changed. It counts private dumps too. */
dumpCount?: number;
}
/**

View File

@@ -595,12 +595,11 @@ export function WSProvider({ children }: WSProviderProps): React.JSX.Element {
}
case "playlist_dumps_updated": {
const { playlistId, dumpIds, dumpCount } = msg;
const { playlistId, dumpIds } = msg;
events.emit("playlist", {
type: "dumps_updated",
playlistId,
dumpIds,
dumpCount,
});
break;
}

View File

@@ -85,10 +85,8 @@ export function usePlaylistListSync(
const idx = prev.findIndex((p) => p.id === ev.playlistId);
if (idx === -1) return prev;
const next = [...prev];
next[idx] = {
...next[idx],
dumpCount: ev.dumpCount ?? ev.dumpIds.length,
};
// The dumps this user may see, which are those the playlist counts.
next[idx] = { ...next[idx], dumpCount: ev.dumpIds.length };
return next;
}

View File

@@ -481,13 +481,11 @@ describe("dumps, playlists, comments, users", () => {
type: "playlist_dumps_updated",
playlistId: "p",
dumpIds: ["x"],
dumpCount: 2,
});
expect(events().playlist.at(-1)).toEqual({
type: "dumps_updated",
playlistId: "p",
dumpIds: ["x"],
dumpCount: 2,
});
socket.receive({ type: "playlist_deleted", playlistId: "p", userId: "u" });
expect(ws().deletedPlaylistIds.has("p")).toBe(true);

View File

@@ -99,7 +99,7 @@ describe("usePlaylistListSync", () => {
expect(ids(items())).toEqual(["c"]);
});
test("dumps_updated sets the count", () => {
test("dumps_updated counts the dumps this viewer may see", () => {
const { emit, items } = setup([makePlaylist({ id: "a", dumpCount: 1 })]);
emit("playlist", {
type: "dumps_updated",
@@ -109,17 +109,6 @@ describe("usePlaylistListSync", () => {
expect(items()[0].dumpCount).toBe(3);
});
test("dumps_updated counts the members this viewer can't see too", () => {
const { emit, items } = setup([makePlaylist({ id: "a", dumpCount: 3 })]);
emit("playlist", {
type: "dumps_updated",
playlistId: "a",
dumpIds: ["x", "y"],
dumpCount: 4,
});
expect(items()[0].dumpCount).toBe(4);
});
test("a renamed owner's playlists take the new name", () => {
const { emit, items } = setup([
makePlaylist({ id: "a", userId: "u1", ownerUsername: "old" }),

View File

@@ -423,7 +423,6 @@ describe("Followed feed", () => {
type: "playlist_dumps_updated",
playlistId: "mix",
dumpIds: ["x"],
dumpCount: 1,
});
expect(await screen.findByText("Added to the mix")).toBeInTheDocument();
});

View File

@@ -452,7 +452,6 @@ describe("PlaylistDetail", () => {
type: "playlist_dumps_updated",
playlistId: "p1",
dumpIds: ["a", "c"],
dumpCount: 3,
});
expect(titles()).toEqual(["Alpha", "Charlie"]);
socket.receive({
@@ -463,7 +462,6 @@ describe("PlaylistDetail", () => {
type: "playlist_dumps_updated",
playlistId: "p1",
dumpIds: ["a", "b", "c"],
dumpCount: 3,
});
expect(await screen.findByText("Bravo")).toBeInTheDocument();
expect(titles()).toEqual(["Alpha", "Bravo", "Charlie"]);
@@ -522,7 +520,6 @@ describe("PlaylistDetail", () => {
type: "playlist_dumps_updated",
playlistId: "p1",
dumpIds: ["d", "a", "b", "c"],
dumpCount: 4,
});
expect(await screen.findByText("Delta")).toBeInTheDocument();
socket.receive({