Task: Codegen: teach the ores.cpp.qt facet to render junction models
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
Make calendar_date (and future read-only, parent-scoped junction datasets) generatable through the ores.cpp.qt facet by teaching the junction loading/enrichment path to produce the same domain_entity-shaped Qt render context the templates already consume, without disturbing any existing domain_entity Qt generation.
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]load_org_junction_model (org_loader.py) parses a junction model's ** Qt drawer the same way load_org_model does for domain_entity (properties, Detail fields / Columns (Qt model) sub-tables), producing a qt sub-dict.[X]The junction model gains entity_singular/entity_plural/entity_pascal-equivalent derived fields (from name_singular/name) so the ores.cpp.qt mustache templates render identically whether fed a domain_entity or a junction – either by aliasing junction fields onto the same keys the domain_entity enrichment block in core.py expects, or by extending that enrichment block (core.py ~line 1706 onward) to also run for is_junction models with 'junction' in data.[X]core.py's generate_qt dispatch/enrichment (currently gated on is_domain_entity and 'domain_entity' in data) is extended to also handle is_junction and 'junction' in data with a 'qt' section present.[X]Regenerating calendar_date –address ores.cpp.qt produces correct, compilable output: real class names (ClientCalendarDateModel, CalendarDateController, CalendarDateMdiWindow), correct includes, correct macro guards – matching the has_readonly_paginated_list/has_parent_scoped_list design from Codegen: design and implement Qt parent-scoping for has_readonly_paginated_list.[X]–diff against an existing domain_entity (e.g. currency, book) with ores.cpp.qt shows zero drift – the domain_entity path must be byte-identical before and after this change.[X]Full local build green with the newly generated calendar_date Qt files compiling and linking.
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
Read the ores.cpp.qt mustache templates directly to inventory every
domain_entity.* (non-.qt.) field they reference:
component, component_include, entity_pascal,
entity_pascal_short, entity_pascal_short_plural, entity_plural,
entity_plural_words, entity_singular, entity_singular_words,
entity_snake, entity_title, entity_upper,
generator_facet_name, primary_key, repository – a much
smaller surface than the full domain_entity enrichment block in
core.py (~1700-2900) computes. primary_key=/=generator_facet_name
turned out to only be referenced inside sections already gated
behind has_readonly_paginated_list being false, or inside the
detail-dialog archetypes (disabled for calendar_date via the
:ores.cpp.qt.detail_dialog_*.enabled: false overrides from the
prior task) – so neither needed aliasing.
Direction taken
- Extracted the existing
** Qtdrawer parser (properties + Detail fields / Columns (Qt model) / Icon columns / Setting-gated actions / Related entity shortcuts) out oforg_document_to_modelinto a standalone_parse_qt_drawer()function inorg_loader.py, then called it from both the domain_entity path (unchanged behaviour) and a new** Qt-drawer branch inload_org_junction_model(). - In that same junction branch, derived the
entity_singular=/ =entity_plural=/=entity_pascal=/... family directly onto the junction dict from =name_singular=/=name=/=name_title, and aliasedrepository.entity_plural_short=/etc. from the junction's own =repository.name_short=/=name_singular_short=/... naming -- only when a =** Qtdrawer is actually present, so a junction with no Qt facet is untouched. - In
core.py, right after the existingis_junctionenrichment block finishes (component_include derivation etc.), added: ifgenerate_qtand the junction has aqtkey, aliasdata['domain_entity'] = junction– the same dict object, now carrying every field the templates need. No separate enrichment pass was needed; the domain_entity-only block (SQL table projection, csv/xml, version-nav, …) never runs for a junction, sinceis_domain_entitystays false throughout. - Iteratively regenerated
calendar_date --address ores.cpp.qtand fixed what broke, each time re-verifying--diffagainstcurrency=/=bookshowed zero drift:- Empty
enum Column { , , , ColumnCount }– mycalendar_date.orgQt drawer's*** Columns (Qt model)table used the wrong column headers (field=/=label=/=typeinstead of the requiredenum_name=/=field=/=header=/=type=/=width, discovered by comparing againstbook.org's worked example). Fixed the model, not the loader. RecencyTrackerkey extractor triedreturn e.date;wheree.dateis astd::chrono::year_month_day, not astd::string– the generickey_fieldmechanism assumes a string-like key. Set the existing but previously-unused:key_to_string_prefix:=/ =:key_to_string_suffix:Qt-drawer properties toores::platform::time::datetime::to_iso8601_date(...).- Unconditional
#include "{{Entity}}DetailDialog.hpp"/"ores.qt/HistoryDialog.hpp"incpp_qt_controller.cpp.mustache– a real template bug, not a junction-specific one: anyhas_readonly_paginated_listentity (including a hypothetical domain_entity one) would hit this, it just never compiled end-to-end before now since no entity had actually exercised the knob with a real build. Wrapped both includes indomain_entity.qt.has_readonly_paginated_listguard. BOOST_LOG_SEV(...) << calendarCode(aQString) inshowListWindow(const QString&)– boost::log's ostream has nooperator<<forQString. Same root cause as #3: a real template bug from the parent-scoping task (1C673C53), only reachable once something actually compiled againsthas_parent_scoped_list. Added.toStdString().
- Empty
- Both template fixes (#3, #4) were made in the
.orgsource and re-tangled viacompass build --direct tangle_codegen_templates, never hand-edited the.mustacheoutput directly. - Final verification:
--diffagainstcurrency=/=book(zero drift, confirming the domain_entity path is untouched), full local build (compass build --preset linux-clang-debug-make, exit 0), fullctest --preset linux-clang-debug-make(74/74 passed).
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 |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1742 | [ores.codegen] Junction-Qt-facet render path: generate calendar_date |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Qt-drawer aliasing block in load_org_junction_model replaces j["repository"] wholesale instead of merging, dropping name_short/name_singular_short/name_words/order_column that the non-Qt repository/service/nats-handler templates depend on – would silently corrupt calendar_date's already-checked-in repository facet on next regeneration (flagged independently by 5 review passes) | org_loader.py | Accepted | Fixed: repo.update({…}) merges the entity_*_short/words aliases into the existing dict instead of replacing it. Verified via –diff against calendar_date's ores.cpp.repository/service/nats-handler/qt facets (zero drift) and currency/book's ores.cpp.qt (zero drift). |
Result
Junction models can now render through the ores.cpp.qt facet.
org_loader.py gained a shared _parse_qt_drawer() (factored out of
the domain_entity path, unchanged behaviour there) and a new
** Qt-drawer branch in load_org_junction_model() that derives the
entity_singular=/=entity_plural=/=entity_pascal=/... family and
aliases =repository.entity_plural_short=/etc. from the junction's own
naming -- only when a Qt drawer is present. =core.py aliases the
fully-enriched junction dict as data['domain_entity'] for
Qt-targeted generation, reusing the existing render path with no
separate enrichment pass.
Along the way, actually compiling calendar_date's generated Qt
facet (the first entity ever to exercise
has_readonly_paginated_list=/=has_parent_scoped_list end to end)
surfaced two real, pre-existing template bugs from the parent-scoping
task, unrelated to junction support itself: an unconditional
#include of the (correctly absent, for a read-only list)
DetailDialog=/=HistoryDialog headers, and a QString streamed
straight into BOOST_LOG_SEV with no operator<<. Both fixed in the
.org template source and re-tangled.
calendar_date --address ores.cpp.qt now generates working
ClientCalendarDateModel, CalendarDateController,
CalendarDateMdiWindow (header+impl each) – correct class names,
includes, macro guards, a working RecencyTracker key extractor
(dates converted via to_iso8601_date), read-only/no-CRUD wiring,
and parent-scoped pagination.
Verification: --diff against currency=/=book (ores.cpp.qt)
shows zero drift throughout every iteration. Full local build
(compass build --preset linux-clang-debug-make) exits 0, including
the newly generated calendar_date Qt files compiling and linking
into ores.qt.refdata.lib. Full ctest --preset
linux-clang-debug-make run: 74/74 tests passed.
The generated calendar_date Qt files themselves are not yet wired
into any controller/plugin or UI action – that's
the browse-holidays task, now unblocked.