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

@@ -46,8 +46,10 @@ export const PROFILE_TABS = [
export type ProfileTab = (typeof PROFILE_TABS)[number];
/**
* The views of a profile's `follow` tab: the users followed at
* `/@name/follow`, the playlists at `/@name/follow/playlists`.
* The views of a `follow` tab, of what is followed: the users, at the bare
* tab, or the playlists. A profile's lists them, `/@name/follow` and
* `/@name/follow/playlists`, and a feed's shows their dumps, `/follow` and
* `/follow/playlists`.
*/
export const FOLLOW_VIEWS = ["users", "playlists"] as const;
/** One of the {@link FOLLOW_VIEWS}. */
@@ -112,11 +114,19 @@ export function playlistPath(playlist: OwnedItem): string {
/**
* Returns the path of a feed: every dump's at the root, or a category's at
* `/<slug>`, on `tab`, or on the viewer's default tab without one.
* `/<slug>`, on `tab`, or on the viewer's default tab without one. The
* `follow` tab takes a `view`, the users followed by default.
*/
export function feedPath(categorySlug?: string, tab?: FeedTab): string {
export function feedPath(
categorySlug?: string,
tab?: FeedTab,
view?: FollowView,
): string {
const base = categorySlug ? `/${seg(categorySlug)}` : "";
return tab ? `${base}/${tab}` : base || "/";
if (!tab) return base || "/";
return tab === "follow" && view && view !== FOLLOW_VIEWS[0]
? `${base}/${tab}/${view}`
: `${base}/${tab}`;
}
/**

View File

@@ -24,6 +24,12 @@ Deno.test("builders: one hierarchy, default views bare", () => {
assertEquals(feedPath(undefined, "hot"), "/hot");
assertEquals(feedPath("music"), "/music");
assertEquals(feedPath("music", "follow"), "/music/follow");
assertEquals(feedPath("music", "follow", "users"), "/music/follow");
assertEquals(
feedPath(undefined, "follow", "playlists"),
"/follow/playlists",
);
assertEquals(feedPath(undefined, "new", "playlists"), "/new");
assertEquals(searchPath(), "/search");
assertEquals(searchPath("users", "a b"), "/search/users?q=a%20b");
assertEquals(searchPath("dumps", "x"), "/search?q=x");