Task: Land setting-gated action-visibility knob as a qt-profile template capability
Table of Contents
This page documents a task in the Commission: currency story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Generalise currency's setting-gated "generate synthetic test data"
button (updateGenerateActionVisibility, driven by the
system.synthetic_data_generation setting) into a conditional
action-visibility knob in the mdi_window and detail_dialog templates,
with currency as the first real consumer. Step 3 of the regenerate
plan (task 6E0CD16E).
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Commission: currency |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-06 |
Acceptance
- Setting-gated action visibility is a generalised, opt-in template knob usable by any action, not specific to the generate button.
- Regenerating currency's mdi_window/detail_dialog reproduces the current setting-gated behaviour.
- Build passes.
Plan
Current state (investigated 2026-07-06)
The mechanism (setupGenerateAction=/=updateGenerateActionVisibility=/
=onSystemSettingNotification) is duplicated near-verbatim in both
CurrencyMdiWindow and CurrencyDetailDialog: subscribe to the
system_setting_changed_event NATS event on login/reconnect, listen
for ClientManager::notificationReceived, and on either a
notification or an explicit refresh, run a QtConcurrent task issuing
variability::messaging::list_settings_request and setting the
action's visibility from the (single) matching setting's boolean
value. ~50 lines duplicated per file, entirely generic apart from the
setting name and which QAction* it gates.
Design
Extract a reusable, non-templated helper — SettingGatedActionController
(QObject, ores.qt/api, same "generalise the wiring, not the entity"
shape as ImportEntityDialog) — rather than generating the ~50 lines
per action in mustache:
explicit SettingGatedActionController(ClientManager*, QObject* parent = nullptr)wires the login/reconnect/notification subscription once, internally.void registerAction(QAction* action, QString setting_name)— adds an action to gate; one controller instance can gate several actions off different settings, so a single NATS subscription and a singlelist_settings_requestbatch-check covers all of them (avoids N separate fetches for N gated actions).void refresh()— force a re-check of every registered action (called after login, and by the caller if already logged in at construction time).- Internally: on a
system_setting_changed_eventnotification, only re-checks if the changed setting name is one of the registered ones; onrefresh(), onelist_settings_request, then set every registered action's visibility from the matching setting.
Codegen side: a new "Setting-gated actions" Qt table section (one row
per action: action field name, setting name — mirrors the shape of
the existing "Icon columns (Qt model)" table rather than a single
boolean flag, since the acceptance criterion is genericity over any
action, potentially more than one per entity). has_setting_gated_actions
is derived (non-empty table), same pattern as has_flag_icon.
mdi_window and detail_dialog templates both get the same
{{#has_setting_gated_actions}} block: construct the controller,
registerAction per row, wire loggedIn=/=reconnected to refresh().
Steps
- Add
SettingGatedActionControllertoores.qt/api. - Migrate currency's
CurrencyMdiWindowandCurrencyDetailDialogto use it in place of the duplicated hand-written mechanism. - Add the "Setting-gated actions" table + derived
has_setting_gated_actionsflag (org_loader.py) and the corresponding mustache sections in bothcpp_qt_mdi_window.*.mustacheandcpp_qt_detail_dialog.*.mustache. - Flip it on for currency (generateAction_ / system.synthetic_data_generation, in both templates); regenerate; diff against the migrated hand-written version from step 2.
- Build passes; acceptance met.
Implementation notes
- The actual
QActioncreation (icon, tooltip, connected slot —generateAction_ = new QAction("Generate", ...)etc.) stays hand-written per entity, same as currency'sgenerateSynthetic()=/ =onGenerateClicked()feature logic itself. Only the subscribe/notify/query wiring is generalised — regenerating currency's files wholesale today would leavegenerateAction_a never-constructednullptr(the template has no opinion on what the action looks like or does), consistent with the broader "Sync Qt codegen for currency" reconciliation still being incremental, capability-by-capability. CurrencyDetailDialog'supdateGenerateActionVisibility()had an extra rule the shared controller initially didn't know about — hide in read-only/history mode, rechecked on every visibility update including notification-triggered ones. The first cut of this task only guarded the initial post-login check, which the PR review round correctly flagged as a regression (a live setting-change notification while a read-only history view was open could transiently re-show the action). Resolved by adding an optionalguardpredicate toregisterAction(), ANDed with the setting's value on everyrefresh()—CurrencyDetailDialogpasses[this]{ return !isReadOnly_; }, and the generic detail_dialog template does the same with its ownreadOnly_member, so futurehas_setting_gated_actionsconsumers get the same protection by default. See PR #1446's review round for the full back-and-forth.- Verified via
codegen entity generate currency --address ores.cpp.qt --diff: the has_setting_gated_actions-gated sections match the hand-migrated code modulo formatting. Did not adopt the full regenerated file set (same pre-existing unrelated drift as the CSV/XML task). - Lesson from this task: commit hand-migration edits to the actual
entity files before running a test full-regenerate-then-revert —
git checkout --reverts to the last commit, not to "before this local experiment," and silently discarded the CurrencyMdiWindow/ CurrencyDetailDialog edits once, requiring a redo.
Notes
PRs
| PR | Title |
|---|---|
| #1446 | [codegen,qt] Land setting-gated action-visibility as a qt-profile capability |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Shared controller drops read-only guard on live notifications (correctness) | SettingGatedActionController.hpp/.cpp, CurrencyDetailDialog.cpp | Fixed | Added optional guard predicate to registerAction(); fixed in b7176c782 |
| 2 | Hand-written files' QTimer::singleShot defer diverges from template's synchronous refresh() | CurrencyMdiWindow.cpp, CurrencyDetailDialog.cpp | Fixed | Deferral was leftover scaffolding, not load-bearing; removed from both, b7176c782 |
| 3 | isConnected() -> isLoggedIn() unification in refresh() gate | SettingGatedActionController.cpp | Declined | Intentional: list_settings_request requires auth either way, isLoggedIn() is the correct gate |
| 4 | No explicit #include <QFutureWatcher> (relies on <QtConcurrent>) | SettingGatedActionController.cpp | Declined | Matches existing codebase convention throughout ores.qt |
| 5 | No unit test for SettingGatedActionController | SettingGatedActionController.cpp | Declined | Interesting paths need a live ClientManager/NATS connection; follow-up, not blocking |
| 6 | Double construction of controller if setClientManager called twice (latent footgun) | CurrencyDetailDialog.cpp, mustache template | Declined | Not live today — every call site builds a fresh dialog per invocation |
| 7 | Follow-up round: fix confirmed correct end-to-end; task doc stale (described gap as still open) | task_land-setting-gated-visibility-qt-template.org | Fixed | Updated implementation notes in 6b05cb53f |
| 8 | Follow-up round: continuation-line indentation doesn't match file's clang-format style | SettingGatedActionController.hpp/.cpp | Fixed | Ran clang-format -i in 6b05cb53f |
Result
Shipped SettingGatedActionController (ores.qt/api), a reusable
non-templated helper that generalises the subscribe/notify/query
mechanism for gating QAction visibility off a boolean system
setting — one controller instance can gate several actions across
different settings with a single NATS subscription and a single
list_settings_request fetch, plus an optional per-action guard
predicate (ANDed with the setting) for cases like a read-only detail
dialog. Migrated CurrencyMdiWindow and CurrencyDetailDialog's
duplicated updateGenerateActionVisibility=/
=onSystemSettingNotification=/=onConnectionEstablished mechanism onto
it. Added the opt-in has_setting_gated_actions qt-profile flag
("Setting-gated actions" table, action/setting rows) to the
mdi_window and detail_dialog mustache/org templates, and flipped it
on for currency as first consumer. A PR review round caught a
correctness regression (the shared controller re-showing the generate
action during a live setting-change notification while a read-only
history dialog was open) — fixed by adding the guard-predicate
mechanism described above; verified fixed by two follow-up review
passes. All three acceptance criteria met: the knob is generic
(usable by any action via the table, not generate-button-specific),
regenerating currency's gated sections reproduces the hand-migrated
behaviour (verified via diff), and the build passes.