diff --git a/apps/journal/app/lib/federation.server.test.ts b/apps/journal/app/lib/federation.server.test.ts index fdad691..f00aa39 100644 --- a/apps/journal/app/lib/federation.server.test.ts +++ b/apps/journal/app/lib/federation.server.test.ts @@ -95,6 +95,12 @@ describe("actor object", () => { expect(actor.preferredUsername).toBe("bruno"); expect(actor.name).toBe("Bruno"); expect(actor.summary).toBe("Riding bikes"); + // Profile-metadata field Mastodon renders ("this is a trails profile") + const attachments = Array.isArray(actor.attachment) ? actor.attachment : [actor.attachment]; + const field = attachments.find((a: { type: string }) => a?.type === "PropertyValue"); + expect(field?.name).toBe("🥾 trails.cool"); + expect(field?.value).toContain('href="http://localhost:3000/users/bruno"'); + expect(field?.value).toContain('rel="me"'); }); it("404s the actor for a private user", async () => { diff --git a/apps/journal/app/lib/federation.server.ts b/apps/journal/app/lib/federation.server.ts index f3fd101..f10cd72 100644 --- a/apps/journal/app/lib/federation.server.ts +++ b/apps/journal/app/lib/federation.server.ts @@ -27,7 +27,7 @@ // to wrapped types we inspect and ignore (e.g. Undo(Like)). import { configure, getConsoleSink } from "@logtape/logtape"; import { createFederation, type Federation } from "@fedify/fedify"; -import { Accept, Follow, Person, Reject, Undo } from "@fedify/fedify/vocab"; +import { Accept, Follow, Person, PropertyValue, Reject, Undo } from "@fedify/fedify/vocab"; import { eq } from "drizzle-orm"; import { users } from "@trails-cool/db/schema/journal"; import { getDb } from "./db.ts"; @@ -158,6 +158,16 @@ function buildFederation(): Federation { // multikey assertionMethods). publicKey: keys[0]?.cryptographicKey, assertionMethods: keys.map((k) => k.multikey), + // Mastodon renders PropertyValue attachments as the profile + // metadata table — a human-visible "this is a trails profile" + // marker. (The machine-readable marker is NodeInfo; this is + // flair.) Mastodon strips most HTML in values but keeps links. + attachments: [ + new PropertyValue({ + name: "🥾 trails.cool", + value: `${getOrigin().replace(/^https?:\/\//, "")}/users/${identifier}`, + }), + ], }); }) .setKeyPairsDispatcher(async (_ctxData, identifier) => {