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]

Reply via email to