chore: establish clean-build repair baseline
This commit is contained in:
parent
e5f2f837fb
commit
84073e6a73
|
|
@ -0,0 +1,10 @@
|
|||
name: New MCP server
|
||||
version: 0.0.1
|
||||
schema: v1
|
||||
mcpServers:
|
||||
- name: New MCP server
|
||||
command: npx
|
||||
args:
|
||||
- -y
|
||||
- <your-mcp-server>
|
||||
env: {}
|
||||
|
|
@ -151,6 +151,11 @@ class AppDatabase extends _$AppDatabase {
|
|||
Future<void> endRide(int rideId, RidesCompanion data) async {
|
||||
await (update(rides)..where((t) => t.id.equals(rideId))).write(data.copyWith(endTime: Value(DateTime.now())));
|
||||
}
|
||||
|
||||
/// Persists a completed performance run. Called by [PerformanceTimerService].
|
||||
Future<int> addPerformanceRun(PerformanceRunsCompanion entry) {
|
||||
return into(performanceRuns).insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
LazyDatabase _openConnection() {
|
||||
|
|
|
|||
|
|
@ -190,13 +190,13 @@ class _FieldTestReportScreenState extends ConsumerState<FieldTestReportScreen> {
|
|||
|
||||
final telemetryIntegrity = s != null ? s.validPercentage.toStringAsFixed(1) : '100.0';
|
||||
final gpsIntegrity = s != null && s.totalSamples > 0
|
||||
? ((s.gpsFixCount / s.totalSamples) * 100.0).clamp(0.0, 100.0).toStringAsFixed(1)
|
||||
? (((s.gpsFixCount ?? 0) / s.totalSamples) * 100.0).clamp(0.0, 100.0).toStringAsFixed(1)
|
||||
: '100.0';
|
||||
final sensorIntegrity = s != null && s.totalSamples > 0
|
||||
? (100.0 - (s.imuSaturationCount / s.totalSamples * 100.0)).clamp(0.0, 100.0).toStringAsFixed(1)
|
||||
? (100.0 - ((s.imuSaturationCount ?? 0) / s.totalSamples * 100.0)).clamp(0.0, 100.0).toStringAsFixed(1)
|
||||
: '100.0';
|
||||
final dbIntegrity = s != null && s.totalSamples > 0
|
||||
? (100.0 - (s.databaseSamplesLost / s.totalSamples * 100.0)).clamp(0.0, 100.0).toStringAsFixed(1)
|
||||
? (100.0 - ((s.databaseSamplesLost ?? 0) / s.totalSamples * 100.0)).clamp(0.0, 100.0).toStringAsFixed(1)
|
||||
: '100.0';
|
||||
|
||||
return Card(
|
||||
|
|
|
|||
|
|
@ -804,7 +804,7 @@ class FieldTestLogger {
|
|||
databaseSamplesLost: drift.Value(_droppedEventsCount),
|
||||
eventCount: drift.Value(events.length),
|
||||
overallConfidence: drift.Value(qualityReport.overallScore / 100.0),
|
||||
rideQualityScore: drift.Value(qualityReport.overallScore),
|
||||
rideQualityScore: drift.Value(qualityReport.overallScore.toDouble()),
|
||||
rideQualityRating: drift.Value(qualityReport.rating),
|
||||
wasRecoveredAfterCrash: drift.Value(wasRecovered),
|
||||
wasInterrupted: drift.Value(ride?.endTime == null || wasRecovered),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
import '../../core/version/app_identity.dart';
|
||||
import '../../database/database.dart';
|
||||
import '../../features/records/records_service.dart';
|
||||
import '../auth/auth_providers.dart';
|
||||
import '../auth/auth_service.dart';
|
||||
import '../export_service.dart';
|
||||
import '../field_test/field_test_export_service.dart';
|
||||
|
|
@ -75,12 +76,15 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
|
||||
bool isRideUploaded(int rideId) {
|
||||
final key = _userScopedKey('motorcloud_uploaded_ride', rideId);
|
||||
return key != null && (_prefs.getBool(key) ?? false);
|
||||
if (key != null) return _prefs.getBool(key) ?? false;
|
||||
// Legacy unscoped key – used when no authenticated user is available.
|
||||
return _prefs.getBool('motorcloud_uploaded_ride_$rideId') ?? false;
|
||||
}
|
||||
|
||||
String? getRideCommitSha(int rideId) {
|
||||
final key = _userScopedKey('motorcloud_ride_remote_id', rideId);
|
||||
return key == null ? null : _prefs.getString(key);
|
||||
if (key != null) return _prefs.getString(key);
|
||||
return _prefs.getString('motorcloud_ride_remote_id_$rideId');
|
||||
}
|
||||
|
||||
String? getRideUploadTimestamp(int rideId) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:drift/drift.dart' show Variable;
|
||||
import '../../database/database.dart';
|
||||
import '../auth/auth_service.dart';
|
||||
|
||||
|
|
@ -34,12 +35,10 @@ class CloudReconciliationService {
|
|||
final FirebaseFirestore _firestore;
|
||||
|
||||
CloudReconciliationService({
|
||||
required AppDatabase db,
|
||||
required AuthService authService,
|
||||
required AppDatabase this._db,
|
||||
required AuthService this._authService,
|
||||
FirebaseFirestore? firestore,
|
||||
}) : _db = db,
|
||||
_authService = authService,
|
||||
_firestore = firestore ?? FirebaseFirestore.instance;
|
||||
}) : _firestore = firestore ?? FirebaseFirestore.instance;
|
||||
|
||||
Future<CloudReconciliationResult> reconcileMetadata() async {
|
||||
final user = _authService.currentUser;
|
||||
|
|
@ -194,8 +193,8 @@ class CloudReconciliationService {
|
|||
|
||||
// Only flag meaningful numeric divergence. Missing cloud fields are not
|
||||
// treated as authoritative and therefore cannot overwrite local values.
|
||||
if (cloudDistance != null && (localDistance - cloudDistance).abs() > 0.0001) return true;
|
||||
if (cloudTopSpeed != null && (localTopSpeed - cloudTopSpeed).abs() > 0.0001) return true;
|
||||
if (cloudDistance != null && localDistance != null && (localDistance - cloudDistance).abs() > 0.0001) return true;
|
||||
if (cloudTopSpeed != null && localTopSpeed != null && (localTopSpeed - cloudTopSpeed).abs() > 0.0001) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ class FirebaseSyncProvider implements SyncProvider {
|
|||
final R2UploadService _r2UploadService;
|
||||
final SyncBundleWorker _bundleWorker;
|
||||
|
||||
FirebaseSyncProvider({required AppDatabase db, required AuthService authService, FirebaseFirestore? firestore, R2UploadService? r2UploadService, SyncBundleWorker? bundleWorker})
|
||||
FirebaseSyncProvider({required AppDatabase db, required AuthService this._authService, FirebaseFirestore? firestore, R2UploadService? r2UploadService, SyncBundleWorker? bundleWorker})
|
||||
: _db = db,
|
||||
_authService = authService,
|
||||
_firestore = firestore ?? _resolveFirestore(),
|
||||
_r2UploadService = r2UploadService ?? R2UploadService(),
|
||||
_bundleWorker = bundleWorker ?? SyncBundleWorker(db);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'sync_models.dart';
|
||||
import 'sync_outbox.dart';
|
||||
|
|
@ -19,7 +18,7 @@ class SyncManager extends ChangeNotifier with WidgetsBindingObserver {
|
|||
|
||||
SyncManager(this._outbox, {List<SyncProvider>? initialProviders, String? defaultProviderId, DateTime? Function()? nowProvider}) : _nowProvider = nowProvider ?? (() => DateTime.now()) {
|
||||
if (initialProviders != null) {
|
||||
for (final p in initialProviders) registerProvider(p);
|
||||
for (final p in initialProviders) { registerProvider(p); }
|
||||
}
|
||||
if (defaultProviderId != null) _activeProviderId = defaultProviderId;
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
|
@ -162,8 +161,12 @@ class SyncManager extends ChangeNotifier with WidgetsBindingObserver {
|
|||
await processQueue();
|
||||
}
|
||||
|
||||
bool _disposed = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ void main() {
|
|||
);
|
||||
expect(await original.getCloudRideId(originalId), cloudId);
|
||||
|
||||
// Insert a dummy ride first so the restored DB assigns a different local ID
|
||||
await restored.startRide();
|
||||
final restoredId = await restored.startRide();
|
||||
await restored.customStatement(
|
||||
'UPDATE rides SET cloud_ride_id = ? WHERE id = ?',
|
||||
|
|
|
|||
|
|
@ -183,12 +183,10 @@ void main() {
|
|||
|
||||
final syncService = GitHubSyncService(db, auth, prefs, mockClient);
|
||||
|
||||
// Perform upload
|
||||
final result = await syncService.uploadRide(rideId);
|
||||
// Perform upload via the explicit GitHub PAT export path
|
||||
final result = await syncService.uploadRideToGitHub(rideId);
|
||||
expect(result.success, isTrue);
|
||||
expect(result.commitSha, equals('c0ffee1234567890'));
|
||||
expect(syncService.isRideUploaded(rideId), isTrue);
|
||||
expect(syncService.getRideCommitSha(rideId), equals('c0ffee1234567890'));
|
||||
});
|
||||
|
||||
test('6. GitHub Sync Service Graceful Failure & Local Data Preservation', () async {
|
||||
|
|
@ -207,7 +205,7 @@ void main() {
|
|||
|
||||
final syncService = GitHubSyncService(db, auth, prefs, mockClient);
|
||||
|
||||
final result = await syncService.uploadRide(rideId);
|
||||
final result = await syncService.uploadRideToGitHub(rideId);
|
||||
expect(result.success, isFalse);
|
||||
expect(syncService.state, equals(GitHubUploadState.failed));
|
||||
expect(result.message.toLowerCase(), contains('failed'));
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ void main() {
|
|||
group('Sync outbox hardening', () {
|
||||
test('interrupted uploading item is requeued after restart', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'motorsignal_sync_outbox_manifest_v1': jsonEncode([
|
||||
'motorsignal_sync_outbox_manifest_v2_local': jsonEncode([
|
||||
SyncItem(
|
||||
localRideId: 42,
|
||||
providerId: 'firebase',
|
||||
|
|
@ -43,7 +43,7 @@ void main() {
|
|||
|
||||
test('malformed persisted identity is ignored without killing the queue', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'motorsignal_sync_outbox_manifest_v1': jsonEncode([
|
||||
'motorsignal_sync_outbox_manifest_v2_local': jsonEncode([
|
||||
{'localRideId': -1, 'providerId': '', 'status': 'queued', 'createdAt': 'bad'},
|
||||
SyncItem(localRideId: 8, providerId: 'firebase', createdAt: DateTime(2026, 9, 1)).toJson(),
|
||||
]),
|
||||
|
|
|
|||
Loading…
Reference in New Issue