Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/candidate-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ env:
jobs:
candidate:
name: Build verified Windows x64 candidate
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'stabilization/') || startsWith(github.head_ref, 'architecture/') || startsWith(github.head_ref, 'legal/') || startsWith(github.head_ref, 'fix/')
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'stabilization/') || startsWith(github.head_ref, 'architecture/') || startsWith(github.head_ref, 'legal/') || startsWith(github.head_ref, 'fix/') || startsWith(github.head_ref, 'feat/')
runs-on: windows-latest
timeout-minutes: 35

Expand Down
25 changes: 25 additions & 0 deletions docs/OPERATOR_UX_ACCEPTANCE_2026-10-18.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Operator UX upgrade — engineering acceptance (2026-10-18)

Source baseline: `main@4cf439eaa2de1a53cca752b5af4428d5a212d6fd`.

## Operator tasks covered

1. **Analyzer** — select one publisher, see its actual sampled signal, scaled 4I+4V assumptions and phasor/RMS. The default scope now prefers the positive-going **Ua zero crossing** from an unbroken chronological window of raw decoded SV sample points. No synthetic sinusoid, per-channel phase shift, or value interpolation is permitted. On startup or when Ua is unavailable, it falls back to the prior slot-locked view. The rising crossing is a display alignment, **not a timestamp or clock-lock measurement**. Displayed values may lag up to one nominal cycle versus arrival when waiting for the most recent complete aligned window.
2. **Diagnostics** — retain current jitter, sample-counter jumps, missing samples, separate quality/sync, and detailed event evidence. Add one contextual, evidence-only operator next check instead of speculative root-cause detection. No new packet-thread text processing.
3. **GOOSE** — empty timeline describes exactly *no events in the selected filtered view*. Do not imply the publisher is offline. Make capture-adapter, VLAN mirror, publisher state, filtering and retransmission checks discoverable.
4. **Timing/PTP** — size factual metrics to content; reclaim remaining height for existing PTP event history. PTP presence remains distinct from verified clock lock.
5. **SCL** — show a concise selected identity verdict with the original expected/observed attributes and evidence under an expandable detail. The candidate similarity score is **not a probability**.

## Visual QA still required on actual Windows WPF

- 1366×768 at 100%, 1600×900 at 100%, 1920×1080 at 125%, 150% DPI and minimum supported window.
- No publisher / one / many / stale and fast stream switching; 4I+4V auto nominal versus operator raw override.
- Zero-cross start of Ua with offset sample counters, distortion, zero-amplitude Ua, counter rollover and missing samples. At the first complete window the legacy slot-locked fallback is expected until enough additional coherent samples arrive.
- GOOSE zero events and filter-empty state, event arrival transition, selection, retransmission on/off.
- SCL matched/mismatch/missing/unexpected and missing fields; open the full evidence expander.
- Long-duration WPF redraw, UI responsiveness, memory and CPU: no new high-rate callback or WPF animation was added. Real CPU/memory/GC numbers cannot be claimed without a Windows bench runner.
- The existing CI, runtime stability, CodeQL, dependency audit and candidate ZIP gates are necessary but do not replace physical acceptance issue #30.

## Safety boundary

Receive-only, raw-passive IEC 61850 SV / GOOSE / PTP. No publisher controls, synchronizing, protection operate, or subscriber acknowledgement. Software/Npcap arrival timestamps are screening evidence only, not certified timing.
95 changes: 74 additions & 21 deletions src/ProcessBus.App.Wpf/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2485,6 +2485,11 @@
FontSize="12.5"
FontWeight="SemiBold"
Margin="0,1,0,0"/>
<TextBlock Text="{Binding DiagnosticOperatorGuidanceText}"
Foreground="#A9C9DF"
FontSize="11.5" Margin="0,5,8,0"
TextWrapping="Wrap" MaxHeight="38"
ToolTip="{Binding DiagnosticOperatorGuidanceText}"/>

</StackPanel>

Expand Down Expand Up @@ -2916,7 +2921,7 @@


<Expander DockPanel.Dock="Bottom" Margin="0,8,0,0"
Header="Selected finding · complete evidence" IsExpanded="False"
Header="Selected finding · raw evidence" IsExpanded="False"
Foreground="#9DBCD7">
<TextBox Text="{Binding SelectedDiagnosticEvent.Message, Mode=OneWay}"
MinHeight="52" MaxHeight="110"
Expand Down Expand Up @@ -3158,6 +3163,43 @@
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- Plain, useful empty-state rather than an inactive sea of cards.
Bound to the filtered live view; no polling or extra VM cache. -->
<Border Grid.Row="2" Background="#0B1B2B" CornerRadius="8" Padding="24"
IsHitTestVisible="False">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding GooseHistoryView.IsEmpty}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
MaxWidth="405">
<TextBlock Text="No GOOSE events in this view"
Foreground="#E6F3FF" FontSize="16"
FontWeight="SemiBold" TextAlignment="Center"/>
<TextBlock Margin="0,9,0,0" TextAlignment="Center" TextWrapping="Wrap"
Foreground="#9FC0DC" FontSize="12"
Text="Listening for GOOSE state changes. An empty event list does not prove the publisher is offline."/>
<Border Margin="0,18,0,0" Background="#0D2236" CornerRadius="8"
Padding="14,12">
<StackPanel>
<TextBlock Text="CHECK THE CAPTURE PATH"
Foreground="#8CC7F2" FontWeight="SemiBold" FontSize="10.5"/>
<TextBlock Margin="0,6,0,0" TextWrapping="Wrap"
Foreground="#BFD5E8" FontSize="11.5"
Text="Verify capture adapter and SPAN/TAP VLAN visibility. Confirm that the IED is publishing GOOSE and that the current filters include its goID."/>
<TextBlock Margin="0,8,0,0" TextWrapping="Wrap"
Foreground="#84A8C6" FontSize="11"
Text="Tip: this timeline defaults to state changes; retransmissions can be inspected through the left-side toggle."/>
</StackPanel>
</Border>
</StackPanel>
</Border>
</Grid>
</Border>

