Skip to content

chore: add one-shot IED relay patch script #2

chore: add one-shot IED relay patch script

chore: add one-shot IED relay patch script #2

name: Apply IED relay icon industrial QA patch
on:
push:
branches:
- agent/ied-relay-icon-industrial-qa
permissions:
contents: write
jobs:
patch-build-test:
if: github.actor != 'github-actions[bot]'
runs-on: windows-latest
steps:
- name: Checkout ARSAS branch
uses: actions/checkout@v4
with:
path: arsas
ref: agent/ied-relay-icon-industrial-qa
fetch-depth: 0
- name: Checkout ARIEC61850 engine beside ARSAS
uses: actions/checkout@v4
with:
repository: masarray/ARIEC61850
path: ARIEC61850
- name: Set up .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Apply focused XAML and QA updates
shell: bash
working-directory: arsas
run: |
python - <<'PY'
from pathlib import Path
import xml.etree.ElementTree as ET
def replace_region(path: str, start_token: str, end_token: str, replacement: str) -> None:
file = Path(path)
text = file.read_text(encoding="utf-8")
marker = text.index(start_token)
start = text.rfind("\n", 0, marker) + 1
end_marker = text.index(end_token, marker)
end = text.rfind("\n", 0, end_marker) + 1
file.write_text(text[:start] + replacement + text[end:], encoding="utf-8")
main_block = ''' <!-- Static protection-relay fascia with a dedicated status label below it. -->
<StackPanel Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Width="60"
Margin="-2,0,-2,0" VerticalAlignment="Center"
HorizontalAlignment="Center" Panel.ZIndex="2">
<Viewbox Width="50" Height="50" Stretch="Uniform"
HorizontalAlignment="Center">
<Control x:Name="RelayDeviceIcon" Width="50" Height="50"
Foreground="#EF4444"
Template="{StaticResource IedRelayFrontPanelTemplate}"/>
</Viewbox>
<Border x:Name="MonitorStateBadge"
HorizontalAlignment="Center" Margin="0,2,0,0"
Background="#F5FFF0F1" BorderBrush="#F3A3A8" BorderThickness="1"
CornerRadius="3" Padding="4,1">
<StackPanel Orientation="Horizontal">
<Ellipse x:Name="MonitorStateDot" Width="3.8" Height="3.8" Fill="#E5484D"
Margin="0,0,2.5,0" VerticalAlignment="Center"/>
<TextBlock x:Name="MonitorStateText" Text="STOP" FontSize="6.8"
FontWeight="SemiBold" Foreground="#C92A32"/>
</StackPanel>
</Border>
</StackPanel>
'''

Check failure on line 74 in .github/workflows/ied-relay-icon-self-patch.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/ied-relay-icon-self-patch.yml

Invalid workflow file

