Skip to content

CQRS Call-Site Migration: Phase 2 Extension Handlers & Phase 3 Base Classes#269

Merged
michaelbeale-IL merged 2 commits intomasterfrom
copilot/migrate-cqrs-call-sites
Feb 24, 2026
Merged

CQRS Call-Site Migration: Phase 2 Extension Handlers & Phase 3 Base Classes#269
michaelbeale-IL merged 2 commits intomasterfrom
copilot/migrate-cqrs-call-sites

Conversation

Copy link
Contributor

Copilot AI commented Feb 24, 2026

Migrates PanelManager.Instance.CreatePanel / Context.AppPanelManager.CreatePanel direct singleton call sites to use ICommandHandler<CreatePanelCommand> via CQRS, with safe DI-or-fallback pattern throughout.

Phase 2 – Extension Command Handlers

Inline safe-fallback pattern (no constructor change possible — framework instantiates these):

  • TalkWindowHandler.csCmdTalkApp panel creation
  • ShowScreenLockHandler.cs – screen lock scanner creation
  • DashboardAppScanner.csShowTalkPanel scanner creation

Phase 3 – Base Classes (property injection)

Single-field + lazy-resolve property added to each base class; propagates automatically to all derived types:

  • ActuatorBase.csShowDefaultScanTimingsConfigureDialog + ShowDefaultTryoutDialog
  • ScannerCommon.csInterpreter_EvtShowPopup popup scanner
  • DialogCommon.cscreateAndShowScannerForWidget scanner
// Property resolves lazily from DI, falls back gracefully
public ICommandHandler<CreatePanelCommand> CreatePanelHandler
{
    get => _createPanelHandler ??
           (Context.ServiceProvider?.GetService(typeof(ICommandHandler<CreatePanelCommand>))
               as ICommandHandler<CreatePanelCommand>);
    set => _createPanelHandler = value;
}

// At each call site:
var handler = CreatePanelHandler;
if (handler != null)
{
    var command = new CreatePanelCommand(panelClass, title);
    handler.Handle(command);
    form = command.CreatedPanel as Form;
}
else
{
    form = PanelManager.Instance.CreatePanel(panelClass, title); // legacy fallback
}

Tests

New CQRSHandlerTests.cs covers both resolution paths for CreatePanelCommandHandler and HandleActuatorSwitchCommandHandler: DI-available (verifies manager delegation) and null-handler fallback (verifies no exception, no state mutation).

Original prompt

This section details on the original issue you should resolve

<issue_title>CQRS Call-Site Migration: Entry Points, Handlers & Base Classes</issue_title>
<issue_description># CQRS Call-Site Migration: Entry Points, Handlers & Base Classes

Labels

phase-3, workstream-a, architecture, cqrs

Milestone

Phase 3: Architecture Completion

Summary

Migrate 76 call sites from direct singleton access (Context.AppPanelManager, Context.AppActuatorManager) to injected CQRS command/query handlers. This covers Phases 1–3 of the CQRS Implementation Guide, targeting the highest-leverage structural changes — application entry points, command handlers, and base classes.

Phase 4 (122 agent query sites) is handled in a separate issue.

Context

CQRS infrastructure was built in Phase 2:

  • ICommandHandler<T> and IQueryHandler<TQuery, TResult> interfaces exist in src/Libraries/ACATCore/Patterns/CQRS/
  • 4 handlers already registered in DI via AddCQRSHandlers():
    • CreatePanelCommandHandlerCreatePanelCommand
    • HandleActuatorSwitchCommandHandlerHandleActuatorSwitchCommand
    • GetActiveAgentNameQueryHandlerGetActiveAgentNameQuerystring
    • GetConfigurationValueQueryHandlerGetConfigurationValueQuerystring
  • DI container is operational via ServiceConfiguration.csServiceCollectionExtensions.cs
  • Context.ServiceProvider bridges DI into legacy singleton code

The established safe-fallback pattern is:

