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 single list_settings_request batch-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_event notification, only re-checks if the changed setting name is one of the registered ones; on refresh(), one list_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

  1. Add SettingGatedActionController to ores.qt/api.
  2. Migrate currency's CurrencyMdiWindow and CurrencyDetailDialog to use it in place of the duplicated hand-written mechanism.
  3. Add the "Setting-gated actions" table + derived has_setting_gated_actions flag (org_loader.py) and the corresponding mustache sections in both cpp_qt_mdi_window.*.mustache and cpp_qt_detail_dialog.*.mustache.
  4. Flip it on for currency (generateAction_ / system.synthetic_data_generation, in both templates); regenerate; diff against the migrated hand-written version from step 2.
  5. Build passes; acceptance met.

Implementation notes

  • The actual QAction creation (icon, tooltip, connected slot — generateAction_ = new QAction("Generate", ...) etc.) stays hand-written per entity, same as currency's generateSynthetic()=/ =onGenerateClicked() feature logic itself. Only the subscribe/notify/query wiring is generalised — regenerating currency's files wholesale today would leave generateAction_ a never-constructed nullptr (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's updateGenerateActionVisibility() 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 optional guard predicate to registerAction(), ANDed with the setting's value on every refresh()CurrencyDetailDialog passes [this]{ return !isReadOnly_; }, and the generic detail_dialog template does the same with its own readOnly_ member, so future has_setting_gated_actions consumers 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.

Emacs 29.3 (Org mode 9.6.15)