feat(journal): trails profile-metadata field on federated actors

Mastodon renders PropertyValue attachments as the profile metadata
table — adds a '🥾 trails.cool' field linking to the user's canonical
profile, so it's human-visible that an account is a trails profile.
(The machine-readable signal stays NodeInfo; this is flair.) The link
carries rel=me so Mastodon can verify it once our HTML profile links
back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-06-06 21:48:07 +02:00
parent d13a0fcd18
commit 08d0a78c57
2 changed files with 17 additions and 1 deletions

View file

@ -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 () => {

View file

@ -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<void> {
// 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: `<a href="${localActorIri(identifier)}" rel="me">${getOrigin().replace(/^https?:\/\//, "")}/users/${identifier}</a>`,
}),
],
});
})
.setKeyPairsDispatcher(async (_ctxData, identifier) => {