Task: Generate calendar_dates Qt facet and wire Browse Holidays action

Table of Contents

This page documents a task in the Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization story. It captures the goal, current status, acceptance, and any notes or results.

Goal

Give calendar_date (the calendar_dates junction) an ores.cpp.qt facet – client model, read-only paginated list dialog, controller – scoped to a calendar_code parent key via the has_readonly_paginated_list=/=has_parent_scoped_list knobs landed in the parent-scoping task, and wire a "Browse Holidays" toolbar action on CalendarDetailDialog that opens it for the currently open calendar. Closes the deferred acceptance item from B2888D7D.

Status

Field Value
State DONE
Parent story Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-07-29

Acceptance

  • [X] ores.refdata.calendar_date.org gains a ** Qt drawer (under * C++, mirroring ores.refdata.book.org's ** Qt drawer shape) setting :has_readonly_paginated_list: true, :has_parent_scoped_list: true, :parent_key_field: calendar_code, :parent_key_param: calendarCode (or equivalent camelCase), plus the required domain/protocol pointers (:get_request_class: refdata::messaging::get_calendar_dates_by_calendar_request, :get_response_class: refdata::messaging::get_calendar_dates_by_calendar_response, :get_message_type:, :collection_name: calendar_dates, :item_var: calendar_date, :key_field:, MDI chrome (:settings_group:, :window_title: Browse Holidays, :icon:).
  • [X] Generating calendar_date's ores.cpp.qt facet produces a working ClientCalendarDateModel, CalendarDateMdiWindow, and CalendarDateController – read-only (no add/edit/delete/history, per has_readonly_paginated_list), paginated, and scoped to the parent calendar_code on every fetch (per has_parent_scoped_list), per the design in 1C673C53.
  • [X] RefdataPlugin constructs a CalendarDateController alongside the other refdata controllers (same constructor-injection pattern as calendarController_=/=businessCentreController_).
  • [X] CalendarDetailDialog gains a "Browse Holidays" toolbar action (same shape as the existing regenerateAction_=/=onRegenerateClicked pair added in #1707), emitting a new browseHolidaysRequested(QString calendarCode) signal instead of reaching for a sibling controller directly (dialogs don't hold controller references today – see calendarSaved=/=calendarDeleted for the existing signal-out pattern).
  • [X] CalendarController relays that signal outward (mirroring BookController::showBookStatusesRequested's cross-domain relay shape), and RefdataPlugin connects it to calendarDateController_->openForParent(calendarCode).
  • [X] Each row of the opened "Browse Holidays" list shows date, is-business-day, and source, per B2888D7D's original acceptance wording and the calendar_date.org "Table display" table.
  • [X] Verified via --diff against an existing entity with the new knobs left unset showing zero unrelated drift (same approach as 1C673C53's verification), plus a real, working generation for calendar_date itself (first entity to actually exercise these knobs end to end).
  • [X] Full local build green; manual QA scenario opening "Browse Holidays" for a seeded QuantLib calendar (e.g. TARGET) and confirming paginated, read-only holiday rows.

Plan

(Implementation strategy. Written when work starts; key decisions are distilled into the parent story's * Decisions at close, but the plan itself stays — it is the historical record of what we did.)

Investigation findings

  • calendar_date's service/protocol/nats-handler layers already exist (landed in the extra_list_requests task): refdata::messaging::get_calendar_dates_by_calendar_request/response in ores.refdata.api/messaging/calendar_date_protocol.hpp, NATS subject refdata.v1.calendar_dates.list_by_calendar_code, fields calendar_code=/=offset=/=limit on the request, calendar_dates=/=total_available_count=/=success=/=message on the response – this is the get_request_class=/=get_response_class pair the new Qt facet points at.
  • ores.refdata.calendar_date.org (the junction model, projects/ores.refdata/modeling/ores.refdata.calendar_date.org) has * Left (calendar_code, already :list_by: true), * Right (date), * Columns (is_business_day, source), * SQL, * Repository, * C++ (flags/includes/conventions/table display) – but no ** Qt drawer yet. calendar_date will be the first junction model to get one; no existing junction has a Qt facet to copy verbatim, but core.py builds the qt dict identically regardless of domain_entity vs. junction source, so the domain_entity shape (ores.refdata.book.org's ** Qt drawer, under its own * C++) is the right template to follow.
  • book.org's ** Qt drawer is the closest worked example of :parent_entity_singular: (the other, unrelated parent-scoping mechanism) – useful for drawer shape (property syntax, table layout for *** Detail fields), not for the new knobs themselves, which have no existing worked example anywhere yet.
  • Cross-controller wiring in this codebase is centralised in RefdataPlugin.cpp: every controller is constructed there and handed ctx_.main_window=/=ctx_.mdi_area=/=ctx_.client_manager=/etc, then =connectControllerSignals()'d generically, plus bespoke connect() calls for cross-domain relays (see bookController_->showBookStatusesRequested being relayed to a lookup-list open, right after construction). CalendarDateController should be constructed the same way, right after calendarController_.
  • Detail dialogs never hold sibling-controller pointers directly – they emit signals and the owning controller (or, for cross-domain cases, the plugin) does the connecting. Confirmed via CalendarDetailDialog's existing calendarSaved=/=calendarDeleted signals, wired up in CalendarController.cpp's connect(detailDialog, ...) calls. The existing regenerateAction_=/=onRegenerateClicked pair (added in PR #1707, CalendarDetailDialog.cpp:96-133,500-565) is the exact toolbar-action shape to copy for "Browse Holidays": a QToolBar action constructed in the dialog's setup, enabled state tied to !createMode_, triggering a handler that (here) just emits browseHolidaysRequested(code()) rather than doing NATS work itself (unlike onRegenerateClicked, which does call NATS directly – browsing needs no request of its own here since the whole point is delegating to the generated CalendarDateController).
  • EntityController::showListWindow() remains the required zero-arg pure-virtual override; CalendarDateController::openForParent(const QString& calendarCode) (generated) is the real entry point, exactly as designed in 1C673C53.

Direction

  • Model first: add the ** Qt drawer to calendar_date.org, then compass codegen entity generate calendar_date --address ores.cpp.qt to produce the six new files (ClientCalendarDateModel header/impl, CalendarDateMdiWindow header/impl, CalendarDateController header/impl).
  • Plug the generated controller into RefdataPlugin (construction + connectControllerSignals), no special wiring needed there beyond what every other controller gets.
  • Hand-write the CalendarDetailDialog toolbar action + signal + CalendarController relay + RefdataPlugin connect, following the regenerateAction_ precedent exactly for the toolbar/action half, and the showBookStatusesRequested precedent for the relay half.
  • --diff an unrelated existing entity (e.g. currency or book) first to confirm the new knobs, left unset, produce zero drift – same regression-guard pattern every prior task in this chain used.

Notes

Blocked: junction models can't render through ores.cpp.qt yet

Added the ** Qt drawer to calendar_date.org (per the Direction above) and widened ores.cpp.qt.org's #+model_types: from domain_entity to domain_entity junction. Verified the routing layer works correctly:

  • compass codegen entity generate calendar_date --address ores.cpp.qt --dry-run produced exactly the right 6 output paths (ClientCalendarDateModel, CalendarDateController, CalendarDateMdiWindow, header+impl each), with the detail_dialog_* archetypes correctly suppressed via :ores.cpp.qt.detail_dialog_{header,impl,ui}.enabled: false overrides.
  • --diff against currency and book (unrelated domain entities) showed zero drift from the model_types widening – the routing change is safe.

But the actual render is broken: class Controller (not CalendarDateController), #include "", ORES_QT__CONTROLLER_HPP macro guard. Root cause: the ores.cpp.qt mustache templates and ~1300 lines of enrichment in core.py=/=org_loader.py (deriving entity_pascal, entity_singular_upper, etc., and parsing the ** Qt drawer's detail_fields=/combo-widget machinery) are hard-wired to the =domain_entity model shape. A junction model loads into a structurally different {"junction": {...}} dict (name_singular instead of entity_singular, no Qt-section parsing in load_org_junction_model at all, no entity_pascal derivation anywhere in the junction path) – core.py's generate_qt dispatch is itself gated on is_domain_entity and 'domain_entity' in data (core.py ~line 3112), so junction data never even reaches the enrichment block that would populate these fields.

Deleted the 6 broken generated files rather than leave garbage C++ in the tree. Left the calendar_date.org Qt drawer and the model_types widening in place – both are correct, verified-safe groundwork the split-out task can build on directly.

Split the render-path fix into Codegen: teach the ores.cpp.qt facet to render junction models rather than freehanding a fix to a ~3600-line file under this task's scope – same call B2888D7D made for this exact chain a sprint ago.

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
Browse Holidays view for a QuantLib calendar PASSED All 4 steps passed against TARGET (and a second calendar for parent-scoping); surfaced two pre-existing bugs fixed along the way (see Notes)

PRs

PR Title
#1749 [ores.qt] Wire Browse Holidays action; fix service-registry grant drift
#1737 [ores.codegen,agile] Prep calendar_date Qt facet, block browse-holidays on render-path gap

Review

# Comment summary File Decision Notes
1 model_types widening on ores.cpp.qt.org is facet-wide – every existing junction (not just calendar_date) becomes a latent candidate for broken Qt generation, not just calendar_date ores.cpp.qt.org Accepted Fixed in follow-up PR #1742: core.py fails fast with a clear error when a junction lacking a ** Qt drawer is targeted at ores.cpp.qt, instead of silently emitting broken output. Verified currency_calendar (an existing Qt-drawer-less junction) now errors cleanly with no files written.
2 story.org's F081079A row says STARTED but the task's own Status table says BLOCKED; truncated/unlinked ID reference; doubled leading-dash bullets in two new task files story.org, task_junction-qt-facet-codegen.org, task_prune-merged-branches.org Accepted Fixed in follow-up PR #1742: story.org row synced to BLOCKED, ID reference is now a proper org link, both doubled bullets fixed.
3 calendar_date.org's Columns (Qt model) table missing enum_name column; source's type "text" isn't a recognised type keyword ores.refdata.calendar_date.org Accepted Fixed in follow-up PR #1742: table now has enum_name (Date/IsBusinessDay/Source) and source's type is string. Verified via a real (not just –diff) generation that now compiles and links.
4 Duplicate "Calendars" menu label – the flat top-level action and the new auxiliary submenu had the identical label/mnemonic within the same Reference Data menu (PR #1749, both reviewers independently flagged) RefdataPlugin.cpp Accepted Renamed the auxiliary submenu to "Calendar Data" to disambiguate.
5 PR #1749 description said Browse Holidays got Icon::Wand, but the diff actually put Wand on Regenerate and Icon::Table (pre-existing) on Browse Holidays (description only) Declined Wording slip in the PR write-up, not a code issue – code is correct as intended (Wand = "generate/regenerate" semantic on Regenerate; Table = list-view semantic on Browse Holidays).
6 Inline rationale comments (why synthetic_service/marketdata_service need certain grants) are lost from the generated SQL since the service_registry.org model has no per-prefix comment mechanism iam_service_db_grants_create.sql Declined Accepted, documented trade-off (noted in this task's own Result section) of moving from hand-patched to fully-generated grants; adding a :note: property mechanism is a reasonable future enhancement but out of scope here.

Result

With the junction-Qt render path (290FBDF6) done and merged, finished wiring "Browse Holidays" onto Calendar's UI:

  • RefdataPlugin constructs CalendarDateController alongside the other refdata controllers, same constructor-injection pattern as calendarController_.
  • CalendarDetailDialog gains a "Browse Holidays" toolbar action (same shape as the existing regenerateAction_=/=onRegenerateClicked pair), emitting a new browseHolidaysRequested(QString) signal rather than reaching for a sibling controller directly.
  • CalendarController relays that signal outward (mirroring BookController::showBookStatusesRequested's cross-domain relay shape); RefdataPlugin connects it to calendarDateController_->openForParent(calendarCode).
  • Reorganised the Reference Data menu's calendar entries per review during manual testing: Calendars stays a flat, alphabetically placed top-level action (was already there); a new, separate Calendars auxiliary submenu (housing Calendar Rules=/=Calendar Exceptions) sits alongside Cross Rates Matrix at the bottom, matching how other auxiliary/lookup data is grouped. Also flattened the Conventions wrapper menu into direct Curve Building=/ =Trading top-level submenus – neither group was really "a convention" as a category, and the wrapper added a needless click.

Bugs found and fixed during manual QA

Manual testing (doc-run-manual-screenshots-style live verification, not just --diff) surfaced two real, pre-existing defects unrelated to this task's own code, both fixed properly rather than patched around:

  1. refdata_service missing a DB grant it has always needed. calendar_materialisation_service (the Regenerate action, landed in PR #1707) reads ores_variability_system_settings_tbl directly, but the service-registry model (projects/modeling/service_registry.org) never granted refdata_service SELECT on it – Regenerate failed with permission denied on every environment provisioned from a clean db recreate. Root-causing this also surfaced that the committed iam_service_db_grants_create.sql had drifted well ahead of what the service-registry codegen profile could actually reproduce (a gap already flagged in capture 9CBEE91B): synthetic_service's DML grants, marketdata_service's cross-read of ores_refdata_, and the entire EXECUTE-grants mechanism for publish_from_dq SECURITY DEFINER functions existed only as hand-added SQL the model/template had no way to express. Rather than hand-patch the live DB (rejected explicitly – SQL files must stay the source of truth) or hand-edit the generated .sql file (silently reverted by the next regen), fixed properly: added Execute prefixes support to load_org_service_registry_model() (mirroring the existing Select prefixes mechanism) and to the ores.sql.service.db_grants archetype template, then reconciled service_registry.org to declare every grant the committed SQL already had, plus the new refdata_service=/=ores_variability_ one. Verified byte-for-byte: diffing the full set of _ores_grant_* statements before/after the regen shows exactly one addition (the new grant) and zero removals – every previously-hand-added grant now has a model-driven source.
  2. Two seed rows were needed, not a code bugcalendar_dates starts empty on every fresh database (it's materialised only by Regenerate, never by provisioning), which is expected behaviour, not a defect; documented as a scenario step so future testers don't rediscover it.

Verification

--diff against calendar_date's ores.cpp.repository=/ =ores.cpp.service=/=ores.cpp.nats-handler=/=ores.cpp.qt facets all show zero drift after the service-registry model/template change. Full local build (compass build --preset linux-clang-debug-make) green; full ctest run 74/74 passed. Live manual QA via the QA Validation Runner: all 4 steps of Browse Holidays view for a QuantLib calendar passed against a freshly re-provisioned Barclays Plc environment (db recreate with the corrected grants baked in, no hand-patching).

Emacs 29.3 (Org mode 9.6.15)