- Shipped
- August 11, 2026 at 4:36 PM UTC
- Author
- kamo
- Commit
- f8afef1
The member's compliance tab had two sub-tabs, Pending and Completed, for one kind of obligation. Those are a STATE, not a kind — so they collapse into a status filter (with "all", which two tabs had nowhere to put, and which previously meant checking both to answer "was it this one I signed?"), and the tab strip is freed to separate the two things that are genuinely different: Legal Packages — instruments to sign, unchanged behaviour behind a filter Assigned Training — courses to take Each panel keeps its own filter in its own param. One shared ?filter= would carry a legal bucket into the training panel, where the same word means a different set of statuses, and switching tabs would silently re-filter the other list. A foreign ?sub= is normalised away on mount, because SettingsView.writeTabToUrl sets ?tab= and deletes ?configure but leaves ?sub= alone — the screen was already correct via the index fallback, but the URL was claiming a state this component never had, and the URL is what gets bookmarked and pasted into a support ticket. NavTop now shows ONE badge for BOTH. A member does not think of "packages to sign" and "courses to take" as two systems, they think of things they owe; two nearly identical marks side by side in fixed chrome would make them work out which one is lit. The count is the sum, and the icon gains a real attention state — two rings on ::before/::after of a wrapper, so the glyph itself never moves and never fights the Badge anchor. Motion is an ADDITIONAL signal, not the only one (the count and the error colour still carry it), which is why prefers-reduced-motion drops it with nothing lost. MemberAssignedTraining deliberately ships NO action column. trainingActionFor already decides start/resume/retake/certificate and is unit-tested, but the course player is not built — and a visibly disabled "Start" is a promise the product cannot keep, which reads as broken rather than unfinished. The column arrives with the player. trainingStatusesFor sends every status EXPLICITLY for "all" rather than omitting the param: the server's no-filter fallback exists to rescue a stale bookmark, not to be a calling convention. Statuses travel as repeated params, never CSV — a comma is double-encoded by the APIService gateway. check-i18n-keys caught the missing keys exactly as designed; hr.training.member.* was pushed to kamo-translation-dictionary first and verified on its origin/main before this commit. npm test: 3 guards clean, 98 files, 1221 tests passing.