feat: add per-tenant theme engine - #675
Conversation
fix: typo in translation.py inline comments
Co-authored-by: Codex <codex@openai.com>
…x#666) Tests and Lint have failed on every pull request since 2026-07-08, and nothing has merged into develop since 2026-07-02. Three independent causes had stacked up on the refreshed macos-latest image: 1. The image moved to Ruby 3.4, which removed abbrev and mutex_m from the default gems. fastlane 2.225.0 needs both (highline 2.0.3 requires abbrev) and the workflows did not pin a Ruby version. Pinned to 3.3, last known good. 2. update_fastlane installs a new fastlane at runtime, firing the RubyGems documentation hook, which loads rdoc 8, whose rbs_helper requires rbs — a bundled gem, so not on the load path under bundle exec. Fails on 3.3 and 3.4 alike. Removed: in CI it also means the version pinned in Gemfile.lock is not the version that actually runs. 3. swiftlint.yml ran `xcodes select 16.1`, but the image now carries only Xcode 26.x. xcodes then dropped into its interactive picker, got nothing on stdin and exited 1. Selects 26.2 now, matching the Fastfile. Bumping fastlane to >= 2.230.0, which declares the gems Ruby 3.4 removed, would let the Ruby pin be dropped later. Left out to keep a release-blocking fix small.
…nedx#667) Every release pull request from develop to main fails commitlint, because it re-lints develop history that was merged before conventional subjects were enforced on merge. Six squash commits are involved, the oldest from May 2026: Fixes: Assignment thumbnails count, ... (openedx#649) type-case, type-enum, 114 chars Fix: certificates not displayed ... (openedx#651) type-case, type-enum Replace SwiftMocky with Mockolo ... (openedx#652) type-empty, subject-empty Migrate from ObservableObject to ... (openedx#653) type-empty, subject-empty Fix/issue 581 (openedx#635) type-empty, subject-empty Fix issues 640, 641, 652 (openedx#661) type-empty, subject-empty They cannot be corrected. Rewriting them means a non-fast-forward update of develop, and develop has allowsForcePushes false plus a required pull request, so no force push is possible without an org admin. Even with one it would strip the GitHub signatures from twelve commits, orphan six pull requests from the generated release notes, which map commits to pull requests by SHA, and leave six forks that are pinned into the range to pull the old commits straight back. The shared openedx workflow downloads the default config only when a repo-local commitlint.config.mjs is absent, so this file is the sanctioned override point. It is the upstream config verbatim plus one ignores predicate holding those six headers as exact strings, pull request number included. Pull request numbers are never reused, so nothing new can match. Verified against commitlint: with the predicate all 13 commits in main..develop pass; with it removed exactly those 6 fail and the other 7 still pass on their own merits. New offenders are still rejected, including near misses such as the same text with a different pull request number or altered case.
Adds the Sendable Tenant value type and TenantsConfig container that later multi-tenant layers build on: per-tenant API/SSO/OAuth config, UI feature flags (UIComponentsConfig, now @unchecked Sendable), and raw branding fields for a later theming layer to render. Tenant(dictionary:) parses config.yaml's new TENANTS block. Not included here: remote catalog fetching, tenant selection/session state, and anything reading TenantProvider — those land as separate PRs. TENANTS data in config.yaml is illustrative, not real tenants.
Adds ThemeColorSet/ThemeDefinition/ThemeFontSet and ThemeManager: derives a tenant's palette from its accent hex plus any per-field THEME overrides, and resolves its logo/header banner from a remote URL (cached in-memory, falling back to the bundled default) or a bundled asset name. Looked up by the tenant's stable key, not a localized display-name string. Takes primitives, not Core's Tenant struct -- Theme has no build dependency on Core. Not called from anywhere yet -- RouteController wires up applyTheme(...) in a later PR. With one generic placeholder asset for customer logo, matching config.yaml's example-tenant entry.
|
Thanks for the pull request, @RawanMatar89! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #675 +/- ##
===========================
===========================
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Base: infra/01-tenant-model
Summary
Adds the theme engine:
ThemeColorSet,ThemeDefinition,ThemeFontSet, andThemeManager. Given a tenant's accent color hex plus optional per-fieldTHEME.light/THEME.darkoverrides, it derives a full color palette — only the fields a tenant actually sets are overridden, everything else falls back to the compiled default. Logo and header-banner images resolve from either a remote URL (cached in-memory, no re-download/re-flash on repeated appearances) or a bundled asset name.Depends only on PR-1 (
Tenant.themeColors/Tenant.logoURLString/Tenant.headerBackgroundURLStringshape it mirrors) — no dependency on PR-2's TenantStore.Design notes
Tenant.key, never a localized display-name string — a display name can be renamed or shown in a different locale; the key can't.ThemeManager/ThemeDefinitiontake primitives (key/colorHex/palette dictionaries/URL strings), notCore'sTenantstruct — the Theme target has no build dependency onCore, and this avoids adding one. Callers that already import both (RouteController, in a later PR) do the unpacking.Not included in this PR
applyTheme(...)yet.Testing
Themetarget buildsThemeTestspass (ThemeColorSetTests, 5 cases covering palette derivation/override/light-dark fallback)