v4: the followed feed's two views are pages — "From people" and "From playlists" on a feed's "follow" tab were a state of the page, which a reload, Back or a shared link lost, while a profile's same split is /@name/follow/playlists. They are now /follow and /follow/playlists, and /music/follow/playlists in a category, feedPath taking the view as userPath does (FOLLOW_VIEWS); picking a category keeps it. Their dumps come from GET /api/dumps?follow=users|playlists, signed in, which the feed's word for it names, instead of GET /api/me/feed?from=; an unknown category is a 404 there, as on the rest of the feed, rather than an empty list.

A feed's tab, as a profile's, a search's or the community page's, now takes the place of the current history entry, since it is a view of the same page; picking a category, another page, still adds one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-09 22:09:12 +00:00
parent 53ceb6bcbc
commit 6dfba07341
13 changed files with 244 additions and 147 deletions

View File

@@ -137,25 +137,28 @@ test("followed-users feed: public dumps of the people I follow, newest first", a
insertDump({ userId: stranger.id, title: "stranger" });
await put(`/api/me/follow/users/${followed.id}`, { auth: me.auth });
const feed = await get<PaginatedData<Dump>>("/api/me/feed?from=users", {
const feed = await get<PaginatedData<Dump>>("/api/dumps?follow=users", {
auth: me.auth,
});
assertEquals(titles(feed), ["new", "old"]);
assertEquals(feed.data.items[0].categoryIds, [music]);
assertEquals(
titles(
await get("/api/me/feed?from=users&category=music", { auth: me.auth }),
await get("/api/dumps?follow=users&category=music", { auth: me.auth }),
),
["new"],
);
// An unknown category filters everything out rather than nothing.
// As on the whole feed, an unknown category is a 404.
assertEquals(
(await get<PaginatedData<Dump>>("/api/me/feed?from=users&category=nope", {
auth: me.auth,
})).data,
{ items: [], hasMore: false },
(await get("/api/dumps?follow=users&category=nope", { auth: me.auth }))
.status,
404,
);
assertEquals((await get("/api/me/feed?from=users")).status, 401);
assertEquals(
(await get("/api/dumps?follow=dumps", { auth: me.auth })).status,
400,
);
assertEquals((await get("/api/dumps?follow=users")).status, 401);
});
test("followed-playlists feed: each dump once, public playlists only", async () => {
@@ -174,7 +177,7 @@ test("followed-playlists feed: each dump once, public playlists only", async ()
await post(`/api/playlists/${p.id}/dumps/${d.id}`, { auth: owner.auth });
}
await post(`/api/playlists/${p1.id}/dumps/${priv.id}`, { auth: owner.auth });
const feed = await get<PaginatedData<Dump>>("/api/me/feed?from=playlists", {
const feed = await get<PaginatedData<Dump>>("/api/dumps?follow=playlists", {
auth: me.auth,
});
assertEquals(titles(feed), ["shared"]);
@@ -183,7 +186,7 @@ test("followed-playlists feed: each dump once, public playlists only", async ()
// A playlist gone private drops out of the feed.
db.prepare(`UPDATE playlists SET is_public = 0;`).run();
assertEquals(
(await get<PaginatedData<Dump>>("/api/me/feed?from=playlists", {
(await get<PaginatedData<Dump>>("/api/dumps?follow=playlists", {
auth: me.auth,
})).data
.items.length,