v4: the avatar menu shows at every width and holds Log out, so a profile keeps only Invite; the profile's dump cards lay out again — your own profile had four buttons in a row, Invite someone, Settings, Manage and Log out, Invite taller than the rest, and on wide screens it was the only way to reach /settings or /manage. The avatar menu now leads the header's icon row at every width, after the name and Playlists text links on wide screens, and ends with Log out under a separator, muted until hovered. The profile keeps Invite someone alone. The menu's button outweighs .app-header-nav button and the themes' button rules (Brutalist and GeoCities list it among their flat buttons), so it reads as one of the menu's links. Since b9ff9c4, a card under 472px is laid out by the narrow-card container query, but the profile columns' dense-card rules still placed the thumb, body and vote themselves, more specifically: the thumb lost its row span, so the title fell under it and every card was a tall, mostly empty box. Those rules now only make the card denser (a smaller thumb, a 2-line title, smaller type) and leave placement to the container query. 670 frontend tests pass, and tsc, eslint, deno fmt, doc:lint and i18n:compile --strict are clean; checked in screenshots in Smooth dark at 1400 and 390px, Brutalist light at 390px, GeoCities light and Kanagawa dark at 1400px, in French.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-08 22:22:15 +00:00
parent 10c9cf4269
commit 886216749e
11 changed files with 179 additions and 219 deletions

View File

@@ -75,8 +75,6 @@ describe("UserPublicProfile", () => {
expect(dumpsAll).toHaveAttribute("href", "/@ann/dumps");
expect(votesAll).toHaveAttribute("href", "/@ann/upvoted");
await waitFor(() => expect(document.title).toMatch(/^ann · /));
expect(screen.queryByRole("link", { name: "Settings" })).not
.toBeInTheDocument();
// The lists are read by the user's id, which a rename doesn't change.
expect(calls("GET /api/users/:u/dumps")[0].path).toBe(
"/api/users/ann/dumps",
@@ -275,11 +273,11 @@ describe("UserPublicProfile", () => {
expect(within(bob).getByRole("link", { name: /@cy/ })).toBeInTheDocument();
});
test("visitors can follow; a settings link is only on your own profile", async () => {
test("visitors can follow; inviting is only on your own profile", async () => {
open({ session: makeSession({ id: "bob" }) });
expect(await screen.findByRole("button", { name: "Follow ann" }))
.toBeInTheDocument();
expect(screen.queryByRole("link", { name: "Settings" })).not
expect(screen.queryByRole("button", { name: "+ Invite someone" })).not
.toBeInTheDocument();
});
@@ -414,29 +412,18 @@ describe("UserPublicProfile", () => {
}
});
test("your settings, and the site's tools for an admin, are a link away", async () => {
test("an admin's own profile has no manage tab: that's for others'", async () => {
open({
session: { ...annSession, user: { ...annSession.user, role: "admin" } },
user: { ...ann, role: "admin" },
});
expect(await screen.findByRole("link", { name: "Settings" }))
.toHaveAttribute("href", "/settings");
expect(screen.getByRole("link", { name: "Manage" })).toHaveAttribute(
"href",
"/manage",
);
// Their own profile has no manage tab: that's for others'.
expect(await screen.findByRole("button", { name: "+ Invite someone" }))
.toBeInTheDocument();
expect(
within(document.querySelector(".profile-tabs") as HTMLElement)
.queryByRole("button", { name: "Manage" }),
).not.toBeInTheDocument();
});
test("log out", async () => {
const { user } = open({ session: annSession });
await user.click(await screen.findByRole("button", { name: "Log out" }));
expect(localStorage.getItem(AUTH_STORAGE_KEY)).toBeNull();
});
});
describe("manage", () => {