From 0d350ce58439e218c8bee98bcff52de0b9683984 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Tue, 5 May 2026 16:28:40 +0300 Subject: [PATCH] fix: decode base64 readme content as utf-8 instead of latin-1 atob() produces Latin-1 strings, mangling multi-byte UTF-8 characters like box-drawing glyphs. Use TextDecoder for correct UTF-8 handling. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/api/client.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/src/api/client.ts b/ui/src/api/client.ts index 4e7fd76..5b928da 100644 --- a/ui/src/api/client.ts +++ b/ui/src/api/client.ts @@ -75,6 +75,12 @@ export interface EventQuery { const API_BASE = '/api/v1'; +/** Decode base64 content as UTF-8 (atob only handles Latin-1). */ +function decodeBase64Utf8(b64: string): string { + const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); + return new TextDecoder().decode(bytes); +} + export async function fetchEvents(q: EventQuery): Promise { const params = new URLSearchParams(); if (q.from) params.set('from', q.from.toISOString()); @@ -109,7 +115,7 @@ export async function fetchReadme(source: Source, host: string, repo: string): P if (!resp.ok) return null; const data = await resp.json(); if (data.encoding === 'base64' && data.content) { - return atob(data.content); + return decodeBase64Utf8(data.content); } return data.content ?? null; } @@ -119,7 +125,7 @@ export async function fetchReadme(source: Source, host: string, repo: string): P if (!resp.ok) continue; const data = await resp.json(); if (data.encoding === 'base64' && data.content) { - return atob(data.content); + return decodeBase64Utf8(data.content); } if (data.content) return data.content; }