037 - Corporate Design Overhaul¶
User Story¶
As an operator of Infinito.Nexus, I want every web UI to follow one configurable corporate design (palette, typography, logo, title, light and dark mode) so that all apps read as one platform while the base color stays changeable from a single setting.
Confirmed Decisions¶
These choices are settled at requirement creation time and bound the implementation. Re-opening any of them MUST be recorded in the implementing PR.
One SPOT role.
web-svc-cssis renamed toweb-svc-design. The content ofgroup_vars/all/13_design.ymlmoves into thedesignentity ofroles/web-svc-design/meta/services.yml, the group_vars file is deleted, and the unusedfiltersblock is dropped. Inventories and docs are migrated. No compatibility shim.Service keys. Consumers replace their
css:key withdesign:. CSS injection is driven bydesignonly. An enableddesignalways brings its own JavaScript channel: the role’s optionaltemplates/design.js.j2, collapsed, CSP-hashed and injected bysys-front-inj-design. Thejavascript:key stays independent and keeps gating the role’s functional script (javascript.js.j2, which carries SSO and logout glue that must not run without SSO).sys-front-inj-cssis renamed tosys-front-inj-designbecause injector feature names map one to one ontosys-front-inj-<feature>roles;sys-front-inj-javascriptstays.Palette engine. An in-repo OKLCH lookup plugin replaces the
colorscheme_generatordependency. Its only input isdesign.colors.base. It derives a primary scale 50 to 950, tinted neutrals, status colors (success, warning, danger, info) with fixed hues and harmonised chroma, on-colors chosen by contrast, and a dedicated dark palette (no lightness inversion). Text tokens meet WCAG 2.2 AA (4.5:1), UI and large text meet 3:1, body text meets AAA (7:1). An invalid hex value fails the deploy.Semantic tokens.
--design-primary,--design-on-primary,--design-link,--design-surface-1to--design-surface-3,--design-text,--design-text-muted,--design-border,--design-border-strong,--design-success,--design-warning,--design-danger,--design-infowith their--design-on-*partners, the interaction states--design-surface-hover,--design-surface-activeand--design-<fill>-hover,--design-<fill>-active(each keeps its text at AA or better), and the--design-primary-*and--design-neutral-*scales 50 to 950 replace--color-NN-SSand--color-rgb-NN-SS. Every color also exists as<token>-rgb.--design-invertis0in light and1in dark mode, sofilter: invert(var(--design-invert))flips a monochrome image with the mode. Scale step 50 sits next to the surface and 950 next to the text in both modes, so a role mapping onto the scale follows dark mode without its own media query. Bootstrap--bs-*variables map onto the tokens. The Bootstrap component mapping (bootstrap.css) is linked only for roles whosedesign:entry setsbootstrap: true. The 22 existing role stylesheets are migrated mechanically in the base and refined in the role loop.Clean, flat base style. No gradients on form controls and tables, subtle elevation shadows, one radius scale, 4/8 px spacing, visible focus rings, form state shown by border and icon. Font is the
system-uistack;design.fontstays configurable. The shared element styles sit in the cascade layerinfinito-design, so an app’s own design system always wins over them and keeps its contrast relations. Such an app takes the palette through its own configuration first: a theming configuration, then an in-house theme or custom CSS option. Its role stylesheet maps the app’s variables only for what no in-house option reaches.Dark mode. An app’s native theme toggle wins. Without one, the design follows
prefers-color-scheme. No theme toggle is injected.Logo and title override semantics.
design.logodefaults toassets/img/logo.png,design.titledefaults totrue, which resolves to the role title (README H1). Set inweb-svc-design, a value applies to every role. Set in a role’s owndesign:entry, it applies to that role only.falseor0disables the replacement for that scope. The legacy keystitle,titel,sitenameandsite_nameare migrated, the hardcoded titles inweb-app-joomlaandweb-app-pretixare removed, andweb-app-decidimandweb-app-socialhomeread the resolved title instead ofdefaults_*.Logo generator. An SVG template renders the lockup, Pillow rasterises PNG and ICO variants. The variant is computed from the target slot format: square slot shows the logo only, wide slot shows logo and title, very wide or two-line slot adds the domain, text-only slot shows the title only. The background stays transparent. The text uses palette colors with an outline so it stands out on light and dark surfaces; the SVG of a slot on a page or panel switches to the dark palette colors under
prefers-color-scheme: dark. A slot declared withframe: truesits on brand-colored chrome and takes the frame text color with a frame-colored outline.Branch and stack. One branch
feature/designer. It has no draft PR of its own: it reachesmainthrough the draft PR offeature/store-i18n, which merges it round by round (changed by operator decision on 2026-10-13 and recorded in that PR). Exactly one compose stack runs at a time. Base changes land as base commits on the same branch. Role changes stay underroles/<role>/and are committed per role after approval.Verification. Every change passes
make quality-highand a minimal compose deploy against the running stack:make compose-deploy apps=<role> variant=0 disable=<every disableable service except design>(defaultinitializemode, no teardown, single pass, nofull_cycle). CSS and branding iterations run without a redeploy throughmake design-sync app=<role> variant=0(re-renders shared CSS, rolestyle.cssand the generated logo assets). A changeddesign.js, task, template ormeta/file runs throughmake compose-role-sync role=<role> variant=0, which re-runs only the app role against the running stack and renders the CSP hash in the vhost with it. A role pass runsmake testand the lint targets of the file types it touched after its last edit, and closes with a role sync and the complete role spec; a pass that changed provisioning runs that role sync twice, and the second run reports no change for the tasks it added.make quality-highruns once per two or three role passes, at every merge of the main branch and before a role is committed. Each role is also asserted with one further base color, blue#001f3f, throughbase=on the sync targets. Swarm verification is out of scope.Review gallery. Each role gets its own private Artifact with at least 20 views, each captured in light and dark mode and in a desktop (1440×900) and a mobile (390×844) viewport, so at least 80 screenshots of the designed state per role. The views favour detail-rich surfaces: settings, administration, forms, lists and detail pages, logged in where the app has accounts, with idempotently seeded showcase content where a list or detail page would otherwise be empty.
make design-gallery app=<role>captures them in one spec run and copies them to/tmp/design-gallery/<role>/. A comparison with the app before the design is captured on request only:before=trueadds every view with every injected snippet stripped from the document (the body filter wraps its injections in<!--infinito-inj-->markers). The operator approves or rejects by comment.Frontend and backend. Every role is styled and tested on its frontend and, where the app has one, on its separate backend (admin surface such as
wp-admin, the Shopware or Magento admin, the Joomla administrator). A role with a backend shows backend views in its gallery in addition to the frontend views, and its Playwright spec covers both surfaces.Scope. In-scope roles are invokable, carry a lifecycle inside the tested envelope (
alphatomaintenance), and expose a web UI. Aweb-app-orweb-svc-role without a UI to design setsdesign.enabled: falsein itsmeta/services.ymlwith# nocheck: design-off Reason: <text>, which a lint enforces, andmake design-queueskips it:web-app-bridgy-fed,web-app-mig(operator decision),web-svc-asset,web-svc-cdn,web-svc-collabora,web-svc-coturn,web-svc-file,web-svc-legal,web-svc-mirror,web-svc-onlyoffice,web-svc-simpleiconsandweb-svc-xmpp.
Acceptance Criteria¶
Base¶
[x]
roles/web-svc-cssno longer exists androles/web-svc-designdeploys and serves the shared CSS.[x]
group_vars/all/13_design.ymlno longer exists and the design configuration resolves fromroles/web-svc-design/meta/services.yml, overridable per inventory.[x] No
roles/*/meta/services.ymldeclares acss:key.[x] A role with
designenabled,javascriptdisabled and atemplates/design.js.j2receives the injected CSS and its design script, and no functional script.[x] A role with
designdisabled andjavascriptenabled receives its functional script and no injected CSS or design script.[x] A minimal deploy with
disable=designinjects neither the shared CSS nor a design script.[x]
colorscheme_generatoris no longer a dependency and the OKLCH lookup plugin produces the full token set.[x] A unit test proves AA contrast for text and UI tokens and AAA contrast for body text in light and dark mode across the base colors
#001f3f,#FFA500,#FFFF00,#00FF00,#808080and#FF00FF.[x] An invalid
design.colors.basefails the deploy with an error naming the value.[x] No
--color-NN-SSor--color-rgb-NN-SSreference remains underroles/.[x] The shared CSS contains no gradient on form controls or tables.
[x] The shared CSS switches palettes via
prefers-color-schemeand injects no theme toggle.[x] The logo generator emits SVG, PNG and ICO variants with transparent background for square, wide, very wide and text-only slots.
[x] A unit test proves the logo and title precedence: global value, per-role override, and
false/0disabling.[x] No
roles/*/meta/services.ymldeclares a site title astitle,titel,sitename,site_nameorsite_titelon its primary entity; every site title lives in the role’sdesignentry and is read throughlookup('design', application_id).[x]
web-app-joomlaandweb-app-pretixcarry no hardcoded site title.[x]
web-app-decidimandweb-app-socialhomehonour an inventory title override. Decidim renames an existing organization and Socialhome an existing household to an inventory title and back.[x] The base gallery on
web-app-dashboard,web-app-giteaandweb-app-nextcloudis approved by the operator.[ ]
feature/designerreachesmainthrough the draft PR offeature/store-i18nlinked below, which merges it round by round. Open: the final designer state is not yet pushed into that PR.
Roles¶
A role is checked when its frontend and, where present, its backend are styled, its CSS and JS use only --design-* tokens, its logo and title are set through the app’s own mechanism in the slot format, its Playwright spec covers frontend and backend and asserts token colors, contrast of core elements, light and dark mode, the native toggle where one exists, and the visible logo and title, make quality-high and the minimal compose deploy are green, its gallery is approved and linked under “Further Resources” in the role README, and the change is committed.
Two base commits changed many roles after their last run: 6c2de017f8 split vendor selector lists in role stylesheets, so rules Chromium used to drop now apply, and 63f3631567 changed the colors of the generated logo lockups. Merges of main also bumped app images after some runs. A role whose open reason is only such a later change needs one more green run of its complete spec on its final state.
[x] web-app-akaunting
[x] web-app-baserow
[x] web-app-bigbluebutton
[x] web-app-bluesky
[x] web-app-bookwyrm
[x] web-app-checkmk
[x] web-app-chess
[x] web-app-dashboard
[x] web-app-decidim
[x] web-app-discourse
[x] web-app-erpnext
[x] web-app-espocrm
[x] web-app-fediwall
[x] web-app-fider
[x] web-app-flowise
[x] web-app-friendica
[x] web-app-funkwhale
[x] web-app-fusiondirectory
[x] web-app-gitea
[x] web-app-gitlab
[x] web-app-hermes
[x] web-app-homeassistant
[x] web-app-hugo
[x] web-app-jellyfin
[x] web-app-jenkins
[x] web-app-jitsi
[x] web-app-joomla
[x] web-app-keycloak
[x] web-app-kix
[x] web-app-lam
[x] web-app-listmonk
[x] web-app-litellm
[x] web-app-littlejs
[x] web-app-magento
[x] web-app-mastodon
[x] web-app-matomo
[x] web-app-matrix
[x] web-app-mattermost
[x] web-app-mediawiki
[x] web-app-mini-qr
[x] web-app-mobilizon
[x] web-app-moodle
[x] web-app-n8n
[x] web-app-nextcloud
[x] web-app-odoo
[x] web-app-openbao
[x] web-app-openclaw
[x] web-app-opencloud
[x] web-app-openproject
[x] web-app-opentalk
[ ] web-app-openwebui: logo and title stay Open WebUI’s own because clause 4 of its license allows replacing the branding only up to 50 users, with written permission or under an enterprise license (operator decision, see
roles/web-app-openwebui/TODO.md).[x] web-app-peertube
[x] web-app-penpot
[x] web-app-pgadmin
[x] web-app-phpmyadmin
[x] web-app-pihole
[x] web-app-pixelfed
[ ] web-app-postmarks: the server HTML and the Atom feeds keep the title
Postmarks, because Postmarks readssite_nameonly fromaccount.json, which switches on fediverse mode;design.jssets the browser title (upstream option inroles/web-app-postmarks/TODO.md).[x] web-app-pretix
[x] web-app-prometheus
[x] web-app-roulette-wheel
[x] web-app-seaweedfs
[x] web-app-semaphore
[x] web-app-shopware
[x] web-app-snipe-it
[x] web-app-socialhome
[x] web-app-sphinx
[ ] web-app-stalwart: handed over to
feature/store-i18n. Underssothe web administration specs need its administrator group grant (199f2a6d41), whichfeature/designerdoes not carry, sofeature/store-i18nticks this item after a green run of the stalwart spec withdesign,ssoandemailon its merged state.[x] web-app-suitecrm
[x] web-app-taiga
[x] web-app-wordpress
[x] web-app-xwiki
[x] web-app-yourls
[x] web-app-zammad
[x] web-svc-html
[x] web-svc-libretranslate
[x] web-svc-logout
Cross-linking¶
Implementing PR: #625, the draft PR of
feature/store-i18n, which carriesfeature/designertomain.
See Also¶
Role style files: style.css.md
Lifecycle envelope: lifecycle.md
Compose loop: compose.md