From 7143f6cfd72cfc8f9fe5a2ae76eb31c9b81a537b Mon Sep 17 00:00:00 2001 From: cyph3rasi Date: Wed, 15 Jul 2026 12:11:11 -0700 Subject: [PATCH] Use the node short description in link preview metadata Hop-State: A_06FPEC5CSW59V0KHWX0W1ER Hop-Proposal: R_06FPEC4XSJJ03M62S8AA9P0 Hop-Task: T_06FPEBTP53BQJHKGSPK894G Hop-Attempt: AT_06FPEBTP50KTCJ3YT789R78 --- src/app/layout.tsx | 20 +++++++++---------- src/lib/node/local-node.ts | 18 ++++++++++++------ src/lib/node/metadata.test.ts | 35 ++++++++++++++++++++++++++++++++++ src/lib/node/metadata.ts | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 src/lib/node/metadata.test.ts create mode 100644 src/lib/node/metadata.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0336624..a7ce2b6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,26 +13,24 @@ const sairaCondensed = Saira_Condensed({ variable: "--font-saira", }); -import { db } from "@/db"; +import { getLocalNode } from "@/lib/node/local-node"; +import { buildNodeLinkMetadata, DEFAULT_NODE_DESCRIPTION } from "@/lib/node/metadata"; export async function generateMetadata(): Promise { - let title = "Synapsis"; + let node = null; try { - const node = await db.query.nodes.findFirst(); - if (node?.name) { - title = node.name; - } + node = await getLocalNode(); } catch (e) { console.error("Failed to fetch node info for metadata", e); } return { - title: { - default: title, - template: `%s | ${title}`, - }, - description: "Synapsis is designed to function like a global signal layer rather than a culture-bound platform. Anyone can run their own node and still participate in a shared, interconnected network, with global identity, clean terminology, and a modern interface that feels current rather than experimental.", + ...buildNodeLinkMetadata( + node, + process.env.NEXT_PUBLIC_NODE_NAME || "Synapsis", + process.env.NEXT_PUBLIC_NODE_DESCRIPTION || DEFAULT_NODE_DESCRIPTION + ), manifest: "/manifest.json", icons: { icon: "/api/favicon", diff --git a/src/lib/node/local-node.ts b/src/lib/node/local-node.ts index 4342db9..81c319a 100644 --- a/src/lib/node/local-node.ts +++ b/src/lib/node/local-node.ts @@ -1,14 +1,20 @@ import { db } from '@/db'; -export async function isLocalNodeNsfw(): Promise { +export async function getLocalNode() { const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || process.env.NODE_DOMAIN || 'localhost:43821'; + let node = await db.query.nodes.findFirst({ where: { domain } }); + if (!node) { + const localNodes = await db.query.nodes.findMany({ limit: 2 }); + if (localNodes.length === 1) node = localNodes[0]; + } + + return node ?? null; +} + +export async function isLocalNodeNsfw(): Promise { try { - let node = await db.query.nodes.findFirst({ where: { domain } }); - if (!node) { - const localNodes = await db.query.nodes.findMany({ limit: 2 }); - if (localNodes.length === 1) node = localNodes[0]; - } + const node = await getLocalNode(); return node?.isNsfw === true; } catch (error) { console.error('Local node NSFW lookup failed:', error); diff --git a/src/lib/node/metadata.test.ts b/src/lib/node/metadata.test.ts new file mode 100644 index 0000000..80f250b --- /dev/null +++ b/src/lib/node/metadata.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { buildNodeLinkMetadata } from './metadata'; + +describe('buildNodeLinkMetadata', () => { + it('uses the node short description everywhere link previews read it', () => { + const metadata = buildNodeLinkMetadata({ + name: 'Example Node', + description: 'A concise description of this community.', + }); + + expect(metadata.description).toBe('A concise description of this community.'); + expect(metadata.openGraph).toMatchObject({ + title: 'Example Node', + description: 'A concise description of this community.', + }); + expect(metadata.twitter).toMatchObject({ + title: 'Example Node', + description: 'A concise description of this community.', + }); + }); + + it('uses configured fallbacks when node branding is blank', () => { + const metadata = buildNodeLinkMetadata( + { name: ' ', description: ' ' }, + 'Fallback Node', + 'Fallback description' + ); + + expect(metadata.description).toBe('Fallback description'); + expect(metadata.openGraph).toMatchObject({ + title: 'Fallback Node', + description: 'Fallback description', + }); + }); +}); diff --git a/src/lib/node/metadata.ts b/src/lib/node/metadata.ts new file mode 100644 index 0000000..0b3952a --- /dev/null +++ b/src/lib/node/metadata.ts @@ -0,0 +1,36 @@ +import type { Metadata } from 'next'; + +export const DEFAULT_NODE_DESCRIPTION = 'A swarm social network node.'; + +type NodeBranding = { + name?: string | null; + description?: string | null; +}; + +export function buildNodeLinkMetadata( + node?: NodeBranding | null, + fallbackName = 'Synapsis', + fallbackDescription = DEFAULT_NODE_DESCRIPTION +): Pick { + const title = node?.name?.trim() || fallbackName; + const description = node?.description?.trim() || fallbackDescription; + + return { + title: { + default: title, + template: `%s | ${title}`, + }, + description, + openGraph: { + type: 'website', + siteName: title, + title, + description, + }, + twitter: { + card: 'summary', + title, + description, + }, + }; +}