Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!-- markdownlint-disable-file -->
# Release Changes: Validation Signature Editing Locks

**Related Plan**: .copilot-tracking/plans/20260105-validation-signatures-plan.md
**Implementation Date**: 2026-01-05

## Summary

Tracking implementation of validation signature checks and edit locking in MotorEditor.

## Changes

### Added

- .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan covering validation signature lock scope and tasks.
- .copilot-tracking/details/20260105-validation-signatures-details.md - Task-level requirements for signature verification and edit locking.
- samples/motor-with-signatures.json - Sample motor file with valid motor/drive/curve signatures for UI validation.
- samples/motor-without-signatures.json - Sample motor file without signatures for editing scenarios.

### Modified

- src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Captures validation signature lock state for motors, drives, and curves during load and tab changes.
- src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs - Tracks signature-locked series for downstream read-only handling.
- src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml - Disables motor/drive/voltage editors when corresponding signatures are valid.
- src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs - Blocks rename/lock/delete and column lock toggles for signature-locked curve series.

### Removed

- None.

## Release Summary

**Total Files Affected**: 8

### Files Created (4)

- .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan for validation signature locking.
- .copilot-tracking/details/20260105-validation-signatures-details.md - Detailed steps for tasks in the plan.
- samples/motor-with-signatures.json - Signed sample motor file.
- samples/motor-without-signatures.json - Unsigned sample motor file.

### Files Modified (4)

- src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Computes validation signature lock state, exposes editability flags, and blocks edits when signed.
- src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs - Adds signature lock tracking for curve series.
- src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml - Disables motor/drive/voltage editors based on signature lock state.
- src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs - Prevents edits and lock toggles for signature-locked curve series.

### Files Removed (0)

- None.

### Dependencies & Infrastructure

- **New Dependencies**: None
- **Updated Dependencies**: None
- **Infrastructure Changes**: None
- **Configuration Updates**: None

### Deployment Notes

- None.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- markdownlint-disable-file -->
# Validation Signature Editing Locks Details

## T1: Evaluate signatures on load

- After a motor file is loaded or a new motor is created, verify motor, drive, and curve validation signatures using the existing `DataIntegrityService`.
- Store lock state per motor, drive, and curve without mutating the underlying model so that future signature add/remove UI can toggle state cleanly.
- Surface computed lock state through view model properties that can be reused by future UI.

## T2: Enforce locks in UI and viewmodels

- Prevent edits to motor properties (including units) when a valid motor signature is present.
- Prevent edits to selected drive and its voltages when that drive has a valid signature; allow adding other drives that are unsigned.
- Prevent edits (rename, delete, data changes) to curve series with valid signatures; ensure data grids and commands respect the lock.
- Keep user feedback minimal but clear (status messaging) while avoiding destructive changes to signatures.

## T3: Tests

- Add targeted view model tests that cover signature verification and editing lock behavior for motor, drive/voltage, and curve series paths.
- Prefer unit-level tests that reuse existing factories/mocks and real `DataIntegrityService` for realistic signatures.
- Ensure tests assert that data does not change when a locked section is edited and that unlocked data remains editable.
15 changes: 15 additions & 0 deletions .copilot-tracking/plans/20260105-validation-signatures-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- markdownlint-disable-file -->
# Validation Signature Editing Locks Plan

## Scope

- Enforce validation signature checks whenever motor files are opened.
- Lock editing of signed motor, drive/voltage, and curve data in MotorEditor while leaving unsigned data editable.
- Prepare code paths so future UI for adding/removing validation signatures can plug into the same lock state.

## Tasks

