Skip to content

Refactor: extract duplicated status-pill XAML into a shared QueueStatusPillControl #162

Description

@JoshuaRowePhantom

Problem

The fix for #127 adds a status pill (Immediate / Queued / Held) to the group header in AgentChatInputQueueControl.axaml. This creates near-identical structural XAML duplicated across two files:

File Lines Binding paths
Controls/QueueComposerControl.axaml 48–82 SetQueueImmediacyCommand, SubmitStatusOption.Label
Controls/AgentChatInputQueueControl.axaml 26–54 SetImmediacyCommand, SelectedImmediacyOption.Label

Both blocks have the same structure: a Border Classes="queue-status-pill dynamic" containing a Button with a MenuFlyout holding three MenuItems (Immediate / Queued / Held), and a StackPanel with a label TextBlock and a caret TextBlock. The only differences are the binding paths and the IsVisible condition.

Because the markup is inlined in two separate files, any future change to the pill's layout, styling tokens, or accessibility attributes must be applied in both places — a fragile maintenance burden.

Design

1. Extract a shared interface IQueueImmediacyViewModel

// Phantom.Workspaces.Agent.Gui/ViewModels/IQueueImmediacyViewModel.cs
namespace Phantom.Workspaces.Agent.Gui.ViewModels;

public interface IQueueImmediacyViewModel
{
    QueueImmediacyOption SelectedImmediacyOption { get; }
    ICommand SetImmediacyCommand { get; }
    QueueImmediacyOption ImmediateImmediacyOption { get; }
    QueueImmediacyOption QueuedImmediacyOption { get; }
    QueueImmediacyOption HeldImmediacyOption { get; }
}

2. Align QueueComposerViewModel to the interface

Rename SubmitStatusOptionSelectedImmediacyOption and SetQueueImmediacyCommandSetImmediacyCommand so the class satisfies IQueueImmediacyViewModel without adapters. Both InputQueueGroupViewModel and QueueComposerViewModel then share the same public surface for immediacy.

// QueueComposerViewModel — before
public QueueImmediacyOption SubmitStatusOption => ...;
public ICommand SetQueueImmediacyCommand { get; }

// QueueComposerViewModel — after (implements IQueueImmediacyViewModel)
public QueueImmediacyOption SelectedImmediacyOption => ...;
public ICommand SetImmediacyCommand { get; }

InputQueueGroupViewModel already uses the target names — no changes needed there beyond adding IQueueImmediacyViewModel to its implements clause.

3. New QueueStatusPillControl UserControl

<!-- Controls/QueueStatusPillControl.axaml -->
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="using:Phantom.Workspaces.Agent.Gui.ViewModels"
             x:Class="Phantom.Workspaces.Agent.Gui.Controls.QueueStatusPillControl"
             x:DataType="vm:IQueueImmediacyViewModel"
             Classes="queue-status-pill dynamic">
    <Button Classes="queue-action-button queue-status-picker-button">
        <Button.Flyout>
            <MenuFlyout Placement="BottomEdgeAlignedLeft">
                <MenuItem Command="{Binding SetImmediacyCommand}"
                          CommandParameter="{Binding ImmediateImmediacyOption}"
                          Header="{Binding ImmediateImmediacyOption}"
                          Classes="queue-immediacy-menu-item"/>
                <MenuItem Command="{Binding SetImmediacyCommand}"
                          CommandParameter="{Binding QueuedImmediacyOption}"
                          Header="{Binding QueuedImmediacyOption}"
                          Classes="queue-immediacy-menu-item"/>
                <MenuItem Command="{Binding SetImmediacyCommand}"
                          CommandParameter="{Binding HeldImmediacyOption}"
                          Header="{Binding HeldImmediacyOption}"
                          Classes="queue-immediacy-menu-item"/>
            </MenuFlyout>
        </Button.Flyout>
        <StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
            <TextBlock Text="{Binding SelectedImmediacyOption.Label}"
                       Classes="queue-immediacy-label queue-status-label"/>
            <TextBlock Text=""
                       Classes="queue-immediacy-caret queue-status-caret"/>
        </StackPanel>
    </Button>
</UserControl>

4. Call-site changes

QueueComposerControl.axaml — replace the inline pill (lines 48–82) with:

<controls:QueueStatusPillControl Grid.Column="2"
                                  DataContext="{Binding}"
                                  IsVisible="{Binding IsDefaultComposer}"/>

AgentChatInputQueueControl.axaml — replace the inline pill (lines 26–54) with:

<controls:QueueStatusPillControl DataContext="{Binding}"
                                  IsVisible="{Binding !IsDefault}"/>

Files to change

File Change
ViewModels/IQueueImmediacyViewModel.cs New — shared interface
ViewModels/QueueComposerViewModel.cs Rename SubmitStatusOptionSelectedImmediacyOption and SetQueueImmediacyCommandSetImmediacyCommand; add : IQueueImmediacyViewModel
ViewModels/InputQueueGroupViewModel.cs Add : IQueueImmediacyViewModel to class declaration
Controls/QueueStatusPillControl.axaml New — extracted pill UserControl
Controls/QueueStatusPillControl.axaml.cs New — code-behind (empty, standard pattern)
Controls/QueueComposerControl.axaml Replace inline pill block with <controls:QueueStatusPillControl>
Controls/AgentChatInputQueueControl.axaml Replace inline pill block with <controls:QueueStatusPillControl>

Tests

Tests in Phantom.Workspaces.Agent.Gui.Tests:

MainWindowAxamlTests

  1. QueueStatusPillControl_ContainsSingleDefinitionOfPillMarkup — reads QueueStatusPillControl.axaml and asserts it contains exactly one queue-status-pill class, one SetImmediacyCommand binding, one SelectedImmediacyOption.Label binding, and all three immediacy CommandParameter bindings.
  2. QueueComposerControl_StatusPill_UsesQueueStatusPillControl — reads QueueComposerControl.axaml and asserts it references QueueStatusPillControl (not an inline queue-status-pill Border), with IsVisible="{Binding IsDefaultComposer}".
  3. AgentChatInputQueueControl_StatusPill_UsesQueueStatusPillControl — reads AgentChatInputQueueControl.axaml and asserts it references QueueStatusPillControl with IsVisible="{Binding !IsDefault}", replacing the old tests added for Bug: non-default queues don't show their status (immediate/queued/held) when composer is collapsed #127.

InputQueueViewModelTests

  1. Update SubmitStatusOption references to SelectedImmediacyOption and SetQueueImmediacyCommand references to SetImmediacyCommand in existing tests.

All tests: deterministic, no Task.Delay, no Moq.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestverifiedIssue has been verified

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions