feat(online): sync client incrémentale LWW et profil/écrans auth (tickets #65, #67)

Ajoute la synchronisation client incrémentale LWW vers l'API serveur
(application/use_cases.dart: SyncUseCases, ports.dart, migration
Drift schemaVersion 10→11 pour les métadonnées de sync et mappings de
ressources, infrastructure/remote/sync_api.dart) et l'écran de profil
avec entrée sur l'accueil (presentation/profile_screen.dart,
home_screen.dart, presentation.dart). Développés dans le même
worktree partagé par DevBackend et DevFrontend ; commit combiné car
test/presentation/home_screen_test.dart mélange authentiquement les
deux tickets (le test de l'entrée Profil et la mise à jour du fake de
bootstrap requise par le nouveau getter syncUseCases sur
AppDependencies). Corrige au passage une couleur `crimson` inexistante
dans le thème (remplacée par colorScheme.error) et une signature de
paramètres positionnels/nommés incohérente sur un fake de test.
flutter pub get OK, build_runner OK, dart format appliqué, analyze
propre (mêmes infos préexistantes), 119/119 tests verts, build APK
debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 21:41:18 +02:00
parent b643988986
commit c0dbce275b
16 changed files with 3913 additions and 3 deletions

View File

@ -48,6 +48,50 @@ class OnlineAccountSessions extends Table {
];
}
class SyncMetadataEntries extends Table {
@override
String get tableName => 'sync_metadata';
TextColumn get id => text().withDefault(const Constant('singleton'))();
TextColumn get serverCursor => text().nullable()();
DateTimeColumn get lastSuccessfulSyncAt => dateTime().nullable()();
DateTimeColumn get lastAttemptAt => dateTime().nullable()();
DateTimeColumn get lastFailureAt => dateTime().nullable()();
TextColumn get status => text().withDefault(const Constant('idle'))();
IntColumn get pendingPushCount => integer().nullable()();
@override
Set<Column> get primaryKey => {id};
@override
List<String> get customConstraints => [
"CHECK (id = 'singleton')",
"CHECK (status IN ('idle', 'syncing', 'success', 'failure'))",
'CHECK (pending_push_count IS NULL OR pending_push_count >= 0)',
];
}
class RemoteResourceMappings extends Table {
@override
String get tableName => 'remote_resource_mappings';
TextColumn get id => text()();
TextColumn get resourceType => text()();
TextColumn get clientId => text().withLength(min: 1)();
TextColumn get serverId => text().withLength(min: 1)();
DateTimeColumn get serverUpdatedAt => dateTime()();
@override
Set<Column> get primaryKey => {id};
@override
List<String> get customConstraints => [
"CHECK (resource_type IN ('exercise', 'program', 'workoutTemplate', "
"'workoutHistory', 'mediaAsset'))",
'UNIQUE (resource_type, client_id)',
];
}
class MediaAssets extends SyncableTable {
@override
String get tableName => 'media_assets';