Task: Add a badge_mapping browser UI
Table of Contents
This page documents a task in the Improve badge colour scheme support story. It captures the goal, current status, acceptance, and any notes or results.
Goal
A user viewing a Code Domain's detail dialog can see every entity_code -> badge mapping registered for that domain, on a "Badge Mappings" tab, with each badge rendered as an actual badge pill – not just hex text. (Originally scoped as a standalone System > Configuration window; redesigned mid-task to a detail-dialog tab – see Plan and Review.)
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Improve badge colour scheme support |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-17 |
Acceptance
- CodeDomainDetailDialog gains a read-only "Badge Mappings" tab showing every badge_mapping row for the displayed code_domain, each row showing entity_code and its resolved badge_definition rendered as a real badge pill.
- Follows the established hand-written-tab-embedded-in-a-generated-dialog pattern (PartyHierarchyTab in PartyDetailDialog), not a standalone top-level window.
- Manual QA: opening a known code_domain's detail dialog (e.g. book_status) shows the same mappings seeded in dq_badge_system_populate.sql.
Plan
- Investigated whether a network round-trip was needed:
BadgeCachealready loads allbadge_definition=/=badge_mappingrows once at startup and exposeslist_by_domain(code_domain_code)— exactly the query this browser needs. No new protocol/service/handler work required; this is a pure client-side, already-cached read. - Added one small public method to
BadgeCache:list_code_domains()— every distinctcode_domain_codepresent in the loaded mappings, sorted. Feeds the domain picker without a separate fetch, and guarantees every listed domain has at least one mapping (no empty-picker entries). badge_mappingis a junction model with no existing Qt facet (the prior junction-codegen story deliberately scoped junctions to domain/generator/repository only, not service/protocol/qt) — and no other junction in the codebase has any Qt UI yet, generated or hand-written. Rather than force this through the CRUD-shapedEntityController=/=EntityListMdiWindowmachinery built for network-backed, writable entities, built a plainQWidget(BadgeMappingBrowserWindow: a domain-pickerQComboBox+ a two-columnQTableWidget) that reads directly from the already- injectedBadgeCache*. Matches the established simpler pattern already used forTelemetryMdiWindow=/=ShellMdiWindow=/etc. (plain widget wrapped in a hand-constructed =DetachableMdiSubWindow, added viamdiArea_->addSubWindow()directly in the plugin) rather than inventing a new abstraction.- Badge cells render with the resolved
badge_definition's actualbackground_colour=/=text_colourset directly as theQTableWidgetItem's background/foreground brush — a real colour swatch, not hex text (task 2's swatch/picker work is for theBadgeDefinitionadmin screens specifically; this task's own badge cells needed a swatch to be useful at all, so did the minimal version inline rather than block on task 2). - Wired into
AdminPlugin: newBadge &Mappingsaction under System > Configuration, alongside the existing Badge Definitions/Severities actions; window tracked directly (no controller) and closed on logout, mirroring the existing Badge Definitions/Severities lifecycle pattern at the level of detail this simpler window actually needs. - Verified:
ores.qt.admin.liband the fullores.qt.exebuild clean;clang-format --dry-run --Werrorclean on the new files. - Redesigned mid-task (see Review #4): the standalone window above
was scrapped. Instead, following the
PartyHierarchyTabpattern (a hand-written tab embedded in a generated detail dialog via a paste block), addedBadgeMappingsTaband wired it intoCodeDomainDetailDialog. Two wrinkles specific to this case:CodeDomainDetailDialog(and its siblingCodeDomainController) turned out to be hand-maintained, not codegen'd fromores.dq.code_domain.org– there's a separate, unused generated scaffold under the never-wiredprojects/ores.qt/dqcomponent (no CMakeLists, not built; pre-existing drift, not touched here). So no paste block was used;BadgeMappingsTabis wired directly into the hand-written.cpp.BadgeCache*reachesCodeDomainDetailDialogvia a new constructor parameter onCodeDomainController(mirroring thehas_badge_columnscodegen pattern, applied by hand sincecode_domainhas no badge columns of its own) threaded fromRefdataPlugin'splugin_context.BadgeMappingsTab::reload()takes the cache as a parameter (not stored at construction) since the tab is attached during the dialog's own constructor, beforesetBadgeCache()runs.
- Removed the standalone
BadgeMappingBrowserWindow(files, System > Configuration menu action,AdminPluginwiring) entirely. - Re-verified:
ores.qt.refdata.lib,ores.qt.admin.lib, and the fullores.qt.exebuild clean;clang-format --dry-run --Werrorclean on all touched files.
Notes
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
| Verify badge mapping browser | FAILED | Targeted the scrapped standalone window; superseded by the scenario below after the tab-embedding redesign. |
| Verify badge mappings tab on Code Domain detail dialog | PENDING | |
| Verify ores.qt.dq plugin: badges and code domains | PASSED | All 7 steps pass; also covers the new ores.qt.dq plugin/menu, the Badge Mappings duplicate-rows fix, and a Portfolios badge-colour spot-check. |
PRs
Result
Resumed after its blocker (D4DE4138, badge datasets migrated to standard codegen + DQ publishing) landed. Manual QA against real tenant-scoped data (PR #1599's BadgeMappingsTab) surfaced three previously-hidden bugs, all fixed in PR #1622:
- Crash:
ores.dq.servicesegfaulted on every Badge Definitions/ Severities list request. Root cause:registrar.cppstill routed those subjects to the pre-migration bespokebadge_handler, whoselist_definitions()had a heap-corruption bug – dead code left wired after D4DE4138 generated the real handlers but never switched the registrar over. Rewired to the generated handlers;badge_handlernow only servesbadge_mapping(no generated handler exists for it yet). - Protocol drift:
ClientBadgeSeverityModel=/=ClientCodeDomainModelstill included the old bespokebadge_protocol.hpp, whose response field names (badge_severities=/=code_domains) don't match the generated protocol's (severities=/=domains), causing deserialization failures. Switched both to the correct generated protocol headers. - Duplicate rows in the Badge Mappings tab:
badge_mapping_junction.orgwas missing the:subcomponent: apiflag every correctly-codegen'd junction sets, socompass codegen entity generatesilently targeted a stale, pre-split output path and the hand-authored repository (with notenant_idfilter on its list query) never got replaced – Postgres RLS on this table deliberately allows both the caller's own tenant AND the system tenant's rows (global-registry-with-override pattern), so any tenant with its own published copy saw every mapping twice. Added the missing model flag, genuinely regenerated the repository (retiring the hand-authored one), and along the way fixed a real codegen template bug too: the junction Mustache branch was missing thefaker-cxxinclude the domain_entity branch has, so any junction usingfaker::in a custom column-generator block (likebadge_mapping's) failed to compile.
Along the way, moved badge_definition=/=badge_severity=/=code_domain
Qt (+ BadgeMappingsTab) out of ores.qt.admin=/
=ores.qt.data_management into a new ores.qt.dq plugin, matching the
ores.dq C++ component boundary – a new "Data &Quality" menu with a
"&Badges" submenu, per user direction.
Filed a follow-up task
documenting that 6 of the 12 has_tenant_id junctions across the
codebase have the same wrong-path codegen issue badge_mapping had,
3 of which (dataset_bundle_member, account_party,
party_counterparty) share the exact missing-tenant-filter bug –
real, unfixed duplicate-rows risk, tracked separately.
Verified: full local build clean (ores.dq.service.exe,
ores.qt.exe), ores.dq.core.tests=/=ores.refdata.core.tests both
100% passing, and the manual QA scenario
(Verify ores.qt.dq plugin)
7/7 steps PASSED, including a spot-check confirming the original
Portfolios badge-colour regression that started this whole
investigation is fixed.
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Domain picker QComboBox spanned the entire screen width on Linux; wrong widget shape for "pick a domain, see its mappings" | BadgeMappingBrowserWindow.{hpp,cpp} | Accepted | Redesigned as a bounded QListWidget (list of domains) + QTableWidget (mappings), split view – matches AccountPartiesWidget's list-driven layout instead of a combo filter. |
| 2 | Badge cells rendered as plain background-coloured text, not an actual badge pill | BadgeMappingBrowserWindow.cpp | Accepted | Now uses EntityItemDelegate with column_style::badge_centered (the same delegate every other badge column in the app uses), resolving colours via BadgeCache::resolve() at paint time. |
| 6 (PR #1622, round 1) | Dropped result->success business-failure check in Client{BadgeDefinition,BadgeSeverity,CodeDomain}Model – backend exceptions silently render as an empty list | ClientBadgeDefinitionModel.cpp, ClientBadgeSeverityModel.cpp, ClientCodeDomainModel.cpp | Accepted | Restored the check in all three, matching the pre-existing pattern. |
| 7 (PR #1622, round 1) | Duplicated sort key: order_by(code_domain_code, code_domain_code) drops the entity_code secondary sort | badge_mapping_repository.cpp | Accepted | Root cause was the model's :order_column: (code_domain_code, duplicating junction.left.column) – fixed in ores.dq.badge_mapping_junction.org and regenerated, not hand-patched. |
| 8 (PR #1622, round 1) | Typo: onSeveritysLoaded should be onSeveritiesLoaded | ClientBadgeSeverityModel.hpp/.cpp | Accepted | Renamed. |
| 9 (PR #1622, round 1) | Stale badge_protocol.hpp include left on ClientBadgeDefinitionModel.cpp | ClientBadgeDefinitionModel.cpp | Accepted | Switched to the generated badge_definition_protocol.hpp, matching severities/domains. |
| 3 | Badge Definitions screen needs a related-entity toolbar link to Severities/Mappings; Severity linkage to Definitions not visible; Definitions' own colours should render as colour, not hex text | BadgeDefinitionMdiWindow, BadgeSeverityMdiWindow | Accepted, folded into task 2 | Out of scope for this task (targets BadgeDefinition/BadgeSeverity screens, not badge_mapping); added to task_badge-palette-swatch-and-fallback.org's Acceptance. |
| 4 | Should not be a standalone window – should show up as a tab in the notebook, like PartyHierarchyTab does for party's detail dialog | BadgeMappingBrowserWindow (whole approach) | Accepted | Scrapped the standalone window; added BadgeMappingsTab embedded in CodeDomainDetailDialog. See Plan items 7-9. |
| 5 | Dead code: BadgeCache::list_code_domains() has no remaining callers after the redesign to an embedded per-dialog tab (it was written for the scrapped standalone window's domain picker) | BadgeCache.hpp, BadgeCache.cpp | Accepted | Removed the method (and its now-unused #include <set>). PR #1599. |