Merge branch 'main' into dependabot/npm_and_yarn/production-6dcdd079e8

This commit is contained in:
Ullrich Schäfer 2026-04-26 11:57:54 +02:00 committed by GitHub
commit fdb2baada0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 76 additions and 30 deletions

View file

@ -80,12 +80,14 @@ describe.skipIf(!runIntegration)("explore.server integration", () => {
expect(rows.find((r) => r.id === id)).toBeUndefined();
});
it("demo persona is excluded from the directory", async () => {
it("demo persona is INCLUDED in the directory", async () => {
const persona = loadPersona();
// Insert a user with the persona's username — should still be filtered out.
// Insert a user with the persona's username — should appear like any
// other public user. The /explore loader is responsible for the
// demo-badge tagging at render time, not the directory query.
const id = await makeUser({ username: persona.username });
const { rows } = await listDirectory({ page: 1, perPage: 50 });
expect(rows.find((r) => r.id === id)).toBeUndefined();
expect(rows.find((r) => r.id === id)).toBeDefined();
});
it("orders by most-recent public activity, NULLS LAST", async () => {

View file

@ -1,7 +1,6 @@
import { and, count, desc, eq, gte, inArray, isNotNull, ne, sql } from "drizzle-orm";
import { and, count, desc, eq, gte, inArray, isNotNull, sql } from "drizzle-orm";
import { getDb } from "./db.ts";
import { activities, follows, users } from "@trails-cool/db/schema/journal";
import { loadPersona } from "./demo-bot.server.ts";
import { localActorIri } from "./actor-iri.ts";
const DEFAULT_PAGE_SIZE = 20;
@ -42,13 +41,11 @@ function clampPage(raw: number | undefined): number {
}
function exclusionFilters() {
// Public-only and not the demo persona. Banned/suspended users would
// Public-only. The demo persona IS included on /explore — its whole
// purpose is to give new users a follow target, and the per-row demo
// badge in the UI signals what it is. Banned/suspended users would
// be filtered here too once such a status column exists — see design.md.
const persona = loadPersona();
return and(
eq(users.profileVisibility, "public"),
ne(users.username, persona.username),
);
return eq(users.profileVisibility, "public");
}
/**

View file

@ -10,6 +10,7 @@ import {
listActiveRecently,
listDirectory,
} from "~/lib/explore.server";
import { loadPersona } from "~/lib/demo-bot.server";
import { FollowButton } from "~/components/FollowButton";
const BIO_TRUNCATE = 120;
@ -44,6 +45,7 @@ export async function loader({ request }: Route.LoaderArgs) {
: new Map();
const isSelf = (rowId: string) => viewer?.id === rowId;
const personaUsername = loadPersona().username;
const decorate = (row: typeof allRows[number]) => ({
id: row.id,
@ -53,6 +55,7 @@ export async function loader({ request }: Route.LoaderArgs) {
followerCount: followerCounts.get(row.id) ?? 0,
followState: followStates.get(row.id) ?? null,
isSelf: isSelf(row.id),
isDemoUser: row.username === personaUsername,
});
// Resolved page size (after loader-side clamping inside listDirectory)
@ -85,6 +88,7 @@ interface DecoratedRow {
followerCount: number;
followState: { following: boolean; pending: boolean } | null;
isSelf: boolean;
isDemoUser: boolean;
}
function DirectoryRow({ row, isSignedIn }: { row: DecoratedRow; isSignedIn: boolean }) {
@ -92,12 +96,22 @@ function DirectoryRow({ row, isSignedIn }: { row: DecoratedRow; isSignedIn: bool
return (
<li className="flex items-start justify-between gap-4 border-b border-gray-100 px-4 py-4 last:border-b-0">
<div className="min-w-0 flex-1">
<Link
to={`/users/${row.username}`}
className="text-sm font-medium text-gray-900 hover:underline"
>
{row.displayName ?? row.username}
</Link>
<div className="flex flex-wrap items-center gap-2">
<Link
to={`/users/${row.username}`}
className="text-sm font-medium text-gray-900 hover:underline"
>
{row.displayName ?? row.username}
</Link>
{row.isDemoUser && (
<span
className="rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium text-amber-800"
title={t("demo.badge")}
>
{t("demo.badge")}
</span>
)}
</div>
<p className="text-xs text-gray-500">
@{row.username} · {t("social.followers.count", { count: row.followerCount })}
</p>