Aman-Mittal opened a new pull request, #437:
URL: https://github.com/apache/fineract-backoffice-ui/pull/437
Closes #436 for the 1.0 scope.
## Why
A deployment that wants its own colours, logo, product name or menu
currently has to edit files
upstream also edits, so it becomes a fork that conflicts on every release.
This gives it a file
upstream never writes instead:
```dockerfile
FROM apache/fineract-backoffice-ui:1.0.0
COPY branding/ /usr/share/nginx/html/branding/
```
Upgrading is then a version bump. No rebase, no merge, no shared file to
conflict on.
Configuration resolves in layers, last wins:
| | Layer | Owner |
| - | - | - |
| L0 | `DEFAULT_CONFIG` compiled in | upstream |
| L1 | `/config.json` shipped in the image | upstream |
| L2 | `/config.json` rendered by `deploy/entrypoint.sh` | operator |
| L3 | **`/branding/config.json`** | **the deployment** |
| L4 | the user's stored preferences | end user |
L3 is gitignored, 404-tolerant and deep-merged, and
`check-branding-path.mjs` fails the build if
anything is ever committed there — so the promise is enforced rather than
documented.
## Three silent failures fixed on the way
**`labelKey` was not an identity.** `NavOverrides.hidden` matched on
`labelKey`, a field upstream
renames freely — the tree already mixes `nav.dashboard`, `SIDEBAR.SEARCH`,
`LOAN_ACCOUNT_LOCK.TITLE`
and the literal `'Campaigns'`. After a rename the override stopped matching
and the menu a
deployment meant to suppress reappeared in production with no error
anywhere. Dividers are
`{ labelKey: '' }`, so `"hidden": [""]` removed every divider at once.
Every entry now carries a stable `id`, and `check-nav-ids.mjs` holds them
present, unique and
stable. Ids are group-qualified because `/admin/wc-cob-tools` is
cross-listed under both Working
Capital and Admin, so a route-derived id would collide for two deliberate
entries.
*This is why the PR is worth landing before the tag:* re-keying costs
nothing today and becomes a
breaking change to a documented config format the day 1.0.0 ships.
**The entrypoint overwrote deployment config.** `deploy/entrypoint.sh`
writes `config.json` whole,
and its comment claimed "every key now has a default here… no key can go
missing". The heredoc
emits five keys and omits `allowedApiOrigins`, `institutionFeatures` and
`nav`, all added to
`AppConfig` afterwards — so a deployment that baked any of those into the
image lost them at
container start, `allowedApiOrigins` included, which backs a security
control.
**The released image shipped a federation host with no remote.**
`deploy/Dockerfile` builds only
the default project and never copies `fineract-mfe`; `src/main.ts` declared
the remote
unconditionally; and `nginx.conf.template` answered the missing
`/remoteEntry.json` from
`try_files … /index.html` with a `200`. `initFederation` therefore parsed
HTML as JSON on every
page load. The rejection was caught so the app still booted, but every
production session opened
with an error in the console. `main.ts` now probes before declaring the
remote, and nginx returns a
real 404 for paths that are fetched rather than navigated to.
## What a deployment can set
- **`branding`** — app name, logo, favicon and an allow-listed set of design
tokens, applied as CSS
custom properties.
- **`nav`** — `hidden`, per-entry `overrides` (rename / re-icon / reorder /
re-parent) and added
`items`, all keyed on `id` and passing the same permission and feature
gates as built-in entries.
- **`branding/i18n/{lang}.json`** — merged over the shipped catalogue via
the `setTranslation(…, merge)`
already on the `I18N` adapter contract.
`DOCS/CUSTOMIZATION.md` documents it; `public/schema/config.schema.json`
gives editors autocomplete
and validation; `DOCS/examples/branding-config.example.json` is a worked
example that CI resolves
against the live tree on every PR.
## Two things worth a reviewer's eye
**`_ionic-theme.scss` referenced the palette by hex, not by token.**
Branding would have recoloured
the app chrome and left every Ionic component on the shipped blue. It now
reads the tokens, and
`BrandingService` derives the `-rgb`, `-shade`, `-tint` and `-contrast`
companions, which CSS cannot.
**The contrast floor applies to two tokens, not all of them.**
`secondary-color` and
`primary-strong` must clear 4.5:1 against white, because the stylesheets
pair them with a literal
`#fff` and there is no label variable to flip. Every other colour derives
its label from the fill —
white and black are the extremes of the WCAG formula and the better of the
two bottoms out at
**4.58:1**, so no colour can fail and a floor there would be unreachable
code. That is what makes a
lighter accent usable in dark mode.
## Deliberately out of scope
Mounting a compiled remote from configuration, and the plugin host API it
needs. A host contract
published in 1.0.0 is a compatibility obligation for the life of the major
version and has had no
external consumer to validate it. `kind: "external"` covers side-by-side
systems in the meantime.
Iframe embedding, external asset origins and a remote `brandingUrl` are also
deferred: each needs a
CSP directive loosened, and each has a same-origin form shipping here.
Notably, everything deferred is developer-tier — nothing a non-developer
could have used.
## Constraints held
- **No new dependency.** Nothing added to `package.json` bar three scripts.
- **The deployed CSP is unchanged.** Assets are same-origin; external nav
entries are plain links,
which the policy does not govern.
- **No backend involved.** The overlay is a static file; nothing here calls
Fineract.
- **A no-op when absent**, which is the state every existing deployment is
in.
Also migrates off `APP_INITIALIZER` and `provideAnimationsAsync`, both
deprecated. Nothing declares
`@angular/animations` triggers and Ionic drives its own transitions.
## Verification
| | |
| - | - |
| Karma | 826 passed |
| Vitest | 527 passed (19 new, `nav-composition.test.ts`) |
| Playwright `mocked` | 18 new in `deployment-customization.spec.ts` |
| lint / format / typecheck (app + e2e) | clean |
| `check:nav-ids`, `check:branding-path`, `check:reference-downstream` |
new, wired into CI |
| `i18n:check`, `check:icons`, `check:route-permissions`,
`check:test-runner` | pass |
`npm run branding:demo` mounts the worked example into `public/branding/` so
the whole mechanism
can be seen running locally; `-- --clean` removes it.
Three new JSON files are added to `.rat-excludes` — JSON admits no comments,
and all three are data
rather than creative work.
## Not included
The responsive/mobile track from #436. It is independent of this work and
shares only the token
allow-list.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]