fix: use app icon across browser and chat
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
date: 2026-11-05
|
||||
pr: pending
|
||||
feature: Application icon consistency
|
||||
impact: Chat empty states, session branding and notifications use the application icon. Chat data and events are unchanged.
|
||||
---
|
||||
|
||||
The browser icon, application metadata and chat branding use the same icon as
|
||||
the desktop application, including the legacy favicon and logo assets.
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/logo.png" />
|
||||
<link rel="icon" type="image/png" href="/app-logo.png" />
|
||||
<link rel="apple-touch-icon" href="/app-logo.png" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="theme-color" content="#1a1d23" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 82 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 224 KiB |
@@ -9,10 +9,10 @@
|
||||
"theme_color": "#f7f7f4",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.png",
|
||||
"sizes": "1254x1254",
|
||||
"src": "/app-logo.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
"purpose": "any"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ useKeyboard()
|
||||
</div>
|
||||
<div class="app-layout" :class="{ 'no-sidebar': isLoginPage || !showAppSidebar }">
|
||||
<button v-if="showMobileMenuButton" class="hamburger-btn" @click="handleMobileMenuClick">
|
||||
<img src="/logo.png" alt="Menu" style="width: 24px; height: 24px;" />
|
||||
<img src="/app-logo.png" alt="Menu" style="width: 24px; height: 24px;" />
|
||||
</button>
|
||||
<div v-if="!isLoginPage && showAppSidebar && appStore.sidebarOpen" class="mobile-backdrop" @click="appStore.closeSidebar" />
|
||||
<AppSidebar v-if="!isLoginPage && showAppSidebar" />
|
||||
|
||||
@@ -192,7 +192,7 @@ defineExpose({
|
||||
>
|
||||
<template #empty>
|
||||
<div class="empty-state">
|
||||
<img :src="'/coding-agents/hermes.png'" alt="Hermes" class="empty-logo" />
|
||||
<img :src="'/app-logo.png'" alt="Hermes" class="empty-logo" />
|
||||
<p>{{ t("chat.emptyState") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -94,7 +94,7 @@ const visibleToolCalls = computed(() =>
|
||||
);
|
||||
|
||||
const emptyState = computed(() => ({
|
||||
logo: "/logo.png",
|
||||
logo: "/app-logo.png",
|
||||
alt: "Hermes",
|
||||
text: t("chat.emptyState"),
|
||||
}));
|
||||
|
||||
@@ -42,7 +42,7 @@ const profileModelsMissing = computed(() =>
|
||||
appStore.profileModelGroups.length > 0 && !profileHasModels.value,
|
||||
)
|
||||
const isGlobalAgentSession = computed(() => props.session.source === 'global_agent')
|
||||
const sessionAgentLogo = computed(() => ({ label: 'Hermes', src: '/logo.png' }))
|
||||
const sessionAgentLogo = computed(() => ({ label: 'Hermes', src: '/app-logo.png' }))
|
||||
|
||||
let longPressTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const longPressTriggered = ref(false)
|
||||
|
||||
@@ -105,7 +105,7 @@ defineExpose({ scrollToBottom })
|
||||
>
|
||||
<template #empty>
|
||||
<div class="empty-state">
|
||||
<img :src="'/coding-agents/hermes.png'" alt="Hermes" class="empty-logo" />
|
||||
<img :src="'/app-logo.png'" alt="Hermes" class="empty-logo" />
|
||||
<p>{{ t("chat.emptyState") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2225,7 +2225,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
function completionNotificationAgent(_session: Session): { icon: string } {
|
||||
return { icon: '/favicon.ico' }
|
||||
return { icon: '/app-logo.png' }
|
||||
}
|
||||
|
||||
function completionNotificationBody(session: Session, message?: Message): string {
|
||||
|
||||
@@ -5,8 +5,9 @@ describe('PWA metadata', () => {
|
||||
it('links manifest and touch icon from the client shell', () => {
|
||||
const html = readFileSync('packages/client/index.html', 'utf8')
|
||||
|
||||
expect(html).toContain('rel="icon" type="image/png" href="/app-logo.png"')
|
||||
expect(html).toContain('rel="manifest" href="/manifest.webmanifest"')
|
||||
expect(html).toContain('rel="apple-touch-icon" href="/logo.png"')
|
||||
expect(html).toContain('rel="apple-touch-icon" href="/app-logo.png"')
|
||||
expect(html).toContain('name="apple-mobile-web-app-title" content="hermes-ui"')
|
||||
})
|
||||
|
||||
@@ -18,10 +19,21 @@ describe('PWA metadata', () => {
|
||||
expect(manifest.start_url).toBe('/#/hermes/chat')
|
||||
expect(manifest.icons).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
src: '/logo.png',
|
||||
src: '/app-logo.png',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
purpose: 'any',
|
||||
}),
|
||||
]))
|
||||
})
|
||||
|
||||
it('ships matching application and legacy icon assets', () => {
|
||||
const logo = readFileSync('packages/client/public/app-logo.png')
|
||||
const legacyLogo = readFileSync('packages/client/public/logo.png')
|
||||
const favicon = readFileSync('packages/client/public/favicon.ico')
|
||||
expect(legacyLogo.equals(logo)).toBe(true)
|
||||
expect(logo.readUInt32BE(16)).toBe(512)
|
||||
expect(logo.readUInt32BE(20)).toBe(512)
|
||||
expect(favicon.readUInt16LE(2)).toBe(1)
|
||||
expect(favicon.subarray(22, 30)).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -151,7 +151,7 @@ describe('SessionListItem', () => {
|
||||
})
|
||||
|
||||
const logo = wrapper.get('.session-item-agent-logo')
|
||||
expect(logo.attributes('src')).toBe('/logo.png')
|
||||
expect(logo.attributes('src')).toBe('/app-logo.png')
|
||||
expect(logo.attributes('alt')).toBe('Hermes')
|
||||
expect(wrapper.find('.session-item-agent-name').exists()).toBe(false)
|
||||
})
|
||||
@@ -172,7 +172,7 @@ describe('SessionListItem', () => {
|
||||
})
|
||||
|
||||
const logo = wrapper.get('.session-item-agent-logo')
|
||||
expect(logo.attributes('src')).toBe('/logo.png')
|
||||
expect(logo.attributes('src')).toBe('/app-logo.png')
|
||||
expect(logo.attributes('alt')).toBe('Hermes')
|
||||
expect(wrapper.find('.session-item-agent-name').exists()).toBe(false)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
test('serves the application icon for the browser tab', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
const icon = page.locator('link[rel="icon"]')
|
||||
await expect(icon).toHaveAttribute('href', '/app-logo.png')
|
||||
await expect(icon).toHaveAttribute('type', 'image/png')
|
||||
const response = await page.request.get('/app-logo.png')
|
||||
expect(response.ok()).toBe(true)
|
||||
expect(response.headers()['content-type']).toContain('image/png')
|
||||
expect((await response.body()).subarray(0, 8)).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
|
||||
})
|
||||
Reference in New Issue
Block a user