diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml index 1246a203..f7bdd071 100644 --- a/.github/workflows/release-package.yml +++ b/.github/workflows/release-package.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: version: - description: Version label, for example 1.4.1 + description: Version label, for example 1.4.2 required: true - default: 1.4.1 + default: 1.4.2 publish_release: description: Publish GitHub Release after successful validation (manual only) required: true @@ -25,7 +25,7 @@ on: release_notes_file: description: Markdown file used as release body required: true - default: docs/RELEASE_NOTES_v1.4.1.md + default: docs/RELEASE_NOTES_v1.4.2.md push: tags: - "v*" @@ -183,7 +183,7 @@ jobs: $sha = "artifacts/release/SHA256SUMS.txt" $manifest = "artifacts/release/release-manifest.json" $notesFile = '${{ github.event.inputs.release_notes_file }}' - if ([string]::IsNullOrWhiteSpace($notesFile)) { $notesFile = 'docs/RELEASE_NOTES_v1.4.1.md' } + if ([string]::IsNullOrWhiteSpace($notesFile)) { $notesFile = 'docs/RELEASE_NOTES_v1.4.2.md' } if (-not (Test-Path $notesFile)) { throw "Release notes file not found: $notesFile" } $releaseArgs = @('release', 'create', $tag, $installer, $zip, $sha, $manifest, '--title', "Process Bus Insight $tag", '--notes-file', $notesFile, '--target', '${{ github.sha }}') diff --git a/Directory.Build.props b/Directory.Build.props index 216f8573..f00caefa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ en - 1.4.1 + 1.4.2 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix) diff --git a/README.md b/README.md index 62f8b76f..f6dc2f75 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The application can record what was configured in SCL, what the selected capture ## Download and run -1. Download the latest **Windows Inno Setup installer** from [GitHub Releases](https://github.com/masarray/DigSubAnalyzer/releases): `ProcessBusInsight-v1.4.1-win-x64-setup.exe`. +1. Download the latest published **Windows Inno Setup installer** (v1.4.2 after its public release) from [GitHub Releases](https://github.com/masarray/DigSubAnalyzer/releases): `ProcessBusInsight-v1.4.2-win-x64-setup.exe`. 2. Run the installer. Its stable app identity supports in-place updates, same-version repair, Windows uninstall, and optional desktop shortcut. 3. **Offline manuals are installed directly inside the app folder** (`User Manual.pdf`, `Quick Start.pdf`, and `Install and Update.md`), with Start Menu shortcuts. 4. Install **Npcap separately** when live raw Ethernet capture is required; it is not bundled. Offline PCAP analysis remains available without it. @@ -58,8 +58,8 @@ An optional **portable ZIP** is also available for manual extraction. The commun Current Windows distribution naming: ```text -ProcessBusInsight-v1.4.1-win-x64-setup.exe -ProcessBusInsight-v1.4.1-win-x64-portable.zip (optional) +ProcessBusInsight-v1.4.2-win-x64-setup.exe +ProcessBusInsight-v1.4.2-win-x64-portable.zip (optional) SHA256SUMS.txt release-manifest.json ``` @@ -100,8 +100,8 @@ dotnet run --project .\src\ProcessBus.App.Wpf\ProcessBus.App.Wpf.csproj -c Relea Create and verify a portable package: ```powershell -.\scripts\publish-windows-portable.ps1 -Version "1.4.1" -.\scripts\verify-release-package.ps1 -PackageZip ".\artifacts\release\ProcessBusInsight-v1.4.1-win-x64-portable.zip" +.\scripts\publish-windows-portable.ps1 -Version "1.4.2" +.\scripts\verify-release-package.ps1 -PackageZip ".\artifacts\release\ProcessBusInsight-v1.4.2-win-x64-portable.zip" ``` Run the repository-quality gate: diff --git a/docs/RELEASE_NOTES_v1.4.2.md b/docs/RELEASE_NOTES_v1.4.2.md new file mode 100644 index 00000000..cb7bfe87 --- /dev/null +++ b/docs/RELEASE_NOTES_v1.4.2.md @@ -0,0 +1,26 @@ +# Process Bus Insight v1.4.2 — GOOSE Selection Hotfix Candidate + +This hotfix follows published v1.4.1 and is **not** an IEC 61850 certification or a claim of completed physical Windows field acceptance. + +## Operator-visible changes +- Publisher explorer holds a stable selected publisher when GOOSE snapshots update rapidly. +- Inspector live values use the latest coherent selected-publisher frame; clicking a timeline row deliberately pins historical evidence without overwriting the chosen live publisher. +- Explicit selection persists across retransmissions, filtering, and temporary publisher absence. STALE retains last valid values and last-received time. +- Publisher identity includes source MAC, APPID, VLAN, and GoCBRef, preventing collisions between similarly named control blocks. +- Typed DataSet values still use generic MMS decode when SCL is unavailable, and resolve matching SCL mapping when supplied. +- No layout spacing, phasor, waveform, PTP, or SV decoder changes. + +## Verification before public release +A Windows hotfix candidate must pass repository health, Windows CI/test, Runtime Stability, Runtime Architecture, CodeQL, verified Inno installation/repair and exact-SHA BuildInfo provenance. A physical click/selection stream test remains a separate required check (100%, 125%, 150% DPI; sustained retransmissions, multiple publishers and filters). + +**Distribution:** candidate artifacts produced by the PR workflow are not a published stable v1.4.2 release. Preserve v1.4.1 tag and published assets. Explicit approval required to release the hotfix. + +## Engineer-focused GOOSE readout refinement + +- Compact chevrons instead of prominent circular disclosure controls, with stable expanded raw MMS details during high-rate retransmissions. +- Each unmapped entry is explicitly named `Entry n`; signal references appear only from matching SCL data. +- `TEST` badges on publisher and Inspector, distinct `RX frames` and `visible events` counters, and GOOSE-specific capture status. +- Explicit `LIVE / STALE / PINNED EVENT` context. Returning to Live clears the timeline's historical selection. +- Strict 13-bit IEC 61850 q bit-string padding validation and additional positive/negative regression coverage. + +Manual Windows GUI/display-scaling field acceptance remains a separate approval gate. diff --git a/docs/index.html b/docs/index.html index d1aa805b..3e80cef7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -37,7 +37,7 @@ "applicationCategory": "EngineeringApplication", "applicationSubCategory": "Digital substation and process bus analysis", "operatingSystem": "Windows 10, Windows 11", - "softwareVersion": "1.4.1", + "softwareVersion": "1.4.2", "license": "https://spdx.org/licenses/GPL-3.0-or-later.html", "isAccessibleForFree": true, "url": "https://masarray.github.io/DigSubAnalyzer/", diff --git a/docs/validation/GOOSE_COMPACT_OPERATOR_UX_2026-10-21.md b/docs/validation/GOOSE_COMPACT_OPERATOR_UX_2026-10-21.md new file mode 100644 index 00000000..d6eb6b2f --- /dev/null +++ b/docs/validation/GOOSE_COMPACT_OPERATOR_UX_2026-10-21.md @@ -0,0 +1,32 @@ +# GOOSE Compact Operator UX — Windows Acceptance + +Scope: GOOSE workspace only. Retain key-stable publisher selection, the bounded live and historical traffic views, the 8-DIP workspace gutters, raw MMS evidence, and the previously validated signal interpretation. + +## Design behavior + +- The source rail has one search field with local WPF vector Material-style search/close icons. An empty search hides the clear control; typing reveals it. Clicking clear resets the existing filter and restores focus to the search input, without rebuilding publisher identity or changing the selected publisher. +- The duplicate message-name dropdown and duplicate explanatory source footer are removed. The repeated-messages toggle still controls timeline visibility only. +- Only the source ListView scrolls. Its Grid star row constrains height and the ListView uses the WPF recyclable virtualizing stack; the title, search and retransmission toggle remain visible. +- The sticky Inspector status remains visible above the scrollable evidence. Within that evidence are three independent sections: Message (open), Network (closed) and Values (open). Users can expand/collapse each in any combination, using mouse, touch or keyboard. The static controls preserve their state through receive updates. +- Values have no forced maximum height. Closing Message/Network reveals more DataSet rows, and the single evidence scrollbar reveals overflowing content. +- The compact circle-chevron icon is drawn as vector geometry with no downloaded font, icon dependency or network access. +- Raw MMS types and true signal identity remain traceable from expanded entry detail; unconfigured bit patterns are not translated into physical switch states. + +## Acceptance matrix + +| Test | Expected | +| --- | --- | +| 20+ publishers at 768 px window height | Source ListView scrolls to last item; search and toggle remain fixed | +| Search by message/source/MAC, then clear | Matching rows update without reset of keyed publisher; clear icon disappears on empty | +| Search miss, then clear | No false publisher removal; all sources return | +| Expand Message + Network + Values | Headers are independently clickable; one evidence scrollbar handles overflow | +| Collapse Message + Network | Values gain available viewport area; entry row expanders still work | +| Receive repeated messages with one or more sections collapsed | No unexpected accordion state flip, Inspector target loss or UI-thread blocking | +| Select a historical event and return to live | Sticky context updates but accordion states persist | +| 100/125/150% Windows scaling | Circle-chevron, search, X and signal/value text remain aligned and not clipped | +| Keyboard focus and accessibility | Search and section headers can be focused; toggle activation works by keyboard | +| Import matching SCL / omit SCL | Verified names and quality when matched; raw data items with no fabricated meaning otherwise | + +## CI discipline + +Use one exact-SHA Windows CI and runtime/security/Inno candidate workflow set after committing the complete UX checkpoint. Review any failure and fix only the affected surface. Do not rerun successful jobs without a substantive reason. Keep v1.4.1 stable assets untouched and treat this as v1.4.2 pre-release candidate until physical acceptance. diff --git a/docs/validation/GOOSE_ENGINEERING_READOUT_P0_P3_2026-10-21.md b/docs/validation/GOOSE_ENGINEERING_READOUT_P0_P3_2026-10-21.md new file mode 100644 index 00000000..597cebd8 --- /dev/null +++ b/docs/validation/GOOSE_ENGINEERING_READOUT_P0_P3_2026-10-21.md @@ -0,0 +1,73 @@ +# GOOSE Engineering Readout — P0–P3 Validation Handoff + +Date: 2026-10-21. Scope: receive-only GOOSE engineering workspace. Baseline: selection/inspector single-source-of-truth hotfix plus v1.4.2 GUI candidate. This is **not** an IEC 61850 conformance claim. + +## Engineering invariants + +1. Selection is keyed to source MAC, APPID, VLAN, and GoCB reference; transient WPF null/retransmission does not delete operator intent. +2. **Live** Inspector always shows the latest coherent frame of the explicitly selected publisher. Choosing an event pins an immutable historical snapshot. Returning to Live explicitly clears timeline selection; switching publisher cannot leave a misleading historic highlight. +3. RX frame count is distinct from event count **visible after filters**. Retransmission toggle governs only event history visibility; it does not stop receiving retransmitted packets. +4. Raw MMS data without matching SCL is labeled `Entry 1…n` with its decoded wire type/value. Signal names, DPC meanings, and IEC 61850 q quality are never inferred from arbitrary bit patterns. +5. Only a matched SCL quality field with a valid 13-bit MMS BIT STRING can be interpreted as IEC 61850 q. Invalid unused-bit padding, unexpected width, bad hex, and non-BitString types fail closed. +6. The initial observed DataSet is a baseline, not a fabricated field change. Subsequent changes use prior observed values. +7. Test-mode publishers show explicit **TEST** identifiers; no red/green Boolean good/bad assumptions. +8. Keyed publisher rows and per-index DataSet rows survive passive refreshes without collapsing an opened MMS detail. Deliberately changing publisher or pinning a different event resets only that DataSet context. + +## Visual contract + +- Workspace shell and panel gutters: 8 DIP. GOOSE detail width: 460 DIP; other workspaces unchanged. +- Compact Signal / Value rows with a subdued chevron, expanded MMS raw value and payload hex, semantically resolved quality details only with a matching SCL. +- Publisher label and full reference available without high-contrast action ornamentation. +- Timeline remains a forensic event list, not a decorative panel filled with unrelated cards. +- Time-sensitive status strip reflects GOOSE traffic when the GOOSE workspace is active, without changing other workspaces' status. + +## Required automatic gates + +Windows repository health, workspace layout/source contract, full .NET solution compilation, unit tests, Runtime Stability, Runtime Architecture, CodeQL, dependency checks and Inno installer package/install/repair. These must all reference the *same commit SHA* and be verified on the PR head. + +### Regression coverage + +- Empty/malformed quality, invalid unused padding, exact 13-bit validity values, substituted/test/operator-blocked detail flags. +- Generic entry names versus explicit matching SCL signal reference names. +- Publisher TEST flag changes on an existing selected keyed row. +- Multi-publisher identity, retransmission, same-GoID split MAC/VLAN, filter-miss, stale/recovery, 1,000 retransmissions, 500-source bounded churn. +- First-frame baseline versus later observed state-change semantics. + +## Manual Windows field acceptance (still required after automated PASS) + +At 1366×768, 1586×893 and 1920×1080 (where available) under 100%, 125%, and 150% display scaling: inspect normal/expanded DataSet rows without clipping, confirm chevron click and keyboard toggle, tab through TEST/LIVE/PINNED/STALE indicators, select multiple GOOSE publishers during continuous retransmissions, pin events, click Return to Live, use filter search/GOOSE ID with no selection loss, and observe updates with real or recorded GOOSE traffic. Import a matching SCL and verify genuine signal/quality mappings, then repeat without SCL to confirm raw-only fallback. + +**Acceptance boundary:** a successful automated CI build does not prove physical UI readability or IEC 61850 interoperability at a specific field installation. Keep candidate publication and branch promotion separate from this manual sign-off. + +## Follow-up: operator screenshot inspection, expanded raw MMS entries + +Three operator screenshots of the P0–P3 candidate demonstrate selection stability during reception and successful simultaneous expansion of two typed DataSet entries. They also reveal three visual defects: DataSet title/count running together, the Inspector context disappearing when the entire pane scrolls, and unnecessarily tall expanded MMS details. + +The targeted follow-up uses a fixed Inspector header row (including PINNED/TEST and Return to Live), a single scrolling evidence region, an explicitly aligned DataSet title/count grid, and compact key/value MMS details. These corrections do not alter packet reception, GOOSE state matching, generic typed decoding, or the 8-DIP workspace gutter. + +Required physical acceptance: expand two entries, scroll to the bottom and back, confirm the Inspector mode and Return to Live remain visible throughout; confirm the DataSet count does not touch the title at 100%, 125%, 150% DPI; verify selecting a different publisher or returning to Live changes Inspector context correctly without replacing unrelated publisher rows. + +## Operator language acceptance — message/field terminology + +The application must explain what an electrical engineer is seeing without requiring familiarity with every IEC 61850 field name. Keep accurate original wire references in tooltips or expandable forensic rows. + +| Main display | Original technical evidence | +| --- | --- | +| Message name | goID | +| Control block | goCbRef | +| Application code | APPID | +| State number / Sequence number | stNum / sqNum | +| Sender address / Group address | Ethernet source / destination MAC | +| Network group | VLAN ID and priority | +| Configuration version | confRev | +| Allowed wait | timeAllowedToLive, milliseconds | +| Binary bit pattern | MMS BitString | +| Data item 1, Data item 2… | Raw decoded values without matching SCL | +| Open/Closed (verified switch-position signal only) | Matched SCL XCBR or XSWI / Pos.stVal | +| First seen / State changed / Repeated | New / State Change / Retrans reception events | + +**Failure safety:** no breaker position can be inferred from a bare `00/01/10/11` bit pattern, unrelated DPC attributes, quality fields, timestamps or incomplete SCL identity. Source data is not renamed when evidence is missing. A configuration-tab shortcut explains how to identify signals. + +**Clock semantics:** the reception time in message history must use the same local clock as the recorded-message Inspector timestamp, avoiding false multi-hour offsets on site. + +**Additional physical acceptance:** use a multi-publisher real capture and a matching imported device configuration. Compare message history time against the Inspector time; validate names, interpreted switch position and decoded quality against a separately verified record. With no configuration, all entries remain generic with readable data type and raw bit/hex evidence. Test 100/125/150% scaling and expand two data rows without losing the sticky Inspector heading. diff --git a/docs/validation/GOOSE_FAMILIAR_FIELD_TERMINOLOGY_2026-10-21.md b/docs/validation/GOOSE_FAMILIAR_FIELD_TERMINOLOGY_2026-10-21.md new file mode 100644 index 00000000..456871e0 --- /dev/null +++ b/docs/validation/GOOSE_FAMILIAR_FIELD_TERMINOLOGY_2026-10-21.md @@ -0,0 +1,38 @@ +# GOOSE Message Terminology — Field Acceptance + +## Purpose + +Use widely recognized GOOSE capture and protocol field terminology in the GOOSE workspace while retaining the existing application layout, stable publisher selection, compact search and three independent Inspector sections. All terms describe actual received evidence, not assumed switching states. + +## Capture table + +| Column | Evidence | +| --- | --- | +| Time | Local receive time from the actual frame timestamp (seven fractional digits where available) | +| Relative time | Seconds since the first observed GOOSE history event after capture start/reset; **not** the time gap to the preceding event | +| Source | Source Ethernet MAC address | +| Destination | Destination Ethernet MAC address | +| Description | Original GOOSE control-block reference (GoCBRef), with full text in a tooltip | + +The small `G` marker denotes a GOOSE frame; its tooltip retains the event distinction (first seen, state change, repeated). The repeated-message filter still controls which events are visible, not which frames are received. The receive count and event count remain separate. + +## Details + +- Control Block reference, Application ID, GOOSE ID, Status number (stNum), Sequence number (sqNum), Entry time. +- Destination MAC address, Source MAC address, DataSet reference, VLAN ID, VLAN priority, Needs commissioning, Configuration revision, Simulation/Test, Time allowed to live (ms). +- Data: Name and Value columns. When no valid SCL identity is available, a received MMS BitString is named **Bitstring N** only as an ordinal/type label, not as an IEC 61850 object identifier. Other recognized wire types likewise may use type/ordinal labels. + +## Operator contract + +- Dark theme, 8-DIP gutters, scrollable publisher list, one Material-style search/clear field and independent Message/Network/Data disclosure controls remain in place. +- DataSet interpretation does **not** infer a breaker position from an unmapped bit pattern. Verifiable SCL matching remains a prerequisite to semantic values and quality. +- Historical event selection remains distinct from live publisher selection. **Show Latest** returns to live evidence. The Inspector displays the selected message snapshot coherently. +- No external product/provider names are introduced in source, commit history, public PR or validation notes. + +## Automated and physical checks + +Automated: exact-SHA Windows compile, full tests, 8-DIP geometry and terminology contract, runtime architecture/stability, CodeQL, dependency analysis and candidate Inno install/repair smoke. + +Physical: inspect 20+ active GOOSE sources, select two different messages, compare Source/Destination with a verified Ethernet capture, ensure Relative time is monotonic and reset with capture clearing, inspect all detail labels without clipping at 100%/125%/150% scaling, import matching SCL for genuine values and test raw Bitstring fallback without SCL. + +Green CI is not a substitute for the final physical reading/interaction test. diff --git a/scripts/verify-workspace-spacing.ps1 b/scripts/verify-workspace-spacing.ps1 index a5988d1e..d610805a 100644 --- a/scripts/verify-workspace-spacing.ps1 +++ b/scripts/verify-workspace-spacing.ps1 @@ -56,7 +56,7 @@ Assert-True (Has-Definitions 'Grid.RowDefinitions' 'RowDefinition' 'Height' '0.8 # All five customer-facing workspaces share the same horizontal or vertical rhythm. Assert-True (Has-Definitions 'Grid.RowDefinitions' 'RowDefinition' 'Height' 'Auto,8,Auto,8,Auto,Auto,*') 'Diagnostics primary vertical gutters' -Assert-True (Has-Definitions 'Grid.ColumnDefinitions' 'ColumnDefinition' 'Width' '*,8,400') 'GOOSE timeline/inspector gutter' +Assert-True (Has-Definitions 'Grid.ColumnDefinitions' 'ColumnDefinition' 'Width' '*,8,460') 'GOOSE timeline/inspector gutter (readable engineering details, 8 DIP separator)' Assert-True (Has-Definitions 'Grid.RowDefinitions' 'RowDefinition' 'Height' 'Auto,8,Auto,8,*') 'Timing/PTP major sections' Assert-True (Has-Definitions 'Grid.ColumnDefinitions' 'ColumnDefinition' 'Width' '1.15*,8,1.1*,8,1.2*') 'Timing/PTP header-card gutters' Assert-True (Has-Definitions 'Grid.ColumnDefinitions' 'ColumnDefinition' 'Width' '1.1*,8,1.4*') 'Timing/PTP evidence gutter' @@ -86,4 +86,98 @@ Assert-True ($legacyCardGapCount -eq 0) 'no legacy 12-DIP gaps between adjacent foreach ($name in @('WorkspacePanelInset', 'WorkspacePanelGapBelow', 'WorkspaceHeroInset')) { Assert-True ($source.Contains("{StaticResource $name}")) "workspace must use spacing token $name" } +# GOOSE-specific P0–P3 readout contracts: only GOOSE receives the +# compact disclosure style; all other workspace layout tokens stay untouched. +$gooseExpanderStyle = $theme.SelectNodes("//w:Style[@x:Key='GooseDatasetExpanderStyle']", $ns) +Assert-True ($gooseExpanderStyle.Count -eq 1) 'GOOSE has one scoped engineer disclosure style' +$gooseExpanderUsage = $window.SelectNodes("//w:Expander[@Style='{StaticResource GooseDatasetExpanderStyle}']", $ns) +Assert-True ($gooseExpanderUsage.Count -eq 1) 'GOOSE typed rows use scoped compact chevrons' +foreach ($binding in @( + 'GooseInspectorRows', + 'GooseTimelineCountersText', + 'GooseInspectorModeBadgeText', + 'GooseInspectorMessage.IsTest', + 'GooseReturnToLiveCommand' +)) { + Assert-True ($source.Contains($binding)) "GOOSE inspector contract: $binding" +} +# Exact UI defect reported in operator screenshots: Inspector controls must +# remain visible when raw evidence scrolls; count must have its own grid cell. +$inspectorStart = $source.IndexOf('') +$inspectorEnd = $source.IndexOf('', $inspectorStart) +Assert-True ($inspectorStart -ge 0 -and $inspectorEnd -gt $inspectorStart) 'GOOSE inspector markup found' +$inspector = $source.Substring($inspectorStart, $inspectorEnd - $inspectorStart) +$headerOffset = $inspector.IndexOf('Text="Details"') +$scrollOffset = $inspector.IndexOf('')) 'DataSet title no longer uses ambiguous DockPanel filling' +Write-Host 'GOOSE Inspector screenshot regression gate: PASS (fixed header, count alignment, compact MMS details)' +# Operator-approved compact GOOSE UX: no duplicated name filter, no +# unbounded ListView inside a StackPanel, and all three independent sections. +$gooseLeftStart = $source.IndexOf('GOOSE LEFT EXPLORER') +$gooseLeftEnd = $source.IndexOf('TIMING / PTP LEFT EXPLORER', $gooseLeftStart) +Assert-True ($gooseLeftStart -ge 0 -and $gooseLeftEnd -gt $gooseLeftStart) 'GOOSE source panel markup available' +$gooseLeft = $source.Substring($gooseLeftStart, $gooseLeftEnd - $gooseLeftStart) +Assert-True (-not $gooseLeft.Contains('')) 'Scrollable source viewport is height constrained' +Assert-True (-not $gooseLeft.Contains('Text="{Binding GooseFilterSummaryText}"')) 'Redundant source footer removed' + +$sectionNodes = $window.SelectNodes("//w:Expander[@Style='{StaticResource GooseInspectorSectionExpanderStyle}']", $ns) +Assert-True ($sectionNodes.Count -eq 3) 'Exactly three collapsible GOOSE Inspector sections' +Assert-True (($sectionNodes[0].GetAttribute('IsExpanded') -eq '{Binding GooseMessageExpanded, Mode=TwoWay}') -and + ($sectionNodes[1].GetAttribute('IsExpanded') -eq '{Binding GooseNetworkExpanded, Mode=TwoWay}') -and + ($sectionNodes[2].GetAttribute('IsExpanded') -eq '{Binding GooseValuesExpanded, Mode=TwoWay}')) 'Three independent section states are bound to session ViewModel' +Assert-True ($vmSource.Contains('private bool _gooseMessageExpanded = true;') -and + $vmSource.Contains('private bool _gooseNetworkExpanded;') -and + $vmSource.Contains('private bool _gooseValuesExpanded = true;')) 'Message and Values open by default; Network closed' +Assert-True ($theme.OuterXml.Contains('GooseMaterialExpandCircle')) 'Inspector and data items use the Material circled chevron' +Assert-True (-not $inspector.Contains('Content="Identify these signals in the Configuration tab"')) 'Long textual onboarding removed from signal list' +Assert-True ($inspector.Contains('Content="Identify signals"')) 'Compact signal mapping action retained' +Assert-True ($source.Contains('Text="Messages"')) 'History is available without changing protocol logic' +Write-Host 'GOOSE calm UX contract: PASS (Material icons, searchable scrolling sources, independent Inspector accordions)' +Write-Host 'GOOSE readout contract: PASS (scoped chevron, RX/events, TEST, stable typed rows, live return)' Write-Host 'WPF workspace spacing contract: PASS (8 DIP columns / 8 DIP panel gaps, named insets)' diff --git a/src/ProcessBus.App.Wpf/MainWindow.xaml b/src/ProcessBus.App.Wpf/MainWindow.xaml index 98612882..dc1fa255 100644 --- a/src/ProcessBus.App.Wpf/MainWindow.xaml +++ b/src/ProcessBus.App.Wpf/MainWindow.xaml @@ -1298,7 +1298,6 @@ - @@ -1307,7 +1306,7 @@ - - - - + + + + + + + + + + + + + + + + + + - - - - - - - + - - - - - - - + - + + + + + + - - + @@ -1486,20 +1560,9 @@ - + + - - - @@ -2999,7 +3062,7 @@ - - - - - - - + - - - - - - - - - - - - - + - - - + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + - + @@ -3273,37 +3346,48 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + diff --git a/src/ProcessBus.App.Wpf/MainWindow.xaml.cs b/src/ProcessBus.App.Wpf/MainWindow.xaml.cs index 1ed73670..bb723db5 100644 --- a/src/ProcessBus.App.Wpf/MainWindow.xaml.cs +++ b/src/ProcessBus.App.Wpf/MainWindow.xaml.cs @@ -259,6 +259,23 @@ private void SvPill_MouseLeave(object sender, MouseEventArgs e) } } + private void GooseClearSearch_Click(object sender, RoutedEventArgs e) + { + // The command clears exactly one filter property. Restore keyboard focus + // without blocking the capture thread or queuing another UI refresh. + GooseSourceSearchBox.Focus(); + } + + private void GoosePublisherRow_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + // WPF SelectionChanged does not fire when the operator clicks an item + // that is already selected. The explicit click still means "inspect live" + // after a historical timeline event had been pinned. + if (DataContext is MainWindowViewModel viewModel && + sender is ListViewItem { DataContext: ProcessBus.Core.Presentation.GoosePublisherRow row }) + viewModel.SelectedGoosePublisherRow = row; + } + private void GooseTraffic_Interaction(object sender, MouseEventArgs e) { DeferGooseTrafficFlush(); diff --git a/src/ProcessBus.App.Wpf/Themes/Controls.xaml b/src/ProcessBus.App.Wpf/Themes/Controls.xaml index 638e7615..a16a3643 100644 --- a/src/ProcessBus.App.Wpf/Themes/Controls.xaml +++ b/src/ProcessBus.App.Wpf/Themes/Controls.xaml @@ -59,6 +59,163 @@ + + M15.5,14 H14.71 L14.43,13.73 A6.5,6.5 0 1 0 13.73,14.43 L14,14.71 V15.5 L19,20.49 L20.49,19 Z M10,14 A4,4 0 1 1 10,6 A4,4 0 0 1 10,14 Z + M19,6.41 L17.59,5 L12,10.59 L6.41,5 L5,6.41 L10.59,12 L5,17.59 L6.41,19 L12,13.41 L17.59,19 L19,17.59 L13.41,12 Z + M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z M12,20 C7.59,20 4,16.41 4,12 C4,7.59 7.59,4 12,4 C16.41,4 20,7.59 20,12 C20,16.41 16.41,20 12,20 Z M12,16 L6,10 L7.41,8.59 L12,13.17 L16.59,8.59 L18,10 Z + + + + + + +