)]}'
{
  "log": [
    {
      "commit": "164f12da2888d19f820320bc1f852333b2d2b48e",
      "tree": "fa60d4d9326da4cee73dcf12866658c0c2dd6f26",
      "parents": [
        "3ac0b0954beb87fb8d12f9edb6dc01ba85a9f33f",
        "2b2266233f1d09a60b0ac7e7fe9612afb8fec107"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Nov 14 09:43:38 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Nov 14 09:43:38 2026 +0530"
      },
      "message": "Merge pull request #887 from Aman-Mittal/docs/security-unreleased-triage\n\ndocs(security): dispose of reports against this unreleased UI first"
    },
    {
      "commit": "2b2266233f1d09a60b0ac7e7fe9612afb8fec107",
      "tree": "fa60d4d9326da4cee73dcf12866658c0c2dd6f26",
      "parents": [
        "3ac0b0954beb87fb8d12f9edb6dc01ba85a9f33f"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Nov 14 09:32:22 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Nov 14 09:32:22 2026 +0530"
      },
      "message": "docs(security): dispose of reports against this unreleased UI first\n\n§13 listed nine dispositions and none of them was \"this has never shipped\".\npackage.json reads 1.0.0-rc.1, there is no release tag, and 1.0.0 is still an\nopen issue -- so every inbound report was being triaged on its merits against an\nartifact no operator can be running.\n\nRule 1 is checked first and short-circuits the table: closed without further\ntriage, no CVE, no security release, no embargo. The nine existing dispositions\nare kept rather than deleted, because they are what triage returns to the day\n1.0.0 ships and what the project already applies to its own findings.\n\n§13a ties the removal to that day: rule 1 goes in the same change that cuts the\nrelease, and the release is not complete while it is still there. The failure\nmode is not abuse, it is everyone forgetting -- a file still saying reports are\nclosed unread after the artifact ships turns a true statement about a release\ncandidate into a false one about a release.\n\nTwo limits are stated because both misreadings would do harm. Rule 1 does not\nsuppress the model above it: the project keeps applying these dispositions to its\nown findings, which is where the CI gates in §14 came from. And rule 1 does not\nreach the Fineract server -- a finding in the API or server-side authorization\nconcerns a released artifact and is routed by OUT-OF-MODEL: backend-layer, not\nclosed here. The release status of this UI says nothing about what it talks to.\n"
    },
    {
      "commit": "3ac0b0954beb87fb8d12f9edb6dc01ba85a9f33f",
      "tree": "d6882a0708fcb4bfd82fdd00e12ee4ecf3122376",
      "parents": [
        "fbd138bfa7c5a3a89315af54427d714cda632614",
        "e21e602b81c53e0ac6232572ec923af46bc80962"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Thu Nov 12 14:29:29 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Nov 12 14:29:29 2026 +0530"
      },
      "message": "Merge pull request #886 from a-mikh/fix/797-account-breadcrumbs\n\nfix(navigation): show account owners in breadcrumbs"
    },
    {
      "commit": "e21e602b81c53e0ac6232572ec923af46bc80962",
      "tree": "35c96fca7f70301676e4e581436d9bdf8905b0d9",
      "parents": [
        "b907da2f151ae5e7f4cdda07ee8d8ff28d1baef0"
      ],
      "author": {
        "name": "Anton",
        "email": "amikhalchenkonton@gmail.com",
        "time": "Wed Nov 11 20:26:00 2026 +0100"
      },
      "committer": {
        "name": "Anton",
        "email": "amikhalchenkonton@gmail.com",
        "time": "Wed Nov 11 20:26:00 2026 +0100"
      },
      "message": "fix(navigation): show account owners in breadcrumbs\n"
    },
    {
      "commit": "fbd138bfa7c5a3a89315af54427d714cda632614",
      "tree": "006ee0450b136564a698648f70b9d20f3f8c6ef5",
      "parents": [
        "d3789a8cb3af5ff5ef4390b3acbe594b7e9a8131",
        "2a09055a33cc5eb5838243ff9e11f726eedb7f7e"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Wed Nov 11 11:30:33 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Nov 11 11:30:33 2026 +0530"
      },
      "message": "Merge pull request #885 from zhangxinyao88/codex/client-dob-platform-bound\n\nfix(clients): bound birth dates by the platform date"
    },
    {
      "commit": "d3789a8cb3af5ff5ef4390b3acbe594b7e9a8131",
      "tree": "bb61259a925a77a32af0ef74793aa778978c5856",
      "parents": [
        "b907da2f151ae5e7f4cdda07ee8d8ff28d1baef0",
        "84e48c449dd60f5813ed1fa1e115e635c9f90f14"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Tue Nov 10 10:40:10 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Nov 10 10:40:10 2026 +0530"
      },
      "message": "Merge pull request #883 from Aman-Mittal/fix/794-overview-row-name-collision\n\nfix(clients): drop the accounts-overview row button, which broke a locator"
    },
    {
      "commit": "2a09055a33cc5eb5838243ff9e11f726eedb7f7e",
      "tree": "3b33610360456313d4bf66c1cb5487292d9b367b",
      "parents": [
        "b907da2f151ae5e7f4cdda07ee8d8ff28d1baef0"
      ],
      "author": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Mon Nov 09 11:04:36 2026 -0500"
      },
      "committer": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Mon Nov 09 11:04:36 2026 -0500"
      },
      "message": "fix(clients): bound birth dates by the platform date\n"
    },
    {
      "commit": "84e48c449dd60f5813ed1fa1e115e635c9f90f14",
      "tree": "bb61259a925a77a32af0ef74793aa778978c5856",
      "parents": [
        "888f8648c15fb46bf747aab80605b0b639d1be5f"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 20:21:44 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 20:21:44 2026 +0530"
      },
      "message": "fix(clients): update the backend spec for the removed overview button\n\nRemoving the control in the previous commit broke the spec that clicked it:\n`client-details-fields.spec.ts` opened the Loans tab through\n`overview-open-loans`, which no longer exists, and `E2E (real Fineract, shard\n1/5)` failed on it.\n\nIt now reaches the tab through the tab bar, which is where it always belonged,\nand asserts the overview carries no control of its own -- the property the\nprevious commit established, so the collision cannot come back unnoticed.\n\nCaught by CI rather than by me: the mocked shards went green and a real-Fineract\nshard went red in the same run, and I had grepped only the mocked suite for\nreferences to the testid I deleted.\n"
    },
    {
      "commit": "888f8648c15fb46bf747aab80605b0b639d1be5f",
      "tree": "e742c5a355406880f2da5d8764f5f4fad334f620",
      "parents": [
        "b907da2f151ae5e7f4cdda07ee8d8ff28d1baef0"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 19:04:13 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 19:04:13 2026 +0530"
      },
      "message": "fix(clients): drop the accounts-overview row button, which broke a locator\n\nRegression from #878. The accounts overview\u0027s first cell was an ion-button that\nswitched to the matching tab, so the client page carried a button accessibly\nnamed \"Loan Accounts\" alongside the New Account menu\u0027s \"Loan Account\". One word\napart, and `functional-coverage.spec.ts` addresses the menu item as\n/Loan Account/i -- which then matched both and failed Playwright\u0027s strict mode.\n\nThe honest fix is to remove the control rather than tighten the locator. It\nduplicated the tab sitting directly above it, and two controls a plural apart on\none page are ambiguous to read out whatever the test does. The cell is a row\nheader now, which is what it always was semantically.\n\nNot caught before merge because I ran the mocked suite filtered to --grep\n\"Client\" and read a truncated log; the whole shard is what fails. 125 tests in\nmocked shard 2/4: 124 passed, this one failed, and it reproduces locally.\n\nVerified: e2e/functional-coverage.spec.ts 42 passed, including the case that\nbroke, and the client view\u0027s 42 unit cases.\n"
    },
    {
      "commit": "b907da2f151ae5e7f4cdda07ee8d8ff28d1baef0",
      "tree": "ed57bb5e69191fed60a2e3e03266b9063daa7bd8",
      "parents": [
        "a1d7ba31bb4c8aa49063502c8d73be1b1de737a1",
        "d185dd183bf6286a778118a184ba61a38247750b"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 09 18:23:21 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 09 18:23:21 2026 +0530"
      },
      "message": "Merge pull request #880 from Aman-Mittal/test/138-sidebar-permission-tooltip\n\ntest(sidebar): cover the permission tooltip on a nav item"
    },
    {
      "commit": "a1d7ba31bb4c8aa49063502c8d73be1b1de737a1",
      "tree": "eb886443f51ff196f544295e8c3d04dedaaaff0b",
      "parents": [
        "11148110d8aff47cf687e71704739c932d5a9413",
        "22a5097cb910c411e518b474458e8476d9fe6d61"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 09 18:16:23 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 09 18:16:23 2026 +0530"
      },
      "message": "Merge pull request #879 from Aman-Mittal/refactor/collection-sheet-spec-merge\n\ntest(collection-sheet): fold the collections spec into the rendering one"
    },
    {
      "commit": "11148110d8aff47cf687e71704739c932d5a9413",
      "tree": "07438bca2bdab1ab777f62684f0139caf72415cb",
      "parents": [
        "e187cdc3bbc6b80048c642793ab93e2a2db40aa9",
        "006a827557573ce982660b5b8ac7a91fd57513f0"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 09 18:15:48 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 09 18:15:48 2026 +0530"
      },
      "message": "Merge pull request #878 from Aman-Mittal/fix/794-client-details-fields\n\nfix(clients): show the identification fields, the groups and an accounts summary"
    },
    {
      "commit": "d185dd183bf6286a778118a184ba61a38247750b",
      "tree": "5c3947804580a16b9daebef7278dd8a31f561f3e",
      "parents": [
        "e187cdc3bbc6b80048c642793ab93e2a2db40aa9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:39:59 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:39:59 2026 +0530"
      },
      "message": "test(sidebar): cover the permission tooltip on a nav item\n\nThe tooltip #138 asked for is on main -- sidebar.component.ts binds\n`[attr.title]\u003d\"capabilities(item)\"`, which summarises the codes the user holds\nfor that item\u0027s entity -- but the Playwright test the issue also asked for was\nnever written.\n\nFour cases, mocked because the subject is the permission set and putting an\narbitrary one in the authentication response is the whole fixture: the tooltip\nreads both codes when both are held, narrows to one when only one is, says\nnothing at all to a superuser holding ALL_FUNCTIONS, and a module the user\ncannot reach has no item to hover at all.\n\nThat last one pins a deliberate divergence. The issue asked a refused item to\nread \"Contact admin to request: CREATE_LOAN\"; the sidebar filters the entry out\ninstead, because a menu is a list of destinations and one that is not yours is\nnot a destination. The spec asserts the choice the code made rather than the\nwording that was proposed for it.\n"
    },
    {
      "commit": "22a5097cb910c411e518b474458e8476d9fe6d61",
      "tree": "d08514f6dd00b20a279bf626df07f623e98ccad4",
      "parents": [
        "e187cdc3bbc6b80048c642793ab93e2a2db40aa9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:34:06 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:34:06 2026 +0530"
      },
      "message": "test(collection-sheet): fold the collections spec into the rendering one\n\nThe spec added in #876 was a second mocked collection-sheet file, and it\ncarried its own copy of the sign-in routes and the JLGCollectionSheetData\nfixture -- about 110 lines that already existed next to it in\ncollection-sheet-table.spec.ts. Two files mocking the same screen the same way\nis two places to update when the screen\u0027s setup changes.\n\nThe cases are unchanged. mockSheet now also answers command\u003dsave and hands back\nthe bodies it was sent, which is what the save cases assert on, and the shared\nfixture\u0027s savings row carries the depositAccountType phrase the platform sends.\nThe ambiguous-deposit case clones the fixture rather than adding a second\naccount to it, so the existing total assertions still read 1,000.00 and say what\nthey always said.\n\nNine cases over one file, down from four plus five over two.\n"
    },
    {
      "commit": "006a827557573ce982660b5b8ac7a91fd57513f0",
      "tree": "07438bca2bdab1ab777f62684f0139caf72415cb",
      "parents": [
        "e187cdc3bbc6b80048c642793ab93e2a2db40aa9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:26:21 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:26:21 2026 +0530"
      },
      "message": "fix(clients): show the identification fields, the groups and an accounts summary\n\nThe details page held first name, last name, external ID, legal form, email,\nactivation date and the submitted date. Gender, client type, classification and\ngroup membership were not on it at all, and the Legal Form row printed the word\n\"Person\" for every client -- so an Entity was labelled a person on its own\npage.\n\nThree of the new fields are undeclared in the generated response, so they are\ndeclared on ClientProfileFields with their shapes traced to Fineract\u0027s\nClientData. The shapes matter: gender, clientType and clientClassification are\nCodeValueData, labelled by `name`, while legalForm is an EnumOptionData,\nlabelled by `value`. Reading one for the other is how #862 shipped a column of\n[object Object].\n\nGroups are listed as links to each group, withheld as plain text for a reader\nwithout READ_GROUP, the way the account numbers on this page already are.\n\nThe accounts overview answers \"what does this client owe and hold?\" on the\nlanding tab, which used to mean opening up to four tabs and adding the rows up\nby eye. The amounts are the platform\u0027s own per-account figures, `loanBalance`\nand `accountBalance`, neither of which the generated account models declare.\nA type holding accounts in more than one currency reports no amount: 500 USD\nplus 500 INR is 1000 of nothing, and the per-account tables still show each\nbalance with its own symbol. Shares are left out rather than reported as zero --\nthey are not in GET /clients/{id}/accounts and are fetched only when their tab\nis opened.\n\nThe assigned staff member is already in the page header, so this asserts that\nrather than adding a second copy of it to the details card.\n\nThe e2e runs against a real Fineract, which is the point of it: what was wrong\nis which field names the page reads, and a mocked fixture reproduces whatever\nnames the spec was written against.\n\nCloses #794\n"
    },
    {
      "commit": "e187cdc3bbc6b80048c642793ab93e2a2db40aa9",
      "tree": "ac709f0a858521c7374f717748f549e807ec1857",
      "parents": [
        "85ff96cd609014e6c5f56f410fae914e2f36be0f",
        "72b525fd5f7fa0245c3835560e903bdc2a56acd0"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 09 17:24:54 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 09 17:24:54 2026 +0530"
      },
      "message": "Merge pull request #877 from Aman-Mittal/test/492-profile-password-e2e\n\ntest(profile): cover changing your own password end to end"
    },
    {
      "commit": "85ff96cd609014e6c5f56f410fae914e2f36be0f",
      "tree": "3f040e4b784b5ab2bbb3e39a36438fc317c45957",
      "parents": [
        "03df7832340e1551f2d09157c30d8d09453750e0",
        "ad8231a06a2cedcdb33b9392e5cae6071f47a830"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 09 17:12:45 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 09 17:12:45 2026 +0530"
      },
      "message": "Merge pull request #876 from Aman-Mittal/feat/670-collection-sheet-collections\n\nfeat(collection-sheet): record what was collected at the meeting"
    },
    {
      "commit": "72b525fd5f7fa0245c3835560e903bdc2a56acd0",
      "tree": "6763ea208131c6389ff7a4b5b56652665dbcd4dc",
      "parents": [
        "03df7832340e1551f2d09157c30d8d09453750e0"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:06:38 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 17:06:38 2026 +0530"
      },
      "message": "test(profile): cover changing your own password end to end\n\n#492 was implemented in #615 and never had a browser test. The issue\u0027s own\nevidence was a DOM query that found zero interactive elements on `/profile`;\nthe first case here runs that same query and expects it to find something.\n\nMocked rather than driven against a real Fineract, deliberately: a successful\nchange against the shared stack would rotate the credential every other backend\nspec signs in with, and the run would fail from that point on. The platform\u0027s\npart is one `POST /users/{id}/pwd`, and this asserts on the body of it, and on\nthe url carrying the session\u0027s own user id rather than one the form asks for.\n\nThe tenant is given the strong policy, because its rules are where this screen\nearns its keep. The password under test passes every obvious rule and repeats a\ncharacter, which is the clause that catches people out and the one Fineract\u0027s\nown refusal does not name. The spec also asserts that the rules it does *not*\nfail stay unlisted, so a list that showed everything regardless would fail here\nrather than pass.\n\nSubmit is read through `aria-disabled` rather than `toBeDisabled()`:\n`ion-button` sets both `disabled` and `aria-disabled`, but declares no role, so\nPlaywright computes it as generic and reports a visibly disabled button as\nenabled.\n"
    },
    {
      "commit": "ad8231a06a2cedcdb33b9392e5cae6071f47a830",
      "tree": "3f040e4b784b5ab2bbb3e39a36438fc317c45957",
      "parents": [
        "c123c9e39f3f93a2069eff9c0c2452e05a94f4d2"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:59:53 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:59:53 2026 +0530"
      },
      "message": "test(collection-sheet): drive the collections through the browser, and widen the shards\n\nThe unit specs assert on the request the adapter builds; this asserts on the\nrequest that leaves a browser, which is where the officeId that the platform\nrefuses came from.\n\nMocked rather than real, and forced rather than preferred: `command\u003dgenerate`\nanswers 200 with an empty body unless a group has a meeting calendar with\nrepayments falling on the date, and no CI instance has one.\n`collection-sheet.spec.ts` still covers the filter and the empty answer against\na real Fineract; this covers the half that only exists once a sheet comes back\npopulated. Each amount is typed into the inner native input, since ion-input\nre-emits its events and the host has no value to fill, and every assertion uses\na figure that differs from the seeded due -- a binding that did nothing fails\nthe spec rather than passing it.\n\nShards: mocked 4 -\u003e 6, backend 3 -\u003e 5. Measured rather than guessed, from the\nrun on #875: the 50-file backend suite finished its three shards in 6:52, 12:08\nand 14:08, and the mocked suite its four in 6:12 to 9:23. The same runner\nminutes now buy a shorter wall clock. Not higher than five on the backend side\nbecause each shard pays a ~2 min stack boot whatever it runs, which is the\nfloor that caps the count.\n\nBranch protection requires the shard checks by name, and both counts rename\nthem, so the required list has to be updated with this change or the new shards\nrun unrequired.\n"
    },
    {
      "commit": "c123c9e39f3f93a2069eff9c0c2452e05a94f4d2",
      "tree": "9e62cc112c6a51684c15afe7d820966d7c7fe231",
      "parents": [
        "03df7832340e1551f2d09157c30d8d09453750e0"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:52:06 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:52:06 2026 +0530"
      },
      "message": "feat(collection-sheet): record what was collected at the meeting\n\nThe sheet has shown what each client owes since #872, but Save posted the\nfilters the sheet was generated with and nothing else, so no collection was\never recorded. Two separate reasons it could not have worked:\n\n- `command\u003dsave` routes to Fineract\u0027s `saveIndividualCollectionSheet`, whose\n  validator runs `checkForUnsupportedParameters` against a set holding neither\n  `officeId` nor `staffId`. The screen sent both, so the platform refused the\n  save outright rather than saving an empty one.\n- Even accepted, the body named no account and no amount.\n\nEach loan and savings row now carries an editable amount, seeded from what is\ndue because that is what is usually paid, and the save posts\n`bulkRepaymentTransactions` and `bulkSavingsDueTransactions`. A row left at\nzero is left out: the platform tolerates it, but a request naming an account it\nis not paying invites the reading that it was.\n\nA savings row needs one more thing the platform does not hand over. The sheet\nreports the kind of account in words -- `CASE WHEN sa.deposit_type_enum \u003d 100\nTHEN \u0027Saving Deposit\u0027 ELSE (CASE WHEN ... \u003d 300 THEN \u0027Recurring Deposit\u0027 ELSE\n\u0027Current Deposit\u0027 END) END` -- while the save takes a `DepositAccountType` id,\nand nothing converts between them. The adapter maps the two labels that name\none type each, and refuses \u0027Current Deposit\u0027, which is the `ELSE` and so covers\n200 and 400 alike. Such a row offers no input at all, because a deposit sent\nwithout a type is accepted and then silently does nothing:\n`updateBulkMandatorySavingsDuePayments` discards whatever the deposit throws.\n\nTowards #670. What remains there is the question the issue raises last:\nwhether the individual collection sheet belongs on this screen too.\n"
    },
    {
      "commit": "03df7832340e1551f2d09157c30d8d09453750e0",
      "tree": "7e78df890fb9b3855affdde7a31515af2e5d8d9f",
      "parents": [
        "839a19283145ff02886a93cb0a0bfb13246d2c0d",
        "6ead0ceb093812e76ef9acf5481836a3a75ba361"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 09 16:39:14 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 09 16:39:14 2026 +0530"
      },
      "message": "Merge pull request #875 from Aman-Mittal/chore/spec-sync-nov09\n\nchore(api): sync the Fineract OpenAPI spec, and fix the call sites it breaks"
    },
    {
      "commit": "6ead0ceb093812e76ef9acf5481836a3a75ba361",
      "tree": "7e78df890fb9b3855affdde7a31515af2e5d8d9f",
      "parents": [
        "649860413e1cdf45a0a7de51e85e297c194dbbba"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:22:06 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:22:06 2026 +0530"
      },
      "message": "fix(api): fix the call sites the November spec sync broke\n\nThe sync itself is the bot\u0027s commit, unchanged. This is the compile fix it\nasked for, and one note about what the summary got wrong: it reported \"no\noperations were removed, so no existing call site can break\". Nothing was\nremoved, and five call sites broke anyway — sixteen operations *changed*, and\nfour schemas were dropped.\n\n**`getAudits` gained eighteen parameters in the middle.** `productId`,\n`transactionId`, the `subresource*` and `*ExternalId` filters and the whole\n`submitted*` family were inserted between `locale` and `offset`, so the\nadapter\u0027s positional call moved `offset` into `productId` and `limit` into\n`transactionId`. It was caught only because those happened to be a `string`;\nhad the inserted parameters been numbers, this would have gone on compiling\nand sent the page size as a transaction id.\n\nThe arguments are now built as `Parameters\u003cAuditsService[\u0027getAudits\u0027]\u003e`, so the\narity and per-position types are checked, and each slot is named in a comment.\nThe adapter test\u0027s own position map — which is what caught this — is updated\nwith a note on why the numbers moved by eighteen.\n\n**Two endpoints gained typed request bodies**, having previously declared bare\nstrings: the share-product dividend POST and the two-factor configuration PUT.\nBoth screens hand-serialised JSON for them, and both now pass the object. The\ntwo-factor screen keeps reading a string *or* an object on load: that\ndeclaration has changed once already, and treating an object as a string is how\na screen comes to show \"[object Object]\".\n\n**`ProgressiveLoanInterestScheduleModel` and `RepaymentPeriod` were dropped.**\n`GET /internal/loan/{id}/model` is now `{ [key: string]: any }`, so the admin\nscreen types its signal as a map. It renders the model as JSON for support\nwork, so nothing is lost there — but the type fidelity is, which is worth\nknowing before anyone tries to read individual periods.\n\n`verify-api-client` passes, so the committed client reproduces from the spec.\n"
    },
    {
      "commit": "649860413e1cdf45a0a7de51e85e297c194dbbba",
      "tree": "9a92342b32dbabc225c4c6b4ba32ae2e94e2a41d",
      "parents": [
        "839a19283145ff02886a93cb0a0bfb13246d2c0d"
      ],
      "author": {
        "name": "github-actions[bot]",
        "email": "41898282+github-actions[bot]@users.noreply.github.com",
        "time": "Mon Nov 09 04:24:42 2026 +0000"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 09 16:08:48 2026 +0530"
      },
      "message": "chore(api): sync the Fineract OpenAPI spec and regenerate the client\n\n"
    },
    {
      "commit": "839a19283145ff02886a93cb0a0bfb13246d2c0d",
      "tree": "ea0f2c3f15e8a6a09e1bf5d3c85f22c8f5372ae4",
      "parents": [
        "f78bcec2e016facdf22a987172c04f479878d53c",
        "256f2cdeb1e0f465147ec86299c3713737487c05"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 23:41:24 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 23:41:24 2026 +0530"
      },
      "message": "Merge pull request #873 from Aman-Mittal/fix/797-account-back-to-client\n\nfix(navigation): take the account pages back to their owner"
    },
    {
      "commit": "256f2cdeb1e0f465147ec86299c3713737487c05",
      "tree": "ea0f2c3f15e8a6a09e1bf5d3c85f22c8f5372ae4",
      "parents": [
        "f78bcec2e016facdf22a987172c04f479878d53c"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 23:17:48 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 23:17:48 2026 +0530"
      },
      "message": "fix(navigation): take the account pages back to their owner\n\nPart of #797 — the navigation half.\n\nThe loan, savings and deposit pages printed \"Client: {name}\" as plain text and\nsent Back to the global account list, which is never where the user came from:\nthese accounts are opened from the client\u0027s own page. Servicing moves between\nthe two constantly — post a repayment, check savings, return to the client —\nand each round trip meant a fresh search.\n\nThe client name is now a link to the client, and Back returns to the owner,\nfalling back to the list for an account whose owner the screen does not know.\n\nThe deposit screen needed `RouterLink` added to its imports, which `tsc` does\nnot catch and `ng build` does: NG8002, \"Can\u0027t bind to \u0027routerLink\u0027 since it\nisn\u0027t a known property of \u0027a\u0027\". Worth remembering that a template-only binding\nerror surfaces in the build, not the typecheck.\n\nWhat this does not do, and why #797 stays open: the breadcrumbs. They are\nderived in `buildBreadcrumbs` from each route\u0027s static `title`, walking the\nroute tree, so \"Products › Savings Account Details\" comes from the URL\u0027s own\nshape. Reading \"Clients › {client} › {account} {no}\" instead needs either\ndynamic crumb labels a screen can supply, or re-parenting the account routes\nout from under /products — which changes their URLs. Both are mechanism\nchanges that deserve their own review rather than riding along here.\n"
    },
    {
      "commit": "f78bcec2e016facdf22a987172c04f479878d53c",
      "tree": "de93aca86978793c41c7c18985c86d09e42f2429",
      "parents": [
        "a8d5d702a7d417d34d480d2df3c5cc7e9e11ffa5",
        "c6f3aee9c5b18927533e1798b769a2955d14aec4"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 23:06:31 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 23:06:31 2026 +0530"
      },
      "message": "Merge pull request #872 from Aman-Mittal/fix/670-collection-sheet-table\n\nfeat(collections): render the generated sheet as a sheet, not a JSON dump"
    },
    {
      "commit": "c6f3aee9c5b18927533e1798b769a2955d14aec4",
      "tree": "de93aca86978793c41c7c18985c86d09e42f2429",
      "parents": [
        "a8d5d702a7d417d34d480d2df3c5cc7e9e11ffa5"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 21:42:07 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 21:42:07 2026 +0530"
      },
      "message": "feat(collections): render the generated sheet as a sheet, not a JSON dump\n\nAddresses #670. A collection sheet is the document a field officer carries to a\nmeeting; it was rendered as `{{ collectionData() | json }}`.\n\nThe reason #669 stopped short was that the payload could not be observed: the\ngenerated `PostCollectionSheetResponse` describes the *save* response — changes,\ngroupId, resourceId — and `command\u003dgenerate` answers 200 with an empty body\nunless a group meeting falls on the date, so no instance to hand could show the\npopulated shape. Guessing it was the specific mistake to avoid.\n\nSo it was not guessed. The shape is traced to Fineract\u0027s own\n`JLGCollectionSheetData` → `JLGGroupData` → `JLGClientData` → `LoanDueData` /\n`SavingsDueData`, and `COLLECTION_SHEET_API` declares it as the application\nneeds it. That is also the ADR 0006 case the issue predicted: the generated\ntype here is not merely thin, it describes a different operation.\n\nTwo details the platform decides and this does not. `totalDue` is the\nplatform\u0027s own field rather than a sum of principal, interest and fees — a\nsheet that disagreed with Fineract about what a client owes would be worse than\nno sheet. And `dueDate` is read through `toIsoFineractDate`, because a\n`LocalDate` arrives as `[y, m, d]` on some endpoints and as a string on others,\nwhich is #653\u0027s class of mismatch.\n\nA client with nothing due still gets a row: the officer has to see who was\nexpected at the meeting, not only who owes.\n\nWhat remains, and why #670 should stay open: the sheet is read-only. Recording\nwhat was collected needs `bulkRepaymentTransactions` and\n`bulkDisbursementTransactions` on `command\u003dsave`, and Save currently posts the\nfilters alone — now documented on the contract method rather than left to be\ndiscovered. That is money-moving, and I would want it measured against a live\nmeeting before writing it.\n"
    },
    {
      "commit": "a8d5d702a7d417d34d480d2df3c5cc7e9e11ffa5",
      "tree": "3224a4e29f48397cb176419e8138c320c698401f",
      "parents": [
        "c774a25674c4e35fc13a8cb29c46fa2ea2a1bf2b",
        "5a42832f4c8fff46715448b7621b0e164546dd11"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 21:25:57 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 21:25:57 2026 +0530"
      },
      "message": "Merge pull request #871 from Aman-Mittal/fix/751-unguarded-routes\n\nfix(rbac): report a refused list read as a refusal, not an empty list"
    },
    {
      "commit": "c774a25674c4e35fc13a8cb29c46fa2ea2a1bf2b",
      "tree": "46a3020c9c4aa13dee61ec691fcad2de02af2d3d",
      "parents": [
        "2683b63d1502fe6ed57a9e30d1106f9b2b83c5d5",
        "be727eed344b08da14faf0238a387faba6f097b7"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 21:25:14 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 21:25:14 2026 +0530"
      },
      "message": "Merge pull request #870 from Aman-Mittal/fix/749-oidc-load-failure\n\nfix(settings): withhold the OIDC form when its read failed"
    },
    {
      "commit": "2683b63d1502fe6ed57a9e30d1106f9b2b83c5d5",
      "tree": "1a25f1440625c941074da54ad2fb3181936d78eb",
      "parents": [
        "6343e20e8f1e87305de681db19902e6a38a6379c",
        "c2e7a1468b520945c7c9b917ca63cfad0e25133c"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 21:24:43 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 21:24:43 2026 +0530"
      },
      "message": "Merge pull request #869 from Aman-Mittal/feat/account-number-formats\n\nfeat(system): configure how Fineract numbers each kind of account"
    },
    {
      "commit": "5a42832f4c8fff46715448b7621b0e164546dd11",
      "tree": "e4211b9d8298d6ef735f7224a3d87ef06796b28c",
      "parents": [
        "6343e20e8f1e87305de681db19902e6a38a6379c"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 19:59:47 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 19:59:47 2026 +0530"
      },
      "message": "fix(rbac): report a refused list read as a refusal, not an empty list\n\nPart of #751 — the half the issue calls the serious one.\n\nCollateral Management and Group Levels caught a 403 into an empty array, so a\nuser who may not read collateral types or group levels was told the institution\nhas none configured. Nothing looks broken in that state, which is why it goes\nunreported. Both now pass the status to `app-data-table`, which already reports\na 403 as a permission problem and withholds the retry, because the same request\nwould only be refused again.\n\nInterest Rate Charts reported its refused product read as a plain failure and\noffered \"Try again\", which can only be refused again; it now passes the status\ntoo.\n\nSurvey Responses swallowed the refusal into an empty survey picker and left an\napparently usable form offering to load and submit responses. It now shows the\nshared load-error instead, with no retry on a 403.\n\nWhat this does not do, deliberately: gate the four routes. Three of them have\nno READ code to gate on — the catalogue carries none for group levels, surveys\nor interest-rate charts, so a guard would have to name a code that does not\nexist, which is the mistake #696 and #691 record. `/collateral-management` is\nleft alone for the same reason: `READ_COLLATERAL` exists, but whether the\nplatform enforces it on that endpoint is not established from here, and\nguessing is how a gate comes to refuse a user the platform would have allowed.\nDeciding those four needs the codes measured against a live instance, so #751\nstays open for it.\n\n`rbac-forbidden-lists.spec.ts` covers all four screens, not just the two the\nissue asks for, and includes the control the fix needs: a 500 still offers a\nretry, so the retry is withheld for a refusal rather than for any failure.\n"
    },
    {
      "commit": "be727eed344b08da14faf0238a387faba6f097b7",
      "tree": "f9707e9815c6f9ab79bb692e890e43cfffa88f9f",
      "parents": [
        "6343e20e8f1e87305de681db19902e6a38a6379c"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 19:50:15 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 19:50:15 2026 +0530"
      },
      "message": "fix(settings): withhold the OIDC form when its read failed\n\nCloses #749, whose two-factor half was done in #846.\n\nA read that failed for anything other than 404 left this screen showing an\neditable form filled with the platform\u0027s defaults, and a toast that scrolls\naway. The screen does not know what the tenant has configured in that state,\nso Save would overwrite a configuration it never managed to read. It now\nrenders the shared load-error state with a Retry instead, which is what the\ntwo-factor screen already does.\n\nA 404 still opens the form on the defaults, because for this endpoint it is an\nanswer rather than a failure: the tenant has no configuration and the form is\nthere to create one. That distinction is the whole point of the issue, so both\nhalves of it are now pinned by tests.\n\nDelete is offered only when a configuration exists. On a 404 the form is open\nto create one, so the action had nothing to act on and would have asked the\nplatform to remove something that is not there.\n"
    },
    {
      "commit": "c2e7a1468b520945c7c9b917ca63cfad0e25133c",
      "tree": "1a25f1440625c941074da54ad2fb3181936d78eb",
      "parents": [
        "6343e20e8f1e87305de681db19902e6a38a6379c"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 19:04:28 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 19:16:05 2026 +0530"
      },
      "message": "feat(system): configure how Fineract numbers each kind of account\n\nA back-office screen the application did not have: `/accountnumberformats`\ncould be read and written by the platform and by nothing in this UI, so an\ninstitution that wanted its savings accounts numbered with the office in front\nof the sequence had no way to say so.\n\nTwo facts about the endpoint shape the screen rather than being worked around.\nFineract keeps at most one format per account type, so Add offers only the\ntypes without one and is withheld entirely once they all have one — an empty\n`createButtonLabel` hides it, which beats explaining a refusal after the click.\nAnd `PUT /accountnumberformats/{id}` carries `prefixType` alone, so the account\ntype is fixed once the format exists and the prefix is its own row action\nrather than a field on an edit form that the platform would ignore.\n\nThe prefixes differ per account type — a loan can be prefixed by its product\nshort name, a client cannot — and Fineract keys them by the type\u0027s label. The\ncontract keeps that keying, and the row action offers exactly the prefixes its\nown type allows, so there is no dependent-select to keep in step.\n\n`ACCOUNT_NUMBER_FORMAT_API` carries list, template, create, setPrefix and\ndelete; the generated calls stay in the adapter, so no suppression is added.\nThe template is read alongside the list rather than when a dialog opens,\nbecause what it answers decides which actions are offered at all.\n\nCovered twice over, because the two answer different questions.\n`account-number-formats.spec.ts` is mocked and proves the screen\u0027s wiring: the\nrow, the withheld Add, that the PUT body carries `prefixType` and nothing else,\nand that declining a delete sends nothing. `account-number-formats-backend.spec.ts`\nruns against a real Fineract and proves the part only a live platform can — that\nthe three requests are shaped the way it accepts, and that it holds the format,\nthe prefix and then the deletion. It reads the platform back after each step\nrather than trusting the table, picks an account type the instance has free, and\nremoves what it created.\n\nWriting the mocked spec caught two things worth knowing. The dialog\u0027s Confirm\nstays disabled until a required select is answered, which is correct and which a\ntest has to honour. And `selectInDialog` cannot address this dialog\u0027s selects:\nit matches `ion-select[name\u003d...]`, while the shared dialog binds `[name]`, which\nIonic keeps as a property without reflecting it to an attribute — so the test id\nis the handle, as it is for the dialog\u0027s other fields.\n"
    },
    {
      "commit": "6343e20e8f1e87305de681db19902e6a38a6379c",
      "tree": "9196e10939542ee30d655a9b29750082d8e67d1d",
      "parents": [
        "856c03907ffde66d65e9aeb43c822698b5c45ab9",
        "70bdd29b0333b25b29e698ca8f485ec9eb8d803f"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 19:03:21 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 19:03:21 2026 +0530"
      },
      "message": "Merge pull request #868 from Aman-Mittal/feat/789-gl-account-port\n\nfeat(accounting): give GL accounts a contract, and delete one through it"
    },
    {
      "commit": "70bdd29b0333b25b29e698ca8f485ec9eb8d803f",
      "tree": "9196e10939542ee30d655a9b29750082d8e67d1d",
      "parents": [
        "856c03907ffde66d65e9aeb43c822698b5c45ab9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 18:33:58 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sun Nov 08 18:34:09 2026 +0530"
      },
      "message": "feat(accounting): give GL accounts a contract, and delete one through it\n\nFinishes #789. The delete was left out of #866 because it could not be tested:\na new test file importing the generated client fails\n`local/no-generated-api-import`, and ADR 0006 says suppressions only ever\nfall. So the way to add the operation was to remove the reason it could not be\ncovered.\n\n`GL_ACCOUNT_API` carries `list` and `delete`; the generated calls move into\n`FineractGlAccountApi`. The chart screen no longer imports `src/app/api` at\nall, so its suppression entry is gone — pruned by `lint:prune`, not by hand.\nThe backlog is one file smaller rather than one entry larger, which is the\ndirection the ratchet is for.\n\nFlattening the payload on the way in fixed a visible defect nobody had filed.\n`type` and `usage` arrive as `{ id, code, value }`, and the chart bound `usage`\nstraight into a table cell, which interpolated the object: every row read\n\"[object Object]\" in the Usage column. The contract hands the screen a label,\nand both a unit test and a browser test now assert the string is not there.\n\nDelete is gated on `DELETE_GLACCOUNT`, confirms destructively with the refusal\nconditions stated up front — children, journal entries, product mappings —\nand reloads the chart only on success, so an account the platform kept stays\non screen. Fineract\u0027s own reason reaches the operator through the error\ninterceptor.\n\n`chart-of-accounts-actions.spec.ts` covers what needs no backend: the label,\nthe dialog, that nothing is sent until it is accepted, and that declining\nsends nothing. The screen had no browser-level cover before, and the last\nchange of this kind reached a twelve-minute backend shard before anything\nnoticed.\n"
    },
    {
      "commit": "856c03907ffde66d65e9aeb43c822698b5c45ab9",
      "tree": "969153a06be0a8bcd11e0267252697dd1f95b48d",
      "parents": [
        "cf29c331b76f7fb1d8a1e13112098c9fc7ee30ee",
        "6b19cbca560edef1c067944d00a3b0b4ffe3c985"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sun Nov 08 18:28:33 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 08 18:28:33 2026 +0530"
      },
      "message": "Merge pull request #867 from vaidikgarg-bit/feat/788-audit-entry-detail\n\nfeat(audit): open audit entries and filter the trail on the server"
    },
    {
      "commit": "6b19cbca560edef1c067944d00a3b0b4ffe3c985",
      "tree": "969153a06be0a8bcd11e0267252697dd1f95b48d",
      "parents": [
        "cf29c331b76f7fb1d8a1e13112098c9fc7ee30ee"
      ],
      "author": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Wed Nov 04 14:44:40 2026 +0530"
      },
      "committer": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Wed Nov 04 15:11:58 2026 +0530"
      },
      "message": "feat(audit): open audit entries and filter the trail on the server\n\nThe audit trail listed entries but could not open one. Its filter bar\ntook free text, and the Success/Failure status it sent as\nprocessingResult is a parameter current Fineract does not read.\n\nEach row now opens /security/audits/:auditId, guarded on READ_AUDIT\nlike the list, in place of the old payload popup. The page shows\naction, entity and resource, maker and checker with their dates, and\nthe processing result. It lists the\noffice, group, client, loan and savings account the command touched,\nlinking each one whose id Fineract sent; centres and fixed and\nrecurring deposits open their own screens. The command payload is\npretty-printed with secret-looking values masked: password, secret,\ntoken, PIN, API-key and access-key fields, Fineract\u0027s S3, FCM and SMS\nkeys, name/value pairs naming a secret, and secrets inside string\nfields, whether nested JSON or key\u003dvalue text. Boolean settings such\nas passwordNeverExpires stay visible.\n\nThe filter bar offers maker, checker, action, entity and status from\nGET /audits/searchtemplate, plus resource, client and loan ids and a\nmade-on date range. Filters, paging and sort go to the server through\na new AUDIT_API adapter, and paging keeps the filters last applied.\nThe table\u0027s search box still narrows only the loaded page; Fineract\nhas no free-text parameter for audits. toCsv now takes any object so\nthe adapter\u0027s typed rows export as they are.\n\nWhere the adapter departs from the OpenAPI spec:\n- Fineract 1.11 reads the status from processingResult; current\n  Fineract reads status. The lookup id is sent under both names.\n- The list arrives through Gson, which writes dates as epoch\n  milliseconds rather than the declared date-time string.\n- Current Fineract sends officeId, groupId and savingsAccountId\n  (FINERACT-2186), which the spec does not declare. Older servers\n  send only the names, so those show as text without a link.\n\nThe to-date is sent as 23:59:59 because Fineract compares the\ntimestamp column with the string, so a bare date dropped the last day.\ndateFormat and locale are no longer sent; neither version reads them\nfor these filters.\n\nFixes #788\n"
    },
    {
      "commit": "cf29c331b76f7fb1d8a1e13112098c9fc7ee30ee",
      "tree": "04627467c6b3764c12ee151d14e507ddd18ba699",
      "parents": [
        "63a3000c4f90291ef90f9645d52dd225785b67e5",
        "7fdaa68eec7e83b8dc93ab420161842d630e9281"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 18:15:12 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 18:15:12 2026 +0530"
      },
      "message": "Merge pull request #866 from Aman-Mittal/feat/789-closure-comments-charge-delete\n\nfeat(accounting): correct a closure\u0027s comments, and delete a charge definition"
    },
    {
      "commit": "63a3000c4f90291ef90f9645d52dd225785b67e5",
      "tree": "3012be20c2165bdfbd0b8eb0a0d643289681ae87",
      "parents": [
        "2da7fb61c5bb6336a08fe1e6e1b059a92fb2d2bd",
        "e979d8c5637bf1f564d11e67f0d1c85e2e371a1d"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 18:14:04 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 18:14:04 2026 +0530"
      },
      "message": "Merge pull request #864 from vaidikgarg-bit/fix/datetime-opens-on-max-month\n\nfix(clients): open the birth-date picker on the platform\u0027s month"
    },
    {
      "commit": "7fdaa68eec7e83b8dc93ab420161842d630e9281",
      "tree": "f3b3717bf298ca2bd9f28c79baf506303b207bcd",
      "parents": [
        "90febc5b9db224a70d4a56a786d49131ec614899"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 17:49:27 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 17:49:27 2026 +0530"
      },
      "message": "fix(accounting): drive the re-open confirmation through the shared dialog\n\nThe backend shard caught what this change broke. `accounting-closure-backend`\naccepted the re-open through `page.once(\u0027dialog\u0027)` — a browser confirm() that\nno longer fires now that the list uses `DialogService` — so the click opened a\nmodal nobody answered and `waitForResponse` waited out its two minutes. The\nspec\u0027s own comment said \"the list guards this with a native confirm()\", and\nthat sentence stopped being true.\n\nIt now clicks `confirm-dialog-confirm`, the way every other backend spec\nalready answers this dialog. Only the accepting click is raced against the\nDELETE; the click that opens the dialog has to settle first.\n\nTwo more things the same run showed.\n\n`created.text()` was awaited inside an assertion *message*, which Playwright\nbuilds whether the assertion passes or fails. That is a page response, and an\naccepted POST navigates the form to the list, which discards the body — so a\nclosure created perfectly well could fail the test with \"No data found for\nresource with given identifier\". The body is read once now, tolerating its\nabsence, and the id comes from that read.\n\nThe submit button read \"Close Period\" in edit mode, where it closes nothing. It\nreads Save there. The create label stays as it was, untranslated with the rest\nof this form\u0027s static text.\n\nAnd the gap that let a spec-only break reach a twelve-minute backend shard:\nthe closures list had no mocked cover at all. `accounting-closure-actions.spec.ts`\nasserts the wiring that needs no backend — a dialog appears, nothing is sent\nuntil it is accepted, declining sends nothing, and editing sends a PUT carrying\ncomments alone with no editable date on screen. It fails without the fix above.\n"
    },
    {
      "commit": "e979d8c5637bf1f564d11e67f0d1c85e2e371a1d",
      "tree": "3012be20c2165bdfbd0b8eb0a0d643289681ae87",
      "parents": [
        "2da7fb61c5bb6336a08fe1e6e1b059a92fb2d2bd"
      ],
      "author": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Mon Nov 02 14:38:23 2026 +0530"
      },
      "committer": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Mon Nov 02 17:36:29 2026 +0530"
      },
      "message": "fix(clients): open the birth-date picker on the platform\u0027s month\n\nion-datetime picks the month it opens on once, in componentWillLoad, by\nclamping the browser\u0027s today to max. maxChanged only recomputes the\nbound, so a max that arrives later never moves the calendar.\n\nThe family-member form set max from PlatformDateService.resolveToday(),\nafter the picker had loaded. With the browser\u0027s clock in a later month\nthan the tenant\u0027s business date, the picker opened on the browser\u0027s\nmonth with every day disabled, and the days the operator could pick\nwere in an earlier month. That broke the mocked spec on 1 November\n2026, and an operator in that position sees the same thing.\n\nThe form now creates the picker once max is known. Its button moves to\napp-deferred-datetime-button, because the page-level pickersReady flag\nis already true by then and a plain button would look for its picker\nbefore the picker exists (#548).\n\napp-date-field has the same flaw for a caller whose min or max arrives\nlate, though no caller passes either yet. It now rebuilds the picker\nwhen its bounds change.\n\nThe spec pins the browser to 2026-11-20, a month after the business\ndate, so it fails without this change in any month.\nclient-family-date-of-birth.spec.ts screenshots the enclosing ion-item,\nsince the button\u0027s parent is now a display: contents host.\n"
    },
    {
      "commit": "90febc5b9db224a70d4a56a786d49131ec614899",
      "tree": "bca6a83fbb3f07b20bcdda2620d5cc4836979d61",
      "parents": [
        "2da7fb61c5bb6336a08fe1e6e1b059a92fb2d2bd"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 17:03:31 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 17:03:31 2026 +0530"
      },
      "message": "feat(accounting): correct a closure\u0027s comments, and delete a charge definition\n\nTwo of the three operations in #789. The third, deleting a GL account, is\ndeliberately not here — see below.\n\n**Closure comments.** `PUT /glclosures/{id}` carries a single optional\n`comments` and nothing else, which is measurable from the generated request\nmodel, so the closing date is immutable at the API rather than by convention.\nThe form therefore serves edit mode with the office and the closing date\nrendered read-only: offering fields the platform ignores would promise a change\nit never makes. Until now correcting a comment meant deleting the closure —\nwhich re-opens the period — and closing it again.\n\n**Charge definitions.** An unused or mistyped charge stayed selectable on every\nproduct and account form. Delete is gated on `DELETE_CHARGE`, and the refusal\nfor a charge still attached to a product or account arrives in Fineract\u0027s own\nwords through the error interceptor.\n\nThe Re-open action was ungated, though it deletes a closure and lets entries be\nposted into a closed period again. It now requires `DELETE_GLCLOSURE`, and both\nit and the charge delete confirm through `DialogService` rather than the\nbrowser\u0027s `confirm()`, which carried an untranslated string and could not be\nasserted on.\n\nOne of the two existing closure tests was passing for the wrong reason: once the\nhandler became async, `expect(remove).not.toHaveBeenCalled()` held because the\ncall had not been reached yet, not because the decline was honoured. Both now\nawait the handler.\n\n**Why the GL account delete is not here.** It needs a test, and a new test file\nimporting the generated client fails `local/no-generated-api-import` — which is\nexactly what ADR 0006 says should happen. The chart-of-accounts screen predates\nthe adapter boundary and is suppressed, and suppressions only ever fall. Doing\nthat operation properly means giving GL accounts a contract in\n`core/adapters/api` first, which also changes how the chart renders `type` and\n`usage`. That belongs in its own change rather than hidden inside this one.\n"
    },
    {
      "commit": "2da7fb61c5bb6336a08fe1e6e1b059a92fb2d2bd",
      "tree": "ee096a9d6e5984ded657243964dfeac983f0b563",
      "parents": [
        "6a3a6409f48a2ff58fc152fb946e52b347a3f369",
        "61ab360f76d5b2fa9bcad18bbfe225ed0015e367"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 16:29:48 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 16:29:48 2026 +0530"
      },
      "message": "Merge pull request #852 from vaidikgarg-bit/feat/784-share-dividend-approve\n\nfeat(share-dividends): approve a dividend and view its per-account payout"
    },
    {
      "commit": "6a3a6409f48a2ff58fc152fb946e52b347a3f369",
      "tree": "b7367ae34636866170776d16166394988b3e2e9a",
      "parents": [
        "609c6808ec6083fa6be7839e26c615912038e04a",
        "d0d3973116e9fcebb12688bf7c43632b3c06815b"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 16:28:44 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 16:28:44 2026 +0530"
      },
      "message": "Merge pull request #865 from Aman-Mittal/feat/786-teller-delete-cashier-edit\n\nfeat(tellers): delete a teller, and edit a cashier allocation"
    },
    {
      "commit": "d0d3973116e9fcebb12688bf7c43632b3c06815b",
      "tree": "b7367ae34636866170776d16166394988b3e2e9a",
      "parents": [
        "609c6808ec6083fa6be7839e26c615912038e04a"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 15:53:02 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 15:53:02 2026 +0530"
      },
      "message": "feat(tellers): delete a teller, and edit a cashier allocation\n\nCloses #786.\n\nBoth operations existed on the platform and had no screen. Deleting a teller\nmeans one created by mistake, or for a counter that closed, stays in every\nteller list for good. Editing an allocation matters more than it looks: without\nit, changing a cashier\u0027s period or note means deleting the cashier and\nrecreating it, and that breaks the link to the cash transactions already\nrecorded against the allocation. The platform\u0027s `PUT` keeps them.\n\nThrough the adapter boundary, as the feature already was: `delete`,\n`getCashier` and `updateCashier` join the `TELLER_API` port, and the generated\ncalls stay inside the Fineract adapter.\n\n`getCashier` reads one allocation rather than filtering `listCashiers`,\nbecause only the single-cashier response carries `description` — the\ncollection inside the teller does not send it. That field is new on `Cashier`\nfor the same reason: the edit form has to load it to send it back unchanged.\n\nThe date pickers had no `[value]` binding at all, so a loaded allocation\u0027s\ndates had nowhere to land and the edit form would have shown today for both.\nThey bind now, and the two fields hold ISO strings instead of `Date` — the\nformat `ion-datetime` reads and emits, and the one the adapter sends, so\nnothing converts in either direction.\n\n`hourStartTime` and `hourEndTime` are accepted by the platform and left out,\nbecause the create form does not collect them either; the two stay at parity.\n\nThe adjacent cashier removal moves onto `DialogService.confirm` as part of\nthis. It used the browser\u0027s `confirm()` with an untranslated English string,\nwhich cannot be styled or asserted on, and it would otherwise have sat beside\na destructive action with a translated confirmation of its own. Its message now\npoints at editing as the alternative to removing and recreating.\n\nGating per the issue: `DELETE_TELLER` and `UPDATECASHIERALLOCATION_TELLER`\nthrough `appRequiresPermission`, with the new edit route behind\n`permissionGuard`. Fineract defines no `READ_` code for tellers, so the reads\nstay as they are.\n\nThe delete refusal — a teller that still holds cashiers or transactions — is\nthe platform\u0027s own message, already surfaced by the error interceptor, so it\nis not restated. The list is reloaded only on success: a row still there is\nthe truthful outcome of a refused delete, and a test pins that.\n"
    },
    {
      "commit": "609c6808ec6083fa6be7839e26c615912038e04a",
      "tree": "a1e14fe799003c97fb50e4ae078ff84e6bc8d7ab",
      "parents": [
        "77ee03691d50d288b0423134a3dcb971eacb42fa",
        "a09f53a2c10c4bd93f5d8f2dcc0b932dbbd69d19"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 15:10:44 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 15:10:44 2026 +0530"
      },
      "message": "Merge pull request #853 from zhangxinyao88/codex/issue-684-disabled-date-field\n\nfix(ui): honor disabled state in the shared date field"
    },
    {
      "commit": "77ee03691d50d288b0423134a3dcb971eacb42fa",
      "tree": "b629e84629f77b64c8520eb1f5c9feb2876b0605",
      "parents": [
        "55ed3dbca8893d2e107bd99a2aecf32d2ea93020",
        "92b5a8923932741b55b0bbe478c7571f7a7f9ff1"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 15:09:53 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 15:09:53 2026 +0530"
      },
      "message": "Merge pull request #862 from Aman-Mittal/chore/sync-fineract-spec-nov02\n\nchore(api): sync the Fineract OpenAPI spec, with checks that actually run"
    },
    {
      "commit": "55ed3dbca8893d2e107bd99a2aecf32d2ea93020",
      "tree": "618dd9a514447ca92e9414f1d9e5e2c80e175df5",
      "parents": [
        "cffd7fc8e272099287e1d2fc8278fb79270e0eb5",
        "1fe2d2150cd8152201c89d67d4c703b4daff1020"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 14:45:31 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 14:45:31 2026 +0530"
      },
      "message": "Merge pull request #851 from Aman-Mittal/fix/wc-date-defaults\n\nThe remaining ten date fields, decided against the platform\u0027s own rule"
    },
    {
      "commit": "92b5a8923932741b55b0bbe478c7571f7a7f9ff1",
      "tree": "d886c0b63b79836ff13348da8ce0f93cc90160f2",
      "parents": [
        "872089a0b8005b3ac3c6d06cea66a6fe6cef0e4d"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 14:35:04 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 14:35:04 2026 +0530"
      },
      "message": "fix(products): the loan product\u0027s frequency labels moved to `value`\n\nThe spec sync drops `description` from `GetLoanProductsInterestRateFrequencyType`\nand `GetLoanProductsRepaymentFrequencyType` and adds `value` in its place, which\nis the shape every other Fineract enum option already has. The loan product\nscreen rendered `description` beside the interest rate and the repayment\ninterval, so AOT compilation failed on both with TS2339.\n\n`value` is also what the mocks already carry — `loan-product-down-payment.spec.ts`\nsupplies `{ id: 3, code: \u0027y\u0027, value: \u0027Per year\u0027 }` — so these two labels were\nrendering blank in that spec before, and now render.\n\nWhy CI caught this and my own check did not: these are template expressions, so\n`tsc -p tsconfig.app.json --noEmit` never looks at them. Only the Angular\ncompiler does, which is why every job that builds the application failed while\nlint, format and the drift check passed. For a generated-client sync the build\nis the gate, not `tsc`.\n"
    },
    {
      "commit": "cffd7fc8e272099287e1d2fc8278fb79270e0eb5",
      "tree": "4f6de642731899f0c04c78e21ed6ff889e8df6a0",
      "parents": [
        "0825f9c79f2f932d70fe41b99d29c10accaa2f35",
        "c46d4a0b0522965e7892c50af3c373495f48079e"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 14:32:05 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 14:32:05 2026 +0530"
      },
      "message": "Merge pull request #863 from Aman-Mittal/chore/node-26-lts\n\nchore: build on Node 26, the current LTS line"
    },
    {
      "commit": "c46d4a0b0522965e7892c50af3c373495f48079e",
      "tree": "4f6de642731899f0c04c78e21ed6ff889e8df6a0",
      "parents": [
        "0825f9c79f2f932d70fe41b99d29c10accaa2f35"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 13:51:29 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 13:51:29 2026 +0530"
      },
      "message": "chore: build on Node 26, the current LTS line\n\nNode 26 \"Lithium\" became the active LTS line in October, which moved Node 24\nto maintenance. Angular 22 already supports it — every `@angular/*` package in\nthe tree declares `engines.node: ^22.22.3 || ^24.15.0 || \u003e\u003d26.0.0` — so this\nis a move onto a supported runtime, not ahead of one.\n\nThe release image and the offline-build job both pin `node:24-alpine` by\ndigest, so neither follows a tag; both move here together, to the current\n`node:26-alpine` manifest-list digest. The CI jobs that ask for `lts/*` need no\nchange: that already resolved to 26 the moment the line went LTS, which is\nprecisely the gap this closes — the container builds were a major behind what\nevery other job was using.\n\n`engines.node` moves to `\u003e\u003d26.0.0`, matching the Angular range\u0027s own\nexpression of this line. npm reports a mismatch as a warning rather than an\nerror, and there is no `.npmrc` setting `engine-strict`, so a contributor still\non 22 or 24 is told rather than blocked.\n"
    },
    {
      "commit": "872089a0b8005b3ac3c6d06cea66a6fe6cef0e4d",
      "tree": "756a08e1b02ac4aa3fe2002cd4331d74ac40c9cf",
      "parents": [
        "0825f9c79f2f932d70fe41b99d29c10accaa2f35"
      ],
      "author": {
        "name": "github-actions[bot]",
        "email": "41898282+github-actions[bot]@users.noreply.github.com",
        "time": "Mon Nov 02 04:23:50 2026 +0000"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 13:49:38 2026 +0530"
      },
      "message": "chore(api): sync the Fineract OpenAPI spec and regenerate the client\n\n"
    },
    {
      "commit": "0825f9c79f2f932d70fe41b99d29c10accaa2f35",
      "tree": "47ea5e5e4cda1fd85cf3efd1d1c8a3d8a025607c",
      "parents": [
        "7f3e2792c1c8578a19d2856b124d76fe5665e5eb",
        "c143d53095ba76a001fb670f4da37e57cad6799f"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 13:43:24 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 13:43:24 2026 +0530"
      },
      "message": "Merge pull request #855 from apache/dependabot/github_actions/codeql-271845e79b\n\nchore(deps): bump the codeql group with 2 updates"
    },
    {
      "commit": "1fe2d2150cd8152201c89d67d4c703b4daff1020",
      "tree": "76804d3abe403990c55321b5fb2b88fce092a62a",
      "parents": [
        "9fd3d26047ccfc077f38f58b1294418e4409a918",
        "7f3e2792c1c8578a19d2856b124d76fe5665e5eb"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 13:43:14 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 13:43:14 2026 +0530"
      },
      "message": "Merge apache/main into fix/wc-date-defaults\n\nPicks up the dependency refresh (#858) and the family birth-date clock pin\n(#859), whose absence was the only remaining red check here.\n"
    },
    {
      "commit": "7f3e2792c1c8578a19d2856b124d76fe5665e5eb",
      "tree": "aa6b2123c140f62f77f08698e36420863dc06140",
      "parents": [
        "154fbda1efd9f18d747dbb06b4ab288f1027bee3",
        "4f6d1ab03ceb8518afec7160980117f0f5363630"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 13:42:44 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 13:42:44 2026 +0530"
      },
      "message": "Merge pull request #860 from apache/dependabot/npm_and_yarn/follow-redirects-1.16.2\n\nchore(deps-dev): bump follow-redirects from 1.16.1 to 1.16.2"
    },
    {
      "commit": "154fbda1efd9f18d747dbb06b4ab288f1027bee3",
      "tree": "c2aa0b6e8db9793798131fd7512a16f43a007f45",
      "parents": [
        "9e7722e45cd569692e9181b1a962d8c15c45951c",
        "1044ee52a366285f991caa5bb0220805b5abed41"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 13:42:24 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 13:42:24 2026 +0530"
      },
      "message": "Merge pull request #859 from Aman-Mittal/fix/family-dob-spec-month-boundary\n\ntest(clients): pin the browser clock in the family birth-date test"
    },
    {
      "commit": "4f6d1ab03ceb8518afec7160980117f0f5363630",
      "tree": "fe7e329039e862eb5e366e0ea3170762c7a8f521",
      "parents": [
        "9e7722e45cd569692e9181b1a962d8c15c45951c"
      ],
      "author": {
        "name": "dependabot[bot]",
        "email": "49699333+dependabot[bot]@users.noreply.github.com",
        "time": "Mon Nov 02 07:45:34 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 07:45:34 2026 +0000"
      },
      "message": "chore(deps-dev): bump follow-redirects from 1.16.1 to 1.16.2\n\nBumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.16.1 to 1.16.2.\n- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)\n- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.16.1...v1.16.2)\n\n---\nupdated-dependencies:\n- dependency-name: follow-redirects\n  dependency-version: 1.16.2\n  dependency-type: indirect\n...\n\nSigned-off-by: dependabot[bot] \u003csupport@github.com\u003e"
    },
    {
      "commit": "9e7722e45cd569692e9181b1a962d8c15c45951c",
      "tree": "bba91816884e5ab24c7f922e1cb97a0d5fc59de3",
      "parents": [
        "b55505e7546a21d3bd82c6df899b15c77ccd1d9e",
        "35ff67344c11b68e047d2190aac9aea06703ae38"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 13:11:41 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 13:11:41 2026 +0530"
      },
      "message": "Merge pull request #856 from apache/dependabot/github_actions/github-actions-d6f809a3bd\n\nchore(deps): bump zizmorcore/zizmor-action from 0.7.1 to 0.8.0 in the github-actions group"
    },
    {
      "commit": "b55505e7546a21d3bd82c6df899b15c77ccd1d9e",
      "tree": "106031dc71a566034ec25187b54ec644ad9b1bd5",
      "parents": [
        "7d77a83a23b44a22068e2a011dd52402de58e79d",
        "f6878b5f162dc8aa6dd90f00a28d018d7f2c52e3"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 13:07:31 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 13:07:31 2026 +0530"
      },
      "message": "Merge pull request #858 from Aman-Mittal/chore/dependency-refresh\n\nchore(deps): refresh every dependency that is not held by an upstream cap"
    },
    {
      "commit": "1044ee52a366285f991caa5bb0220805b5abed41",
      "tree": "535a3e2954f041fb9f1beae86ffc7a9730ed44a0",
      "parents": [
        "7d77a83a23b44a22068e2a011dd52402de58e79d"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 12:58:31 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 12:58:31 2026 +0530"
      },
      "message": "test(clients): pin the browser clock in the family birth-date test\n\n`main` is red, and not from anything merged into it: the test names calendar\ncells in October while the picker renders the browser\u0027s month, so it passed\nevery day of October 2026 and began failing on 1 November, when the picker\nstarted opening on November and \"Friday, October 16\" stopped existing. The\n`max` it asserts was never in question — that comes from the mocked business\ndate and still reads 2026-10-15.\n\nFixed where the assumption lives, by fixing the clock before the app loads.\n`setFixedTime` rather than `install`, for the reason recorded in\n`tenant-date-defaults-backend.spec.ts`: it changes what `new Date()` answers\nwithout replacing the timer queue Angular and Ionic run on.\n\nThe pinned day is deliberately a different, later day than the business date,\nso the point of the test survives: if the form ever took its ceiling from the\nbrowser, `max` would read 2026-10-20 and the assertion would catch it.\n\nThe two other specs that name April cells need no such pin — their pickers\nopen on a loaded value, not on today, so no month boundary can move them.\n"
    },
    {
      "commit": "f6878b5f162dc8aa6dd90f00a28d018d7f2c52e3",
      "tree": "106031dc71a566034ec25187b54ec644ad9b1bd5",
      "parents": [
        "7d77a83a23b44a22068e2a011dd52402de58e79d"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 12:37:21 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 12:37:21 2026 +0530"
      },
      "message": "chore(deps): refresh every dependency that is not held by an upstream cap\n\nThe Angular family moves together, which is the only way it moves at all:\nthe pinned `@angular/*` packages peer-depend on each other by exact version,\nand `@angular-devkit/build-angular` depends on `@angular/build` exactly, so\n`npm install` refuses any subset. Run through `ng update` rather than by hand\nso the whole set resolves at once: core and its siblings to 22.2.4, the CLI\nfamily to 22.2.5.\n\nAlso current now: @angular/cdk, @angular-architects/native-federation,\n@softarc/native-federation-orchestrator, @ngx-translate/{core,http-loader},\ntslib, eslint-plugin-security, and typescript-eslint.\n\nDeliberately not included, each capped upstream rather than merely untried:\n\n  - typescript 7: `@angular/build` peers `typescript \u003e\u003d6.0 \u003c6.1`. This is\n    what makes the open Angular-group bump uninstallable, since Dependabot\n    groups typescript with Angular.\n  - eslint 10: eslint-plugin-import 2.32.0 is its newest release and peers\n    eslint ^9, so the whole linting group fails with or without grouping.\n  - eslint-plugin-unicorn 79 and @ionic/angular 9: majors that need their own\n    review, and Ionic 9 currently fails the production build.\n\n`npm ci` installs clean, which is the check the open dependency pull requests\nfail. Verified beyond it: lint, tsc, format, 329 unit files / 2822 tests, and\na production build.\n"
    },
    {
      "commit": "9fd3d26047ccfc077f38f58b1294418e4409a918",
      "tree": "b76e810034ea08ca142dd8ad9d1c728bbe59055d",
      "parents": [
        "958cb2503738ee14c072c62dda0ed2091cce79c3"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 12:20:57 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Mon Nov 02 12:20:57 2026 +0530"
      },
      "message": "fix(ui): the modify path does not require an expected disbursement date\n\nRequiring the field in both modes disabled Save on a submitted application\nwhose timeline carries no expected disbursement date, with nothing on screen\nnaming the field at fault. Angular\u0027s own RequiredValidator attaches to the\nfield host, which carries both `ngModel` and `required`, so the marker is not\ndecorative: it decides `loanForm.invalid`.\n\nThe platform does not require it there. `validateForCreate` calls `notNull()`\non the parameter unconditionally, but the update path validates it only when\nthe request carries it and otherwise resolves it from the loan\u0027s existing\ndisbursement detail, so a modification that leaves the date alone is\nlegitimate.\n\nCaught by `working-capital-loan-actions.spec.ts`, whose modify-route case\nmocks a loan without the date and timed out waiting for a PUT that a disabled\nbutton never sent. That case is the regression test: the unit suite cannot see\nthis, because its edit-mode fixture carries the date and a second\n`createComponent` registers no controls to validate.\n"
    },
    {
      "commit": "7d77a83a23b44a22068e2a011dd52402de58e79d",
      "tree": "9b0ed81ec71bb54c84961be77502ba3cbd060668",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700",
        "4ba28e3438d4d79f799bb8140ef1c43260f5a6d3"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Mon Nov 02 11:44:14 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 11:44:14 2026 +0530"
      },
      "message": "Merge pull request #850 from Aman-Mittal/fix/date-fields-showing-today\n\nFive optional date fields showed today for a value nobody set"
    },
    {
      "commit": "35ff67344c11b68e047d2190aac9aea06703ae38",
      "tree": "430b94bca3faf7459bef6f9d598c83d81a19c4f6",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700"
      ],
      "author": {
        "name": "dependabot[bot]",
        "email": "49699333+dependabot[bot]@users.noreply.github.com",
        "time": "Sun Nov 01 11:48:34 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 11:48:34 2026 +0000"
      },
      "message": "chore(deps): bump zizmorcore/zizmor-action\n\nBumps the github-actions group with 1 update: [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).\n\n\nUpdates `zizmorcore/zizmor-action` from 0.7.1 to 0.8.0\n- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)\n- [Commits](https://github.com/zizmorcore/zizmor-action/compare/85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e...53290badfa369cb0bf7ad16342cb62c2fa47ac7b)\n\n---\nupdated-dependencies:\n- dependency-name: zizmorcore/zizmor-action\n  dependency-version: 0.8.0\n  dependency-type: direct:production\n  update-type: version-update:semver-minor\n  dependency-group: github-actions\n...\n\nSigned-off-by: dependabot[bot] \u003csupport@github.com\u003e"
    },
    {
      "commit": "c143d53095ba76a001fb670f4da37e57cad6799f",
      "tree": "faf9f08b9812610ba1376eb2bb8b50fc29afb132",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700"
      ],
      "author": {
        "name": "dependabot[bot]",
        "email": "49699333+dependabot[bot]@users.noreply.github.com",
        "time": "Sun Nov 01 11:47:44 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 11:47:44 2026 +0000"
      },
      "message": "chore(deps): bump the codeql group with 2 updates\n\nBumps the codeql group with 2 updates: [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).\n\n\nUpdates `github/codeql-action/init` from 4.39.0 to 4.39.1\n- [Release notes](https://github.com/github/codeql-action/releases)\n- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/github/codeql-action/compare/6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)\n\nUpdates `github/codeql-action/analyze` from 4.39.0 to 4.39.1\n- [Release notes](https://github.com/github/codeql-action/releases)\n- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/github/codeql-action/compare/6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)\n\n---\nupdated-dependencies:\n- dependency-name: github/codeql-action/init\n  dependency-version: 4.39.1\n  dependency-type: direct:production\n  update-type: version-update:semver-patch\n  dependency-group: codeql\n- dependency-name: github/codeql-action/analyze\n  dependency-version: 4.39.1\n  dependency-type: direct:production\n  update-type: version-update:semver-patch\n  dependency-group: codeql\n...\n\nSigned-off-by: dependabot[bot] \u003csupport@github.com\u003e"
    },
    {
      "commit": "a09f53a2c10c4bd93f5d8f2dcc0b932dbbd69d19",
      "tree": "906e65e26014d6db0e03f29bf4be6e58f895f8c8",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700"
      ],
      "author": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Sun Nov 01 06:07:05 2026 -0500"
      },
      "committer": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Sun Nov 01 06:07:05 2026 -0500"
      },
      "message": "fix(ui): honor disabled state in the shared date field\n"
    },
    {
      "commit": "61ab360f76d5b2fa9bcad18bbfe225ed0015e367",
      "tree": "8a2c1dd651e01668fb17bd06c05730e2ce1f3323",
      "parents": [
        "c9e0e97bffd2770498595df1bb68045952f12f49"
      ],
      "author": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Sun Nov 01 00:59:45 2026 +0530"
      },
      "committer": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Sun Nov 01 01:20:00 2026 +0530"
      },
      "message": "feat(share-dividends): open a dividend, see its payouts and approve it\n\nFineract posts a share dividend to the accounts only after it is approved.\nThe UI had no way to approve one, so every dividend declared here stayed\npending.\n\nEach row in the dividend list now has a View link to a detail page at\nshares/:productId/dividends/:dividendId, guarded on\nREAD_DIVIDEND_SHAREPRODUCT. The page shows the period, total and status.\nThese come from the product\u0027s dividend list, because the detail endpoint\nreturns only the account rows. Below them is a paged table of what each\naccount receives, searchable by exact account number.\n\nA pending dividend offers Approve and Delete, gated on\nAPPROVE_DIVIDEND_SHAREPRODUCT and DELETE_DIVIDEND_SHAREPRODUCT. Approve\nasks for confirmation, stating the total and the number of accounts.\nOnce an approval goes through, Approve and Delete stay disabled for the\nrest of the visit. The page refreshes in place, and if the refresh fails\nwith anything other than 403 or 404, the page stays as it was.\n\nA queued maker-checker approval comes back with rollbackTransaction set\nand the dividend\u0027s id as resourceId, so isMakerCheckerPending alone would\nread it as applied. The adapter checks rollbackTransaction as well. When\nan approval is queued, the user is told it was submitted and that the\ndividend stays pending until a checker acts. Keeping both buttons disabled\nmeans the user cannot queue the command twice or delete the dividend while\na checker is acting on it.\n\nThe list gains a status column. Its Delete appears only on pending rows\nand is disabled without DELETE_DIVIDEND_SHAREPRODUCT.\n\nSHARE_DIVIDEND_API gains list, get and delete. Both screens now go through\nit, which removes their generated-client lint suppressions. The Hindi\ncatalogue gains COMMON.MUTATION_SUBMITTED_FOR_APPROVAL, which the approve\ntoast uses.\n"
    },
    {
      "commit": "c9e0e97bffd2770498595df1bb68045952f12f49",
      "tree": "b608ac6264483c9d16d6a333c4bc9ada341f6e97",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700"
      ],
      "author": {
        "name": "Vaidik Garg",
        "email": "vaidik2008garg@gmail.com",
        "time": "Fri Oct 30 18:20:31 2026 +0530"
      },
      "committer": {
        "name": "Vaidik Garg",
        "email": "319366490+vaidikgarg-bit@users.noreply.github.com",
        "time": "Sun Nov 01 00:48:01 2026 +0530"
      },
      "message": "feat(share-dividends): add an adapter for approving a dividend\n\nGroundwork for #784. SHARE_DIVIDEND_API.approve sends\nPUT /shareproduct/{productId}/dividend/{dividendId}?command\u003dapprove\nthrough the generated client and reports whether maker-checker queued\nit. The detail view and the list changes follow.\n"
    },
    {
      "commit": "958cb2503738ee14c072c62dda0ed2091cce79c3",
      "tree": "f13f5a7efe03fd189ff30f0e92d844987bcf4998",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 22:07:59 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 22:07:59 2026 +0530"
      },
      "message": "fix(ui): decide the remaining ten date fields against the platform\u0027s own rule\n\n#850 fixed five optional date fields that showed today for a value nobody\nset, and deferred ten because the right fix depends on whether the platform\nrequires the field — a placeholder on a mandatory date only buys a\nserver-side refusal. Those ten are settled here, from Fineract\u0027s own\nvalidators rather than by inspection of the UI.\n\n**Optional, so the field says it is unset**\n\n- `wc-loan-form` submittedOnDate. `WorkingCapitalLoanApplicationDataValidator`\n  documents it as optional and substitutes the platform\u0027s current date when\n  it is absent — which is the tenant\u0027s date, better than anything the browser\n  could offer.\n- `wc-loan-product-form` startDate and closeDate: validated only when both\n  are present.\n- `business-dates` businessDate and cobDate: both arrive from the server, so\n  the placeholder covers the window before they land rather than showing\n  today as if it were the tenant\u0027s date.\n- `email-campaign-form` scheduledStartDate: `EmailCampaignValidator` requires\n  it only for a SCHEDULE campaign. The field is not gated by campaign type in\n  this form, so it is left optional here and marking it required per type is\n  its own change.\n\n**Mandatory with a sensible default, so it is seeded**\n\n- `wc-loan-form` expectedDisbursementDate. The same validator marks it\n  mandatory, so it is seeded with the platform\u0027s today through\n  `PlatformDateService`, as the client and loan forms do. Seeded after the\n  edit-mode decision, not before: the resolution can answer immediately from\n  cache, and seeding first would write today over a loan\u0027s own date.\n\n**Mandatory with no sensible default, so the field blocks Save**\n\n- `wc-breach-action-form` and `wc-delinquency-action-form` endDate. Both are\n  `notNull()` for the Pause action, which these forms already gate correctly,\n  but the platform also enforces `startDate \u003c endDate` — so today is not a\n  default, it is a value that would be refused. The field is required and\n  shows a placeholder until the operator chooses.\n- `wc-loan-action-form` expectedDisbursementDate, offered only for disburse.\n  I could not establish that command\u0027s rule from the module source, so it\n  asks explicitly rather than defaulting a date for an action that moves\n  money.\n\nAll five existing specs for these forms pass unedited (67 cases), and the one\nbehaviour change — seeded versus deliberately unset, in the same form — is\npinned by a new case naming the validator that decides it.\n\nFive of the converted items carried layout classes that the structural\nconversion dropped. They are back on the field host, where `.form-item`\u0027s\n`--border-radius` still reaches the `ion-item` inside because custom\nproperties inherit.\n"
    },
    {
      "commit": "4ba28e3438d4d79f799bb8140ef1c43260f5a6d3",
      "tree": "9b0ed81ec71bb54c84961be77502ba3cbd060668",
      "parents": [
        "6f3f5674f1ce0b81d085d67efa516215ef6bb700"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 21:50:50 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 21:50:50 2026 +0530"
      },
      "message": "fix(ui): stop five optional date fields showing today for a value nobody set\n\nAn `ion-datetime` with no value still renders *today* on its button. Three\nforms hand-roll a placeholder for that; the rest do not, so an optional date\nthat has never been touched reads as the current date.\n\nMeasured across the feature tree: 76 hand-written date triads carry no\nplaceholder, and of those, **15 are bound to a model that starts empty** —\nthe ones where the button is actively wrong rather than merely showing a\nseeded default. The clearest is a loan guarantor\u0027s date of birth, which\nshowed every guarantor as born this morning.\n\nFive of the fifteen are converted here, the ones whose semantics are\nunambiguously optional: the guarantor\u0027s date of birth, three charge due\ndates (loan, savings, working-capital) and a reschedule date. They now use\n`app-date-field`, which shows a placeholder while unset and offers a clear\naction once set, so the fix and the migration are the same change.\n\nThe regression test lives with the defect, in the guarantor form\u0027s own spec.\nNone of the four existing specs for these forms needed editing.\n\nThe other ten are left for now and not because they are harder. For a field\nthe platform actually requires, the right fix is a seeded default — as the\nclient and loan forms do through `PlatformDateService` — not a placeholder\nthat lets an operator submit nothing. Deciding that per field needs the\nplatform\u0027s own rule for each, which is a different piece of work from this\none. The email campaign form\u0027s triad is also shaped differently enough that\nthe structural conversion did not match it.\n"
    },
    {
      "commit": "6f3f5674f1ce0b81d085d67efa516215ef6bb700",
      "tree": "f12fc6eb6334ec69c2fb41f34c5f47b5606a16df",
      "parents": [
        "c53139431431165796fcefde4b8d2f3a0fee54f9",
        "0be98095f3e15a8056a7f14ae225e393148375ee"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 21:43:27 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 21:43:27 2026 +0530"
      },
      "message": "Merge pull request #849 from Aman-Mittal/feat/date-and-card-primitives\n\nA date field and a card: the primitives #684 pairs with the form field"
    },
    {
      "commit": "0be98095f3e15a8056a7f14ae225e393148375ee",
      "tree": "f12fc6eb6334ec69c2fb41f34c5f47b5606a16df",
      "parents": [
        "c53139431431165796fcefde4b8d2f3a0fee54f9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 20:47:58 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 20:47:58 2026 +0530"
      },
      "message": "feat(ui): a date field and a card, the primitives #684 pairs with the form field\n\nFinishes #287\u0027s component set and answers #684\u0027s thesis: the card and form\nprimitives only pay off together, because a form is a card with fields in it.\n\n**app-date-field** owns the `ion-datetime-button` + `ion-modal` +\n`ion-datetime` triad, written by hand in 59 components and the most\nerror-prone block of the set. Three hazards live in it:\n\n- the button resolves its picker by id in `componentWillLoad`, so it must not\n  mount first (#544, #548). Composes `app-deferred-datetime-button`, which\n  already solves that per instance rather than per page.\n- an unset picker renders *today* on its button, which reads as a value the\n  operator chose. Three forms hand-roll a placeholder for it; here it is the\n  default whenever there is no value.\n- nothing names the control. Unlike a text field there is no `label` property\n  to slot into — the focusable element is a button whose accessible name would\n  be the date it shows — so the label is referenced by `aria-labelledby`.\n  Proven in a browser: `getByLabel(\u0027Opening Date\u0027)` resolves the button.\n\nUnlike `app-form-field` this one owns its value. Three elements that have to\nknow each other\u0027s ids cannot be projected, so it is a `ControlValueAccessor`\ninstead — `name` and `[(ngModel)]` work on it exactly as they did on the\n`ion-datetime` inside, and its own spec pins that `required` still registers\nin the form.\n\n**app-card** replaces the four Ionic card elements. ADR 0005 closes that\nboundary to new code, so a new screen either reached past it or hand-rolled\nits own panel markup — #847 did the latter for want of this. Its heading is\n`heading`, not `title`: a `title` input also lands on the host element, where\nbrowsers render it as a hover tooltip over the whole card.\n\nConverted, both with their specs untouched: the office form\u0027s opening date,\nwhich completes that form and removes the last seven Ionic imports it had, so\nthe feature now names no vendor element at all; and the working-capital\noverview\u0027s panels, which go back to being cards.\n\nA placeholder passed in from outside turned out not to reach the slot: a\nselected `ng-content` does not match a node declared inside an `@if`, so the\nbutton renders its own placeholder from an input instead. Measured, with the\nbutton still showing today\u0027s date before the change.\n\nSTYLE.md documents both and no longer points at the hand-written shapes.\n"
    },
    {
      "commit": "c53139431431165796fcefde4b8d2f3a0fee54f9",
      "tree": "69453271e9534745d6252221dd04c17204095a9c",
      "parents": [
        "8d577d077e9046b3d3d6b3207999b43e466d7105",
        "3f4a326786b4ffb166b135667d844acf889c10cc"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 20:21:45 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 20:21:45 2026 +0530"
      },
      "message": "Merge pull request #848 from Aman-Mittal/feat/form-field-primitive\n\nA shared form field whose label actually labels its control (#287, part one)"
    },
    {
      "commit": "3f4a326786b4ffb166b135667d844acf889c10cc",
      "tree": "69453271e9534745d6252221dd04c17204095a9c",
      "parents": [
        "8d577d077e9046b3d3d6b3207999b43e466d7105"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 19:55:20 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 19:55:20 2026 +0530"
      },
      "message": "feat(ui): a form field whose label actually labels its control\n\nCloses the first part of #287: the components plus one converted form, which\nis the scope that issue asks for.\n\nEvery form in this application writes the same block by hand — an\n`ion-item`, an `ion-label position\u003d\"stacked\"` beside the control, an\n`[attr.aria-label]` repeating the same string, and ten more lines for the\nerror. 627 of the first and 633 of the second.\n\nIn Ionic 8 that sibling label names nothing. `getLabelledById()` in\n`@ionic/core` reads the control\u0027s own `label` property or its `label` slot\nand nothing else, so the accessible name came only from the hand-repeated\n`aria-label` — and deleting one as redundant silently unnames the field.\n\n`app-form-field` renders the label itself and then moves that node into the\ncontrol\u0027s `label` slot after the first render. Angular keeps owning the node,\nso the translation and the required marker keep updating in place; Ionic sees\na slotted label and emits `aria-labelledby` against its own inner input. The\nalternative, setting the control\u0027s `label` string property, cannot carry the\nmarker as markup and would have put an asterisk inside the accessible name.\n\nThe control stays the caller\u0027s — projected, not rendered here. 574 `ngModel`\nbindings and the `name` that template-driven validity registers by would\notherwise all have changed shape.\n\nThe error is shown on request rather than derived. Deriving it was tried:\n`contentChild(NgModel)` resolves and both `invalid` and `touched` are true at\nthe point the error should appear, but a block conditioned on them never\nrenders — measured by removing the bindings and watching the converted form\u0027s\nerror cases fail, then restoring them and watching them pass. The likely\ncause is that the only signal such a condition reads is the query itself,\nwhich does not change when the control\u0027s plain getters do. That mechanism is\na guess and is written down as one; the behaviour is not.\n\nThe office form is converted, as #287 suggests: 82 lines of template out, 43\nin, for three of its four fields. The date triad is left alone — it has no\n`label` property to slot into and wants its own `app-date-field`, which the\nissue lists separately.\n\n`.app-form-container` lands in `_common.scss`, with the width as a custom\nproperty so a form overrides one value instead of re-declaring padding and\ncentring. 92 components still declare their own copy at five different\nwidths; converting them is two lines each and deliberately not done in bulk.\n\nSTYLE.md taught the old shape, which is why it kept being written. It now\nteaches this one and shows the old one as wrong, with the reason.\n"
    },
    {
      "commit": "8d577d077e9046b3d3d6b3207999b43e466d7105",
      "tree": "487433e0148f710f66c6becd596b372660d6cc6a",
      "parents": [
        "1d1a5e9f7ea6b33e81a05b62d2bb243e5a805277",
        "01ee4e7806aac9ada2a79c0ad4615565ebb3eee3"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 19:31:01 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 19:31:01 2026 +0530"
      },
      "message": "Merge pull request #847 from Aman-Mittal/feat/wc-portfolio-overview\n\nWorking capital: a portfolio overview, loan lookup by external id, and the internal commands behind developer tools"
    },
    {
      "commit": "01ee4e7806aac9ada2a79c0ad4615565ebb3eee3",
      "tree": "487433e0148f710f66c6becd596b372660d6cc6a",
      "parents": [
        "0ca4f52ffc5e4289766afc29265355a2804ebfae"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 19:10:36 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 19:10:36 2026 +0530"
      },
      "message": "test(working-capital): cover the portfolio overview in the browser\n\nTwo mocked cases: that the overview adds the book up and bands its\ndelinquency where the platform put it, and that an external reference\nmatching no loan is reported with the reference named back.\n\nThe fixture book is three loans, one current, one 45 days down and one closed\nand charged off, so the tiles, the band split and the flag count each have a\nfigure that could only come from adding the right rows.\n"
    },
    {
      "commit": "0ca4f52ffc5e4289766afc29265355a2804ebfae",
      "tree": "cb2b84ba721cf4afa857e5a39dec22595a71a8e1",
      "parents": [
        "983acca4380d31950cf30295ddd20ceaffa3e631"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 19:09:03 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 19:09:03 2026 +0530"
      },
      "message": "feat(working-capital): gate the loan test commands behind developer tools\n\nThe three `/v1/internal/**` working-capital commands — activate a loan,\nregenerate its amortization schedule, step it into the next delinquency\nperiod — had no way in. They drive a test instance into states the ordinary\ncommands cannot reach.\n\nThey are not product operations and are not offered as such. Fineract serves\n`/v1/internal/**` only under its `test` Spring profile, which upstream says\nmust not be enabled in production, so on any other deployment they answer\n404. The route is matched only where `developerToolsEnabled` is set, the nav\nentry is a `developerTool`, and the screen states the constraint before\noffering anything rather than leaving an operator to discover the 404.\n\nThey live in their own port rather than joining the working-capital read\nport: a product contract should not be able to grow a dependency on a\ntest-only endpoint by accident, and `check-internal-endpoints.mjs` then has\none file to pin instead of a screen each — its allow-list carries the new\nentry with that reason.\n\nThe optional date parameters each command accepts are left to the platform\u0027s\ndefault. Their format cannot be verified without an instance running that\nprofile, and a guessed format would turn a tool into a source of confusing\n400s; the port takes them for when there is somewhere to check them against.\n\nFor the same reason the success message says the platform accepted the\ncommand rather than claiming an outcome: the screen has not re-read the loan.\nThese three are the one part of this work I could not exercise end to end,\nand the tests pin the shape rather than the behaviour of a live instance.\n"
    },
    {
      "commit": "983acca4380d31950cf30295ddd20ceaffa3e631",
      "tree": "52c416484acdef89ef4ef845007ff764328437ff",
      "parents": [
        "08a6b320c60362c2bb54fc59f9ed5f3906e35b0b"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 18:59:15 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 18:59:15 2026 +0530"
      },
      "message": "feat(working-capital): open a loan by its external id\n\nFineract addresses every working-capital resource twice — by its own numeric\nid and by the external id the institution set — and this application only\never used the numeric half. An operator holding their own reference had to\nfind the loan by scrolling the list.\n\nA lookup on the loans list now resolves a reference through\n`GET /working-capital-loans/external-id/{loanExternalId}` and hands over to\nthe ordinary loan view, replacing the URL so the back button returns where\nthe operator came from rather than to a redirect that bounces forward again.\n`/working-capital/loans/external/\u003cref\u003e` is a shareable address for a loan\nnobody has the numeric id for.\n\nA reference matching nothing answers `null`, not an error: that is the lookup\nworking, and the screen says which reference it was. A real failure keeps its\nown path with a retry, because \"no such loan\" and \"the server broke\" are\ndifferent answers and used to be indistinguishable.\n\nThe route is declared ahead of every `loans/:id/...` pattern so an external\nid is never matched as a numeric one.\n\nThe port is renamed from `working-capital-portfolio` to\n`working-capital-loan`, since it now holds both reads and a token named for\nthe portfolio would have described half of it.\n"
    },
    {
      "commit": "08a6b320c60362c2bb54fc59f9ed5f3906e35b0b",
      "tree": "372cc621e8b00e464d2eef482c8ca0f651189da7",
      "parents": [
        "03172412cced9c25ad3baff2bbd0a062bbb1e3a0"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 18:54:13 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 18:54:13 2026 +0530"
      },
      "message": "feat(working-capital): chart the aging bands and the status mix\n\nTwo charts on the portfolio overview, each paired with the table that carries\nthe same figures.\n\nThe aging bands are an ordinal ramp — one blue hue, light to dark — so \"older\"\nreads as \"darker\" without asking anyone to learn five hues. The statuses are\ncategorical and take the fixed slot order; a sixth status is not a sixth hue,\nit folds into one deliberately neutral \"Other\", so a slice keeps its colour as\nlong as it keeps its rank.\n\nBoth charts plot loan counts. The amounts stay in the tables: charting one of\nthem here would put two scales in one picture.\n\nEvery step was checked with the data-visualisation validator against its own\nsurface, in ordinal mode for the ramp and categorical mode for the statuses,\nand the dark steps are selected per theme rather than flipped — `ThemeService`\nalready exposes the mode as a signal. The light categorical steps sit below\n3:1 on the light surface, which obliges a non-colour reading of the same data:\nhence a table under each chart and a label on every slice.\n\nWorth knowing separately: the palette the dashboard\u0027s existing donuts use was\nmeasured the same way and does not pass — one of its four steps falls below\nthe chroma floor and reads as grey. It is deliberately not extended here, and\nis left for its own change rather than quietly re-used.\n\nReuses the app-owned chart components, so no charting dependency and nothing\ncrosses the ADR 0005 boundary.\n"
    },
    {
      "commit": "03172412cced9c25ad3baff2bbd0a062bbb1e3a0",
      "tree": "7df4ab41ef41000e13cc2a3d41c8190add75f2c6",
      "parents": [
        "1d1a5e9f7ea6b33e81a05b62d2bb243e5a805277"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 16:56:04 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 16:56:04 2026 +0530"
      },
      "message": "feat(working-capital): read the book at portfolio level\n\nEvery working-capital screen is one loan or one piece of reference data, so\nnothing said what the book as a whole looks like. This adds the module\u0027s\nfirst cross-loan view: outstanding, delinquency and its aging, status mix,\nand the flags the platform sets, with a link through to the loans list.\n\nIt states figures Fineract states per loan and adds them up. Nothing is\ninferred beyond addition and banding day counts, and in particular it does\nnot claim which loans are \"in breach\" — the payload does not say. `breach`\nis the definition attached to the loan and every existing screen reads breach\nas per-loan *actions*, so a derived breach flag would put a risk judgement\nthe module does not make onto a screen that reads as authoritative. The one\nbreach figure the platform does name, `balance.breachPastDueAmount`, is\nreported under that name.\n\nThe arithmetic lives in `summarisePortfolio`, a pure function in the adapter\nport, because these totals are the screen\u0027s whole claim and a template cannot\nbe asked to prove them. The aging bands are tested at every boundary, and the\nband totals are asserted to sum back to the portfolio total — the one thing a\nreader checks.\n\nNew code cannot import the generated client (ADR 0006), so this comes with a\nnarrow port: one read, a snapshot model whose numbers are non-optional, and a\nmapper that resolves every absent figure to zero. The generated type marks all\n80-odd fields optional; a single `undefined` in a column of money renders as\n\"NaN\", so absence is decided once in a tested mapper rather than at each `+`.\n\nThe screen uses plain semantic markup rather than Ionic cards: ADR 0005\ncloses the vendor boundary to new files and `app/ui` has no card primitive\nyet, which is the gap #684 describes. Tables carry `scope` on their headers\nand a visually-hidden caption instead.\n"
    },
    {
      "commit": "1d1a5e9f7ea6b33e81a05b62d2bb243e5a805277",
      "tree": "4f7076223c4ba45f8371ce60228cceb996347a65",
      "parents": [
        "6f4da67d703f3740f42c400a159f6081e82f373f",
        "9c953535b11e9c28b5e94625d4639af1f5885ee1"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 16:10:17 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 16:10:17 2026 +0530"
      },
      "message": "Merge pull request #846 from Aman-Mittal/fix/two-factor-config-load-failure\n\nSettings: tell the two-factor config screen\u0027s two failures apart"
    },
    {
      "commit": "6f4da67d703f3740f42c400a159f6081e82f373f",
      "tree": "0fe19a1ba5981861c21e12f9abdefd72793d27af",
      "parents": [
        "9217c32a66a4ebde8c974b43d46595b0bdb7772f",
        "056fc7a473fc6c67668cdc119088ad2487f6e8b9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 16:03:11 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 16:03:11 2026 +0530"
      },
      "message": "Merge pull request #845 from Aman-Mittal/fix/loan-form-required-fields\n\nLoans: mark the Create Loan form\u0027s required fields and name what blocks Save"
    },
    {
      "commit": "9c953535b11e9c28b5e94625d4639af1f5885ee1",
      "tree": "44e13a090314c6d22cdb7049aa70fad0c7dbe158",
      "parents": [
        "9217c32a66a4ebde8c974b43d46595b0bdb7772f"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 15:35:48 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 15:53:05 2026 +0530"
      },
      "message": "fix(settings): tell the two-factor screen\u0027s two failures apart\n\nCloses the remaining half of #749. The OIDC screen was fixed earlier and\nreads 404 as \"no configuration yet, create one\"; this screen treated every\nanswer the same way — `error: () \u003d\u003e isLoading.set(false)` — so a 404 and a\n500 both left an empty JSON editor that looks exactly like a tenant with no\nsecond factor configured. Saving from that state would have written the empty\neditor over whatever was really there.\n\n404 now opens the editor, deliberately and for the same reason the OIDC\nscreen does. Anything else shows the shared load-error panel with Retry and\nraises an error toast, and withholds the editor rather than offering one that\ncannot be trusted.\n\nAlso: a JSON parse failure on save was reported through\n`notifications.success`, which put \"Invalid JSON\" in a green toast. It is an\nerror, and says so now.\n"
    },
    {
      "commit": "056fc7a473fc6c67668cdc119088ad2487f6e8b9",
      "tree": "0fe19a1ba5981861c21e12f9abdefd72793d27af",
      "parents": [
        "9217c32a66a4ebde8c974b43d46595b0bdb7772f"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 15:31:10 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 15:31:10 2026 +0530"
      },
      "message": "fix(loans): mark the loan form\u0027s required fields and name what blocks Save\n\nCloses #793 apart from its third point, which has since been fixed: the\nrepayment-start picker already shows a placeholder when unset.\n\nThirteen controls carry `required` and not one of them said so, and Save\ndisabled itself without naming the field responsible — on a form of twenty\ncontrols, with the empty one often off screen. Marked with the same\n`required-marker` asterisk the client form uses, and the hint beside Save now\nnames the fields rather than saying \"complete the required fields\": on a\ntwenty-control form the generic wording tells the operator nothing they did\nnot already know.\n\nThe client field is marked in `app-client-search` itself, so every form that\nembeds it gains the marker. Its accessible name comes from `aria-label` and\nis unchanged, which is what the e2e specs locate it by.\n\nSave also tests `loan().clientId` now. `app-client-search` holds its own\nreactive control and never joins this `NgForm`, so the form read as valid\nwith no client at all and the button enabled on it; the platform then\nrefused the result. The hint names the client first for the same reason.\n\nNot using `aria-required` on the controls: on `ion-select`, which renders as\n`role\u003d\"button\"`, that is the Ionic defect #514 is about, and it would add\nthree accessibility-baseline entries to paper over. The marker is visible and\nthe Save button is described by the hint, which is what carries the\ninformation.\n\nTests assert the invariant rather than a count: every control the rendered\nform declares required must have a marker in its `ion-item`, and clearing any\none of them must produce a hint naming exactly that field — so a field\nrenamed without updating the label map fails, naming itself.\n"
    },
    {
      "commit": "9217c32a66a4ebde8c974b43d46595b0bdb7772f",
      "tree": "d8d0bdcfd097d7b99aba57fb94a2d1cb2a35e3be",
      "parents": [
        "69cda9d8323383ac9a51a7b580d6c43f2b3958b8",
        "a4529d2663518f30ae992675256604059b7e0346"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 15:16:14 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 15:16:14 2026 +0530"
      },
      "message": "Merge pull request #844 from Aman-Mittal/fix/pending-clock-test-and-adapter-imports\n\nFinish the leftovers: the UTC+14 clock case, and a silent lint run"
    },
    {
      "commit": "69cda9d8323383ac9a51a7b580d6c43f2b3958b8",
      "tree": "ca0b40bba5b766b9a5f44c1112bce07959f70f8c",
      "parents": [
        "9e5af13ce046b7522ce9e16b930164dbedd0fba9",
        "95fdea5931563aa2173de62390ee7f41779077be"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Sat Oct 31 15:00:12 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 15:00:12 2026 +0530"
      },
      "message": "Merge pull request #843 from Aman-Mittal/fix/family-member-form-save-feedback\n\nClients: the add-family-member form, and an edit that saved 2 December 1901"
    },
    {
      "commit": "a4529d2663518f30ae992675256604059b7e0346",
      "tree": "5ea87d97a33b04164d455eb2348fd7944e5d2af2",
      "parents": [
        "8f06faa4a88c45f5d19f0c10edf52754b86fa029"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:55:29 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:55:29 2026 +0530"
      },
      "message": "style(cron): account for the four security warnings in the cron parser\n\nThe last warnings in the lint run, all four false positives, and all four in\none file. Silenced where they fire, with the reasoning next to the code, so\nthe rules stay live for a real finding elsewhere.\n\nThree are `security/detect-unsafe-regex` on `safe-regex`\u0027s star-height\nmeasure, which counts a `+` inside an optional group as nesting. Star height\nis not backtracking: catastrophic backtracking needs two quantifiers that can\neach claim the same characters, and these have none — `[A-Z]{3}` and `\\d+`\nmatch disjoint classes, and the literal `-` and `/` fix where one group ends\nand the next begins. Timed on the shape that provokes a ReDoS where there is\none, a long run followed by a character that cannot match: 0.06ms at 1e3\ncharacters, 0.15ms at 1e4, 4.8ms at 1e5, 16.8ms at 1e6. Linear, against an\ninput that is one field of a cron expression typed into a form.\n\nThe fourth is `security/detect-possible-timing-attacks` on\n`if (token \u003d\u003d\u003d \u0027L\u0027)`. The rule matches on the identifier\u0027s name alone, and\nthis `token` is a cron field, not a credential.\n\nThe directives sit on the line immediately above each expression rather than\nafter the prose: `eslint-disable-next-line` applies to the next line, and a\ncomment that wraps consumes it — which reports as an unused directive beside\nthe warning it was meant to cover.\n\n`npm run lint` is now silent, so the next warning is visible as new.\n"
    },
    {
      "commit": "95fdea5931563aa2173de62390ee7f41779077be",
      "tree": "ca0b40bba5b766b9a5f44c1112bce07959f70f8c",
      "parents": [
        "346675d22e66c939d5b517be0aad907988319713"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:28:08 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:30:54 2026 +0530"
      },
      "message": "fix(clients): stop an unchanged family edit saving 2 December 1901\n\n`GET /clients/{id}/familymembers/{memberId}` answers\n`\"dateOfBirth\": \"2026-10-31\"`. The edit form read that value as\n`[year, month, day]`, so indexing it took the string\u0027s characters — \u00272\u0027, \u00270\u0027,\n\u00272\u0027 — and `new Date(\u00272\u0027, -1, \u00272\u0027)` resolves to 2 December 1901. Nothing\nshowed it: the picker has bound the correct day since #841, because that path\nnarrows the value through `toIsoFineractDate` first. Only the `Date` behind\nthe payload was wrong, so opening a member and saving with no edit at all\nreplaced a real birth date with 1901.\n\nMeasured against a real Fineract, which is the only place it is visible. The\nmocked specs fixture this field as `[1980, 4, 30]`, the shape much of the API\ndoes use and the shape the form was written for, so they passed throughout\n(#653 is the general case). The unit case is parametrised over both shapes\nnow, and fails on the old code with \u002708 September 1901\u0027.\n\nThe `Date` is derived from the same narrowed ISO string the picker gets, so\nthe day shown and the day saved cannot drift apart again.\n\nA real-Fineract spec covers the three things only the platform can answer:\nthat a member added through the form is stored and listed, that an unchanged\nedit leaves its birth date alone, and what the platform does with the rules\nthe form now enforces. That last one is worth reading before trusting any of\nit to the server — `ClientFamilyMemberCommandFromApiJsonDeserializer`\ncollects every validation error it declares and never throws them, so a\nmember with no first name is accepted, a birth date in the future is\naccepted, and the only rule anything enforces is a NOT NULL constraint that\nreaches the operator as the database\u0027s sentence about `relationship_cv_id`.\n\nThe spec seeds a RELATIONSHIP code value, because a stock Fineract ships that\ncode with no values and the field the form requires then cannot be filled at\nall.\n"
    },
    {
      "commit": "346675d22e66c939d5b517be0aad907988319713",
      "tree": "52647c14879b531d7f32e7e641f54274826ee72f",
      "parents": [
        "57ed0ef9bee388f1ebed077c3a308d936f934fde"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:30:54 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:30:54 2026 +0530"
      },
      "message": "test(clients): cover the add-family-member form\u0027s guidance in the browser\n\nTwo mocked cases: that the three fields this form requires are marked and\nthe hint stands until they are filled, and that the picker refuses the day\nafter the platform\u0027s own while confirming the save it does accept.\n\nThe three are the form\u0027s own rules, not the platform\u0027s — the backend spec\nmeasures what Fineract actually enforces, which is close to nothing — so the\nmarks are the only thing telling the operator which field is holding Save.\n\nThe business date is mocked to 2026-10-15, which no runner\u0027s clock will be\non, so a picker bound read from the browser fails the second case at every\nhour rather than only in the evening window where the two disagree.\n\nThe required markers sit flush against their label text, as the journal\nentry form\u0027s do, because `ionSelect()` matches `^\u003clabel\u003e\\*?$` and a space\nbefore the asterisk would put the shared helper out of reach for this form.\n"
    },
    {
      "commit": "57ed0ef9bee388f1ebed077c3a308d936f934fde",
      "tree": "72af090ff002be74981e059de0148532bacab336",
      "parents": [
        "9e5af13ce046b7522ce9e16b930164dbedd0fba9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:07:01 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:07:01 2026 +0530"
      },
      "message": "fix(clients): mark the family form\u0027s required fields, bound the birth date and confirm the save\n\nThree things the add-family-member form left to the operator to work out.\n\n**Save was dead with nothing to explain it.** First name, last name and\nrelationship carry `required`, and the button is disabled until the form is\nvalid, but nothing on the screen said which of eleven fields was the one\nholding it. Marked with the same `required-marker` asterisk, per-field\n`role\u003d\"alert\"` note and `COMPLETE_REQUIRED_FIELDS` hint that the client form\nalready uses, so the three forms read alike.\n\n**The birth-date picker offered days the platform refuses.** Unbounded, it\nopened on the current month with every day selectable, including today\u0027s and\nthe rest of the year; `validateForCreate` refuses a `dateOfBirth` later than\n`DateUtils.getBusinessLocalDate()`, so a future date was one click from a\nrefusal that only the toast explained. Bounded now at the platform\u0027s today,\nresolved through `PlatformDateService` rather than read from the browser:\nfor a tenant east of the operator those are different days for part of every\nday, which is the defect behind #758.\n\n**Nothing confirmed the save.** Both commands navigated to the client page\nand logged any refusal to a console the operator cannot see. They go through\n`MutationOutcomeService` now, which toasts the outcome and handles a\nmaker-checker queue, and the refusal path leaves the form usable instead of\nstuck behind a disabled Save — the error interceptor already toasts what the\nplatform said. The client id is passed explicitly, because the `resourceId`\nthese two commands return is the family member\u0027s and routing on it would\nopen an unrelated client.\n\nDeliberately left alone: the form sends `middleName`, `qualification` and\n`mobileNumber` as empty strings when untouched, which looks worth trimming\nuntil you read `JsonCommand.stringValueOfParameterNamed`, which defaults a\nmissing parameter to `\"\"` anyway. Omitting them would change the payload and\nnothing else.\n"
    },
    {
      "commit": "8f06faa4a88c45f5d19f0c10edf52754b86fa029",
      "tree": "15cdc4d12afcf2bd20484d3314a3f0a361b4e968",
      "parents": [
        "34bd44e7b8571a325d9d886c4af59da7dfe1504b"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:01:19 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:01:43 2026 +0530"
      },
      "message": "style(products): consolidate adapter imports\n\nThree product forms import `core/adapters` twice, once for `TranslatePipe`\nand again for `I18N`, which is the whole of the repository\u0027s remaining\n`import/no-duplicates` warnings. Merged, as the clients-side cleanup in\n#839 did, so the lint run is quiet and a genuine duplicate shows up as new.\n"
    },
    {
      "commit": "34bd44e7b8571a325d9d886c4af59da7dfe1504b",
      "tree": "d0a9ec44d7fefcbbdd2717d8b45bef9c0342fed6",
      "parents": [
        "9e5af13ce046b7522ce9e16b930164dbedd0fba9"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:01:06 2026 +0530"
      },
      "committer": {
        "name": "Aman Mittal",
        "email": "aman.mittal3052000@gmail.com",
        "time": "Sat Oct 31 14:01:06 2026 +0530"
      },
      "message": "test(dates): assert the platform instant, not the runner\u0027s offset\n\n`prefers the platform clock over the browser clock` asserted the literal\n\u00272026-08-15\u0027 against a service that, with no tenant timezone configured,\nrenders the recorded instant in the browser\u0027s own zone. That made the\nrunner\u0027s UTC offset part of the assertion: at UTC+14 (Pacific/Kiritimati)\n10:00Z is already the 16th, and the case failed on a service that was\nbehaving exactly as designed.\n\nAssert the local rendering of the platform\u0027s own instant instead, and move\nthe browser\u0027s clock to a different day so the case still fails if the\nservice ever reads `new Date()` in place of what the platform last said.\nThat regression is what it exists to catch, and against the real clock it\ncaught it only by the accident of two dates differing.\n\nGreen at UTC, America/New_York, Asia/Kolkata and UTC+14.\n"
    },
    {
      "commit": "9e5af13ce046b7522ce9e16b930164dbedd0fba9",
      "tree": "0ca985a8cfda8682be4ddfd8668150244b75ca4c",
      "parents": [
        "ef4c047ee6aff7ce9d5bef26672a777d58c43fe1",
        "107083e60640edc81aa6198be7b9fea17b8ff766"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Fri Oct 30 11:48:01 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 11:48:01 2026 +0530"
      },
      "message": "Merge pull request #842 from vaidikgarg-bit/feat/790-sms-gateway-config\n\nfeat(external-services): configure the SMS message gateway"
    },
    {
      "commit": "107083e60640edc81aa6198be7b9fea17b8ff766",
      "tree": "0ca985a8cfda8682be4ddfd8668150244b75ca4c",
      "parents": [
        "ef4c047ee6aff7ce9d5bef26672a777d58c43fe1"
      ],
      "author": {
        "name": "Vaidik Garg",
        "email": "vaidik2008garg@gmail.com",
        "time": "Thu Oct 29 15:35:15 2026 +0530"
      },
      "committer": {
        "name": "Vaidik Garg",
        "email": "vaidik2008garg@gmail.com",
        "time": "Thu Oct 29 15:55:01 2026 +0530"
      },
      "message": "feat(external-services): configure the SMS message gateway\n\nAdds SMS to the External Services screen, alongside S3, SMTP and\nNotification. The screen always lists the four properties Fineract\naccepts for the gateway (host_name, port_number, end_point and\ntenant_app_key), even when none of them is stored yet.\n\nThe tenant app key is treated as a credential. Fineract returns it in\nclear text, so its stored value never enters the form: the field is a\npassword input that only says whether a key is set, and the key is sent\nonly when a new one is typed. The error interceptor now drops the\nargs of any parameter whose name contains app_key, as it already does\nfor passwords and secrets, so a refused key\u0027s value is not echoed in\nthe error toast.\n\nSave now sends only the fields that were edited, as text, and says so\nwhen nothing changed. Fineract updates just the keys it receives, so\nuntouched settings keep their stored values.\n\nAlso for every service: a 403 on load shows \"no access\" instead of an\nempty form, other load failures offer a retry, a stale response is\nignored once a newer load has started, Save stays disabled without\nUPDATE_EXTERNALSERVICES, and a successful save shows a toast and\nrefreshes the values in place, clearing them if that refresh fails.\n\nCloses #790\n"
    },
    {
      "commit": "ef4c047ee6aff7ce9d5bef26672a777d58c43fe1",
      "tree": "70fdf6436214d8e5f92de12fcfa2c9b90b9296b1",
      "parents": [
        "586da474b7ccc3f3ef4ff09925ef0463337cc5d9",
        "4df18f90543b1750d8d9e90c960678e8afcb29d3"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Thu Oct 29 10:39:18 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 10:39:18 2026 +0530"
      },
      "message": "Merge pull request #841 from zhangxinyao88/codex/fineract-family-edit-dob\n\nfix(clients): show saved birth dates when editing family"
    },
    {
      "commit": "586da474b7ccc3f3ef4ff09925ef0463337cc5d9",
      "tree": "efa9aab055b19b2aa71c82fbdc17fd1f789ae51e",
      "parents": [
        "d1421693aa4ad7d442050cfa9e23cf976001f998",
        "0827db93a9e6fb0e3d5ffdd1b7e0811ebe86c806"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Thu Oct 29 10:37:57 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 10:37:57 2026 +0530"
      },
      "message": "Merge pull request #840 from zhangxinyao88/codex/issue-750-table-header-layout\n\nfix(tables): inset list headers and align primary actions"
    },
    {
      "commit": "d1421693aa4ad7d442050cfa9e23cf976001f998",
      "tree": "ec81fbc9f3cbb6bd39368bb7bc2508aa0b0d2060",
      "parents": [
        "22d8a14d22e960789ca71ac8ba515a6bd0fa3952",
        "895bce9d8448f393076ed1930b28a816fc65e3a7"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Thu Oct 29 10:31:19 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 10:31:19 2026 +0530"
      },
      "message": "Merge pull request #822 from CaptainAni187/fix/795-client-charges-collateral-share-links\n\nfix(clients): link client charges, collateral and a new share account from the client page"
    },
    {
      "commit": "22d8a14d22e960789ca71ac8ba515a6bd0fa3952",
      "tree": "3932b49b461d21da50061fe9dcd150315227bdd0",
      "parents": [
        "bf66c7adf73d5847de62dc005c26d35a1e0b6d78",
        "1325381e3b0f726a64b0cab9b5c823a23b70f065"
      ],
      "author": {
        "name": "Aman Mittal",
        "email": "32237955+Aman-Mittal@users.noreply.github.com",
        "time": "Thu Oct 29 10:18:46 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 10:18:46 2026 +0530"
      },
      "message": "Merge pull request #838 from vaidikgarg-bit/feat/787-edit-scheduler-job\n\nfeat(scheduler): edit a job\u0027s cron expression, name and active flag"
    },
    {
      "commit": "4df18f90543b1750d8d9e90c960678e8afcb29d3",
      "tree": "7f0babc9a63706006deb4013ac8d645f77c47a56",
      "parents": [
        "bf66c7adf73d5847de62dc005c26d35a1e0b6d78"
      ],
      "author": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Wed Oct 28 12:17:52 2026 -0400"
      },
      "committer": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Wed Oct 28 12:17:52 2026 -0400"
      },
      "message": "fix(clients): show saved birth dates when editing family\n\nBind the saved family-member DOB to the picker and show Not set for an\nabsent value. Keep local timestamp precision so selecting another day\npreserves the existing Date-based payload in timezones west of UTC.\n\nCover saved and absent edit values, unchanged saves, explicit replacement,\nand existing create/clear/retry flows with unit and browser regressions.\n\nGenerated-by: OpenAI Codex\n"
    },
    {
      "commit": "0827db93a9e6fb0e3d5ffdd1b7e0811ebe86c806",
      "tree": "60859d1792cc4d034340ff4f0b2b1272ad0244cf",
      "parents": [
        "bf66c7adf73d5847de62dc005c26d35a1e0b6d78"
      ],
      "author": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Wed Oct 28 12:00:42 2026 -0400"
      },
      "committer": {
        "name": "Xinyao Zhang",
        "email": "43081360+zhangxinyao88@users.noreply.github.com",
        "time": "Wed Oct 28 12:00:42 2026 -0400"
      },
      "message": "fix(tables): inset list headers and align primary actions\n"
    }
  ],
  "next": "895bce9d8448f393076ed1930b28a816fc65e3a7"
}