Expand Down Expand Up @@ -3387,10 +3429,10 @@
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="14"/>
<RowDefinition Height="1.05*"/>
<RowDefinition Height="14"/>
<RowDefinition Height="0.95*"/>
<RowDefinition Height="12"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="12"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Border Grid.Row="0" Style="{StaticResource DenseGroupBorderStyle}" Padding="16">
Expand Down Expand Up @@ -3441,7 +3483,7 @@

<Border Grid.Column="0" Style="{StaticResource DenseGroupBorderStyle}" Padding="16">
<StackPanel>
<TextBlock Text="Capture confidence" Style="{StaticResource PanelTitleTextStyle}"/>
<TextBlock Text="Timing evidence" Style="{StaticResource PanelTitleTextStyle}"/>


<Border Style="{StaticResource DenseMetricCardStyle}" Padding="14" Margin="0,0,0,10">
Expand Down Expand Up @@ -3935,9 +3977,9 @@
</Grid.RowDefinitions>
<DockPanel>
<StackPanel DockPanel.Dock="Left">
<TextBlock Text="SV / GOOSE Semantic Stream Catalog" Style="{StaticResource PanelTitleTextStyle}"/>
<TextBlock Text="Engineering streams" Style="{StaticResource PanelTitleTextStyle}"/>
<TextBlock Margin="0,4,0,0"
Text="Filtered by selected IED in the SCL Explorer."
Text="Choose an IED to compare expected streams with capture."
Foreground="#8FAAC3"
FontSize="11"/>
</StackPanel>
Expand Down Expand Up @@ -4047,7 +4089,7 @@
<StackPanel DockPanel.Dock="Left">
<TextBlock Text="Live Binding Matrix" Style="{StaticResource PanelTitleTextStyle}"/>
<TextBlock Margin="0,4,0,0"
Text="Expected SCL stream vs observed network traffic."
Text="Expected configuration compared with live capture."
Foreground="#8FAAC3"
FontSize="11"/>
</StackPanel>
Expand Down Expand Up @@ -4174,7 +4216,25 @@
<TextBlock Margin="0,5,0,0" Text="{Binding SclSelectedDetailSubtitle}" Foreground="#8FAAC3" FontSize="11.5" TextWrapping="Wrap"/>
</StackPanel>

<Grid Grid.Row="2">
<Border Grid.Row="2" Background="#0E263A" BorderBrush="#315575"
BorderThickness="1" CornerRadius="8" Padding="12,10">
<StackPanel>
<TextBlock Text="IDENTITY VERIFICATION" FontSize="10.5"
Foreground="#8FC0E4" FontWeight="SemiBold"/>
<TextBlock Text="{Binding SclSelectedVerdictText}"
Foreground="#F6D781" FontSize="14" FontWeight="SemiBold"
Margin="0,5,0,3" TextWrapping="Wrap"/>
<TextBlock Text="{Binding SclSelectedVerdictDetailText}"
Foreground="#BCD6E9" FontSize="11.5"
TextWrapping="Wrap"/>
</StackPanel>
</Border>

<Expander Grid.Row="4" Header="Expected / observed · technical details"
IsExpanded="False" Foreground="#9FBBD5"
ToolTip="Full source attributes and original matching evidence are retained">
<StackPanel>
<Grid Margin="0,8,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
Expand All @@ -4193,18 +4253,11 @@
</StackPanel>
</Border>
</Grid>

<Border Grid.Row="4" Style="{StaticResource PremiumSectionCardStyle}">
<StackPanel>
<TextBlock Text="Binding Evidence" Foreground="#F4FAFF" FontSize="13" FontWeight="SemiBold"/>
<TextBlock Margin="0,7,0,0"
Text="{Binding SclSelectedBindingText}"
Foreground="#D6E8F8"
FontSize="12"
TextWrapping="Wrap"
LineHeight="17"/>
<TextBlock Text="{Binding SclSelectedBindingText}"
Foreground="#ACCAE0" FontSize="11"
Margin="3,0,0,5" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</Expander>

<Border Grid.Row="6" Style="{StaticResource PremiumSectionCardStyle}">
<Grid>
Expand Down
Loading
Loading