You have an error in your yaml syntax on line 74
replace_region(
"MainWindow.xaml",
"<!-- Compact feeder-protection relay fascia; state remains readable without covering the LCD. -->",
'<StackPanel Grid.Row="0" Grid.Column="1"',
main_block,
)
io_block = ''' <StackPanel Grid.Column="0" Width="56"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Viewbox Width="46" Height="46" HorizontalAlignment="Center">
<Control x:Name="RelayIcon" Width="50" Height="50" Foreground="#D94B55"
Template="{StaticResource IedRelayFrontPanelTemplate}"/>
</Viewbox>
<Border x:Name="StateBadge" HorizontalAlignment="Center" Margin="0,2,0,0"
Background="#FFF1F2" BorderBrush="#F0B7BC"
BorderThickness="1" CornerRadius="3" Padding="4,1">
<Border.Visibility>
<MultiBinding Converter="{StaticResource AllPassedVisibilityConverter}" ConverterParameter="Inverse">
<Binding Path="TestPoints.Count"/>
<Binding Path="PassedCount"/>
</MultiBinding>
</Border.Visibility>
<TextBlock x:Name="StateText" Text="{Binding CardStateText}"
FontSize="7" FontWeight="SemiBold" Foreground="#C53A45"/>
</Border>
</StackPanel>
<Grid Grid.Column="1" Margin="3,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding IedName}" FontSize="12.8" FontWeight="SemiBold"
Foreground="{StaticResource Ink}" TextTrimming="CharacterEllipsis"
Margin="0,0,10,0" VerticalAlignment="Center"/>
<Border Grid.Column="1" Background="#EAF8F1" BorderBrush="#8FD1B1"
BorderThickness="1" CornerRadius="9" Padding="7,2"
Margin="10,0,0,0" VerticalAlignment="Center">
<Border.Visibility>
<MultiBinding Converter="{StaticResource AllPassedVisibilityConverter}">
<Binding Path="TestPoints.Count"/>
<Binding Path="PassedCount"/>
</MultiBinding>
</Border.Visibility>
<TextBlock Text="✔ PASS" FontSize="8.5" FontWeight="Bold" Foreground="#16845A"/>
</Border>
</Grid>
'''
replace_region(
"IoListTestingWindow.xaml",
'<Viewbox Width="50" Height="50" HorizontalAlignment="Center" VerticalAlignment="Center">',
'<TextBlock Grid.Row="1" Text="{Binding IpAddress}"',
io_block,
)
tests = Path("tests/ARSAS.Tests/IoTestingUiContractTests.cs")
test_text = tests.read_text(encoding="utf-8")
test_start = test_text.index(' [Fact]\n public void IedCards_UseReusableNumericalRelayFrontPanelInsteadOfCalculatorKeypad()')
test_end = test_text.index(' [Fact]\n public void IoTestingWindow_UsesBalancedInitialGridWidthsAndCenteredRelayHeaders()', test_start)
test_method = ''' [Fact]
public void IedCards_UseReusableNumericalRelayFrontPanelInsteadOfCalculatorKeypad()
{
XNamespace presentation = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";
var resources = XDocument.Load(FindRepoFile("App.xaml"));
var template = resources
.Descendants(presentation + "ControlTemplate")
.Single(node => (string?)node.Attribute(x + "Key") == "IedRelayFrontPanelTemplate");
var namedParts = template
.Descendants()
.Select(node => (string?)node.Attribute(x + "Name"))
.Where(name => !string.IsNullOrWhiteSpace(name))
.Cast<string>()
.ToHashSet(StringComparer.Ordinal);
Assert.Contains("RelayFasciaArtwork", namedParts);
Assert.Contains("RelayStateRail", namedParts);
Assert.Contains("Assets/ied-protection-relay-fascia.png", template.ToString(), StringComparison.Ordinal);
Assert.True(File.Exists(FindRepoFile("Assets/ied-protection-relay-fascia.png")));
var project = File.ReadAllText(FindRepoFile("ArIED61850Tester.csproj"));
Assert.Contains("Assets\\\\ied-protection-relay-fascia.png", project, StringComparison.Ordinal);
var explorer = XDocument.Load(FindRepoFile("MainWindow.xaml"));
var explorerIcon = explorer
.Descendants(presentation + "Control")
.Single(node => (string?)node.Attribute(x + "Name") == "RelayDeviceIcon");
var explorerBadge = explorer
.Descendants(presentation + "Border")
.Single(node => (string?)node.Attribute(x + "Name") == "MonitorStateBadge");
Assert.Empty(explorerIcon.Descendants(presentation + "DropShadowEffect"));
Assert.Equal("StackPanel", explorerBadge.Parent?.Name.LocalName);
Assert.Contains(
explorerBadge.Parent!.Descendants(presentation + "Control"),
node => (string?)node.Attribute(x + "Name") == "RelayDeviceIcon");
var ioTesting = XDocument.Load(FindRepoFile("IoListTestingWindow.xaml"));
var ioBadge = ioTesting
.Descendants(presentation + "Border")
.Single(node => (string?)node.Attribute(x + "Name") == "StateBadge");
Assert.Equal("StackPanel", ioBadge.Parent?.Name.LocalName);
Assert.Contains(
ioBadge.Parent!.Descendants(presentation + "Control"),
node => (string?)node.Attribute(x + "Name") == "RelayIcon");
var explorerText = explorer.ToString();
var ioTestingText = ioTesting.ToString();
Assert.Contains("IedRelayFrontPanelTemplate", explorerText, StringComparison.Ordinal);
Assert.Contains("IedRelayFrontPanelTemplate", ioTestingText, StringComparison.Ordinal);
Assert.DoesNotContain("M 2 0 L 2 20", explorerText, StringComparison.Ordinal);
Assert.DoesNotContain("M 2 0 L 2 20", ioTestingText, StringComparison.Ordinal);
}
'''
tests.write_text(test_text[:test_start] + test_method + test_text[test_end:], encoding="utf-8")
Path("design-qa.md").write_text('''# IED protection relay fascia design QA
## Scope
- Source artwork: `Assets/ied-protection-relay-fascia.png`
- Reusable WPF consumer: `IedRelayFrontPanelTemplate` in `App.xaml`
- Runtime surfaces: IED Explorer (`MainWindow.xaml`) and IO List FAT (`IoListTestingWindow.xaml`)
- Production icon size: 50 × 50 device-independent pixels
## Implementation checks
- The dedicated relay fascia PNG is packaged as a WPF resource and reused on both IED card surfaces.
- The former calculator-style inline path is absent from both views.
- LIVE/STOP state remains data-driven and is rendered as a compact label below the relay artwork.
- The state label is no longer layered over the fascia or LCD.
- The IED Explorer icon has no `DropShadowEffect`; status is conveyed by the restrained label and state rail only.
- IO List FAT keeps the final `✔ PASS` result badge in the text area while operational LIVE/STOP state stays below the icon.
## Validation
The patch branch is committed only after these Windows checks pass:
```powershell
dotnet build ArIED61850Tester.csproj -c Release
dotnet test tests/ARSAS.Tests/ARSAS.Tests.csproj -c Release --filter "FullyQualifiedName~IoTestingUiContractTests"
```
The focused UI contract suite contains 11 tests, including structural checks that both card surfaces use the shared fascia, keep status labels outside the artwork, and do not restore the old calculator path.
## Visual QA status
The source asset itself is repository-visible and auditable. A fresh full-window runtime screenshot remains recommended as P3 evidence for spacing at every Windows scaling factor; it is not represented by inaccessible machine-local paths in this document.
Final result: code structure and UI contracts passed; runtime screenshot evidence remains a follow-up polish item.
''', encoding="utf-8")
ET.parse("MainWindow.xaml")
ET.parse("IoListTestingWindow.xaml")
print("XAML patch and XML parse checks passed")
PY
- name: Build ARSAS Release
shell: pwsh
working-directory: arsas
run: dotnet build ArIED61850Tester.csproj -c Release
- name: Run 11 focused UI contracts
shell: pwsh
working-directory: arsas
run: dotnet test tests/ARSAS.Tests/ARSAS.Tests.csproj -c Release --filter "FullyQualifiedName~IoTestingUiContractTests"
- name: Remove one-shot helper and commit validated patch
shell: pwsh
working-directory: arsas
run: |
Remove-Item ".github/workflows/ied-relay-icon-self-patch.yml"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add MainWindow.xaml IoListTestingWindow.xaml tests/ARSAS.Tests/IoTestingUiContractTests.cs design-qa.md .github/workflows/ied-relay-icon-self-patch.yml
git commit -m "fix: place IED status below relay fascia"
git push origin HEAD:agent/ied-relay-icon-industrial-qa