Aman-Mittal opened a new issue, #530:
URL: https://github.com/apache/fineract-backoffice-ui/issues/530

   A proposal for discussion, not a change. Nothing here has been implemented.
   
   We want this application to be future-proof and customizable. Those are one 
question seen from two angles: **how much of the UI this codebase owns versus a 
vendor.** Every piece the app owns is a piece a deployment can change, and a 
piece a migration would not have to touch.
   
   This issue measures where that line currently falls. The measurement 
produced one conclusion I did not expect, and it argues against the remedy 
people normally reach for.
   
   ## First, a correction: the number we point at is the wrong number
   
   `npm run ga:check` reports:
   
   ```
   FAIL  Adapter boundary migration backlog (advisory)
           323 call sites still reach past the boundary.
   ```
   
   That reads like Ionic debt. It is not. Counted straight out of 
`eslint-suppressions.json` — the file the gate reads:
   
   | Rule | Count |
   |---|---|
   | `no-restricted-imports` | 308 |
   | `no-restricted-globals` | 15 |
   | `no-restricted-properties` | 0 |
   
   Classifying the 308 by what the offending files import: **285 are 
unambiguously `@ngx-translate` only, at most 23 (7.1%) could relate to Ionic at 
all, and zero are Ionic components** — because `eslint.config.js` deliberately 
does not restrict them.
   
   So the advisory gate is tracking an **i18n migration backlog**, and it is 
working: ADR-0003 was written when it stood at 435. Worth knowing before anyone 
spends a sprint on the wrong problem.
   
   ## What the coupling actually is
   
   `5,424` `<ion-*>` element occurrences, 34 distinct elements, across 296 
files. 299 files import `@ionic/angular/standalone` — about 47% of hand-written 
`.ts` under `src/app`.
   
   **Considerably cheaper than that suggests.** There is **no Ionic app 
shell**: no `ion-app`, `ion-content`, `ion-router-outlet`, `ion-menu`, 
`ion-tabs`, `ion-header`, `ion-toolbar`. The layout is hand-rolled in 
`src/app/layout/`. That removes the most invasive category outright. Also: no 
`IonicModule`, no Ionic lifecycle hooks, zero shadow-root reaching in `src/`, 
only three custom event types across 69 bindings, and zero uses of Ionic CSS 
utility classes.
   
   Roughly **62% of occurrences are cosmetic** — `ion-card` family (782), 
`ion-button` (646), `ion-icon` (471), grid (179), `ion-spinner` (137). 
Element-and-CSS swaps.
   
   The cost concentrates in five places:
   
   | Area | Size | Why it is hard |
   |---|---|---|
   | Form controls | ~770 `ngModel` bindings over 6 element types | **Zero 
custom `ControlValueAccessor`s exist** — we rely entirely on Ionic's. 
(`compareWith` is used in 0 files, so at least there are no object-valued 
selects with custom equality.) |
   | Date picker | 86 × (`ion-datetime-button` + 
`ion-modal[keepContentsMounted]` + `ion-datetime`) | An Ionic invention with no 
equivalent elsewhere; each is a hand rewrite. |
   | `ion-select` | 240 | Renders as `button[aria-haspopup="dialog"]`, options 
as `role="radio"`, and folds the current value into the accessible name. |
   | Tabs | `ion-segment` 16 + `ion-segment-button` 89 | Supplies `role="tab"`, 
`aria-selected`, roving focus. Re-implemented, not restyled. |
   | E2E locators and a11y baselines | ~400 lines of helpers, 39 of 52 specs | 
See below — this is the finding. |
   
   ## The finding: the obvious remedy does not address the dominant cost
   
   The instinct is to wrap `<ion-*>` behind app-owned components. That protects 
**source files**. But the source is the cheap half.
   
   | | Exposure | Why |
   |---|---|---|
   | Unit tests | **15–20% of 237 files**, mostly a one-line import swap | 
ADR-0003's boundary worked. 46 files route through `provideFakeAdapters` and 
are structurally immune; only 9 assert on `ion-*` DOM. |
   | Source | 296 files, but ~62% of occurrences cosmetic | Mechanical. |
   | **E2E** | **39 of 52 specs (75%)**, and **16 of those bypass the helper 
layer entirely** | Locators assert against rendered DOM. **No source-side 
abstraction changes this.** |
   | A11y baselines | All 9 entries in `e2e/accessibility.spec.ts` | Waivers 
for Ionic's own defects, keyed on `#ion-sel-*` internal ids. Re-derived by 
running axe, not translated. |
   
   `e2e/utils/select-in-dialog.ts`, `select-option.ts` and `ionic-locators.ts` 
are roughly 400 lines whose comments are a failure log — shadow-DOM name 
folding, `reflect:false` disabled state, options as radios, overlay class 
taxonomy, pointer-event interception. That knowledge gets re-derived against a 
new library, not ported.
   
   **So a full design-system layer would be the largest possible piece of work 
and would leave the largest cost untouched.**
   
   ## What the last migration actually cost us
   
   This is not hypothetical. The codebase already migrated Angular Material → 
