feat: add BrandSettings env-var surface for favicon and email branding#1720
Merged
Conversation
1529e76 to
8a679ef
Compare
DaisyUI tokens and CSS variables cover everything that renders inside the page, but a few brand surfaces are out of reach: favicon meta tags (`mask-icon`, `msapplication-TileColor`, `theme-color`) are read by the browser before any CSS runs; the PWA manifest's `theme_color` / `background_color` are consumed by the OS install UI; and email clients (Gmail, Outlook) strip `<style>` blocks and ignore CSS variables. `BrandSettings` is an app-level pydantic-settings surface for those specific surfaces, driven by three env vars: - `BRAND_PRIMARY_COLOR` — Safari mask-icon, Windows tile, magic-link email button (when no override is set). - `BRAND_BROWSER_THEME_COLOR` — mobile browser chrome, PWA manifest. - `BRAND_EMAIL_BUTTON_COLOR` — explicit override slot for the email button when it must differ from the primary brand color. Inputs accept any pydantic-Color form (named, `rgb()`, hex). Values are canonicalised to long-form `#rrggbb` lowercase via a `HexColor` subclass that pins `__str__` to `as_hex(format="long")` — bare `Color` would render `#ffffff` as the string `white`, which is awkward to interpolate into HTML attributes. `settings.brand` is exposed in every Jinja render via the shipped `_brand_context` provider; the bundled `base/favicons.html.jinja`, `meta/site.webmanifest.jinja`, and `meta/browserconfig.xml.jinja` already consume it. `send_magic_link_email()` passes `settings.brand.email_button` into the email template. Per-tenant brand colors stay with `TenantTheme` (favicon assets are static files served before tenant resolution; the email service does not see request context). Drive-by fix: `_render_template_with_env` now falls back to the bare template name after `<lang>/<name>` and `default/<name>`. Framework email templates ship flat at the namespace root, not under `default/<lang>/`, so without the flat fallback the magic-link email failed to render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8a679ef to
660333b
Compare
davidpoblador
pushed a commit
that referenced
this pull request
Apr 28, 2026
🤖 I have created a release *beep* *boop* --- ## [10.8.0](v10.7.0...v10.8.0) (2026-04-28) ### Features * add BrandSettings env-var surface for favicon and email branding ([#1720](#1720)) ([3d22717](3d22717)) * i18n primitives for per-tenant locale flows ([#1718](#1718)) ([177fe5e](177fe5e)) ### Bug Fixes * restore gettext plural-form support in lint-po ([#1725](#1725)) ([fe0dacb](fe0dacb)) * **template:** scope babel.cfg python extraction to src/ ([#1722](#1722)) ([b729964](b729964)) * theme default screens with DaisyUI semantic tokens ([#1714](#1714)) ([4467168](4467168)) ### Performance Improvements * **testing:** share MongoDB across vibetuner_db tests ([#1726](#1726)) ([c9064e4](c9064e4)) ### Miscellaneous Chores * **py:** use PEP 639 SPDX license expression ([#1727](#1727)) ([855f9f4](855f9f4)) ### Documentation Updates * **i18n:** clarify locale_names vs language_picker overlap ([#1723](#1723)) ([87733bd](87733bd)) * **template:** document i18n primitives in scaffolded agent rules ([#1724](#1724)) ([27a3b98](27a3b98)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DaisyUI tokens and CSS variables cover everything that renders inside the page, but a few brand surfaces are out of reach:
mask-icon,msapplication-TileColor,theme-color) are read by the browser before any CSS runs.theme_color,background_color) is consumed by the OS install UI, also before CSS.<style>blocks and ignore CSS variables.This PR adds
BrandSettings, an app-level pydantic-settings surface that drives those specific surfaces.Configuration
BRAND_PRIMARY_COLOR→ Safari pinned-tabmask-icon, Windows tile (browserconfig.xml), magic-link email button (when no override).BRAND_BROWSER_THEME_COLOR→ mobile browser chrome (<meta name="theme-color">), PWA manifest'stheme_color/background_color.BRAND_EMAIL_BUTTON_COLOR→ override slot for when the email button needs to differ from the primary brand color.Inputs accept any pydantic-
Colorform (named,rgb(), hex short or long). Values canonicalise to long-form#rrggbblowercase via aHexColorsubclass that pins__str__toas_hex(format="long")— bareColorwould render#ffffffas the stringwhite, which is awkward to interpolate into HTML attributes.settings.brandis exposed in every Jinja render via the shipped_brand_contextprovider, so templates read{{ brand.primary_color }}without wiring anything up. The bundledbase/favicons.html.jinja,meta/site.webmanifest.jinja, andmeta/browserconfig.xml.jinjaalready consume it;send_magic_link_email()passessettings.brand.email_buttoninto the email template.Why app-level, not per-tenant
Favicon assets are static files served before any tenant resolver runs, and the email service does not see request context. For per-tenant in-page colors, keep using
TenantTheme.Drive-by fix
_render_template_with_envnow falls back to the bare template name after<lang>/<name>anddefault/<name>. Framework email templates ship flat at the namespace root (not underdefault/<lang>/), so without the flat fallback the magic-link email failed to render.Test plan
uv run python -m pytest tests/unit— 777 passeduv run python -m pytest tests/unit/test_brand_settings.py— 21 new tests coverHexColorsubclass,BrandSettingsdefaults / env-var binding / fallbacks, favicons partial render, webmanifest + browserconfig render, magic-link email render with button color, brand context providerjust lint-py— passesjust lint-jinja— passesjust lint-md— passesjust type-check— passes🤖 Generated with Claude Code