feat(activities): brand-kit logo + app name in the events page header

Replace the bare "Events" h1 with the brand-kit logo paired with the
standalone's localized name. Deployers get per-standalone logo
control via branding/<dep>/icons/events/logo.{svg,png}; the
component itself stays brand-agnostic.

Brand-kit plumbing:

- `resolveAppLogo(app?)` in vite-branding.ts mirrors the resolution
  chain pwa-assets.config.ts already uses for PWA icons
  (per-standalone svg → png → global svg → png).
- `brandAppLogoAliasEntry(app)` returns a vite alias array entry. A
  regex matches `@brand-app-logo` with or without a `?url` query so
  the file resolves cleanly under either form.
- vite.events.config.ts switches its resolve.alias to the array form
  so the per-standalone regex doesn't clash with the bare `@brand`
  string alias.

Component side: a single `import brandAppLogoUrl from '@brand-app-logo?url'`
gives EventsPage the best-resolved logo without any fallback chain
in the component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-10 18:29:39 +02:00
commit 443c8b6a37
4 changed files with 86 additions and 9 deletions

View file

@ -1,6 +1,6 @@
import { spawnSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { existsSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import type { Plugin } from 'vite'
/**
@ -43,6 +43,55 @@ export const brandAlias = {
'@brand': BRAND_DIR,
} as const
/**
* Resolution order for the in-app logo of a given standalone. Mirrors
* what pwa-assets.config.ts does for PWA icons: per-standalone override
* first (SVG then PNG), then the brand's primary logo (SVG then PNG).
*
* Returned path is absolute so vite alias can map directly to it.
*/
export function resolveAppLogo(app?: string): string {
const candidates: string[] = []
if (app) {
candidates.push(
join(BRAND_DIR, 'icons', app, 'logo.svg'),
join(BRAND_DIR, 'icons', app, 'logo.png'),
)
}
candidates.push(
join(BRAND_DIR, 'logo.svg'),
join(BRAND_DIR, 'logo.png'),
)
const found = candidates.find((p) => existsSync(p))
if (!found) {
throw new Error(
`No brand logo found for app="${app ?? ''}". Tried:\n ${candidates.join('\n ')}\n` +
`See branding/README.md for the brand kit contract.`,
)
}
return found
}
/**
* Standalone-aware brand-logo alias entry. Append to a vite config's
* `resolve.alias` array alongside the rest of the alias map. The
* regex matches `@brand-app-logo` with or without a `?url` query so
* `import logoUrl from '@brand-app-logo?url'` resolves to the active
* standalone's logo file (per-app override or global), with no
* fallback chain in the component itself.
*
* Note: when used with the object form of resolve.alias, a bare
* `@brand` entry would shadow this combine the two as an array
* (see vite.events.config.ts).
*/
export function brandAppLogoAliasEntry(app?: string) {
const resolved = resolveAppLogo(app)
return {
find: /^@brand-app-logo(\?.*)?$/,
replacement: `${resolved}$1`,
} as const
}
/**
* PWA manifest name for a standalone. Combines the brand name with the
* app's own label, or returns the bare brand when no label is given.