feat(activities): UI tweaks across feed, detail, hosting, calendar, scan, shell #91

Merged
padreug merged 25 commits from feat/ui-tweaks into dev 2026-06-10 16:35:50 +00:00
4 changed files with 86 additions and 9 deletions
Showing only changes of commit 443c8b6a37 - Show all commits

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>
Padreug 2026-06-10 18:29:39 +02:00

View file

@ -9,6 +9,7 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible'
import { SlidersHorizontal, CalendarDays, Plus } from 'lucide-vue-next'
import brandAppLogoUrl from '@brand-app-logo?url'
import { useEvents } from '../composables/useEvents'
import { useEventsStore } from '../stores/events'
import EventSearchOverlay from '../components/EventSearchOverlay.vue'
@ -75,8 +76,16 @@ function openCalendar() {
<template>
<div class="container mx-auto py-4 px-4">
<!-- Page header -->
<h1 class="mb-3 text-xl sm:text-2xl font-bold text-foreground">
<!-- Page header brand-kit logo (per-standalone override or
global) paired with the standalone's localized name. Resolved
at build time via @brand-app-logo so deployers can override
without touching this component. -->
<h1 class="mb-3 flex items-center gap-2 text-xl sm:text-2xl font-bold text-foreground">
<img
:src="brandAppLogoUrl"
:alt="t('events.title')"
class="h-7 w-7 sm:h-8 sm:w-8 shrink-0"
/>
{{ t('events.title') }}
</h1>

9
src/vite-env.d.ts vendored
View file

@ -1,2 +1,11 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />
// Brand-kit alias for the active standalone's logo. Resolved at build
// time by vite-branding.ts (per-standalone override or global). The
// `?url` import returns the asset's URL string just like any other
// vite static asset.
declare module '@brand-app-logo?url' {
const src: string
export default src
}

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.

View file

@ -5,7 +5,13 @@ import { defineConfig, type Plugin } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
import { visualizer } from 'rollup-plugin-visualizer'
import { brand, brandAlias, brandAssetsPlugin, brandManifestName } from './vite-branding'
import {
brand,
brandAlias,
brandAppLogoAliasEntry,
brandAssetsPlugin,
brandManifestName,
} from './vite-branding'
/**
* Plugin to rewrite dev server requests to events.html
@ -117,10 +123,14 @@ export default defineConfig(({ mode }) => ({
}),
],
resolve: {
alias: {
...brandAlias,
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
// Array form so we can mix the per-standalone logo regex (needs to
// match `@brand-app-logo?url` query suffix) with the bare string
// aliases without one shadowing the other.
alias: [
brandAppLogoAliasEntry('events'),
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
],
},
build: {
outDir: 'dist-events',