feat: developer tools page, auto-test orchestrator, BIP display, tests redesign

Bundles several feature streams that have been iterating on the working tree:

- Developer Tools page (Debug-only via DEVELOPER_TOOLS symbol): hosts the
  identification card, manual KWP write + transaction log, ROM/EEPROM dump
  card with progress banner and completion message, persisted custom-commands
  library, persisted EEPROM passwords library. New service primitives:
  IKwpService.SendRawCustomAsync / ReadEepromAsync / ReadRomEepromAsync.
  Persistence mirrors the Clients XML pattern in two new files
  (custom_commands.xml, eeprom_passwords.xml).
- Auto-test orchestrator (IAutoTestOrchestrator + AutoTestState): linear
  K-Line read -> unlock -> bench-on -> test sequence with snackbar UI and
  progress dialog VM, gated on dashboard alarms.
- BIP-STATUS display: BipDisplayViewModel + BipDisplayView, RAM read at
  0x0106 via IKwpService.ReadBipStatusAsync; status definitions in
  BipStatusDefinition.
- Tests page redesign: TestSectionCard + PhaseTileView replacing the old
  TestPlanView/TestRunningView/TestDoneView/TestPreconditionsView/
  TestSectionView controls and their VMs.
- Pump command sliders: Fluent thick-track style with overhang thumb,
  click-anywhere-and-drag, mouse-wheel adjustment.
- Window startup: app.manifest declares PerMonitorV2 DPI awareness,
  MainWindow installs a WM_GETMINMAXINFO hook in OnSourceInitialized and
  maximizes there (after the hook is in place) so the app fits the work
  area exactly on any display configuration.
- Misc: PercentToPixelsConverter, seed_aliases.py one-shot pump-alias
  importer, tools/Import-BipStatus.ps1, kline_eeprom_spec.md and
  dump-functions reference docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 13:59:50 +02:00
parent da0581967b
commit 827b811b39
102 changed files with 7522 additions and 1798 deletions

View File

@@ -1,57 +1,11 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:conv="clr-namespace:HC_APTBS.Converters">
<!-- Shared converter: enum ↔ int for TabControl.SelectedIndex -->
<conv:EnumToIntConverter x:Key="EnumToInt"/>
<!-- Nav rail ListBoxItem: 56px tall, left accent bar on selection -->
<Style x:Key="NavItem" TargetType="ListBoxItem">
<Setter Property="Height" Value="56"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Root" Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Accent" Grid.Column="0" Background="Transparent"/>
<ContentPresenter Grid.Column="1"
VerticalAlignment="Center"
Margin="16,0,0,0"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Root" Property="Background" Value="#FF3A4050"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Root" Property="Background" Value="#FF404860"/>
<Setter TargetName="Accent" Property="Background" Value="#FF2196F3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Nav rail ListBox -->
<Style x:Key="NavRail" TargetType="ListBox">
<Setter Property="Background" Value="#FF2F3440"/>
<Setter Property="Foreground" Value="#FFE6E6E6"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource NavItem}"/>
</Style>
<!-- TabControl that hides its tab strip so the nav rail is the only selector -->
<Style x:Key="HiddenTabsTabControl" TargetType="TabControl">
<Setter Property="BorderThickness" Value="0"/>
@@ -72,4 +26,122 @@
</Setter>
</Style>
<!--
Boxy nav item template: vertical icon-over-label tile inside a rounded
card. Replaces WPF-UI's default horizontal row layout
(LeftCompactNavigationViewItemTemplate) when assigned to
NavigationView.ItemTemplate.
Mechanism: WPF-UI's NavigationView assigns each NavigationViewItem's
Template programmatically via UpdateMenuItemsTemplate (in
NavigationView.Base.cs). A direct property assignment beats any
Style.Setter, so the only reliable way to override is to provide a
keyed ControlTemplate and assign it to NavigationView.ItemTemplate
from MainWindow.xaml.
Active = current page. Per user request, the active state is a muted
grey instead of a bright accent fill, signalling "you are here / not a
clickable destination right now". Hover and pressed states still
layer a transparent darkening overlay on top.
Triggers on IsActive (WPF-UI's selected-state DP on
NavigationViewItemBase), not IsSelected.
-->
<ControlTemplate x:Key="BoxyNavItemTemplate"
TargetType="{x:Type ui:NavigationViewItem}">
<!-- Asymmetric horizontal margin compensates for the 4px scrollbar
gutter that WPF-UI's NavigationView reserves on the right
(DynamicScrollViewer Padding="0,0,4,0"); this lands the visible
item edges at 10px from both the left and right of the pane. -->
<Grid Margin="6,3" MinHeight="80">
<!-- Background lives in Border.Style (level-8 style setter), NOT
as a local-value attribute (level 3), so the IsActive
template trigger below (level 7) can actually override it.
Setting Background="{DynamicResource ...}" as an attribute
here would silently block the trigger — same precedence
trap as Background="Transparent" on HoverOverlay. -->
<Border x:Name="Root"
CornerRadius="8"
BorderThickness="1"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
SnapsToDevicePixels="True">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background"
Value="{DynamicResource ControlFillColorDefaultBrush}"/>
</Style>
</Border.Style>
</Border>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Icon: hosts the ui:SymbolIcon assigned via
NavigationViewItem.Icon. TextElement inheritance
propagates FontSize/Foreground into the SymbolIcon. -->
<ContentControl x:Name="IconHost"
Grid.Row="0"
Content="{TemplateBinding Icon}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Focusable="False"
IsTabStop="False"
TextElement.FontSize="32"
TextElement.Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<ContentPresenter x:Name="LabelHost"
Grid.Row="1"
Content="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,4,0,0"
TextBlock.FontFamily="{DynamicResource ContentControlThemeFontFamily}"
TextBlock.FontSize="12"
TextBlock.FontWeight="SemiBold"
TextBlock.Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
</Grid>
<!-- Overlay darkens on hover/press/active without disturbing the
Root background colour. Background is intentionally LEFT
UNSET — assigning Background="Transparent" here would be a
local value, which beats template-trigger setters in WPF
property precedence and would silently block the
IsMouseOver/IsPressed/IsActive triggers below from changing
the colour. With Background unset, those triggers drive the
overlay. -->
<Border x:Name="HoverOverlay"
CornerRadius="8"
IsHitTestVisible="False"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="HoverOverlay" Property="Background" Value="#18000000"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="HoverOverlay" Property="Background" Value="#30000000"/>
</Trigger>
<!-- Active = current page: pressed-looking dark fill + muted
foreground, signalling "you are here / not a destination"
with a held-down feel.
IMPORTANT: we paint the dark fill onto Root directly with a
translucent-BLACK brush (#30000000 ≈ 19% black) instead of
going through HoverOverlay or a WPF-UI ControlFillColor*
brush. WPF-UI's ControlFillColorSecondaryBrush is
#80F9F9F9 (translucent WHITE) — over the white pane it
doesn't actually darken anything. A translucent-black brush
darkens reliably regardless of the parent colour and
matches the IsPressed overlay strength. -->
<Trigger Property="IsActive" Value="True">
<Setter TargetName="Root" Property="Background" Value="#30000000"/>
<Setter TargetName="IconHost" Property="TextElement.Foreground"
Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Setter TargetName="LabelHost" Property="TextBlock.Foreground"
Value="{DynamicResource TextFillColorSecondaryBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.4"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>