Ionic, and the residue is still here:
   
   - **5 dead CSS rules** target `mat-form-field`, an element that exists 
nowhere — in `external-events`, `loan-schedule-modify`, `loans-point-in-time`, 
`interest-rate-chart-slabs` and `global-search`.
   - **11 files hand-track selection state the previous library owned**, each 
carrying a variant of the comment "mat-tab-group tracked this internally, 
ion-segment does not".
   - `core/services/navigation-config.service.ts:72` still documents the nav 
`icon` field as a **"Material icon name"**, while 
`core/services/config.service.ts:40` correctly calls it an ionicon that must be 
registered in `core/icons.ts`. Same field a deployment sets; only one is true.
   - Stale `mat-select` and `.tab-group` selectors in the guided tour pointed 
at nothing until #526 — a live, user-visible bug caused by an old migration's 
leftovers.
   
   And for calibration: `scripts/codemod-aria-labels.mjs` records that an Ionic 
**7 → 8 label change alone touched 132 files**. A whole-library swap is 
strictly larger.
   
   ## The customizability half
   
   The direction is already right — app tokens feed Ionic, never the reverse — 
but the vendor still caps what a deployment can change:
   
   - A deployment can retint **16 tokens** (`BRANDABLE_TOKENS`); anything else 
is refused with "Not a brandable token".
   - Ionic's **20-step `--ion-color-step-50` … `-950` neutral scale is 
hard-coded** in `src/styles/_ionic-theme.scss`. It drives borders, dividers, 
placeholders and disabled states — a large share of the visible surface — and 
**no deployment can reach it.** That is a vendor-shaped hole in the 
customization contract rather than a decision anyone made.
   - `BrandingService` computes Ionic's `-rgb`/`-shade`/`-tint`/`-contrast` 
companions at runtime because CSS cannot derive them. That code exists only to 
feed Ionic.
   - Nothing prompts an author to consider a new theme token for the 
allow-list. `--guidance-highlight-color`, added this week, is not on it.
   
   ## Proposal
   
   In priority order, and deliberately modest:
   
   1. **Bring the 16 e2e specs that bypass the helper layer through it.** This 
is the only step that reduces the dominant cost, and it improves the suite 
today whether or not a swap ever happens.
   2. **Adopt CVA-carrying wrappers for the six form element types — when those 
files are next touched, not as a campaign.** Keeping the current selectors 
means ~770 bindings move without editing 296 templates. This is the one 
abstraction that would pay for itself.
   3. **Widen `BRANDABLE_TOKENS`** and add a note that a new theme token should 
be considered for it. Cheap, and independent of any migration.
   4. **Leave cosmetic elements alone.** They are a find-and-replace in any 
future swap.
   5. **Do not build a design-system layer.** Largest cost, smallest return, 
and it misses the e2e half entirely.
   6. Clear the migration residue above — five dead rules and one contradictory 
doc comment.
   
   The Ionic neutral-scale gap is real but not cheap; it should be sized 
separately rather than lumped in with (3).
   
   ## What supersedes what
   
   `DOCS/adr/0003-adapter-boundary.md` explicitly excluded `<ion-*>` 
components, estimating "250 files" and judging the risk "rarer and more visible 
than the ones above" — while noting the boundary "is built so this can be added 
later". That call was made without a measurement. This issue supplies one, and 
the measurement broadly **supports** the original decision while sharpening it: 
the exclusion was right, but for a better reason than was given, and the real 
exposure is in the e2e layer rather than the component tree.
   
   ADR-0003's own figures are stale — it cites 435 where the tree now has 323.
   
   If there is appetite, the natural home for this is a new ADR recording the 
measurement, the decision and the trigger that would change it. I have not 
written one, and I would rather agree the direction here first.
   
   ## How every figure above was produced
   
   ```
   # the backlog, and what it consists of
   node -e "…"                                    # parse 
eslint-suppressions.json, classify by import
   # element counts
   grep -rhoE "<ion-[a-z-]+" src/app --include=*.ts | sort | uniq -c | sort -rn
   # import surface
   grep -rl "@ionic/angular/standalone" src/app --include=*.ts | wc -l
   # form bindings, CVAs, compareWith
   grep -rn "ControlValueAccessor\|compareWith" src/app --include=*.ts
   # e2e coupling
   grep -rl "ion-" e2e/*.spec.ts | wc -l
   # brandable tokens
   grep -n "BRANDABLE_TOKENS" src/app/core/services/branding.service.ts
   ```
   
   Measured against `main` at `55abe539`.
   
   ## Stated limits of this measurement
   
   - The a11y mechanism behind `ion-label position="stacked"` (702 uses) was 
**not verified** — whether it emits a real label association or relies on 
shadow-DOM slotting changes whether a naive replacement is a regression or an 
improvement.
   - Whether all 30 `provideIonicTesting` call sites genuinely need the 
provider was **not verified**; that needs a run with it removed.
   - The e2e proportion is given as a range (60–75%) because it depends on 
whether a replacement uses shadow DOM, which is undecided. Narrowing it further 
would be false precision.
   


-- 
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