var handler = Context.ServiceProvider?.GetService(typeof(ICommandHandler<CreatePanelCommand>)) as ICommandHandler<CreatePanelCommand>;
if (handler != null)
{
    handler.Handle(new CreatePanelCommand { PanelClass = panelClass });
}
else
{
    // Legacy fallback
    Context.AppPanelManager.CreatePanel(panelClass);
}

Implementation Steps

Phase 1: Application Entry Points (4 sites, 2 files)

These already have access to _serviceProvider. Lowest risk.

  1. src/Applications/ACATApp/Program.cs (2 panel creation sites)

    • Resolve ICommandHandler<CreatePanelCommand> from _serviceProvider
    • Replace direct PanelManager.Instance.CreatePanel() calls
    • Use safe fallback pattern
  2. src/Applications/ACATTalk/Program.cs (2 panel creation sites)

    • Same pattern as ACATApp

Phase 2: Extension Handlers (5 sites, ~5 files)

Command handlers that respond to user actions. Use constructor injection.

  1. src/Extensions/Default/UI/Scanners/DashboardAppScanner.cs (1 site)

    • Add ICommandHandler<CreatePanelCommand> as constructor parameter
    • Update DI registration if needed
  2. src/Extensions/Default/UI/CommandHandlers/ (4 sites across ~4 files)

    • TalkWindowHandler.cs
    • ShowScreenLockHandler.cs
    • Other command handlers creating panels
    • Constructor injection for each

Phase 3: Base Classes (67 sites, ~3 files — highest leverage)

Changes to base classes automatically fix all derived classes. Use property injection with safe fallback to avoid breaking constructor signatures in 30+ derived classes.

  1. src/Libraries/ACATCore/ActuatorManagement/ActuatorBase.cs (2 panel creation sites)

    • Add ICommandHandler<HandleActuatorSwitchCommand> as injectable property
    • Populate via Context.ServiceProvider in Init() or property setter
    • Replace direct Context.AppActuatorManager.Pause()/Resume() calls
  2. src/Libraries/ACATCore/PanelManagement/ScannerCommon.cs (~30 actuator pause/resume sites)

    • Add injectable property for ICommandHandler<HandleActuatorSwitchCommand>
    • Single base class change propagates to all derived scanners
    • Fallback: _handler ?? Context.AppActuatorManager
  3. src/Libraries/ACATCore/PanelManagement/DialogCommon.cs (~20 actuator sites)

    • Same property injection pattern as ScannerCommon
    • Handles dialogs that pause/resume actuators
  4. Remaining base class sites (~15 across other base classes)

    • Apply same property injection pattern

Acceptance Criteria

  • All 76 call sites migrated to use CQRS handlers
  • Safe fallback pattern used at every migration point (no hard failure if DI unavailable)
  • Property injection used for base classes (no constructor signature changes)
  • Constructor injection used for entry points and command handlers
  • Solution builds successfully with zero errors
  • All existing tests pass (run full test suite)
  • No new direct PanelManager.Instance or Context.AppActuatorManager.Pause()/Resume() access in migrated files
  • Add unit tests for at least 2 handler resolution paths (DI available vs. fallback)

Key Files

File Role
src/docs/SECTION_3_3_IMPLEMENTATION_GUIDE.md Step-by-step migration guide
src/Libraries/ACATCore/Patterns/CQRS/ CQRS interfaces and handlers
`...

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

… classes

Co-authored-by: michaelbeale-IL <63321611+michaelbeale-IL@users.noreply.github.com>
Copilot AI changed the title [WIP] Migrate call sites to injected CQRS handlers CQRS Call-Site Migration: Phase 2 Extension Handlers & Phase 3 Base Classes Feb 24, 2026
@michaelbeale-IL michaelbeale-IL marked this pull request as ready for review February 24, 2026 01:13
@michaelbeale-IL michaelbeale-IL merged commit 04b7b0e into master Feb 24, 2026
2 checks passed
@michaelbeale-IL michaelbeale-IL deleted the copilot/migrate-cqrs-call-sites branch February 24, 2026 01:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CQRS Call-Site Migration: Entry Points, Handlers & Base Classes

2 participants