From cfa61ccc7fb6cc69cabf7aa8cf352547794b16b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 21:00:26 +0000 Subject: [PATCH] refactor: use a static favicon for the time tracking dot Swap to a pre-rendered favicon instead of drawing the red dot on a canvas at runtime. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NWfQvx8fzZoE7ozLk8RWRf --- .../images/icons/favicon-tracking-32x32.png | Bin 0 -> 1355 bytes .../src/composables/useTimeTrackingFavicon.ts | 69 ++---------------- 2 files changed, 6 insertions(+), 63 deletions(-) create mode 100644 frontend/public/images/icons/favicon-tracking-32x32.png diff --git a/frontend/public/images/icons/favicon-tracking-32x32.png b/frontend/public/images/icons/favicon-tracking-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..02f5bffd7f48554c76416eb2cc3b771653ad05f0 GIT binary patch literal 1355 zcmV-R1+@B!P)1wk%8lCMG6w zWBlO)NsLMslm__;Vhmyp4Um=)2ocH_mXa3Q-R|tn94}_K-FCOTrSRVDoZ0t$-#hO& z-#KR_vTG~l(A6I^8pAAhY&L1wV45sZnr0xFr6e^B02eHW6KRX1qwVxYhNE`5*iuSB zSAWcC3iGr;r{fZ75A(A7^Q z?Y;z@wPLGn#eU#j5$Z(X)pf9JYga{gJy3w_VfbI zredqKut&k7{3ojpL4DA|8X3XWkg~iIWE`;begi%T`dsX^MNJ?>D3A@{iFi~CdwQ(c z%KV&W?&3l!)+&n6B`?!@wW#@!l5CZprI{v<;(UP^C1^sL7>U zeuk5iIa#bX4R&_*$3BR(MjZvvlwhUSbQVyv3Pw^eVi}mBN{osM{AOX`fC;sgU*J)ip0>qS+xr-8A_bvtdm82!tCDr5G=4F}`8oudV@+5-b|D z%WGZzF+(*bcpM=z9a969bD+9XfigjBTq}vTi}Yd^hJpnRp7`mYgv~7tEHo8t0$NLO zv2DoY>=Z$n0o+V#d@RaWP?MBZ$|nHkyDl4*)@%TbQp^fJ%<)Z(Gmb`l!O0}8$=DM` z4+Tm)fMANSQE6dGDW29aH|U7~=#LMu@5ciq28R$cfEa!dp}XfizZ~o$owfj&7cANV zlVihDQ)!xuNxjj#dSY-9_|>~Ke>lYYwQDd<&ou3N&0b$uOMP7}PIep$6mC|9CeUIs zv85I!E`r8wTL5s6oFX-J4cE31rD$siqXL!VJh#YZRRB$B(o$S5c|O$h>lhhMaCc~s zJGXC=x*NxJG83_eitm^OaS%rDH+c@>&bQh3(_yY%A3(b{+Vx(-qy>Lpo#OLEyb~=O>wgTL=e>O59u@!q N002ovPDHLkV1i_ea6141 literal 0 HcmV?d00001 diff --git a/frontend/src/composables/useTimeTrackingFavicon.ts b/frontend/src/composables/useTimeTrackingFavicon.ts index 0b0a819c7..1b201121d 100644 --- a/frontend/src/composables/useTimeTrackingFavicon.ts +++ b/frontend/src/composables/useTimeTrackingFavicon.ts @@ -4,83 +4,26 @@ import {storeToRefs} from 'pinia' import {useTimeTrackingStore} from '@/stores/timeTracking' -const FAVICON_SIZE = 32 -// Drawn from a PNG rather than the .ico because ICO decoding into a canvas is -// unreliable across browsers. -const BASE_FAVICON = '/images/icons/favicon-32x32.png' -const DOT_COLOR = '#ff4136' +const TRACKING_FAVICON = '/images/icons/favicon-tracking-32x32.png' function getFaviconLink(): HTMLLinkElement | null { return document.querySelector('link[rel="icon"]') } -// Marks the favicon with a small red dot in the lower left corner while a timer +// Swaps in a favicon with a small red dot in the lower left corner while a timer // is running, so an active time tracking session is visible even when the tab // isn't focused. export const useTimeTrackingFavicon = createSharedComposable(() => { const {hasActiveTimer} = storeToRefs(useTimeTrackingStore()) - const link = getFaviconLink() - const originalHref = link?.getAttribute('href') ?? '/favicon.ico' - - let baseImage: HTMLImageElement | null = null - - function loadBaseImage(): Promise { - if (baseImage?.complete) { - return Promise.resolve(baseImage) - } - return new Promise((resolve, reject) => { - const img = new Image() - img.addEventListener('load', () => { - baseImage = img - resolve(img) - }) - img.addEventListener('error', reject) - img.src = BASE_FAVICON - }) - } - - async function drawBadgedFavicon() { - const targetLink = getFaviconLink() - if (targetLink === null) { - return - } - - const img = await loadBaseImage() - const canvas = document.createElement('canvas') - canvas.width = FAVICON_SIZE - canvas.height = FAVICON_SIZE - const ctx = canvas.getContext('2d') - if (ctx === null) { - return - } - - ctx.drawImage(img, 0, 0, FAVICON_SIZE, FAVICON_SIZE) - - const radius = FAVICON_SIZE * 0.28 - const cx = radius - const cy = FAVICON_SIZE - radius - ctx.beginPath() - ctx.arc(cx, cy, radius, 0, Math.PI * 2) - ctx.fillStyle = DOT_COLOR - ctx.fill() - - targetLink.href = canvas.toDataURL('image/png') - } - - function restoreFavicon() { - const targetLink = getFaviconLink() - if (targetLink !== null) { - targetLink.href = originalHref - } - } + const originalHref = getFaviconLink()?.getAttribute('href') ?? '/favicon.ico' function update(active: boolean) { - if (active) { - void drawBadgedFavicon() + const link = getFaviconLink() + if (link === null) { return } - restoreFavicon() + link.href = active ? TRACKING_FAVICON : originalHref } watch(hasActiveTimer, update, {flush: 'post'})