feat(apps): distinguish local development with blue blueprint favicons (#51420)

## Problem

- Local apps are hard to distinguish from hosted apps in browser tabs.

## Solution

- Add blue blueprint icons to local development in all 8 apps and Studio
CLI.
- Keep normal branding for production, hosted staging and self-hosted
Studio.
- Builds on #51419.


| Before | After |
| --- | --- |
| <img width="1290" height="76" alt="CleanShot 2026-10-08 at 13 29
14@2x"
src="https://github.com/user-attachments/assets/72bc50d7-37e0-47d0-8dfd-50e233a443bd"
/> | <img width="1282" height="72" alt="CleanShot 2026-10-08 at 13 30
03@2x"
src="https://github.com/user-attachments/assets/afb15a70-3938-4cff-9b41-dc6c25460b7c"
/> |

## Review instructions

- Start an app locally. Its browser tab should show the blue icon.
- Check Studio CLI uses blue and hosted staging uses white.
- Check production icons are unchanged, including Learn's black and
white icon.
- Run `node scripts/validate-favicons.mjs`. All 16 sets should pass.

Asset and routing checks, formatting and Common/Studio type checks
passed. Full builds have not been run.

## Checklist

- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
This commit is contained in:
Danny White authored and GitHub committed 2026-10-12 10:26:51 +11:00
1 parent f44dc42b0c
commit 7b48b298ee
59 files changed
+305 -26

No files matched your search

+3
View File
@@ -1,6 +1,9 @@
export const faviconTargets = [
['production', 'apps/studio/public/favicon'],
['staging', 'apps/studio/public/favicon/staging'],
...['studio', 'www', 'docs', 'learn', 'ui-library', 'design-system', 'kb', 'lite-studio'].map(
(app) => ['local', `apps/${app}/public/favicon/local`]
),
...['www', 'ui-library', 'design-system'].map((app) => [
'production',
`apps/${app}/public/favicon`,
+2 -2
View File
@@ -32,7 +32,7 @@ for (const [variant, relativeDirectory] of faviconTargets) {
const directory = path.join(root, relativeDirectory)
await mkdir(directory, { recursive: true })
const svg = await readFile(path.join(root, `assets/favicons/${variant}.svg`))
const background = variant === 'staging' ? '#FFFFFF' : '#1C1C1C'
const background = variant === 'staging' ? '#FFFFFF' : variant === 'local' ? '#087BFF' : '#1C1C1C'
const render = (size) => sharp(svg).resize(size, size)
const images = await Promise.all(
[16, 32, 48].map(async (size) => ({ size, data: await render(size).png().toBuffer() }))
@@ -60,4 +60,4 @@ for (const [variant, relativeDirectory] of faviconTargets) {
}
// Next.js layouts declare their icons explicitly. Do not add app/favicon.ico,
// which would duplicate the shared icon declarations.
// which would also advertise the production artwork during local development.
+4
View File
@@ -14,6 +14,10 @@ for (const [variant, directory] of faviconTargets) {
const svg = await readFile(path.join(root, `assets/favicons/${variant}.svg`), 'utf8')
assert.match(svg, /width="512" height="512"/)
assert.match(svg, /M0 153\.6C0 99\.835/)
if (variant === 'local') {
assert.doesNotMatch(svg, /stroke-dasharray|stroke-width/)
assert.match(svg, /<path opacity="0.3"/)
}
const ico = await readFile(file('favicon.ico'))
assert.equal(ico.readUInt16LE(0), 0)
assert.equal(ico.readUInt16LE(2), 1)