diff --git a/apps/web/index.html b/apps/web/index.html
index 75c4e5bac6..f5c2ffac06 100644
--- a/apps/web/index.html
+++ b/apps/web/index.html
@@ -4,7 +4,8 @@
-
+
+
DSH Local Build
diff --git a/apps/web/public/favicon-dark.svg b/apps/web/public/favicon-dark.svg
new file mode 100644
index 0000000000..648fe4f2b9
--- /dev/null
+++ b/apps/web/public/favicon-dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg
index c92f15d43b..754615a37a 100644
--- a/apps/web/public/favicon.svg
+++ b/apps/web/public/favicon.svg
@@ -1,8 +1,3 @@
diff --git a/apps/web/src/favicon.ts b/apps/web/src/favicon.ts
deleted file mode 100644
index ac08a9f84f..0000000000
--- a/apps/web/src/favicon.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/** Browser-tab icons follow the system scheme independently of the application theme. */
-import favicon from '../public/favicon.svg?raw'
-
-/**
- * Install fixed-color icons and a document-lifetime system-scheme listener.
- * Browser favicon rasterizers can cache SVG media-query results across scheme changes.
- * Distinct data URLs select explicit colors without duplicating the source artwork.
- */
-export function installFavicon(): void {
- const link = document.querySelector('link[rel="icon"]')
- if (link === null) throw new Error('web app: missing favicon link')
- const svg = new DOMParser().parseFromString(favicon, 'image/svg+xml')
- svg.querySelector('style')?.remove()
- const path = svg.querySelector('path')
- if (path === null) throw new Error('web app: missing favicon path')
- const icon = (fill: string): string => {
- path.setAttribute('fill', fill)
- return `data:image/svg+xml,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`
- }
- const light = icon('#000')
- const dark = icon('#fff')
- const scheme = window.matchMedia('(prefers-color-scheme: dark)')
- const update = (): void => { link.href = scheme.matches ? dark : light }
- update()
- scheme.addEventListener('change', update)
-}
diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts
index 9f1fa593a1..1a84cd5f63 100644
--- a/apps/web/src/main.ts
+++ b/apps/web/src/main.ts
@@ -1,6 +1,5 @@
/** Browser entry for the Web client. */
import { AppWebEntry, applyIndexInjections } from '@deepseek-ai/dsh-client-web'
-import { installFavicon } from './favicon.ts'
interface DesktopBootGlobal {
dshDesktopBoot?: {
@@ -15,7 +14,6 @@ const reportFailure = (reason: unknown): void => {
}
try {
- installFavicon()
const el = document.getElementById('root')
if (el === null) throw new Error('web app: missing #root')
const entry = new AppWebEntry(el)
diff --git a/apps/web/tests/desktop-boot.spec.ts b/apps/web/tests/desktop-boot.spec.ts
index 9bea3def5f..cac91ad7e7 100644
--- a/apps/web/tests/desktop-boot.spec.ts
+++ b/apps/web/tests/desktop-boot.spec.ts
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
-import { afterEach, beforeEach, expect, it, vi } from 'vitest'
+import { afterEach, expect, it, vi } from 'vitest'
const boot = vi.hoisted(() => ({
run: vi.fn(),
@@ -10,16 +10,10 @@ vi.mock('@deepseek-ai/dsh-client-web', () => ({
applyIndexInjections: boot.applyIndexInjections,
}))
-beforeEach(() => {
- document.head.innerHTML = ''
- vi.stubGlobal('matchMedia', () => ({ matches: false, addEventListener: vi.fn() }))
-})
-
afterEach(() => {
vi.unstubAllGlobals()
vi.clearAllMocks()
vi.resetModules()
- document.head.replaceChildren()
document.body.replaceChildren()
})
diff --git a/apps/web/tests/favicon.spec.ts b/apps/web/tests/favicon.spec.ts
deleted file mode 100644
index af932bc4fc..0000000000
--- a/apps/web/tests/favicon.spec.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// @vitest-environment jsdom
-import { afterEach, expect, it, vi } from 'vitest'
-import { installFavicon } from '../src/favicon.ts'
-
-const originalHead = document.head.innerHTML
-
-afterEach(() => {
- document.head.innerHTML = originalHead
- document.documentElement.style.removeProperty('color-scheme')
- vi.unstubAllGlobals()
-})
-
-it.each([false, true])('selects fixed-color tab icons on boot and system changes (dark=%s)', (initialDark) => {
- const link = document.createElement('link')
- link.rel = 'icon'
- link.href = '/favicon.svg'
- document.head.append(link)
- let dark = initialDark
- let onChange = (): void => { throw new Error('missing scheme listener') }
- const matchMedia = vi.fn(() => ({
- get matches() { return dark },
- addEventListener: (event: string, listener: () => void) => {
- expect(event).toBe('change')
- onChange = listener
- },
- }))
- vi.stubGlobal('matchMedia', matchMedia)
- const readIcon = (): string => decodeURIComponent(link.href.slice('data:image/svg+xml,'.length))
- const expectFill = (fill: string): void => {
- expect(link.href).toMatch(/^data:image\/svg\+xml,/)
- const svg = new DOMParser().parseFromString(readIcon(), 'image/svg+xml')
- expect(svg.querySelector('parsererror')).toBeNull()
- expect(svg.querySelector('style')).toBeNull()
- expect(svg.querySelector('path')?.getAttribute('fill')).toBe(fill)
- expect(svg.documentElement.getAttribute('viewBox')).toBe('0 0 50 50')
- }
-
- document.documentElement.style.colorScheme = initialDark ? 'light' : 'dark'
- installFavicon()
- expect(matchMedia).toHaveBeenCalledWith('(prefers-color-scheme: dark)')
- expectFill(initialDark ? '#fff' : '#000')
- const initial = link.href
- dark = !initialDark
- onChange()
- expectFill(initialDark ? '#000' : '#fff')
- expect(link.href).not.toBe(initial)
- dark = initialDark
- onChange()
- expect(link.href).toBe(initial)
-})
-
-it('requires the document favicon link', () => {
- document.querySelector('link[rel="icon"]')?.remove()
- expect(installFavicon).toThrow('web app: missing favicon link')
-})
diff --git a/apps/web/tests/pwa-manifest.e2e.ts b/apps/web/tests/pwa-manifest.e2e.ts
index db5e4c66bf..2cfc21e2c9 100644
--- a/apps/web/tests/pwa-manifest.e2e.ts
+++ b/apps/web/tests/pwa-manifest.e2e.ts
@@ -28,10 +28,14 @@ it('ships install metadata with the built web application', async () => {
})
})
-it('ships a favicon that switches to a light mark under dark color scheme', async () => {
- const favicon = await readFile(join(DIST_ROOT, 'favicon.svg'), 'utf8')
- // The light fill must live inside the dark-scheme media query, so the icon
- // stays black in light mode and only turns white under a dark scheme.
- expect(favicon).toMatch(/@media \(prefers-color-scheme: dark\)\s*{\s*path\s*{[^}]*fill:\s*#fff/i)
- expect(favicon).toContain('fill="#000"')
+it('ships fixed-color favicons selected by document media queries', async () => {
+ const index = await readFile(join(DIST_ROOT, 'index.html'), 'utf8')
+ expect(index).toContain('')
+ expect(index).toContain('')
+ const light = await readFile(join(DIST_ROOT, 'favicon.svg'), 'utf8')
+ const dark = await readFile(join(DIST_ROOT, 'favicon-dark.svg'), 'utf8')
+ expect(light).not.toContain('