diff --git a/src/tangledGraph.ts b/src/tangledGraph.ts index ae0725a..cbf95a8 100644 --- a/src/tangledGraph.ts +++ b/src/tangledGraph.ts @@ -38,7 +38,8 @@ const HEADER_TEXT_X = 16; const HEADER_TITLE_Y = 53; const LOGO_WIDTH = 140; const LOGO_HEIGHT = 24; -const LOGO_URL = 'https://assets.tangled.network/tangled_logotype_black_on_trans.svg'; +const LOGO_URL_LIGHT = 'https://assets.tangled.network/tangled_logotype_black_on_trans.svg'; +const LOGO_URL_DARK = 'https://assets.tangled.network/tangled_logotype_white_on_trans.svg'; const MIN_CANVAS_WIDTH = 700; const HEADER_CHAR_WIDTH = 8.2; const SUMMARY_CHAR_WIDTH = 6.2; @@ -48,7 +49,6 @@ const WEEKDAY_LABELS = [ { label: 'Fri', row: 5 }, ]; const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; -const HEATMAP_COLORS = ['#ebedf0', '#d1d5db', '#9ca3af', '#4b5563', '#111827'] as const; export function normalizeAccount(input: string | undefined): string | null { if (!input) { @@ -127,38 +127,87 @@ export function buildGraphSvg({ account, range, days, error }: GraphSvgOptions): .map((day) => { const x = LEFT_GUTTER + day.weekIndex * (CELL_SIZE + CELL_GAP); const y = graphStartY + day.weekdayIndex * (CELL_SIZE + CELL_GAP); - const fill = day.inRange ? HEATMAP_COLORS[day.level] : '#f8fafc'; - const stroke = day.inRange ? 'none' : '#e5e7eb'; + const cellClass = day.inRange ? `heat-${day.level}` : 'oor'; const tooltip = `${day.isoDate}: ${day.count} activit${day.count === 1 ? 'y' : 'ies'}`; - return `${escapeXml(tooltip)}`; + return `${escapeXml(tooltip)}`; }) .join(''); const weekdays = WEEKDAY_LABELS.map(({ label, row }) => { const y = graphStartY + row * (CELL_SIZE + CELL_GAP) + CELL_SIZE - 1; - return `${label}`; + return `${label}`; }).join(''); const months = monthLabels .map(({ label, weekIndex }) => { const x = LEFT_GUTTER + weekIndex * (CELL_SIZE + CELL_GAP); - return `${label}`; + return `${label}`; }) .join(''); const summary = error - ? `${escapeXml(error)}` - : `${escapeXml(summaryText)}`; + ? `${escapeXml(error)}` + : `${escapeXml(summaryText)}`; const accountUrl = `https://tangled.org/@${encodeURIComponent(account)}`; return ` ${escapeXml(headerText)} Calendar heatmap of Tangled activities for ${escapeXml(account)} + - - ${escapeXml(headerText)} + + ${escapeXml(headerText)} ${months} ${weekdays} @@ -281,7 +330,10 @@ function renderLogo(width: number, height: number): string { const x = width - RIGHT_GUTTER - LOGO_WIDTH; const y = height - FOOTER_HEIGHT + 1; - return ``; + return ( + `` + + `` + ); } function subtractUtcMonths(date: Date, months: number): Date { -- 2.51.2 From 6eca62a2f047f663116610f8093d2c7010457b1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 02:11:15 +0000 Subject: [PATCH 3/3] fix: embed logos as base64 data URIs to fix broken images in GitHub Agent-Logs-Url: https://github.com/nove-b/tangled-activity/sessions/5f80b035-ce21-4c85-837b-059131bb90ec Co-authored-by: nove-b <68768186+nove-b@users.noreply.github.com> --- api/graph.ts | 19 ++++++++++++++++++- src/tangledGraph.ts | 31 ++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/api/graph.ts b/api/graph.ts index e1d71b9..cae07d4 100644 --- a/api/graph.ts +++ b/api/graph.ts @@ -4,9 +4,13 @@ import { buildGraphSvg, clampRange, fetchActivityByDay, + fetchLogoDataUri, normalizeAccount, } from '../src/tangledGraph'; +const LOGO_URL_LIGHT = 'https://assets.tangled.network/tangled_logotype_black_on_trans.svg'; +const LOGO_URL_DARK = 'https://assets.tangled.network/tangled_logotype_white_on_trans.svg'; + export default async function handler( req: VercelRequest, res: VercelResponse, @@ -36,9 +40,20 @@ export default async function handler( return; } + const [logoLightDataUri, logoDarkDataUri] = await Promise.all([ + fetchLogoDataUri(LOGO_URL_LIGHT), + fetchLogoDataUri(LOGO_URL_DARK), + ]); + try { const days = await fetchActivityByDay(account, range); - res.status(200).send(buildGraphSvg({ account, range, days })); + res.status(200).send(buildGraphSvg({ + account, + range, + days, + logoLightDataUri, + logoDarkDataUri, + })); } catch (error) { const message = error instanceof Error ? error.message : 'Failed to load feed'; res.status(502).send( @@ -47,6 +62,8 @@ export default async function handler( range, days: [], error: message, + logoLightDataUri, + logoDarkDataUri, }), ); } diff --git a/src/tangledGraph.ts b/src/tangledGraph.ts index cbf95a8..94647dd 100644 --- a/src/tangledGraph.ts +++ b/src/tangledGraph.ts @@ -17,6 +17,8 @@ type GraphSvgOptions = { range: number; days: HeatmapDay[]; error?: string; + logoLightDataUri?: string; + logoDarkDataUri?: string; }; type ParsedFeed = { @@ -103,7 +105,24 @@ export async function fetchActivityByDay(account: string, range: number): Promis return buildDays(counts, rangeStart, calendarStart, now); } -export function buildGraphSvg({ account, range, days, error }: GraphSvgOptions): string { +export async function fetchLogoDataUri(url: string): Promise { + try { + const response = await fetch(url, { + headers: { 'User-Agent': 'tangled-activity-graph' }, + }); + if (!response.ok) { + return undefined; + } + const contentType = response.headers.get('content-type') ?? 'image/svg+xml'; + const buffer = await response.arrayBuffer(); + const base64 = Buffer.from(buffer).toString('base64'); + return `data:${contentType};base64,${base64}`; + } catch { + return undefined; + } +} + +export function buildGraphSvg({ account, range, days, error, logoLightDataUri, logoDarkDataUri }: GraphSvgOptions): string { const weekCount = days.length === 0 ? 1 : Math.max(...days.map((day) => day.weekIndex)) + 1; const graphWidth = weekCount * (CELL_SIZE + CELL_GAP) - CELL_GAP; const graphHeight = 7 * (CELL_SIZE + CELL_GAP) - CELL_GAP; @@ -213,7 +232,7 @@ export function buildGraphSvg({ account, range, days, error }: GraphSvgOptions): ${weekdays} ${rects} ${summary} - ${renderLogo(width, height)} + ${renderLogo(width, height, logoLightDataUri, logoDarkDataUri)} `; } @@ -326,13 +345,15 @@ function activityLevel(count: number): 0 | 1 | 2 | 3 | 4 { return 4; } -function renderLogo(width: number, height: number): string { +function renderLogo(width: number, height: number, logoLightDataUri?: string, logoDarkDataUri?: string): string { const x = width - RIGHT_GUTTER - LOGO_WIDTH; const y = height - FOOTER_HEIGHT + 1; + const lightSrc = logoLightDataUri ?? LOGO_URL_LIGHT; + const darkSrc = logoDarkDataUri ?? LOGO_URL_DARK; return ( - `` + - `` + `` + + `` ); }