Apply configurable-demo-persona: per-instance demo identity + voice
Adds DEMO_BOT_PERSONA env var (inline JSON or file:<path>) so self-hosted instances can replace Bruno-in-Berlin with their own demo account identity and content pools. No-op for trails.cool — the built-in Bruno persona remains the default. - DemoPersona type + Zod schema validation - loadPersona() cached at boot; falls back to default on any failure - ensureDemoUser throws DemoPersonaUsernameClashError when the persona username is already a real user; server declines to schedule demo jobs for that process - isDemoUser flag moved from hardcoded "bruno" check to loader-supplied boolean computed from the running persona - docs/demo-persona.md explains the schema + operator flow Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f447852d70
commit
fc4485f6ef
15 changed files with 877 additions and 113 deletions
|
|
@ -7,6 +7,7 @@ import { eq } from "drizzle-orm";
|
|||
import { getSessionUser } from "~/lib/auth.server";
|
||||
import { listPublicRoutesForOwner } from "~/lib/routes.server";
|
||||
import { listPublicActivitiesForOwner } from "~/lib/activities.server";
|
||||
import { loadPersona } from "~/lib/demo-bot.server";
|
||||
import { ClientDate } from "~/components/ClientDate";
|
||||
|
||||
export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
|
|
@ -31,6 +32,11 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
|||
throw data({ error: "User not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Demo-account badge: true when this profile matches the instance's
|
||||
// configured demo persona username. Computed server-side so we don't
|
||||
// ship the persona config through client HTML.
|
||||
const isDemoUser = user.username === loadPersona().username;
|
||||
|
||||
return data({
|
||||
user: {
|
||||
username: user.username,
|
||||
|
|
@ -57,6 +63,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
|||
createdAt: a.createdAt.toISOString(),
|
||||
})),
|
||||
isOwn,
|
||||
isDemoUser,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -82,11 +89,9 @@ export function meta({ data: loaderData }: Route.MetaArgs) {
|
|||
}
|
||||
|
||||
export default function UserProfilePage({ loaderData }: Route.ComponentProps) {
|
||||
const { user, routes, activities, isOwn } = loaderData;
|
||||
const { user, routes, activities, isOwn, isDemoUser } = loaderData;
|
||||
const { t } = useTranslation("journal");
|
||||
|
||||
const isDemo = user.username === "bruno";
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl px-4 py-8">
|
||||
<div className="flex items-start gap-4">
|
||||
|
|
@ -98,7 +103,7 @@ export default function UserProfilePage({ loaderData }: Route.ComponentProps) {
|
|||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
{user.displayName ?? user.username}
|
||||
</h1>
|
||||
{isDemo && (
|
||||
{isDemoUser && (
|
||||
<span className="rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800">
|
||||
{t("demo.badge")}
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue