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>

View File

@@ -39,6 +39,8 @@
<sys:String x:Key="Dashboard.LastTestFail">Last test: FAIL</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest">Start Test</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest.Tip">Requires a selected pump and an open CAN connection.</sys:String>
<sys:String x:Key="Dashboard.Action.AutoTest">Connect &amp; Auto Test</sys:String>
<sys:String x:Key="Dashboard.Action.AutoTest.Tip">Opens K-Line, reads the pump, unlocks if required, turns on the bench and starts the test in one step.</sys:String>
<sys:String x:Key="Dashboard.Action.Stop">Stop</sys:String>
<sys:String x:Key="Dashboard.Action.EmergencyStop">EMERGENCY STOP</sys:String>
@@ -71,6 +73,12 @@
<sys:String x:Key="Dashboard.Devices.State.Failed">Error</sys:String>
<sys:String x:Key="Dashboard.Devices.None">No devices found</sys:String>
<sys:String x:Key="Dashboard.Devices.BenchRow">Bench controller</sys:String>
<!-- CAN connect/disconnect snackbar -->
<sys:String x:Key="Dashboard.Devices.Snackbar.Connecting">Connecting to CAN…</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Disconnecting">Disconnecting CAN…</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Connected">CAN connected</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Disconnected">CAN disconnected</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Failed">Operation failed</sys:String>
<!-- Confirmation dialogs -->
<sys:String x:Key="Devices.Confirm.Title">Confirm device change</sys:String>
<sys:String x:Key="Devices.Confirm.Body.Active">The {0} session is active. Disconnect?</sys:String>
@@ -180,6 +188,15 @@
<sys:String x:Key="Pump.LiveData.Fbkw">FBKW</sys:String>
<sys:String x:Key="Pump.LiveData.RpmChart">RPM trend</sys:String>
<sys:String x:Key="Pump.Status.Active">Active</sys:String>
<sys:String x:Key="Pump.Bip.Title">BIP-STATUS</sys:String>
<sys:String x:Key="Pump.Bip.RawLabel">BIP:</sys:String>
<sys:String x:Key="Pump.Bip.Desc.0000.9">BIP captured, no current fault</sys:String>
<sys:String x:Key="Pump.Bip.Desc.000F.9">No BIP within entire detection window</sys:String>
<sys:String x:Key="Pump.Bip.Desc.00F0.9">No BIP in detection window, MV off (o.k.)</sys:String>
<sys:String x:Key="Pump.Bip.Desc.0F00.9">No MV drive due to MAB</sys:String>
<sys:String x:Key="Pump.Bip.Desc.F00F.9">BIP captured, but deviation from characteristic curve too large</sys:String>
<sys:String x:Key="Pump.Bip.Desc.A00F.9">BIP captured, but too close to HLU switching point</sys:String>
<sys:String x:Key="Pump.Bip.Desc.000F.11">No BIP within entire detection window (Ford)</sys:String>
<sys:String x:Key="Pump.Dfi.Title">Idling Calibration</sys:String>
<sys:String x:Key="Pump.Dfi.Version">Version</sys:String>
<sys:String x:Key="Pump.Dfi.Current">Current DFI</sys:String>
@@ -299,16 +316,22 @@
<sys:String x:Key="Test.Required">Required:</sys:String>
<sys:String x:Key="Test.TestLabel">Test:</sys:String>
<sys:String x:Key="Test.Critical">Critical</sys:String>
<sys:String x:Key="Test.Plan.Title">Plan — select phases to run</sys:String>
<sys:String x:Key="Test.Plan.Remaining">estimated</sys:String>
<sys:String x:Key="Test.Plan.EnableAll">All phases</sys:String>
<sys:String x:Key="Test.Plan.HoverHint">Hover a phase for required / send values</sys:String>
<sys:String x:Key="Test.Plan.MetaCond">Cond</sys:String>
<sys:String x:Key="Test.Plan.MetaMeas">Meas</sys:String>
<sys:String x:Key="Test.Plan.MetaMps">M/s</sys:String>
<sys:String x:Key="Test.Plan.NoIndicators">no measurements</sys:String>
<sys:String x:Key="Test.Started">Test started...</sys:String>
<sys:String x:Key="Test.Stopped">Test stopped.</sys:String>
<!-- ── Tests-page wizard ──────────────────────────────────────────────── -->
<sys:String x:Key="Test.Wizard.Plan">1. Plan</sys:String>
<sys:String x:Key="Test.Wizard.Preconditions">2. Preconditions</sys:String>
<sys:String x:Key="Test.Wizard.Running">3. Running</sys:String>
<sys:String x:Key="Test.Wizard.Done">4. Done</sys:String>
<sys:String x:Key="Test.Wizard.Next">Next ▶</sys:String>
<sys:String x:Key="Test.Wizard.Back">◀ Back</sys:String>
<!-- ── Tests-page status bar ──────────────────────────────────────────── -->
<sys:String x:Key="Test.Status.Ready">Ready to start</sys:String>
<sys:String x:Key="Test.Status.NotReady">Not ready</sys:String>
<sys:String x:Key="Test.Status.Running">Running</sys:String>
<sys:String x:Key="Test.Status.NoPhases">Select at least one phase to start.</sys:String>
<!-- ── Preconditions checklist ────────────────────────────────────────── -->
<sys:String x:Key="Test.Precheck.Title">Preconditions</sys:String>
@@ -345,6 +368,7 @@
<sys:String x:Key="Test.Done.Failed">FAILED</sys:String>
<sys:String x:Key="Test.Done.ViewFullResults">View full results</sys:String>
<sys:String x:Key="Test.Done.RunAgain">Run again</sys:String>
<sys:String x:Key="Test.Done.ClearData">Clear test data</sys:String>
<!-- ── Abort confirmation ─────────────────────────────────────────────── -->
<sys:String x:Key="Test.Abort.Title">Abort test?</sys:String>
@@ -443,6 +467,7 @@
<sys:String x:Key="Dialog.Unlock.Unlocked">UNLOCKED</sys:String>
<sys:String x:Key="Dialog.Unlock.Failed">UNLOCK FAILED</sys:String>
<sys:String x:Key="Dialog.Unlock.TypeLabel">Type {0}</sys:String>
<sys:String x:Key="Dialog.Unlock.Retry">Retry unlock</sys:String>
<!-- ── Dialog: K-Line errors ────────────────────────────────────────── -->
<sys:String x:Key="Dialog.KlineErrors.Title">K-Line Fault Codes</sys:String>
@@ -470,6 +495,7 @@
<sys:String x:Key="Dialog.Settings.ToleranceUp">UP tolerance extension:</sys:String>
<sys:String x:Key="Dialog.Settings.TolerancePfp">PFP tolerance extension:</sys:String>
<sys:String x:Key="Dialog.Settings.IgnoreTin">Ignore T-in by default</sys:String>
<sys:String x:Key="Dialog.Settings.AutoTestSkipsOilPumpConfirm">Skip oil-pump confirmation during Auto Test (operator accepts leak-check responsibility up front)</sys:String>
<sys:String x:Key="Dialog.Settings.PidP">Proportional (P):</sys:String>
<sys:String x:Key="Dialog.Settings.PidI">Integral (I):</sys:String>
@@ -502,6 +528,7 @@
<sys:String x:Key="Dialog.Settings.RefreshPumpParams">Pump params (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.BlinkInterval">Blink interval (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.FlasherInterval">Flasher interval (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.RpmChartUpdateHz">RPM chart rate (Hz):</sys:String>
<sys:String x:Key="Dialog.Settings.UsersHeader">Users</sys:String>
<sys:String x:Key="Dialog.Settings.ManageUsers">Manage Users...</sys:String>
@@ -526,6 +553,35 @@
<sys:String x:Key="Dialog.UserManage.Error.LastUserTitle">Cannot Remove</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.LastUser">Cannot remove the last remaining user.</sys:String>
<!-- ── Auto Test (Dashboard snackbar) ───────────────────────────────── -->
<sys:String x:Key="AutoTest.TypeLabel">Auto Test</sys:String>
<sys:String x:Key="AutoTest.State.Preflight">Running pre-flight checks…</sys:String>
<sys:String x:Key="AutoTest.State.Connecting">Connecting K-Line…</sys:String>
<sys:String x:Key="AutoTest.State.Reading">Reading pump identification…</sys:String>
<sys:String x:Key="AutoTest.State.Unlocking">Unlocking pump…</sys:String>
<sys:String x:Key="AutoTest.State.UnlockingWithDetail">Unlocking pump — {0}</sys:String>
<sys:String x:Key="AutoTest.State.BenchOn">Turning on bench…</sys:String>
<sys:String x:Key="AutoTest.State.OilPump">Starting oil pump…</sys:String>
<sys:String x:Key="AutoTest.State.TestStart">Starting test procedure…</sys:String>
<sys:String x:Key="AutoTest.State.Running">Test running…</sys:String>
<sys:String x:Key="AutoTest.State.RunningWithPhase">Test running — {0}</sys:String>
<sys:String x:Key="AutoTest.State.Completed">Auto Test completed.</sys:String>
<sys:String x:Key="AutoTest.State.Aborted">Aborted — {0}</sys:String>
<sys:String x:Key="AutoTest.Failure.UserCancelled">Cancelled by operator</sys:String>
<sys:String x:Key="AutoTest.Failure.PreflightDenied">Pre-flight conditions not met</sys:String>
<sys:String x:Key="AutoTest.Failure.KLineConnectFailed">Failed to open K-Line session</sys:String>
<sys:String x:Key="AutoTest.Failure.KLineLost">K-Line session lost</sys:String>
<sys:String x:Key="AutoTest.Failure.ReadFailed">Failed to read pump identification</sys:String>
<sys:String x:Key="AutoTest.Failure.PumpNotRecognized">Pump ID not recognised</sys:String>
<sys:String x:Key="AutoTest.Failure.UnlockFailed">Immobilizer unlock failed</sys:String>
<sys:String x:Key="AutoTest.Failure.BenchCanLost">Bench CAN bus lost</sys:String>
<sys:String x:Key="AutoTest.Failure.PumpCanLost">Pump ECU CAN lost</sys:String>
<sys:String x:Key="AutoTest.Failure.AlarmTriggered">Critical alarm triggered</sys:String>
<sys:String x:Key="AutoTest.Failure.OilPumpNotConfirmed">Oil-pump confirmation cancelled by operator</sys:String>
<sys:String x:Key="AutoTest.Failure.TestInterrupted">Test interrupted</sys:String>
<sys:String x:Key="AutoTest.Failure.TestFailed">Test failed</sys:String>
<sys:String x:Key="AutoTest.Failure.Unexpected">Unexpected error</sys:String>
<!-- ── Error messages ───────────────────────────────────────────────── -->
<sys:String x:Key="Error.ReportGeneration">Failed to generate report:\n{0}</sys:String>
<sys:String x:Key="Error.ReportTitle">Report Error</sys:String>

View File

@@ -39,6 +39,8 @@
<sys:String x:Key="Dashboard.LastTestFail">Última prueba: FALLIDA</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest">Iniciar prueba</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest.Tip">Requiere una bomba seleccionada y conexión CAN abierta.</sys:String>
<sys:String x:Key="Dashboard.Action.AutoTest">Conectar y probar</sys:String>
<sys:String x:Key="Dashboard.Action.AutoTest.Tip">Abre K-Line, lee la bomba, desbloquea si procede, enciende el banco y comienza la prueba en un solo paso.</sys:String>
<sys:String x:Key="Dashboard.Action.Stop">Detener</sys:String>
<sys:String x:Key="Dashboard.Action.EmergencyStop">PARADA DE EMERGENCIA</sys:String>
@@ -71,6 +73,12 @@
<sys:String x:Key="Dashboard.Devices.State.Failed">Error</sys:String>
<sys:String x:Key="Dashboard.Devices.None">Sin dispositivos</sys:String>
<sys:String x:Key="Dashboard.Devices.BenchRow">Controlador del banco</sys:String>
<!-- CAN connect/disconnect snackbar -->
<sys:String x:Key="Dashboard.Devices.Snackbar.Connecting">Conectando con CAN…</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Disconnecting">Desconectando CAN…</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Connected">CAN conectado</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Disconnected">CAN desconectado</sys:String>
<sys:String x:Key="Dashboard.Devices.Snackbar.Failed">Operación fallida</sys:String>
<!-- Confirmation dialogs -->
<sys:String x:Key="Devices.Confirm.Title">Confirmar cambio de dispositivo</sys:String>
<sys:String x:Key="Devices.Confirm.Body.Active">La sesión {0} está activa. ¿Desea desconectar?</sys:String>
@@ -180,6 +188,15 @@
<sys:String x:Key="Pump.LiveData.Fbkw">FBKW</sys:String>
<sys:String x:Key="Pump.LiveData.RpmChart">Tendencia RPM</sys:String>
<sys:String x:Key="Pump.Status.Active">Activo</sys:String>
<sys:String x:Key="Pump.Bip.Title">BIP-STATUS</sys:String>
<sys:String x:Key="Pump.Bip.RawLabel">BIP:</sys:String>
<sys:String x:Key="Pump.Bip.Desc.0000.9">BIP detectado, sin fallo actual</sys:String>
<sys:String x:Key="Pump.Bip.Desc.000F.9">Sin BIP en todo el período de detección</sys:String>
<sys:String x:Key="Pump.Bip.Desc.00F0.9">Sin BIP en período de detección, MV desactivada (o.k.)</sys:String>
<sys:String x:Key="Pump.Bip.Desc.0F00.9">Sin excitación de MV por MAB</sys:String>
<sys:String x:Key="Pump.Bip.Desc.F00F.9">BIP detectado, pero desviación excesiva respecto a la curva característica</sys:String>
<sys:String x:Key="Pump.Bip.Desc.A00F.9">BIP detectado, pero demasiado cerca del punto de conmutación HLU</sys:String>
<sys:String x:Key="Pump.Bip.Desc.000F.11">Sin BIP en todo el período de detección (Ford)</sys:String>
<sys:String x:Key="Pump.Dfi.Title">Calibración Ralentí</sys:String>
<sys:String x:Key="Pump.Dfi.Version">Versión</sys:String>
<sys:String x:Key="Pump.Dfi.Current">DFI Actual</sys:String>
@@ -299,16 +316,22 @@
<sys:String x:Key="Test.Required">Requerido:</sys:String>
<sys:String x:Key="Test.TestLabel">Test:</sys:String>
<sys:String x:Key="Test.Critical">Crítico</sys:String>
<sys:String x:Key="Test.Plan.Title">Plan — selecciona las fases a ejecutar</sys:String>
<sys:String x:Key="Test.Plan.Remaining">estimado</sys:String>
<sys:String x:Key="Test.Plan.EnableAll">Todas las fases</sys:String>
<sys:String x:Key="Test.Plan.HoverHint">Pasa el cursor sobre una fase para ver valores</sys:String>
<sys:String x:Key="Test.Plan.MetaCond">Cond</sys:String>
<sys:String x:Key="Test.Plan.MetaMeas">Med</sys:String>
<sys:String x:Key="Test.Plan.MetaMps">M/s</sys:String>
<sys:String x:Key="Test.Plan.NoIndicators">sin mediciones</sys:String>
<sys:String x:Key="Test.Started">Test iniciado...</sys:String>
<sys:String x:Key="Test.Stopped">Test detenido.</sys:String>
<!-- ── Asistente de la página de tests ────────────────────────────────── -->
<sys:String x:Key="Test.Wizard.Plan">1. Planificar</sys:String>
<sys:String x:Key="Test.Wizard.Preconditions">2. Precondiciones</sys:String>
<sys:String x:Key="Test.Wizard.Running">3. En ejecución</sys:String>
<sys:String x:Key="Test.Wizard.Done">4. Finalizado</sys:String>
<sys:String x:Key="Test.Wizard.Next">Siguiente ▶</sys:String>
<sys:String x:Key="Test.Wizard.Back">◀ Atrás</sys:String>
<!-- ── Barra de estado de la página de tests ──────────────────────────── -->
<sys:String x:Key="Test.Status.Ready">Listo para comenzar</sys:String>
<sys:String x:Key="Test.Status.NotReady">No listo</sys:String>
<sys:String x:Key="Test.Status.Running">Ejecutando</sys:String>
<sys:String x:Key="Test.Status.NoPhases">Selecciona al menos una fase para comenzar.</sys:String>
<!-- ── Checklist de precondiciones ────────────────────────────────────── -->
<sys:String x:Key="Test.Precheck.Title">Precondiciones</sys:String>
@@ -345,6 +368,7 @@
<sys:String x:Key="Test.Done.Failed">FALLIDO</sys:String>
<sys:String x:Key="Test.Done.ViewFullResults">Ver resultados completos</sys:String>
<sys:String x:Key="Test.Done.RunAgain">Ejecutar de nuevo</sys:String>
<sys:String x:Key="Test.Done.ClearData">Borrar resultados</sys:String>
<!-- ── Confirmación de aborto ─────────────────────────────────────────── -->
<sys:String x:Key="Test.Abort.Title">¿Abortar el test?</sys:String>
@@ -443,6 +467,7 @@
<sys:String x:Key="Dialog.Unlock.Unlocked">DESBLOQUEADO</sys:String>
<sys:String x:Key="Dialog.Unlock.Failed">DESBLOQUEO FALLIDO</sys:String>
<sys:String x:Key="Dialog.Unlock.TypeLabel">Tipo {0}</sys:String>
<sys:String x:Key="Dialog.Unlock.Retry">Reintentar desbloqueo</sys:String>
<!-- ── Dialog: K-Line errors ────────────────────────────────────────── -->
<sys:String x:Key="Dialog.KlineErrors.Title">Códigos de Falla K-Line</sys:String>
@@ -470,6 +495,7 @@
<sys:String x:Key="Dialog.Settings.ToleranceUp">Extensión tolerancia UP:</sys:String>
<sys:String x:Key="Dialog.Settings.TolerancePfp">Extensión tolerancia PFP:</sys:String>
<sys:String x:Key="Dialog.Settings.IgnoreTin">Ignorar T-in por defecto</sys:String>
<sys:String x:Key="Dialog.Settings.AutoTestSkipsOilPumpConfirm">Omitir confirmación de bomba de aceite en Auto Test (el operario asume la comprobación de fugas)</sys:String>
<sys:String x:Key="Dialog.Settings.PidP">Proporcional (P):</sys:String>
<sys:String x:Key="Dialog.Settings.PidI">Integral (I):</sys:String>
@@ -502,6 +528,7 @@
<sys:String x:Key="Dialog.Settings.RefreshPumpParams">Parámetros bomba (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.BlinkInterval">Intervalo parpadeo (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.FlasherInterval">Intervalo flasher (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.RpmChartUpdateHz">Tasa gráfico RPM (Hz):</sys:String>
<sys:String x:Key="Dialog.Settings.UsersHeader">Usuarios</sys:String>
<sys:String x:Key="Dialog.Settings.ManageUsers">Administrar Usuarios...</sys:String>
@@ -526,6 +553,35 @@
<sys:String x:Key="Dialog.UserManage.Error.LastUserTitle">No Se Puede Eliminar</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.LastUser">No se puede eliminar el último usuario restante.</sys:String>
<!-- ── Auto Test (snackbar del panel) ───────────────────────────────── -->
<sys:String x:Key="AutoTest.TypeLabel">Auto Test</sys:String>
<sys:String x:Key="AutoTest.State.Preflight">Comprobaciones previas…</sys:String>
<sys:String x:Key="AutoTest.State.Connecting">Conectando K-Line…</sys:String>
<sys:String x:Key="AutoTest.State.Reading">Leyendo identificación de bomba…</sys:String>
<sys:String x:Key="AutoTest.State.Unlocking">Desbloqueando bomba…</sys:String>
<sys:String x:Key="AutoTest.State.UnlockingWithDetail">Desbloqueando bomba — {0}</sys:String>
<sys:String x:Key="AutoTest.State.BenchOn">Encendiendo banco…</sys:String>
<sys:String x:Key="AutoTest.State.OilPump">Arrancando bomba de aceite…</sys:String>
<sys:String x:Key="AutoTest.State.TestStart">Iniciando procedimiento de prueba…</sys:String>
<sys:String x:Key="AutoTest.State.Running">Prueba en curso…</sys:String>
<sys:String x:Key="AutoTest.State.RunningWithPhase">Prueba en curso — {0}</sys:String>
<sys:String x:Key="AutoTest.State.Completed">Auto Test completado.</sys:String>
<sys:String x:Key="AutoTest.State.Aborted">Abortado — {0}</sys:String>
<sys:String x:Key="AutoTest.Failure.UserCancelled">Cancelado por el operario</sys:String>
<sys:String x:Key="AutoTest.Failure.PreflightDenied">No se cumplen las condiciones previas</sys:String>
<sys:String x:Key="AutoTest.Failure.KLineConnectFailed">No se pudo abrir la sesión K-Line</sys:String>
<sys:String x:Key="AutoTest.Failure.KLineLost">Sesión K-Line perdida</sys:String>
<sys:String x:Key="AutoTest.Failure.ReadFailed">Fallo al leer la identificación de la bomba</sys:String>
<sys:String x:Key="AutoTest.Failure.PumpNotRecognized">ID de bomba no reconocida</sys:String>
<sys:String x:Key="AutoTest.Failure.UnlockFailed">Fallo en el desbloqueo del inmovilizador</sys:String>
<sys:String x:Key="AutoTest.Failure.BenchCanLost">Bus CAN del banco perdido</sys:String>
<sys:String x:Key="AutoTest.Failure.PumpCanLost">CAN de la ECU perdido</sys:String>
<sys:String x:Key="AutoTest.Failure.AlarmTriggered">Alarma crítica disparada</sys:String>
<sys:String x:Key="AutoTest.Failure.OilPumpNotConfirmed">Confirmación de bomba de aceite cancelada por el operador</sys:String>
<sys:String x:Key="AutoTest.Failure.TestInterrupted">Prueba interrumpida</sys:String>
<sys:String x:Key="AutoTest.Failure.TestFailed">Prueba fallida</sys:String>
<sys:String x:Key="AutoTest.Failure.Unexpected">Error inesperado</sys:String>
<!-- ── Error messages ───────────────────────────────────────────────── -->
<sys:String x:Key="Error.ReportGeneration">Error al generar informe:\n{0}</sys:String>
<sys:String x:Key="Error.ReportTitle">Error de Informe</sys:String>

View File

@@ -121,7 +121,7 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Margin" Value="6"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="MinHeight" Value="140"/>
</Style>
@@ -211,6 +211,34 @@
<Setter Property="Height" Value="30"/>
</Style>
<!-- ── Tests page: meta pill (Tacond / Tmeas / M·s⁻¹ chips in section header) ── -->
<Style x:Key="TestMetaPill" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="9"/>
<Setter Property="Padding" Value="7,2"/>
<Setter Property="Margin" Value="0,0,4,0"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<Style x:Key="TestMetaPillText" TargetType="TextBlock">
<Setter Property="FontSize" Value="10"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- ── Tests page: phase tile (compact card inside a TestSectionCard) ── -->
<Style x:Key="PhaseTile" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="6"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="Margin" Value="3"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<!-- Snackbar bottom-overlay shell -->
<Style x:Key="SnackbarShell" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource CardBackgroundFillColorDefaultBrush}"/>
@@ -272,4 +300,120 @@
</Setter>
</Style>
<!--
Fluent-style vertical slider with a thick pill-shaped track.
- Track background fills the entire slider width (Width set on the Slider element).
- Lower (decrease) portion is filled with the accent brush; upper portion is the
unfilled rail in ControlStrongFillColorDefaultBrush.
- Thumb is a horizontal Fluent-style pill with subtle border.
Designed for Orientation="Vertical" only.
-->
<Style x:Key="FluentThickVerticalSlider" TargetType="Slider">
<Setter Property="Focusable" Value="False"/>
<!-- Intentionally NOT setting IsMoveToPointEnabled. The built-in implementation
marks the preview event handled, which would suppress the code-behind
handler that drives our click-anywhere-and-drag gesture. -->
<Setter Property="MinWidth" Value="20"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<!-- Background="Transparent" makes the Slider itself the click target,
so the code-behind's PreviewMouseLeftButtonDown can capture and drive
the value. Track repeat buttons are hit-test invisible to prevent
ButtonBase's class handler from stealing mouse capture. -->
<Grid HorizontalAlignment="Stretch" Background="Transparent" ClipToBounds="False">
<!-- Unfilled rail. Pinned to a fixed cross-axis width and centered
so the Slider can be widened to host an overhanging Thumb
without growing the visible track. -->
<Border CornerRadius="6"
Background="{DynamicResource ControlStrongFillColorDefaultBrush}"
Opacity="0.35"
Width="32"
HorizontalAlignment="Center"
IsHitTestVisible="False"
SnapsToDevicePixels="True"/>
<!-- Track + thumb. Orientation/direction must mirror the templated
Slider so Track.ValueFromPoint computes against the vertical axis. -->
<Track x:Name="PART_Track"
Orientation="{TemplateBinding Orientation}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}">
<Track.DecreaseRepeatButton>
<RepeatButton Command="Slider.DecreaseLarge"
IsTabStop="False" Focusable="False"
IsHitTestVisible="False">
<RepeatButton.Template>
<ControlTemplate TargetType="RepeatButton">
<!-- Filled portion pinned to the rail width so it
stays inside the visible track regardless of how
wide the Slider/Thumb are set. -->
<Border CornerRadius="0,0,6,6"
Background="{DynamicResource AccentFillColorDefaultBrush}"
Width="32"
HorizontalAlignment="Center"
SnapsToDevicePixels="True"/>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="Slider.IncreaseLarge"
IsTabStop="False" Focusable="False"
IsHitTestVisible="False">
<RepeatButton.Template>
<ControlTemplate TargetType="RepeatButton">
<Border Background="Transparent"/>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="ThumbPill" ClipToBounds="False">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Grid ClipToBounds="False">
<!-- Outer pill: white fill with subtle gray border.
Width is fixed and centered so the pill can
overhang the rail without being clipped. -->
<Border x:Name="ThumbOuter"
Height="20"
Width="52"
CornerRadius="8"
Background="{DynamicResource ControlSolidFillColorDefaultBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1"
HorizontalAlignment="Center"
SnapsToDevicePixels="True"/>
<!-- Inner accent dot -->
<Border x:Name="ThumbInner"
Width="44" Height="12"
CornerRadius="8"
Background="{DynamicResource AccentFillColorDefaultBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SnapsToDevicePixels="True"/>
</Grid>
<ControlTemplate.Triggers>
<!-- Press-color feedback only; thumb size stays constant. -->
<Trigger Property="IsDragging" Value="True">
<Setter TargetName="ThumbInner" Property="Background"
Value="{DynamicResource AccentFillColorSecondaryBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Track.Thumb>
</Track>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.4"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>