Repository navigation
Put a boundary around the generated API client, and catch untranslated template text - #651
Conversation
E2E — mocked backend🎭 E2E Tests✅ All green — 409 passed · 0 failed · 1 skipped · 1 flaky, across 38 spec files in 5m 51s.
|
| Spec | ✅ | ❌ | ⏭️ | 🔒 | Time |
|---|---|---|---|---|---|
| accessibility.spec.ts | 5 | 0 | 0 | 0 | 33.1s |
| all-functions-read-shortcut.spec.ts | 5 | 0 | 0 | 0 | 10.5s |
| branded-deployment.spec.ts | 9 | 0 | 0 | 0 | 18.8s |
| breadcrumb.spec.ts | 2 | 0 | 0 | 0 | 6.3s |
| client-date-picker-revisit.spec.ts | 1 | 0 | 0 | 0 | 6.7s |
| client-servicing-gaps.spec.ts | 14 | 0 | 0 | 0 | 1m 1s |
| client.spec.ts | 1 | 0 | 0 | 0 | 3.6s |
| collateral-currency-search.spec.ts | 5 | 0 | 0 | 0 | 17.1s |
| date-picker-revisit.spec.ts | 6 | 0 | 0 | 0 | 29.3s |
| deployment-customization.spec.ts | 19 | 0 | 0 | 0 | 35.6s |
| e2e-journey.spec.ts | 81 | 0 | 0 | 0 | 2m 30s |
| feature-coverage.spec.ts | 49 | 0 | 0 | 0 | 1m 21s |
| functional-coverage.spec.ts | 42 | 0 | 0 | 0 | 1m 30s |
| global-search-nav-shortcuts.spec.ts | 4 | 0 | 0 | 0 | 7.9s |
| global-search-navigation.spec.ts | 8 | 0 | 0 | 0 | 17.9s |
| group-detail.spec.ts | 8 | 0 | 0 | 0 | 24.4s |
| guidance-tour.spec.ts | 5 | 0 | 1 | 0 | 10.8s |
| keyboard-shortcuts.spec.ts | 5 | 0 | 0 | 0 | 8.3s |
| list-load-failure.spec.ts | 5 | 0 | 0 | 0 | 13.9s |
| list-pagination.spec.ts | 13 | 0 | 0 | 0 | 41.1s |
| loan-delinquency-actions.spec.ts | 11 | 0 | 0 | 0 | 38.7s |
| loan-portfolio-summary.spec.ts | 4 | 0 | 0 | 0 | 11.9s |
| loan-product-down-payment.spec.ts | 13 | 0 | 0 | 0 | 57.3s |
| charge-refund.spec.ts | 1 | 0 | 0 | 0 | 5.2s |
| client-empty-account-cta.spec.ts | 1 | 0 | 0 | 0 | 4.3s |
| client-load-error.spec.ts | 2 | 0 | 0 | 0 | 5.3s |
| contract-termination.spec.ts | 1 | 0 | 0 | 0 | 6.2s |
| loan-schedule-type-gating.spec.ts | 6 | 0 | 0 | 0 | 15.8s |
| loan-servicing-gaps.spec.ts | 18 | 0 | 0 | 0 | 56.1s |
| logout-reason-notice.spec.ts | 4 | 0 | 0 | 0 | 2.9s |
| oidc-login.spec.ts | 8 | 0 | 0 | 0 | 9.9s |
| rbac-feature-flag.spec.ts | 11 | 0 | 0 | 0 | 16.2s |
| rbac-route-protection.spec.ts | 18 | 0 | 0 | 0 | 1m 25s |
| report-enhancements.spec.ts | 2 | 0 | 0 | 0 | 6.4s |
| reporting.spec.ts | 2 | 0 | 0 | 0 | 3.3s |
| scheduler-jobs.spec.ts | 3 | 0 | 0 | 0 | 8.4s |
| two-factor-authentication.spec.ts | 10 | 0 | 0 | 0 | 15.2s |
| working-capital-loan-actions.spec.ts | 7 | 0 | 0 | 0 | 25.8s |
All 410 tests — click to expand
accessibility.spec.ts
- ✅ Runtime accessibility › client list has no blocking violations —
4.0s - ✅ Runtime accessibility › client list links meet color contrast in dark mode —
4.5s - ✅ Runtime accessibility › client form has no blocking violations —
5.1s - ✅ Runtime accessibility › banner and dashboard content have no blocking violations —
14.8s - ✅ Runtime accessibility › create office dialog has no blocking violations —
4.6s
all-functions-read-shortcut.spec.ts
- ✅ ALL_FUNCTIONS_READ shortcut › grants read access to the Clients list without any explicit READ_CLIENT permission —
2.0s - ✅ ALL_FUNCTIONS_READ shortcut › does not grant the Create Client action —
2.4s - ✅ ALL_FUNCTIONS_READ shortcut › does not grant Create Client even combined with an explicit READ_CLIENT permission —
2.4s - ✅ ALL_FUNCTIONS_READ shortcut › a user with the real CREATE_CLIENT permission still sees the button —
1.7s - ✅ ALL_FUNCTIONS_READ shortcut › ALL_FUNCTIONS (full superuser) still sees the Create Client button —
2.0s
branded-deployment.spec.ts
- ✅ a fully branded deployment › wears the institution’s name and mark, not Fineract’s —
1.7s - ✅ a fully branded deployment › carries the institution’s palette into its own chrome and into Ionic —
1.7s - ✅ a fully branded deployment › drops the sections the institution does not run —
2.1s - ✅ a fully branded deployment › renames and reorders entries to the institution’s vocabulary —
1.8s - ✅ a fully branded deployment › offers the institution’s own systems beside Fineract’s screens —
2.1s - ✅ a fully branded deployment › restates the labels the institution words differently —
2.1s - ✅ a fully branded deployment › keeps a readable palette in dark mode, with a label colour to match —
1.7s - ✅ a fully branded deployment › does not leak a light colour into the dark palette —
2.1s - ✅ a fully branded deployment › reports no configuration defects for the published example —
3.4s
breadcrumb.spec.ts
- ✅ a single-crumb page shows no breadcrumb trail —
2.3s - ✅ a nested page shows a trail, and the ancestor crumb navigates back —
4.0s
client-date-picker-revisit.spec.ts
- ✅ Client form date pickers › stay usable on a second visit to the create form —
6.7s
client-servicing-gaps.spec.ts
- ✅ Client transfers › proposing a transfer sends the destination, the date and the format that parses it —
4.9s - ✅ Client transfers › a one-step transfer omits the date the platform refuses —
4.5s - ✅ Client transfers › the client’s own office is not offered as a destination —
4.3s - ✅ Client transfers › accepting a transfer sends a note and nothing else —
4.0s - ✅ Client transfers › accepting with no note sends an empty body —
4.7s - ✅ Client transfers › rejecting a transfer is offered while one is pending —
4.9s - ✅ Client transfers › withdrawing is offered from on hold, which is the only way back —
4.2s - ✅ Client transfers › answers to a transfer are not offered when none is pending —
4.1s - ✅ Client staff and default savings account › assigning a member of staff sends the staff id —
4.8s - ✅ Client staff and default savings account › unassigning echoes the current holder back, which the platform requires —
4.3s - ✅ Client staff and default savings account › unassigning is not offered when nobody is assigned —
3.9s - ✅ Client staff and default savings account › the assigned officer is shown on the header —
3.8s - ✅ Client staff and default savings account › setting the default savings account sends the account id —
4.9s - ✅ Undoing a rejection › posts the command name the platform recognises, not the menu identifier —
3.9s
client.spec.ts
- ✅ Client Management › should create a new client —
3.6s
collateral-currency-search.spec.ts
- ✅ Collateral product currency — searchable select (#626) › the field is closed with no option list until it is opened —
3.5s - ✅ Collateral product currency — searchable select (#626) › typing narrows a long, alphabetical list to the matching currency —
3.6s - ✅ Collateral product currency — searchable select (#626) › matches by currency code as well as by name —
3.1s - ✅ Collateral product currency — searchable select (#626) › shows a "no results" message rather than an empty panel —
3.4s - ✅ Collateral product currency — searchable select (#626) › Cancel closes the picker without changing the selection —
3.5s
date-picker-revisit.spec.ts
- ✅ Date pickers on revisited forms › office form keeps its pickers usable on every visit —
4.3s - ✅ Date pickers on revisited forms › staff form keeps its pickers usable on every visit —
4.7s - ✅ Date pickers on revisited forms › holiday form keeps its pickers usable on every visit —
4.7s - ✅ Date pickers on revisited forms › teller form keeps its pickers usable on every visit —
4.4s - ✅ Date pickers on revisited forms › accounting closure form keeps its pickers usable on every visit —
4.5s - ✅ Date pickers on revisited forms › floating rate form keeps its pickers usable on every visit —
6.7s(retried 1×)
deployment-customization.spec.ts
- ✅ with no overlay mounted › boots, keeps the shipped branding, and shows the full menu —
2.0s - ✅ with no overlay mounted › does not ask for an overlay this deployment has not declared —
1.7s - ✅ with no overlay mounted › logs no federation parse error on load —
1.6s - ✅ branding › applies colour and density tokens to the document —
1.7s - ✅ branding › recolours Ionic through the same tokens —
2.1s - ✅ branding › renames the product in the header and the tab title —
3.4s - ✅ branding › serves the deployment logo and names it after the deployment —
1.6s - ✅ branding › refuses a fill that cannot carry the white text baked into the stylesheet —
1.7s - ✅ branding › accepts a light accent and derives a dark label for it —
2.0s - ✅ branding › ignores a token outside the published allow-list, and says so —
1.6s - ✅ navigation › hides an entry named by its id —
1.7s - ✅ navigation › does not hide an entry named by its labelKey —
2.1s - ✅ navigation › renames and reorders entries —
1.7s - ✅ navigation › adds an external entry that opens in a new tab —
1.7s - ✅ navigation › nests an added entry under an existing group —
1.6s - ✅ navigation › gates an added entry on permissions like any built-in one —
1.6s - ✅ navigation › drops an external entry with no usable url rather than rendering a dead link —
2.3s - ✅ layering › merges over config.json instead of replacing it —
2.0s - ✅ layering › a malformed overlay leaves the application usable —
1.6s
e2e-journey.spec.ts
- ✅ E2E: Login Flow › should display login page with all required fields —
1.4s - ✅ E2E: Login Flow › should have Sign In button disabled when fields are empty —
1.3s - ✅ E2E: Login Flow › should enable Sign In when credentials are entered —
1.4s - ✅ E2E: Login Flow › should redirect to dashboard after successful login —
2.1s - ✅ E2E: Login Flow › should display language selector on login page —
1.4s - ✅ E2E: Login Flow › should display API endpoint selector with value —
1.3s - ✅ E2E: Dashboard › should display dashboard summary cards —
2.1s - ✅ E2E: Dashboard › should display system health as Online —
1.7s - ✅ E2E: Dashboard › should display pending approvals section —
1.6s - ✅ E2E: Dashboard › should display loan and savings status distribution charts —
2.1s - ✅ E2E: Dashboard › should display system operational status —
1.7s - ✅ E2E: Dashboard › should have header with business date, render time, and logout —
1.6s - ✅ E2E: Dashboard › should have sidebar toggle button —
1.6s - ✅ E2E: Dashboard › should have global search bar in header —
1.7s - ✅ E2E: Dashboard › should have Guide button in header —
2.1s - ✅ E2E: Dashboard › should have dark mode toggle button —
2.0s - ✅ E2E: Sidebar Navigation › should have all main navigation links —
1.7s - ✅ E2E: Sidebar Navigation › should have Products section with sub-links —
1.8s - ✅ E2E: Sidebar Navigation › should have Transfers section with sub-links —
1.7s - ✅ E2E: Sidebar Navigation › should have Accounting section with sub-links —
2.4s - ✅ E2E: Sidebar Navigation › should have Security section —
1.7s - ✅ E2E: Sidebar Navigation › should have Settings section —
1.8s - ✅ E2E: Sidebar Navigation › should have Organization section —
1.7s - ✅ E2E: Sidebar Navigation › should have System section —
2.2s - ✅ E2E: Sidebar Navigation › should navigate to all major pages via sidebar —
4.5s - ✅ E2E: Offices › should display offices list with table —
2.0s - ✅ E2E: Offices › should have Create Office button —
1.9s - ✅ E2E: Offices › should navigate to office create form —
2.1s - ✅ E2E: Offices › should validate office create form - save disabled when empty —
2.1s - ✅ E2E: Offices › should fill office create form and enable save —
3.1s - ✅ E2E: Offices › should cancel office creation and return to list —
2.7s - ✅ E2E: Offices › should have search input on offices page —
1.9s - ✅ E2E: Offices › should show edit button for each office —
2.0s - ✅ E2E: Offices › should have pagination controls —
2.0s - ✅ E2E: Clients › should display clients list page —
2.4s - ✅ E2E: Clients › should display client data in table —
2.1s - ✅ E2E: Clients › should show ACTIVE status badge —
1.7s - ✅ E2E: Clients › should have Create Client button —
1.5s - ✅ E2E: Clients › should show empty state when no clients exist —
1.5s - ✅ E2E: Clients › should have search input and status filter —
1.5s - ✅ E2E: Clients › should have pagination controls —
1.5s - ✅ E2E: Client Creation › should navigate to client create form —
1.9s - ✅ E2E: Client Creation › should display all required client form fields, across the wizard steps —
2.3s - ✅ E2E: Client Creation › should display optional client form fields, across the wizard steps —
2.3s - ✅ E2E: Client Creation › should have Next disabled until the current step is valid —
2.2s - ✅ E2E: Client Creation › should populate office dropdown from API —
1.9s - ✅ E2E: Client Creation › should fill client form and enable save on the last step —
2.9s - ✅ E2E: Client Creation › should submit client and redirect to clients list —
3.5s - ✅ E2E: Client Creation › should cancel client creation and return to clients list —
1.7s - ✅ E2E: Client Creation › should have add office quick-action button —
1.6s - ✅ E2E: Client Creation › should have help icons for form fields —
1.5s - ✅ E2E: Client Creation › should have calendar buttons for date fields —
2.3s - ✅ E2E: Client Creation › should have pre-filled dates in Submitted On and Activation Date —
2.2s - ✅ E2E: Client Creation › should have Active checkbox checked by default —
1.9s - ✅ E2E: Client Creation › should handle Entity legal form showing fullname field —
2.7s - ✅ E2E: Loan Products Page › should display loan products list page —
1.6s - ✅ E2E: Loan Products Page › should have Create Loan Product button —
1.5s - ✅ E2E: Loan Products Page › should display table with correct headers —
1.5s - ✅ E2E: Loan Products Page › should show empty state when no products exist —
1.5s - ✅ E2E: Loan Products Page › should have search input —
1.7s - ✅ E2E: Loan Products Page › should navigate to loan product create form —
1.7s - ✅ E2E: Savings Products Page › should display savings products list page —
1.6s - ✅ E2E: Other Products Pages › Fixed Deposit Products page loads —
1.6s - ✅ E2E: Other Products Pages › Recurring Deposit Products page loads —
1.5s - ✅ E2E: Other Products Pages › Share Products page loads —
1.5s - ✅ E2E: Other Products Pages › Tax Components page loads —
1.7s - ✅ E2E: Other Products Pages › Tax Groups page loads —
1.7s - ✅ E2E: Other Products Pages › Floating Rates page loads —
1.4s - ✅ E2E: Loans Portfolio › should display loans portfolio page —
1.5s - ✅ E2E: Loans Portfolio › should have Create Loan Account button —
1.6s - ✅ E2E: Loans Portfolio › should display table with correct headers —
2.0s - ✅ E2E: Loans Portfolio › should show empty state when no loans exist —
1.7s - ✅ E2E: Loans Portfolio › should have search input —
1.8s - ✅ E2E: Loans Portfolio › should have pagination controls —
1.5s - ✅ E2E: Header & Global Elements › should display Fineract logo and title in header —
1.4s - ✅ E2E: Header & Global Elements › should display logged-in username —
1.3s - ✅ E2E: Header & Global Elements › should display office name for user —
1.4s - ✅ E2E: Header & Global Elements › should have Logout button —
1.6s - ✅ E2E: Header & Global Elements › should have language selector in header —
1.4s - ✅ E2E: Header & Global Elements › should display render time —
1.4s - ✅ E2E: Header & Global Elements › should display business date —
1.3s
feature-coverage.spec.ts
- ✅ Navigation & Sidebar › all main navigation links are visible in sidebar —
1.7s - ✅ Navigation & Sidebar › sidebar navigation routes to correct pages —
2.3s - ✅ Navigation & Sidebar › navigating to /accounting redirects to /accounting/chart-of-accounts —
2.6s - ✅ Navigation & Sidebar › navigating to /organization redirects to /organization/offices —
2.4s - ✅ Navigation & Sidebar › navigating to /system redirects to /system/data-tables —
2.3s - ✅ Navigation & Sidebar › an unknown route keeps its URL and offers recovery actions —
2.6s - ✅ Navigation & Sidebar › header shows logged-in user info —
1.4s - ✅ Navigation & Sidebar › help tour button is visible and interactive —
1.4s - ✅ Navigation & Sidebar › logout button is visible —
1.3s - ✅ Navigation & Sidebar › language selector is present —
1.4s - ✅ Dashboard › dashboard shows key metrics cards —
1.5s - ✅ Dashboard › dashboard shows pending approvals section —
1.3s - ✅ Dashboard › dashboard shows loan and savings status distribution —
1.3s - ✅ Dashboard › dashboard shows system operational status —
1.4s - ✅ Groups › groups page renders with title —
1.5s - ✅ Groups › groups page has create button —
1.6s - ✅ Centers › centers page renders with title —
1.6s - ✅ Centers › centers page has create button —
1.5s - ✅ Organization › offices page loads with list —
1.6s - ✅ Organization › staff page loads —
1.4s - ✅ Organization › funds page loads —
1.8s - ✅ Organization › payment types page loads —
1.6s - ✅ Accounting › chart of accounts loads —
1.5s - ✅ Accounting › journal entries page loads —
1.7s - ✅ Accounting › accounting closures loads —
1.5s - ✅ Accounting › accounting rules loads —
1.7s - ✅ Accounting › financial activity mappings loads —
1.5s - ✅ Accounting › charges page loads —
1.6s - ✅ Security › users page loads —
1.5s - ✅ Security › roles page loads —
1.6s - ✅ Security › audit logs page loads —
1.8s - ✅ Security › leftover audit trails path lands on audits, not the dashboard —
2.4s - ✅ Products - Loan & Savings › loan products page loads —
1.6s - ✅ Products - Loan & Savings › savings products page loads —
1.7s - ✅ Products - Loan & Savings › tax components page loads —
1.6s - ✅ Products - Loan & Savings › tax groups page loads —
1.6s - ✅ Products - Loan & Savings › floating rates page loads —
1.5s - ✅ Products - Loan & Savings › savings accounts page loads —
1.5s - ✅ Settings › global configurations page loads —
1.5s - ✅ Settings › holidays page loads —
1.6s - ✅ Settings › working days page loads —
1.4s - ✅ System › data tables page loads —
1.5s - ✅ System › bulk import page loads —
1.5s - ✅ System › delinquency page loads —
1.6s - ✅ Tellers › tellers page loads with create button —
1.6s - ✅ Transfers › account transfer page loads —
1.5s - ✅ Transfers › standing instructions page loads —
1.5s - ✅ Transfers › SI history page loads —
1.4s - ✅ Embedded Fintech › asset owners page loads —
1.6s
functional-coverage.spec.ts
- ✅ Client CRUD Workflow › should complete full client creation flow — Person —
3.2s - ✅ Client CRUD Workflow › should complete full client creation flow — Entity —
2.8s - ✅ Client CRUD Workflow › should cancel client creation and return to list —
1.8s - ✅ Client CRUD Workflow › should disable office and legal form in edit mode —
2.4s - ✅ Client View & Status Transitions › should display client view with correct details —
2.5s - ✅ Client View & Status Transitions › should show actions menu for pending client (status 100) —
2.5s - ✅ Client View & Status Transitions › should show actions menu for active client (status 300) —
2.4s - ✅ Client View & Status Transitions › should show actions menu for closed client (status 600) —
2.5s - ✅ Client View & Status Transitions › should show New Account menu for active client —
2.4s - ✅ Client View & Status Transitions › should navigate from client view to client list via breadcrumb —
2.9s - ✅ Client View & Status Transitions › should navigate back from client view —
3.3s - ✅ Client View Tabs › should display all expected tabs on client view —
3.4s - ✅ Client View Tabs › should switch between client view tabs —
2.7s - ✅ Search & Filter Interactions › should have search input on clients page and accept input —
2.0s - ✅ Search & Filter Interactions › should have office filter on groups page —
1.7s - ✅ Search & Filter Interactions › should have office filter on loans page —
1.5s - ✅ Search & Filter Interactions › should have search input on charges page —
2.1s - ✅ Navigation Flow Verification › should navigate full client lifecycle: list → create → back → list —
1.9s - ✅ Navigation Flow Verification › should navigate from dashboard to all top-level pages via sidebar —
3.2s - ✅ Navigation Flow Verification › should go from loan products → create → back —
1.9s - ✅ Office CRUD › should validate office create form — save disabled when empty —
1.8s - ✅ Office CRUD › should validate office create form — save enabled when filled —
2.3s - ✅ Office CRUD › should cancel office creation —
2.4s - ✅ Organization Create Flows › staff create form loads —
1.8s - ✅ Organization Create Flows › funds create form loads —
1.7s - ✅ Organization Create Flows › payment types create form loads —
1.7s - ✅ Accounting CRUD › accounting closure create form loads —
1.7s - ✅ Accounting CRUD › accounting rule create form loads —
1.7s - ✅ Accounting CRUD › financial activity mapping create form loads —
2.3s - ✅ Accounting CRUD › charge create form loads —
1.7s - ✅ Accounting CRUD › chart of accounts create form loads —
1.6s - ✅ Settings CRUD › global configurations page has editable toggle —
1.6s - ✅ Settings CRUD › holiday create form loads —
1.8s - ✅ Settings CRUD › working days page has save button —
1.6s - ✅ System CRUD › data table create form loads —
1.9s - ✅ System CRUD › delinquency page has create buttons for ranges and buckets —
1.6s - ✅ Security CRUD › role create form loads —
1.7s - ✅ Security CRUD › audit logs search form has date range —
1.8s - ✅ Security CRUD › audit logs list exports the loaded page as CSV —
1.7s - ✅ Reporting — Run Report Flow › should navigate to report and verify report parameters —
2.4s - ✅ Teller CRUD › teller create form loads —
2.0s - ✅ Create Office Dialog from Client Form › should open create office dialog from client create form —
2.0s
global-search-nav-shortcuts.spec.ts
- ✅ Global search navigation shortcuts › offers a settings page the entity search cannot return —
1.7s - ✅ Global search navigation shortcuts › navigates to the page behind a shortcut —
1.9s - ✅ Global search navigation shortcuts › keeps the results open across the mousedown that starts a click, so a slow render cannot race the dropdown closed —
2.1s - ✅ Global search navigation shortcuts › withholds a shortcut the user has no permission to reach —
2.2s
global-search-navigation.spec.ts
- ✅ Global search navigation › does not query the backend for a single character —
2.5s - ✅ Global search navigation › sends one request for a phrase typed straight through —
2.7s - ✅ Global search navigation › opens the client behind a client result —
1.9s - ✅ Global search navigation › opens the loan behind a loan result —
1.9s - ✅ Global search navigation › opens the savings account behind a savings result —
2.1s - ✅ Global search navigation › clears the query and closes the dropdown after a selection —
1.8s - ✅ Global search navigation › closes the dropdown when focus leaves without a selection —
2.6s - ✅ Global search navigation › shows nothing rather than an empty dropdown when there are no matches —
2.3s
group-detail.spec.ts
- ✅ Group detail › custom field tabs expose the app keyboard and panel contract at 1280px —
3.0s - ✅ Group detail › custom field tabs expose the app keyboard and panel contract at 390px —
3.0s - ✅ Group detail › asks for the associations, and shows every member rather than only the active ones —
2.8s - ✅ Group detail › closes a group with the reason id and a Fineract-formatted date —
3.8s - ✅ Group detail › does not offer a client who is already a member, and associates the one it does —
3.0s - ✅ Group detail › unassigns a committee role by the assignment id, not the role id —
3.0s - ✅ Group detail › offers a retry when the group cannot be loaded —
2.9s - ✅ Group detail › says so when no closure reason has been defined —
3.0s
guidance-tour.spec.ts
- ✅ the guided tour › highlights the System Status card, not the sidebar, on the dashboard tour —
1.9s - ✅ the guided tour › presents the card as a dialog named and described by its own copy —
1.5s - ✅ the guided tour › announces the step counter as a polite live region —
1.8s - ✅ the guided tour › closes on Escape and hands focus back to the Guide button —
1.7s - ✅ the guided tour › names an unmatched route after itself instead of handing it the dashboard tour —
2.5s - ⏭️ the guided tour at a phone viewport › opens from the overflow menu as a full-width bottom sheet —
1.3s
keyboard-shortcuts.spec.ts
- ✅ Alt+C jumps straight to Create Client from anywhere —
1.6s - ✅ Alt+G jumps to Create Group —
2.2s - ✅ Alt+E jumps to Create Center —
1.6s - ✅ Alt+H opens the guidance tour for the current page —
1.4s - ✅ typing in the global search box does not trigger the Create Client shortcut —
1.5s
list-load-failure.spec.ts
- ✅ entity-to-entity mapping › renders the rows the endpoint returns, rather than claiming there are none —
2.9s - ✅ entity-to-entity mapping › shows a failure state, not an empty one, when the payload cannot be read —
2.5s - ✅ entity-to-entity mapping › retries and recovers —
2.6s - ✅ report mailing jobs › reads rows out of the paged envelope the endpoint returns —
3.3s - ✅ report mailing jobs › shows a failure state when the request errors —
2.5s
list-pagination.spec.ts
- ✅ List pagination › advances through pages and back again —
2.8s - ✅ List pagination › jumps to the last page —
3.3s - ✅ List pagination › returns to the first page when a filter changes —
3.1s - ✅ List pagination › keeps search text when paging and shows the v2 account number —
3.0s - ✅ List pagination › redirects the old search page and exposes only one client navigation item —
3.9s - ✅ List pagination › keeps an account-number search broad and disables unsupported Office sorting —
3.9s - ✅ List pagination › returns to the first page when searching —
3.5s - ✅ Profile › renders the signed-in user rather than hanging on a spinner —
3.3s - ✅ Profile › changes the signed-in user password from the profile screen —
3.2s - ✅ Profile › shows an error when the profile cannot be loaded —
2.6s - ✅ List load failure › reports the failure and reloads when the user retries —
3.3s - ✅ List load failure › calendars: reports the failure and reloads when the user retries —
2.6s - ✅ List load failure › accounting rules: reports the failure and reloads when the user retries —
2.5s
loan-delinquency-actions.spec.ts
- ✅ Delinquency pause and resume on a regular loan › shows the dates as dates, and the pause periods under their real fields —
3.7s - ✅ Delinquency pause and resume on a regular loan › pauses delinquency for a period, from the business date —
3.7s - ✅ Delinquency pause and resume on a regular loan › will not take a pause that ends before, or when, it starts —
3.8s - ✅ Delinquency pause and resume on a regular loan › sends nothing when the pause dialog is cancelled —
4.0s - ✅ Delinquency pause and resume on a regular loan › shows the platform’s own reason when it refuses a pause, and stays usable —
3.7s - ✅ Delinquency pause and resume on a regular loan › resumes a pause that is in effect, dated the business date and with no end date —
3.7s - ✅ Delinquency pause and resume on a regular loan › stops offering resume once the pause has been ended —
4.3s - ✅ Delinquency pause and resume on a regular loan › does not offer resume when no pause is in effect —
3.1s - ✅ Delinquency pause and resume on a regular loan › offers neither action without the permission to create a delinquency action —
2.5s - ✅ Delinquency pause and resume on a regular loan › offers the permitted user the actions that permission opens, and no others —
3.0s - ✅ Delinquency pause and resume on a regular loan › offers neither action on a loan that is not active —
3.1s
loan-portfolio-summary.spec.ts
- ✅ Loan Portfolio Summary › searching sends entities: loans and shows the grouped results —
2.7s - ✅ Loan Portfolio Summary › Edit Parameters returns from the results view to the filter form —
3.2s - ✅ Loan Portfolio Summary › checking the outstanding-amount filter sends the between min/max values —
2.7s - ✅ Loan Portfolio Summary › switching to a single-value comparison condition sends one value —
3.3s
loan-product-down-payment.spec.ts
- ✅ Loan product down payment and tranches › down payment is offered only for a progressive product —
3.8s - ✅ Loan product down payment and tranches › the percentage and auto-repayment appear only once down payment is on —
3.6s - ✅ Loan product down payment and tranches › multi-tranche disbursement reveals its tranche count, on either engine —
2.7s - ✅ Loan product down payment and tranches › submits the down payment and tranche settings it was given —
4.0s - ✅ Loan product down payment and tranches › does not send a down payment on a cumulative product —
4.6s - ✅ Loan product down payment and tranches › income recognition is offered only for a progressive product —
3.5s - ✅ Loan product down payment and tranches › the capitalisation details appear only once it is on —
4.1s - ✅ Loan product down payment and tranches › submits the income recognition settings it was given —
3.6s - ✅ Loan product down payment and tranches › does not send income recognition on a cumulative product —
4.5s - ✅ Loan product down payment and tranches › interest recalculation reveals its mandatory settings only once it is on —
2.9s - ✅ Loan product down payment and tranches › the rest interval appears only when the frequency is not the repayment period —
13.8s - ✅ Loan product down payment and tranches › submits the interest recalculation settings it was given —
2.9s - ✅ Loan product down payment and tranches › does not send recalculation settings once it is switched back off —
3.4s
charge-refund.spec.ts
- ✅ refunds the remaining balance of a collected loan charge —
5.2s
client-empty-account-cta.spec.ts
- ✅ empty account tabs offer the matching creation action —
4.3s
client-load-error.spec.ts
- ✅ shows a not-found state and returns to the list, without an error toast —
2.6s - ✅ offers a retry for a transient failure, and recovers on success —
2.7s
contract-termination.spec.ts
- ✅ terminates and undoes a progressive loan contract —
6.2s
loan-schedule-type-gating.spec.ts
- ✅ Loan schedule type gating › a cumulative loan offers neither progressive-only tab, and fetches neither —
2.5s - ✅ Loan schedule type gating › a progressive loan shows only the capabilities it actually has —
2.4s - ✅ Loan schedule type gating › fetches buy-down fees for a progressive loan that has no external id —
3.0s - ✅ Loan schedule type gating › a progressive loan with both capabilities shows both —
2.6s - ✅ Loan schedule type gating › a charged-off loan says so and offers the reversal instead —
2.5s - ✅ Loan schedule type gating › a healthy loan offers charge-off and no marker —
2.8s
loan-servicing-gaps.spec.ts
- ✅ Loan servicing gaps › edits an interest pause through PUT while preserving its existing dates —
2.9s - ✅ Loan servicing gaps › undo approval sends an empty body, without the locale the platform refuses —
3.6s - ✅ Loan servicing gaps › undo approval carries the reason when one was given —
3.9s - ✅ Loan servicing gaps › undo approval is offered only while the loan is approved —
2.5s - ✅ Loan servicing gaps › the delinquency tab shows the summary the loan carries and the tags it fetches —
2.9s - ✅ Loan servicing gaps › the data-only tabs stay hidden when the loan carries nothing for them —
2.6s - ✅ Loan servicing gaps › each data-only tab appears and renders once the loan carries its data —
3.2s - ✅ Loan servicing gaps › disburse to savings sends the date and amount, with the platform date format —
3.3s - ✅ Loan servicing gaps › disburse to savings is offered only on an approved loan —
3.2s - ✅ Loan servicing gaps › unassign loan officer sends the date, and appears only when one is assigned —
3.3s - ✅ Loan servicing gaps › unassign loan officer is hidden on a loan with no officer —
3.3s - ✅ Loan servicing gaps › undo last disbursal appears only on a multi-disbursal loan, and sends an empty body —
3.5s - ✅ Loan servicing gaps › undo last disbursal is hidden on a single-disbursal loan —
2.9s - ✅ Loan servicing gaps › close as rescheduled is offered on an active loan —
2.5s - ✅ Loan servicing gaps › revising the amounts is offered on an approved loan —
3.1s - ✅ Loan servicing gaps › revising the amounts is hidden before approval —
2.9s - ✅ Loan servicing gaps › revising the approved amount sends the new amount and locale —
3.3s - ✅ Loan servicing gaps › revising the available disbursement amount sends the new amount, including zero —
3.2s
logout-reason-notice.spec.ts
- ✅ explains an inactivity sign-out —
1.3s - ✅ explains an expired session, with different wording —
1.2s - ✅ stays silent when the user came to sign in of their own accord —
201ms - ✅ stays silent for a reason it does not recognise, rather than showing a blank notice —
167ms
oidc-login.spec.ts
- ✅ identity-provider route on the login page › a deployment that never set the flag sees the screen it always saw —
1.2s - ✅ identity-provider route on the login page › does not offer the route when the flag is false —
1.2s - ✅ identity-provider route on the login page › does not offer the route when the flag is "true" —
1.2s - ✅ identity-provider route on the login page › does not offer the route when the flag is 1 —
1.3s - ✅ identity-provider route on the login page › offers the route above the password form, which stays in place —
1.2s - ✅ identity-provider route on the login page › says plainly that it is not available yet, and goes nowhere —
1.1s - ✅ identity-provider route on the login page › username and password still sign in when the route is offered —
1.4s - ✅ identity-provider route on the login page › is not offered while a second factor is being asked for —
1.3s
rbac-feature-flag.spec.ts
- ✅ RBAC permission gating (rbacEnabled = true) › a limited user does not see permission-gated nav groups —
1.5s - ✅ RBAC permission gating (rbacEnabled = true) › a user with READ_USER sees only the Security entries that permission covers —
1.3s - ✅ RBAC permission gating (rbacEnabled = true) › the Security group fills in as the user is granted each of its permissions —
1.2s - ✅ RBAC permission gating (rbacEnabled = true) › a superuser (ALL_FUNCTIONS) sees all permission-gated groups —
1.4s - ✅ RBAC institution-feature gating (rbacEnabled = true) › universal institution shows Groups, Centers and Collection Sheet —
1.5s - ✅ RBAC institution-feature gating (rbacEnabled = true) › commercial bank (cb) hides all group-lending features —
2.1s - ✅ RBAC institution-feature gating (rbacEnabled = true) › credit union (cu) shows Groups but hides Centers and Collection Sheet —
1.4s - ✅ rbacEnabled = false › restores the pre-RBAC behaviour of showing everything —
1.5s - ✅ deployment navigation overrides › removes the entries the deployment names by id, leaving the rest alone —
1.4s - ✅ deployment navigation overrides › ignores a labelKey where an id is required —
1.4s - ✅ deployment navigation overrides › removes them even where RBAC is off —
1.5s
rbac-route-protection.spec.ts
- ✅ route authorization › a superuser reaches every sampled route and sees the full navigation —
10.7s - ✅ route authorization › a single-module user is confined to that module —
8.1s - ✅ route authorization › a read-only user gets the lists and not the forms —
9.4s - ✅ route authorization › a user with an empty permission list is refused everywhere and the app still works —
7.8s - ✅ route authorization › a session carrying no permissions field is treated as carrying none —
4.4s - ✅ route authorization › an unrecognised permission code grants nothing —
5.0s - ✅ route authorization › an unauthenticated visitor is asked to sign in, not told they are forbidden —
5.0s - ✅ route authorization › a deployment with RBAC turned off behaves as it did before the guard —
6.5s - ✅ the Access Denied page › says what happened, under a single heading —
2.8s - ✅ the Access Denied page › moves focus to the heading, since the user did not ask to come here —
2.7s - ✅ the Access Denied page › announces itself politely —
3.0s - ✅ the Access Denied page › names the permissions the screen wanted, so the user can ask for them —
3.0s - ✅ the Access Denied page › offers a way back that the user is allowed to take —
2.9s - ✅ the Access Denied page › is reachable by keyboard alone —
3.2s - ✅ permission feedback › a visible nav entry says what the user can do in that module —
1.3s - ✅ permission feedback › a nav entry carries no hint when there is nothing specific to say —
1.3s - ✅ navigation and routes agree › a hidden menu entry is also refused by URL, and a shown one is not —
4.5s - ✅ navigation and routes agree › the create button is withheld from a user whose create route would refuse them —
3.1s
report-enhancements.spec.ts
- ✅ Report Enhancements, Pagination, and Help Tour › should run report, show paginated data, and download CSV —
3.6s - ✅ Report Enhancements, Pagination, and Help Tour › should trigger guidance help tour and navigate steps —
2.9s
reporting.spec.ts
- ✅ Reporting › should display list of reports —
1.6s - ✅ Reporting › should navigate to run report page —
1.8s
scheduler-jobs.spec.ts
- ✅ Scheduler Jobs bulk run › Run Selected is disabled until a job is checked —
2.6s - ✅ Scheduler Jobs bulk run › Select All checks every job, and running confirms then triggers each one —
3.0s - ✅ Scheduler Jobs bulk run › declining the confirmation runs nothing —
2.9s
two-factor-authentication.spec.ts
- ✅ two-step verification › asks for a code and lets the user in once it is accepted —
1.6s - ✅ two-step verification › sends the token on every later request —
1.5s - ✅ two-step verification › keeps the user on the step when the code is wrong, and says why —
1.3s - ✅ two-step verification › accepts a correct code after a wrong one —
1.5s - ✅ two-step verification › offers the choice when there is more than one channel —
1.3s - ✅ two-step verification › says so when the account has no channel configured —
1.3s - ✅ two-step verification › returns to the choice when the code could not be sent —
1.2s - ✅ two-step verification › does not let a half-finished session reach the application —
2.9s - ✅ two-step verification › backing out returns to the password form —
1.2s - ✅ two-step verification › a deployment without a second factor signs in exactly as before —
1.5s
working-capital-loan-actions.spec.ts
- ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › shows delinquency action history and submits a new one —
3.6s - ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › shows breach action history and submits a new one —
4.3s - ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › shows near-breach action history and submits a new one —
3.8s - ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › attaches and detaches a loan originator —
3.2s - ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › adds a new charge and waives an existing one —
3.7s - ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › shows rate-change history, amortization schedule, and delinquency range tags —
2.8s - ✅ Working Capital loan delinquency, breach and near-breach actions, and originators › marks the loan as fraud, applies a discount, and changes the payment rate —
4.5s
Slowest 10 — what the shard counts should be tuned against
| Test | Spec | Time |
|---|---|---|
| Runtime accessibility › banner and dashboard content have no blocking violations | accessibility.spec.ts |
14.8s |
| Loan product down payment and tranches › the rest interval appears only when the frequency is not the repayment period | loan-product-down-payment.spec.ts |
13.8s |
| route authorization › a superuser reaches every sampled route and sees the full navigation | rbac-route-protection.spec.ts |
10.7s |
| route authorization › a read-only user gets the lists and not the forms | rbac-route-protection.spec.ts |
9.4s |
| route authorization › a single-module user is confined to that module | rbac-route-protection.spec.ts |
8.1s |
| route authorization › a user with an empty permission list is refused everywhere and the app still works | rbac-route-protection.spec.ts |
7.8s |
| Client form date pickers › stay usable on a second visit to the create form | client-date-picker-revisit.spec.ts |
6.7s |
| Date pickers on revisited forms › floating rate form keeps its pickers usable on every visit | date-picker-revisit.spec.ts |
6.7s |
| route authorization › a deployment with RBAC turned off behaves as it did before the guard | rbac-route-protection.spec.ts |
6.5s |
| terminates and undoes a progressive loan contract | contract-termination.spec.ts |
6.2s |
📼 Download the HTML report, videos and traces — see the playwright-report-mocked artifact.
Generated by run 36982485022 from 7806444. The run executed a fork branch, so treat its contents as unverified.
E2E — real Fineract🎭 E2E Tests✅ All green — 96 passed · 0 failed · 0 skipped, across 31 spec files in 7m 1s. By spec file
All 96 tests — click to expand
Slowest 10 — what the shard counts should be tuned against
📼 Download the HTML report, videos and traces — see the Generated by run 36982485022 from |
The phrase check was scoped to files importing ColumnDef, because that contract makes label a translation key. apache#627 showed the defect also lives where no contract reaches: three screens rendered {{ day.label }} with no pipe at all, so the text stayed English in every language and nothing reported it. All 590 label literals under src/app are now key-shaped, so widening the check costs nothing today and keeps the 46 outside ColumnDef files from regressing.
…reading ADR 0001 made generated method names deterministic, and check-api-surface.mjs reports an endpoint that disappears upstream. Neither covers a change to an emitted shape: a field becoming optional, an enum gaining a member, a response nesting one level deeper. Names stay stable, the surface manifest still passes, and the change lands on every file that binds that shape. That is 463 files today, 278 of them feature components, because the generated response type is the model those components bind into their templates. The number has been documented since ADR 0001 and grew by seven files last week. local/no-generated-api-import reports an import resolving into src/app/api from outside the API adapters and the composition root, matching on the resolved path so depth does not matter. The existing 475 are seeded into the suppressions baseline, which this repository documents as only ever shrinking, so nothing breaks on adoption and the count cannot grow. It gets its own rule id for the reason no-vendor-ui-import has one: suppression counts are per rule, and a shared counter lets one boundary's violation pay for another's.
The closures list bound `closure.isClosed` through an untyped appCellTemplate context. GetGlClosureResponse has no isClosed, and neither does the payload Fineract returns — verified against a running instance, which sends id, officeId, officeName, closingDate, deleted, the audit fields and allowedOffices. So the expression was undefined on every row and every closed period rendered as 'Open', in the one column whose job is to say otherwise. strictTemplates is on and cannot see into an untyped template context; the unit test did not catch it either, because its fixtures were built from the same generated type. This is ADR 0006's worked example, so the fix is the boundary rather than a patched expression. AccountingClosureApi states the three operations the two screens use, over an AccountingClosure model whose fields are non-optional and whose absence is null; isClosed is derived once, in a tested mapper, from the fact that a closure row which is not deleted means the period is closed. Also moved: dateFormat and locale, which the form used to set on the request itself — how Fineract parses a date is the adapter's business, not a screen's. The status text was hardcoded English and is now COMMON.CLOSED / COMMON.OPEN. Both specs now mock the contract instead of the generated service, which drops the `as unknown as Observable<HttpEvent<…>>` casts its overloads forced on every mock, and the ratchet falls 475 -> 473.
…dency ADAPTERS.md said the generated client was deliberately not adapted, which ADR 0006 changes — not to a facade, which is still rejected, but to a boundary with a sanctioned place for a mapper. Documents the three rules the worked example follows and why each one exists, and fills in what else is deliberately left alone: RxJS, @angular/cdk, Angular itself. DEPENDENCY_UPGRADES.md is new and answers the question the boundaries are for: when a dependency moves, which checks to run, in what order, and which boundary a failure implicates. Two rows in its table are admissions rather than reassurance — Angular has no boundary by design, and an upstream spec change still reaches every consumer for the 473 domains not yet migrated. It also carries the backend-project warning, because a red backend shard is not evidence about an upgrade until the unpinned apache/fineract:latest has been ruled out, and that has gone wrong before.
bb84dad to
d6d530f
Compare
The four i18n:check checks and the runtime handler all need something
key-shaped to exist before they can say anything. This defect has no key:
<ion-label position="stacked">Repayments Rescheduling Rule</ion-label>
Nothing is looked up, so nothing misses and no raw key ever appears. It renders
correctly in English and stays English in Hindi and Korean indefinitely. A
review does not catch it because it reads exactly like the working line above
it, and every screenshot is in English. apache#627 was three screens of this found by
hand; there are 68 more.
The check reports a text node of two or more words starting with a capital,
comments stripped first so a JSDoc example is not mistaken for UI. It
under-reports on purpose: a single word is too often a unit or an acronym to
flag without a false-positive rate that would get the check switched off.
Baseline is per file and shrinks only, like the suppressions file — a new
occurrence fails, and so does a fix, until --update records the lower number,
which keeps the fix and the baseline in one commit. Seeded at 71 in 18 files,
then the three single-occurrence files fixed to prove the loop: 68 in 15.
…call site GetOfficesResponse declares openingDate?: string. Fineract sends [2009, 1, 1] — verified against a running instance. It also sends parentId and parentName, which the generated type never declares; it offers allowedParents instead, which GET /offices does not return. Issue apache#653 has the payloads. The application already knew about the date, 35 times over. formatArrayDate() takes `unknown` because the declared type cannot be used, fixtures wrote `openingDate: [2026, 6, 16] as unknown as number[]` to get a realistic value past the compiler, and offices-list.component.ts carried a third inline copy of the conversion. OfficeApi.list() returns an Office whose openingDate is an ISO string, converted once in a tested mapper. OfficePayload writes the disagreement down as a type, and needs Omit to do it: declaring `string | number[]` on a subtype of a `string` field is not a legal override, which is TypeScript saying correctly that the generated type and the payload are not compatible. toIsoFineractDate() accepts both forms, so a corrected spec upstream needs no change here, and returns null rather than formatArrayDate()'s '-'. A placeholder stored as data cannot be sorted or compared, and it hides the difference between 'no opening date' and 'a date we failed to read'; the view turns null into a dash. list() only. Creating and editing an office stays on the generated client until office-form is migrated — adding create() and update() before anything calls them would be the speculative abstraction ADR 0006 warns against. Migrating offices-list and the closure form's office lookup takes the ratchet from 473 to 469, and leaves accounting-closure-form.component.ts with no import of the generated client at all.
d6d530f to
7806444
Compare
…s off the generated client
main dropped the TranslateModule import from accounting-closure-form, so the
two keys added there no longer compiled. Use TranslatePipe from core/adapters.
The nine new specs now run against provideHttpClientTesting instead of
importing generated services (no-generated-api-import, ADR 0006), and the
template-text baseline drops to {} now that apache#651 has landed.
Why
Two unguarded dependencies, each found by asking what the existing checks cannot see.
The generated OpenAPI client. ADR 0001 made generated method names deterministic, and
check-api-surface.mjsreports an endpoint that disappears upstream. Neither covers a change to an emitted shape — a field becoming optional, an enum gaining a member, a response nesting one level deeper. Names stay stable, the surface manifest still passes, and the change lands on every file that binds that shape. That was 475 files, 278 of them feature components, because the generated response type is the model those components bind into their templates.That is not hypothetical. Both worked examples below exist because a screen had a defect the generated type could not prevent, and in each case the payload was captured from a running instance rather than inferred.
English written straight into a template. The four
i18n:checkchecks and the runtime handler all need something key-shaped to exist first.<ion-label>Repayments Rescheduling Rule</ion-label>looks nothing up, so nothing misses and no raw key appears — it renders correctly in English and stays English in Hindi and Korean indefinitely. #627 was three screens of this found by hand. There were 71 more.What this changes
Architectural boundary — ADR 0006
local/no-generated-api-importreports an import resolving intosrc/app/apifrom outside the API adapters and the composition root. Matching is on the resolved path, not the specifier, so'../../api'and'../api/model/getClientsResponse'are caught alike regardless of the importing file's depth.It gets its own rule id for the reason
no-vendor-ui-importhas one: suppression counts are per rule, and a shared counter lets one boundary's violation pay for another's. The existing violations are seeded intoeslint-suppressions.json, which this repository documents as only ever shrinking, so nothing breaks on adoption and the count cannot grow.This is not the facade ADR 0001 rejected, and that rejection stands. There is no wrapper over the 155 generated services and none is proposed. The rule renames nothing and adds no indirection on day one; it makes the coupling countable and non-increasing, and gives a mapping a sanctioned place to live when a mapping earns its keep.
Worked example 1 — accounting closures: a field that does not exist
The closures list bound
closure.isClosedthrough an untypedappCellTemplatecontext.GetGlClosureResponsehas noisClosed, and neither does the payload Fineract returns — it sendsid,officeId,officeName,closingDate,deleted, the audit fields andallowedOffices. So the expression wasundefinedon every row, and every closed period rendered as "Open", in the one column whose job is to say otherwise.strictTemplatesis on and cannot see into an untyped template context; the unit test did not catch it either, because its fixtures were built from the same generated type.AccountingClosureApistates the three operations the two closure screens use, over a model that differs from the generated type in the three ways that matter:null, notundefined, so a template cannot silently read past it;isClosedis derived once in a tested mapper, from the fact that a closure row which is not deleted means the period is closed.dateFormatandlocalemoved into the adapter too: the form used to set them on the request object, so a screen knew how Fineract parses dates.Worked example 2 — offices: a type that is simply wrong
GetOfficesResponsedeclaresopeningDate?: string. Fineract sends[2009, 1, 1]. It also sendsparentIdandparentName, which the generated type never declares — it offersallowedParentsinstead, whichGET /officesdoes not return. Filed as #653 with the captured payloads.The application already knew about the date, 35 times over:
formatArrayDate()takesunknownbecause the declared type cannot be used, fixtures wroteopeningDate: [2026, 6, 16] as unknown as number[]to get a realistic value past the compiler, andoffices-list.component.tscarried a third inline copy of the conversion.OfficeApi.list()returns anOfficewhoseopeningDateis an ISO string, converted once. The adapter writes the disagreement down as a type, and needsOmitto do it — declaringstring | number[]on a subtype of astringfield is not a legal override, which is TypeScript stating correctly that the two are not compatible.toIsoFineractDate()accepts both forms, so a corrected spec upstream needs no change here, and returnsnullrather thanformatArrayDate()'s'-': a placeholder stored as data cannot be sorted or compared, and it hides the difference between "no opening date" and "a date we failed to read". The view turns null into a dash.list()only. Creating and editing an office stays on the generated client untiloffice-formis migrated; addingcreate()andupdate()before anything calls them would be the speculative abstraction ADR 0006 andAGENTS.mdboth warn against.Together the two migrations take the ratchet to 469, and leave
accounting-closure-form.component.tswith no import of the generated client at all.All four specs now mock the contract instead of the generated service. That drops the
as unknown as Observable<HttpEvent<…>>casts the generated overloads forced on every mock, and it matters for a second reason — a fixture built from a generated type inherits the spec's blind spots and will confirm a screen that is broken in a browser.New check —
check:template-textReports a text node of two or more words starting with a capital, comments stripped first so a JSDoc example is not mistaken for UI. It under-reports on purpose: a single word is too often a unit or an acronym (
USD,ID,JSON) to flag without a false-positive rate that would get the check switched off.Per-file baseline, shrinking only — a new occurrence fails, and so does a fix, until
--updaterecords the lower number, which keeps the fix and the baseline in one commit. Seeded at 71 in 18 files, then the three single-occurrence files fixed to prove the loop works in both directions: 68 in 15. The rest are listed in #652.i18n:checkPHRASES widenedWas scoped to files importing
ColumnDef, because that contract makeslabela key. #627 showed the defect also lives where no contract reaches. All 590label:literals undersrc/appare now key-shaped, so widening it costs nothing today and stops the 46 outsideColumnDeffiles from regressing.Boundaries after this change
@ngx-translate)I18N,| appTranslatesrc/app/ui/OVERLAYSTORAGEDOWNLOADlocal/no-generated-api-importDeliberately not adapted, now written down in
DOCS/ADAPTERS.md: RxJS (an Angular peer dependency),@angular/cdk(retained on purpose when Material went;src/app/ui/is built on it), and Angular itself.Documentation
DOCS/adr/0006-generated-api-boundary.md— the decision, and why it is not ADR 0001's facade.DOCS/ADAPTERS.md— previously said the generated client was deliberately not adapted; now describes the boundary, both worked examples, and what is still left alone.DOCS/DEPENDENCY_UPGRADES.md— new. Which checks to run when a dependency moves, in the order that localises a fault fastest, and which boundary each failure implicates. Two rows are admissions rather than reassurance: Angular has no boundary by design, and an upstream spec change still reaches every consumer for the domains not yet migrated.DOCS/CI_CHECKS.md,AGENTS.md— the new rule and check where contributors look.Checks run
npm run lintnpm run test:eslint-rulesnpm run test:unitnpm run format:checknpm run i18n:checknpm run check:template-textbash scripts/check-license.shnpx tsc --noEmit -p tsconfig.app.jsoncheck:*scriptsBoth ratchets were verified in both directions rather than assumed: a new import of
src/app/apifails lint with a message naming the ADR, a new untranslated text node failscheck:template-text, and fixing one fails until the baseline is tightened.CI is the authority on E2E here. A local run of
mocked,mobileandbackendconcurrently on one machine produced timeouts that CI's sharded jobs do not; the backend image is also unpinned and moved twice during this work, so I will quote the digest rather than imply reproducibility:sha256:c1d4aee9, created 2026-10-01, on a volume destroyed withe2e:stack:fresh.Risk and what is left
id. Defaulting would re-open or navigate to the wrong record, so they fail loudly; Fineract always sends one.stringarrive as[y, m, d], and some fields are undeclared #653 ranks the domains by what a mapper would settle.AuthServiceinterface over a single implementation would be an abstraction with no second implementer today — it becomes justified when OIDC can be configured but never used — the login page offers only username and password #370 lands, and that is the natural PR to introduce it in.Follow-up to #624 and #627. Files #652 and #653 for the backlogs it makes visible.