From 7ac7bb7958e76a4f57ba0e0dadfd3d1af7cd7d52 Mon Sep 17 00:00:00 2001 From: RhenCloud Date: Sat, 15 Aug 2026 18:25:23 +0800 Subject: [PATCH] fix(render): link title head to the event object URL --- AGENTS.md | 11 ++++++----- server/lib/drivers/discord/render.ts | 8 +++----- server/lib/drivers/telegram/render.ts | 8 ++++---- server/lib/formatters/helpers.ts | 17 ----------------- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9e0d066..6dc6a12 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,11 +166,12 @@ tests/__snapshots__/ # formatter snapshot golden files (toMatchSnapshot) `{repo}{#number}: {subject}` (e.g. `acme/widget#7: Add feature`). Repo comes from `payload.repository.full_name`; fall back to `t("common.repository")` when missing. - Only the repo head is hyperlinked (never the whole title). Drivers split the title via - `splitMessageTitle`/`repoUrlFromMessage` (`server/lib/formatters/helpers.ts`): the Discord - embed title is `{repo}{#number}` linked to the repository URL and `: {subject}` renders as - the first description line; Telegram keeps the one-line title with an inline repo link and - a plain subject. Messages without a colon separator (a `:` followed by a space) keep the - legacy whole-title link — use colon-free wording for such titles. + `splitMessageTitle` (`server/lib/formatters/helpers.ts`): the Discord embed title is + `{repo}{#number}` linked to the event's object URL (`message.url`, e.g. the issue/PR/comment + `html_url`) and `: {subject}` renders as the first description line; Telegram keeps the + one-line title with an inline repo link and a plain subject. Messages without a colon + separator (a `:` followed by a space) keep the legacy whole-title link — use colon-free + wording for such titles. - Do NOT use `"Comment on org/repo"` / `"Review on org/repo"` prefixes. Comments, reviews and inline comments use the same `{repo}{#number}: {title}` title as their parent object. - All event-specific emoji live in `server/lib/formatters/` (via the `emojiPrefix` helper), never in diff --git a/server/lib/drivers/discord/render.ts b/server/lib/drivers/discord/render.ts index 6576ae9..cfd5762 100644 --- a/server/lib/drivers/discord/render.ts +++ b/server/lib/drivers/discord/render.ts @@ -6,7 +6,6 @@ import { MAX_FIELD_VALUE, MAX_FOOTER, MAX_TITLE, - repoUrlFromMessage, splitMessageTitle, } from "../../formatters/helpers"; @@ -27,10 +26,9 @@ export function renderNeutralMessage(message: NeutralMessage): FormattedMessage : undefined; // Discord embed titles can only be linked as a whole, so only the repo head - // goes into the title (linked to the repository); the `: subject` text is - // rendered as the first line of the description, unlinked. + // goes into the title (linked to the event's object URL); the `: subject` + // text is rendered as the first line of the description, unlinked. const { head, subject } = splitMessageTitle(message.title); - const repoUrl = repoUrlFromMessage(message.url); const rawDescription = subject ? message.description ? `${subject}\n${message.description}` @@ -48,7 +46,7 @@ export function renderNeutralMessage(message: NeutralMessage): FormattedMessage embeds: [ { title: cap(head, MAX_TITLE), - url: subject ? repoUrl : message.url, + url: message.url, color: message.color, description: cap(rawDescription, MAX_DESCRIPTION) || undefined, author: message.author diff --git a/server/lib/drivers/telegram/render.ts b/server/lib/drivers/telegram/render.ts index fc9d720..80f7bd9 100644 --- a/server/lib/drivers/telegram/render.ts +++ b/server/lib/drivers/telegram/render.ts @@ -1,5 +1,5 @@ import type { NeutralMessage } from "../../types"; -import { repoUrlFromMessage, splitMessageTitle } from "../../formatters/helpers"; +import { splitMessageTitle } from "../../formatters/helpers"; /** Telegram caps a single message at 4096 characters (HTML entities included). */ const MAX_TEXT = 4096; @@ -64,11 +64,11 @@ export function renderNeutralMessage(message: NeutralMessage): string { const parts: string[] = []; // HTML allows partial links, so keep the `{repo}{#number}: {subject}` line - // intact with only the repo head linked (the subject stays plain text). + // intact with only the repo head linked to the event's object URL (the + // subject stays plain text). const { head, subject } = splitMessageTitle(message.title); - const repoUrl = repoUrlFromMessage(message.url); const title = subject - ? `${repoUrl ? `${mdToHtml(head)}` : mdToHtml(head)}: ${mdToHtml(subject)}` + ? `${message.url ? `${mdToHtml(head)}` : mdToHtml(head)}: ${mdToHtml(subject)}` : message.url ? `${mdToHtml(message.title)}` : `${mdToHtml(message.title)}`; diff --git a/server/lib/formatters/helpers.ts b/server/lib/formatters/helpers.ts index c8ee428..61b38c7 100644 --- a/server/lib/formatters/helpers.ts +++ b/server/lib/formatters/helpers.ts @@ -31,23 +31,6 @@ export function splitMessageTitle(title: string): TitleParts { return { head: title.slice(0, idx), subject: title.slice(idx + 2) }; } -/** - * Repository URL derived from an event URL (origin + owner + repo). Returns - * undefined when there is no URL to derive from — callers then render the - * title without a link. - */ -export function repoUrlFromMessage(url: string | undefined): string | undefined { - if (!url) return undefined; - try { - const parsed = new URL(url); - const segments = parsed.pathname.split("/").filter(Boolean); - if (segments.length >= 2) return `${parsed.origin}/${segments[0]}/${segments[1]}`; - } catch { - // not a parseable URL — no link - } - return undefined; -} - export function buildMessage( partial: Omit, "title"> & { title: string }, t: T,