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

  1. Investigated whether a network round-trip was needed: BadgeCache already loads all badge_definition=/=badge_mapping rows once at startup and exposes list_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.
  2. Added one small public method to BadgeCache: list_code_domains() — every distinct code_domain_code present 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).
  3. badge_mapping is 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-shaped EntityController=/=EntityListMdiWindow machinery built for network-backed, writable entities, built a plain QWidget (BadgeMappingBrowserWindow: a domain-picker QComboBox + a two-column QTableWidget) that reads directly from the already- injected BadgeCache*. Matches the established simpler pattern already used for TelemetryMdiWindow=/=ShellMdiWindow=/etc. (plain widget wrapped in a hand-constructed =DetachableMdiSubWindow, added via mdiArea_->addSubWindow() directly in the plugin) rather than inventing a new abstraction.
  4. Badge cells render with the resolved badge_definition's actual background_colour=/=text_colour set directly as the QTableWidgetItem's background/foreground brush — a real colour swatch, not hex text (task 2's swatch/picker work is for the BadgeDefinition admin 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).
  5. Wired into AdminPlugin: new Badge &Mappings action 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.
  6. Verified: ores.qt.admin.lib and the full ores.qt.exe build clean; clang-format --dry-run --Werror clean on the new files.
  7. Redesigned mid-task (see Review #4): the standalone window above was scrapped. Instead, following the PartyHierarchyTab pattern (a hand-written tab embedded in a generated detail dialog via a paste block), added BadgeMappingsTab and wired it into CodeDomainDetailDialog. Two wrinkles specific to this case:
    • CodeDomainDetailDialog (and its sibling CodeDomainController) turned out to be hand-maintained, not codegen'd from ores.dq.code_domain.org – there's a separate, unused generated scaffold under the never-wired projects/ores.qt/dq component (no CMakeLists, not built; pre-existing drift, not touched here). So no paste block was used; BadgeMappingsTab is wired directly into the hand-written .cpp.
    • BadgeCache* reaches CodeDomainDetailDialog via a new constructor parameter on CodeDomainController (mirroring the has_badge_columns codegen pattern, applied by hand since code_domain has no badge columns of its own) threaded from RefdataPlugin's plugin_context. BadgeMappingsTab::reload() takes the cache as a parameter (not stored at construction) since the tab is attached during the dialog's own constructor, before setBadgeCache() runs.
  8. Removed the standalone BadgeMappingBrowserWindow (files, System > Configuration menu action, AdminPlugin wiring) entirely.
  9. Re-verified: ores.qt.refdata.lib, ores.qt.admin.lib, and the full ores.qt.exe build clean; clang-format --dry-run --Werror clean 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

PR Title
#1622 [dq,qt] Make ores.qt.dq a plugin; fix badge crash and RLS dupes
#1599 [refdata,qt,admin] Embed badge mappings tab in Code Domain detail dialog

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.service segfaulted on every Badge Definitions/ Severities list request. Root cause: registrar.cpp still routed those subjects to the pre-migration bespoke badge_handler, whose list_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_handler now only serves badge_mapping (no generated handler exists for it yet).
  • Protocol drift: ClientBadgeSeverityModel=/=ClientCodeDomainModel still included the old bespoke badge_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.org was missing the :subcomponent: api flag every correctly-codegen'd junction sets, so compass codegen entity generate silently targeted a stale, pre-split output path and the hand-authored repository (with no tenant_id filter 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 the faker-cxx include the domain_entity branch has, so any junction using faker:: in a custom column-generator block (like badge_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.

Result

Emacs 29.3 (Org mode 9.6.15)