Skip to content

Fix broken project logos: validate candidates + more sources + self-heal - #70

Merged
ralyodio merged 1 commit into
masterfrom
better-logos
Jul 4, 2026
Merged

ralyodio merged 1 commit into
masterfrom
better-logos

Conversation

@ralyodio

@ralyodio ralyodio commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Broken logos on /dashboard had two root causes, both fixed here.

1. discoverLogoUrl never checked its pick actually loads

It returned the highest-weight candidate without verifying it — so a linked apple-touch-icon (or og:image) that 404s got stored and rendered as a broken <img>.

Now it validates each candidate (real request → 2xx + image/* content-type, or a clear image extension on 2xx) and returns the first that actually loads, else null. It also looks harder for a real logo:

  • web-app-manifest icons (<link rel="manifest"> → icons[], sized) — often the best brand mark
  • mask-icon (Safari pinned tab — clean monochrome SVG)
  • respects <base href>, and treats sizes="any" as large

2. ProjectLogo had no error fallback

It was server-rendered <img>, so a broken URL just showed broken. It's now a client component: on error it falls back to the letter avatar and fires a one-shot refetchProjectLogo, so a stale/404 logo_url self-heals — re-discovery overwrites it with a working one, or clears it to a clean letter avatar. (The old backfill only ran for null logos, so it couldn't fix already-broken ones.)

Verification

  • tsc --noEmit clean
  • New tests/discover-logo.test.ts (4): skips a broken candidate → next valid one; prefers highest-weight when it loads; null when nothing loads; accepts an image served with a generic content-type by extension
  • No migration

Existing broken tiles fix themselves on next dashboard view (onError → refetch); new projects only ever store a validated URL.

🤖 Generated with Claude Code

Broken dashboard logos had two causes: discoverLogoUrl stored the first
candidate by weight without checking it loads (so a 404'ing apple-touch-icon
became a broken <img>), and ProjectLogo was server-rendered with no error
fallback (broken URLs showed as broken images).

- discoverLogo now VALIDATES each candidate (real request → 2xx + image
  content-type, or a clear image extension) and returns the first that actually
  loads, else null. Also looks at more sources: web-app-manifest icons (often
  the best brand mark), mask-icon, and respects <base href>. "any"-sized icons
  rank large.
- ProjectLogo is now a client component: falls back to the letter avatar on a
  broken image AND fires a one-shot refetchProjectLogo so a stale/404 logo_url
  self-heals (re-discovery overwrites it, or clears it to the letter avatar).
- refetchProjectLogo re-discovers even when a logo is already set (the null-only
  backfill couldn't fix already-broken ones).

Tests: candidate validation/fallback, highest-weight preference, null when
nothing loads, extension-based acceptance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit 84c3fc5 into master Jul 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant