feat(branding): admin-configurable app name, logo, favicon & primary colour - #214
Merged
Conversation
Modules can register a Callable[[Request], dict] via register_inertia_shared_provider(app, fn); InertiaLayoutDataMiddleware merges each provider's output into the per-page shared props after the built-in auth/menus/i18n blocks. A provider that raises is skipped and logged, never failing the request. Mirrors the principal_serializer precedent so the framework never imports plugin modules (SM009).
…d-props) New branding module: app name, logo, favicon and primary colour, persisted via the settings store (no own table), surfaced to every page through a registered Inertia shared-props provider. JSON API (PUT text fields, logo/ favicon upload+clear) gated by branding.manage; Inertia view at /branding gated by branding.view; sidebar entry + permissions registered. Also fixes two make-new-module scaffold bugs hit here: host/pyproject used the bare package name instead of simple_module_<name>, and the module pyproject was missing [tool.hatch.build.targets.wheel] packages.
…min page - Type the branding shared prop; SidebarLayout renders the configured app name + logo (fallback to the SM badge); PublicLayout wordmark too. - BrandingHead (rendered globally in app.tsx Root) sets the favicon and overrides the --primary CSS vars from the configured colour, reactively. - Document <title> suffix uses the configured app name. - Branding/Manage admin page: app name, colour picker, logo + favicon upload with live preview; saves via fetch then router.reload(). - Regenerated module pages manifest + i18n keys.
- Use _MODULE_*/_PAGE_* constants for depends_on and the Inertia page id (keeping the render literal inline for SM003/SM004 AST pairing) + a drift-guard test. - Add required pyproject metadata (readme, license, keywords, repo URL). - Add modules/branding/README.md with Install/Usage sections.
Proves PUT /api/branding -> persist + hot-swap -> shared-props provider -> InertiaLayoutDataMiddleware -> branding block in a real Inertia page's props.
- CRITICAL: render BrandingHead inside Inertia context (SidebarLayout, PublicLayout, AuthCardShell) instead of as a sibling of <App> in Root, where usePage()/<Head> threw and the ErrorBoundary replaced every page. - Blank/whitespace app_name now returns 422 (validated in BrandingUpdate) instead of 500 from BrandingSettings re-validation. - Harden the shared-prop provider merge: a provider can no longer clobber framework-owned keys (auth/menus/i18n) or an earlier provider's key. - Reuse file_storage's route constants for the download URL; centralize the hex-colour regex; drop the duplicate usePage() in Manage.tsx. - Add regression tests for the 422 and the provider-clobber guard.
Browser QA showed the document <title> never reflects the configured app name (this app's title updates are page-driven and Inertia's title callback can't read live page props), so the brandAppName machinery was dead code with a misleading comment. Revert to the original static suffix; the in-app branding (sidebar name/logo, favicon, primary colour) works and is verified. Branding the browser-tab title is a documented follow-up.
…hared Moves the provider-merge loop out of InertiaLayoutDataMiddleware into merge_shared_prop_providers() so middleware.py stays under the 300-line cap and the merge logic is unit-testable in isolation. Behaviour unchanged.
Deploying simple-module-python with |
| Latest commit: | 5c37b48 |
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6f924362.simple-module-python.pages.dev |
| Branch Preview URL: | https://feat-branding.simple-module-python.pages.dev |
Uh oh!
There was an error while loading. Please reload this page.
2 tasks
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 freeto 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
Adds a branding module letting an admin customise the application's identity from the admin UI (
/branding), applied everywhere the framework hard-coded "SimpleModule":/branding(gated by a newbranding.managepermission), with image upload + colour picker + live preview.settingsstore (SYSTEM scope), hydrate at boot, and hot-swap on save. Logo/favicon images reuse thefile_storagemodule (referenced by UUID).register_inertia_shared_provider(app, fn)lets any module contribute layout-wide Inertia shared props without the framework importing the plugin (mirrors theprincipal_serializerprecedent, keeps SM009 intact). A provider that raises is skipped; it can't clobber framework-owned keys.brandingshared prop;SidebarLayout/PublicLayoutrender the configured name + logo (fallback to defaults);BrandingHead(in the layouts) sets the favicon and overrides the--primaryCSS vars reactively.Scope notes: branding is SYSTEM-scoped for now (the settings store already supports tenant scope, leaving room for per-tenant branding later). Branding the browser-tab
<title>is a deferred follow-up. The numericprimary-*colour scale isn't regenerated from the hex, so a few scale-based accents (e.g. the sidebar-active highlight) keep the default tint — the semanticprimarytoken (buttons, swatches) does update.Screenshots
The admin page at
/branding, the same page rebranded as "Acme Analytics" (custom logo + name +#7c3aed), and the branded sidebar across the app:Verification
BrandingHeadwas first rendered as a sibling of<App>, outside Inertia'sPageContext/HeadContext, which would have thrown on every page; now rendered inside the layouts. Plus hardening: blankapp_name→ 422 (not 500), defensive provider merge, reuse offile_storageroute constants./brandingall render with no React/console errors; uploading a logo + setting app name "Acme Analytics" + colour#7c3aedapplied live to the sidebar logo/wordmark and--primaryCSS var (screenshots above).make lint✓ · Python 1365 passed (incl. 21 new branding/framework tests + an end-to-end Inertia shared-props integration test) · JS 22 passed · production build ✓ ·make doctorreports zero branding diagnostics.Test plan
/brandingas an admin, sets a name/colour and uploads a logo, and confirms the sidebar + favicon + accent colour update.