- [x] T1: Evaluate validation signatures on motor load and capture lock state for motor, drives, and curves.
- [x] T2: Enforce editing locks in UI/viewmodels based on signature validity for motor properties, drives/voltages, and curve series.
- [ ] T3: Add or update tests covering validation signature detection and lock behavior.
- [ ] T4: Update tracking changes file after each task and mark tasks complete.
112 changes: 112 additions & 0 deletions samples/motor-with-signatures.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{
"schemaVersion": "1.0.0",
"motorName": "Validation Sample Motor",
"manufacturer": "Contoso Motors",
"partNumber": "VAL-1000",
"power": 1500,
"maxSpeed": 4800,
"ratedSpeed": 3000,
"ratedContinuousTorque": 42,
"ratedPeakTorque": 52,
"weight": 8.5,
"rotorInertia": 0.0025,
"feedbackPpr": 131072,
"hasBrake": false,
"brakeTorque": 0,
"brakeAmperage": 0,
"brakeVoltage": 0,
"brakeReleaseTime": 0,
"brakeEngageTimeDiode": 0,
"brakeEngageTimeMOV": 0,
"brakeBacklash": 0,
"units": {
"torque": "Nm",
"speed": "rpm",
"power": "W",
"weight": "kg",
"voltage": "V",
"current": "A",
"inertia": "kg-m^2",
"torqueConstant": "Nm/A",
"backlash": "arcmin",
"responseTime": "ms",
"percentage": "%",
"temperature": "C"
},
"drives": [
{
"name": "Sample Drive",
"manufacturer": "Contoso Drives",
"partNumber": "DRV-208",
"voltages": [
{
"voltage": 208,
"power": 1400,
"maxSpeed": 4700,
"ratedSpeed": 2950,
"ratedContinuousTorque": 41.5,
"ratedPeakTorque": 51.5,
"continuousAmperage": 9.5,
"peakAmperage": 21.8,
"percent": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100
],
"rpm": [0, 47, 94, 141, 188, 235, 282, 329.00000000000006, 376, 423, 470, 517,
564, 611, 658.00000000000011, 705, 752, 799.00000000000011, 846, 893, 940, 987, 1034, 1081,
1128, 1175, 1222, 1269, 1316.0000000000002, 1363, 1410, 1457, 1504, 1551, 1598.0000000000002, 1645,
1692, 1739, 1786, 1833, 1880, 1926.9999999999998, 1974, 2021, 2068, 2115, 2162, 2209,
2256, 2303, 2350, 2397, 2444, 2491, 2538, 2585, 2632.0000000000005, 2678.9999999999995, 2726, 2773,
2820, 2867, 2914, 2961, 3008, 3055, 3102, 3149, 3196.0000000000005, 3242.9999999999995, 3290, 3337,
3384, 3431, 3478, 3525, 3572, 3619, 3666, 3713, 3760, 3807.0000000000005, 3853.9999999999995, 3901,
3948, 3995, 4042, 4089, 4136, 4183, 4230, 4277, 4324, 4371, 4418, 4465,
4512, 4559, 4606, 4653, 4700
],
"series": {
"Peak": {
"locked": false,
"torque": [51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5
],
"curveSignature": {
"checksum": "b60cb2611316701d7ed93e5cd5a0d5af6c8b487427120d2df4ad3fe81a3cfff0",
"timestamp": "2026-01-05T12:47:15.1860204Z",
"verifiedBy": "qa@example.com",
"algorithm": "SHA256"
}
}
}
}
],
"driveSignature": {
"checksum": "f8586fbcca6ddd1e92cf206ff06e126de7a0666f59517c407d300f2acc917af3",
"timestamp": "2026-01-05T12:47:15.1824272Z",
"verifiedBy": "qa@example.com",
"algorithm": "SHA256"
}
}
],
"metadata": {
"created": "2026-01-05T12:47:14.4786849Z",
"modified": "2026-01-05T12:47:14.478685Z"
},
"motorSignature": {
"checksum": "8e44f0e5efa24cabaa742ff6fe261dd842ce8c9ad3b9dea26df3a4fcbdca62af",
"timestamp": "2026-01-05T12:47:15.1779223Z",
"verifiedBy": "qa@example.com",
"algorithm": "SHA256"
}
}
94 changes: 94 additions & 0 deletions samples/motor-without-signatures.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"schemaVersion": "1.0.0",
"motorName": "Validation Sample Motor",
"manufacturer": "Contoso Motors",
"partNumber": "VAL-1000",
"power": 1500,
"maxSpeed": 4800,
"ratedSpeed": 3000,
"ratedContinuousTorque": 42,
"ratedPeakTorque": 52,
"weight": 8.5,
"rotorInertia": 0.0025,
"feedbackPpr": 131072,
"hasBrake": false,
"brakeTorque": 0,
"brakeAmperage": 0,
"brakeVoltage": 0,
"brakeReleaseTime": 0,
"brakeEngageTimeDiode": 0,
"brakeEngageTimeMOV": 0,
"brakeBacklash": 0,
"units": {
"torque": "Nm",
"speed": "rpm",
"power": "W",
"weight": "kg",
"voltage": "V",
"current": "A",
"inertia": "kg-m^2",
"torqueConstant": "Nm/A",
"backlash": "arcmin",
"responseTime": "ms",
"percentage": "%",
"temperature": "C"
},
"drives": [
{
"name": "Sample Drive",
"manufacturer": "Contoso Drives",
"partNumber": "DRV-208",
"voltages": [
{
"voltage": 208,
"power": 1400,
"maxSpeed": 4700,
"ratedSpeed": 2950,
"ratedContinuousTorque": 41.5,
"ratedPeakTorque": 51.5,
"continuousAmperage": 9.5,
"peakAmperage": 21.8,
"percent": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100
],
"rpm": [0, 47, 94, 141, 188, 235, 282, 329.00000000000006, 376, 423, 470, 517,
564, 611, 658.00000000000011, 705, 752, 799.00000000000011, 846, 893, 940, 987, 1034, 1081,
1128, 1175, 1222, 1269, 1316.0000000000002, 1363, 1410, 1457, 1504, 1551, 1598.0000000000002, 1645,
1692, 1739, 1786, 1833, 1880, 1926.9999999999998, 1974, 2021, 2068, 2115, 2162, 2209,
2256, 2303, 2350, 2397, 2444, 2491, 2538, 2585, 2632.0000000000005, 2678.9999999999995, 2726, 2773,
2820, 2867, 2914, 2961, 3008, 3055, 3102, 3149, 3196.0000000000005, 3242.9999999999995, 3290, 3337,
3384, 3431, 3478, 3525, 3572, 3619, 3666, 3713, 3760, 3807.0000000000005, 3853.9999999999995, 3901,
3948, 3995, 4042, 4089, 4136, 4183, 4230, 4277, 4324, 4371, 4418, 4465,
4512, 4559, 4606, 4653, 4700
],
"series": {
"Peak": {
"locked": false,
"torque": [51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5,
51.5, 51.5, 51.5, 51.5, 51.5
]
}
}
}
]
}
],
"metadata": {
"created": "2026-01-05T12:47:14.4786849Z",
"modified": "2026-01-05T12:47:14.478685Z"
}
}
17 changes: 16 additions & 1 deletion src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public partial class CurveDataTableViewModel : ViewModelBase
{
private Voltage? _currentVoltage;
private CellPosition? _anchorCell;
private ISet<Curve> _signatureLockedSeries = new HashSet<Curve>();

[ObservableProperty]
private ObservableCollection<CurveDataRow> _rows = [];
Expand Down Expand Up @@ -227,6 +228,19 @@ public Voltage? CurrentVoltage
}
}

/// <summary>
/// Gets or sets the set of series that are locked due to validation signatures.
/// </summary>
public IEnumerable<Curve> SignatureLockedSeries
{
get => _signatureLockedSeries;
set
{
_signatureLockedSeries = new HashSet<Curve>(value ?? Enumerable.Empty<Curve>());
RefreshData();
}
}

/// <summary>
/// Applies a scalar torque value to all currently selected cells.
/// </summary>
Expand Down Expand Up @@ -418,7 +432,8 @@ public double GetTorque(int rowIndex, string seriesName)
public bool IsSeriesLocked(string seriesName)
{
var series = _currentVoltage?.Curves.FirstOrDefault(s => s.Name == seriesName);
return series?.Locked ?? false;
var isSignatureLocked = series is not null && _signatureLockedSeries.Contains(series);
return (series?.Locked ?? false) || isSignatureLocked;
}

/// <summary>
Expand Down
Loading