From df1f61d6ccd047c65ceda47c338abdf115557ed5 Mon Sep 17 00:00:00 2001 From: null Date: Mon, 6 Jul 2026 13:32:37 -0500 Subject: [PATCH] feat(brand): render the real product glyph SVGs (not inline approximations) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BrandGlyph drew hardcoded monochrome shapes from an inline switch and ignored the actual /brand/glyphs/*.svg files. Render the real files via a robust (fail-soft onError, intrinsic width/height so no layout shift, alt for a11y parity) so every existing placement — PageHeader (Tracker/About/Release), Command Palette, Release Notes, Onboarding, Login — shows the full-color brand tiles. Deletes the ~85-line dead BrandGlyphShape switch and the now-unused useId import. Senior-review while-here: - Guard test (client/lib/brand.test.ts): asserts every BrandGlyphName asset — and every /brand asset in brand.ts — resolves to a real, non-empty file on disk. Catches the "image added but path wrong / renamed" regression TS can't see. - Widen vitest include to {js,jsx,ts,tsx}: TypeScript client tests were silently never run (client/hooks/useAutoSave.test.ts). Now 6 files / 50 tests run (was 4 / 42). - Remove dead brand.ts entries: legacyLogo (pointed at the OLD pre-brand /img/logo.png) and the unused ADMIN_GLYPH const. - PageHeader glyph class inline-grid -> inline-block for the . Verified: typecheck + lint clean; test:client 50/50; build (glyphs land in dist/brand/glyphs, served 200 as image/svg+xml); e2e probe 17/17; login visual baseline unchanged. Co-Authored-By: Claude Opus 4.8 --- client/components/brand/Brand.tsx | 110 +++--------------------- client/components/ui/app-primitives.tsx | 2 +- client/lib/brand.test.ts | 29 +++++++ client/lib/brand.ts | 3 - vite.config.mjs | 2 +- 5 files changed, 43 insertions(+), 103 deletions(-) create mode 100644 client/lib/brand.test.ts diff --git a/client/components/brand/Brand.tsx b/client/components/brand/Brand.tsx index 5ec6bcb..0a0f3ea 100644 --- a/client/components/brand/Brand.tsx +++ b/client/components/brand/Brand.tsx @@ -1,4 +1,4 @@ -import { useId, type ComponentProps } from 'react'; +import { type ComponentProps } from 'react'; import { cn } from '@/lib/utils'; import { BRAND, BRAND_GLYPHS, type BrandGlyphName } from '@/lib/brand'; import { useTheme } from '@/contexts/ThemeContext'; @@ -35,104 +35,18 @@ export function BrandGlyph({ className?: string; }) { const glyph = BRAND_GLYPHS[name]; - const gradientId = `bt-glyph-${useId().replace(/:/g, '')}`; return ( - - - + {glyph.label} { e.currentTarget.style.display = 'none'; }} + className={cn('inline-block h-10 w-10 shrink-0 select-none rounded-xl object-contain shadow-sm', className)} + /> ); } - -function BrandGlyphShape({ name, gradientId }: { name: BrandGlyphName; gradientId: string }) { - const fill = `url(#${gradientId})`; - const commonStroke = 'currentColor'; - - switch (name) { - case 'bills': - return ( - <> - - - - - ); - case 'calendar': - return ( - <> - - - - - - ); - case 'analytics': - return ( - <> - - - - - - ); - case 'data': - return ( - <> - - - - - - ); - case 'banking': - return ( - <> - - - - - ); - case 'snowball': - return ( - <> - - - - - - ); - case 'security': - return ( - <> - - - - ); - case 'tracker': - default: - return ( - <> - - - - - - ); - } -} diff --git a/client/components/ui/app-primitives.tsx b/client/components/ui/app-primitives.tsx index 6bcc4d4..db04db6 100644 --- a/client/components/ui/app-primitives.tsx +++ b/client/components/ui/app-primitives.tsx @@ -46,7 +46,7 @@ export function PageHeader({ return (
- {glyph && } + {glyph && } {Icon && !glyph && ( diff --git a/client/lib/brand.test.ts b/client/lib/brand.test.ts new file mode 100644 index 0000000..48b615a --- /dev/null +++ b/client/lib/brand.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync, existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { BRAND, BRAND_GLYPHS } from './brand'; + +// Assets are referenced by absolute URL (/brand/...) and served from client/public. +const publicDir = resolve(dirname(fileURLToPath(import.meta.url)), '../public'); +const toFile = (assetUrl: string) => resolve(publicDir, assetUrl.replace(/^\//, '')); + +describe('brand glyph assets', () => { + // TS proves each BrandGlyphName has an asset path; it can't prove the FILE exists. + // This is the exact failure mode of "images added but the path is wrong / renamed". + it('every glyph resolves to a real, non-empty SVG file on disk', () => { + for (const [name, glyph] of Object.entries(BRAND_GLYPHS)) { + expect(glyph.asset, `${name} asset path shape`).toMatch(/^\/brand\/glyphs\/.+\.svg$/); + const file = toFile(glyph.asset); + expect(existsSync(file), `${name} → ${glyph.asset} missing on disk`).toBe(true); + expect(readFileSync(file, 'utf8'), `${name} is a valid `).toMatch(/]/); + } + }); + + it('every /brand asset referenced in brand.ts exists on disk', () => { + for (const [key, url] of Object.entries(BRAND.assets)) { + if (!url.startsWith('/brand/')) continue; // skip third-party (/img/) icons + expect(existsSync(toFile(url)), `${key} → ${url} missing on disk`).toBe(true); + } + }); +}); diff --git a/client/lib/brand.ts b/client/lib/brand.ts index 777200e..f975fa3 100644 --- a/client/lib/brand.ts +++ b/client/lib/brand.ts @@ -19,7 +19,6 @@ export const BRAND = { socialPreview: '/brand/social-preview.svg', socialPreviewPng: '/brand/social-preview.png', emptyState: '/brand/empty-state.svg', - legacyLogo: '/img/logo.png', }, colors: { ink: '#101417', @@ -51,5 +50,3 @@ export const BRAND_GLYPHS: Record