From 871ac95298f1872248ba948d32e635eb2b341579 Mon Sep 17 00:00:00 2001 From: Blomios Date: Mon, 20 Jul 2026 07:50:18 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(server):=20serveur=20non=20lan=C3=A7abl?= =?UTF-8?q?e=20faute=20de=20r=C3=A9pertoire=20build/=20(ticket=20#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dart compile exe échouait car le répertoire build/ n'existait pas encore dans le conteneur au moment de la compilation. Ajoute mkdir -p build avant les deux dart compile exe dans le Dockerfile. Bug reproduit et fix confirmé manuellement en isolant le scénario exact (échec sans le fix, succès avec). Co-Authored-By: Claude Opus 4.8 --- server/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/Dockerfile b/server/Dockerfile index 77ce8a0..025d1d0 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -6,7 +6,8 @@ COPY pubspec.* ./ RUN dart pub get COPY . . -RUN dart compile exe bin/server.dart -o build/server \ +RUN mkdir -p build \ + && dart compile exe bin/server.dart -o build/server \ && dart compile exe bin/migrate.dart -o build/migrate FROM debian:bookworm-slim AS runtime From ef2c3ffffdbeced2d4c12064fd16db84b5bfd157 Mon Sep 17 00:00:00 2001 From: Blomios Date: Mon, 20 Jul 2026 07:50:26 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix(exercice):=20s=C3=A9lection=20d'un=20sc?= =?UTF-8?q?ore=20par=20d=C3=A9faut=20=C3=A0=200=20impossible=20(ticket=20#?= =?UTF-8?q?71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige la contrainte CHECK sur la table exercises (tables.dart) qui empêchait à tort une valeur de score par défaut à 0, et ajoute la migration Drift schemaVersion 12→13 reconstruisant la table avec la contrainte corrigée (app_database.dart). build_runner OK, test de régression ajouté. Co-Authored-By: Claude Opus 4.8 --- lib/infrastructure/local/app_database.dart | 112 +++++++++++++++++- lib/infrastructure/local/tables.dart | 2 +- .../drift_repositories_test.dart | 19 +++ 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/lib/infrastructure/local/app_database.dart b/lib/infrastructure/local/app_database.dart index fae882d..05c47a1 100644 --- a/lib/infrastructure/local/app_database.dart +++ b/lib/infrastructure/local/app_database.dart @@ -46,7 +46,7 @@ final class AppDatabase extends _$AppDatabase { } @override - int get schemaVersion => 12; + int get schemaVersion => 13; @override MigrationStrategy get migration { @@ -97,6 +97,9 @@ final class AppDatabase extends _$AppDatabase { if (from < 12) { await _migrateToSchema12(migrator); } + if (from < 13) { + await _migrateToSchema13(); + } await _createIndexes(); }, beforeOpen: (details) async { @@ -359,4 +362,111 @@ extension on AppDatabase { await migrator.createTable(shareInboxItems); await migrator.createTable(pendingShareActions); } + + Future _migrateToSchema13() async { + await customStatement('PRAGMA foreign_keys = OFF'); + await customStatement(''' +CREATE TABLE exercises_new ( + id TEXT NOT NULL PRIMARY KEY, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + deleted_at INTEGER, + schema_version INTEGER NOT NULL DEFAULT 1, + sync_state TEXT NOT NULL CHECK (sync_state IN ('localOnly', 'dirty', 'synced', 'deleted')), + local_revision INTEGER NOT NULL CHECK (local_revision >= 0), + origin_device_id TEXT NOT NULL, + future_owner_profile_id TEXT, + last_synced_at INTEGER, + remote_revision TEXT, + name TEXT NOT NULL, + description TEXT, + icon_media_id TEXT REFERENCES media_assets(id), + video_media_id TEXT REFERENCES media_assets(id), + has_time_measure INTEGER NOT NULL CHECK (has_time_measure IN (0, 1)), + has_reps_measure INTEGER NOT NULL CHECK (has_reps_measure IN (0, 1)), + has_score_measure INTEGER NOT NULL CHECK (has_score_measure IN (0, 1)), + score_input_mode TEXT NOT NULL DEFAULT 'manual', + score_label TEXT, + score_unit TEXT, + default_target_time_seconds INTEGER, + default_target_reps INTEGER, + default_target_score REAL, + default_target_score_time_ms INTEGER, + archived_at INTEGER, + CHECK (length(trim(origin_device_id)) > 0), + CHECK (length(trim(name)) > 0), + CHECK (has_time_measure OR has_reps_measure OR has_score_measure), + CHECK (score_input_mode IN ('manual', 'stopwatch')), + CHECK (has_score_measure OR score_input_mode = 'manual'), + CHECK (score_input_mode != 'manual' OR NOT has_score_measure OR (score_label IS NOT NULL AND length(trim(score_label)) > 0 AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0)), + CHECK (default_target_time_seconds IS NULL OR default_target_time_seconds > 0), + CHECK (default_target_reps IS NULL OR default_target_reps > 0), + CHECK (default_target_score IS NULL OR default_target_score >= 0), + CHECK (default_target_score_time_ms IS NULL OR default_target_score_time_ms > 0), + CHECK (future_owner_profile_id IS NULL OR length(trim(future_owner_profile_id)) > 0), + CHECK (remote_revision IS NULL OR length(trim(remote_revision)) > 0) +) +'''); + await customStatement(''' +INSERT INTO exercises_new ( + id, + created_at, + updated_at, + deleted_at, + schema_version, + sync_state, + local_revision, + origin_device_id, + future_owner_profile_id, + last_synced_at, + remote_revision, + name, + description, + icon_media_id, + video_media_id, + has_time_measure, + has_reps_measure, + has_score_measure, + score_input_mode, + score_label, + score_unit, + default_target_time_seconds, + default_target_reps, + default_target_score, + default_target_score_time_ms, + archived_at +) +SELECT + id, + created_at, + updated_at, + deleted_at, + schema_version, + sync_state, + local_revision, + origin_device_id, + future_owner_profile_id, + last_synced_at, + remote_revision, + name, + description, + icon_media_id, + video_media_id, + has_time_measure, + has_reps_measure, + has_score_measure, + score_input_mode, + score_label, + score_unit, + default_target_time_seconds, + default_target_reps, + default_target_score, + default_target_score_time_ms, + archived_at +FROM exercises +'''); + await customStatement('DROP TABLE exercises'); + await customStatement('ALTER TABLE exercises_new RENAME TO exercises'); + await customStatement('PRAGMA foreign_keys = ON'); + } } diff --git a/lib/infrastructure/local/tables.dart b/lib/infrastructure/local/tables.dart index 36d534d..51c2583 100644 --- a/lib/infrastructure/local/tables.dart +++ b/lib/infrastructure/local/tables.dart @@ -204,7 +204,7 @@ class Exercises extends SyncableTable { 'CHECK (default_target_time_seconds IS NULL OR ' 'default_target_time_seconds > 0)', 'CHECK (default_target_reps IS NULL OR default_target_reps > 0)', - 'CHECK (default_target_score IS NULL OR default_target_score > 0)', + 'CHECK (default_target_score IS NULL OR default_target_score >= 0)', 'CHECK (default_target_score_time_ms IS NULL OR ' 'default_target_score_time_ms > 0)', ]; diff --git a/test/infrastructure/drift_repositories_test.dart b/test/infrastructure/drift_repositories_test.dart index c1b3f2b..ba2925b 100644 --- a/test/infrastructure/drift_repositories_test.dart +++ b/test/infrastructure/drift_repositories_test.dart @@ -79,6 +79,25 @@ void main() { expect(changes.every((change) => change.entityType == 'Exercise'), isTrue); }); + test('exercise repository accepts zero manual default score', () async { + final exercise = Exercise( + metadata: _metadata('exercise-score-zero', DateTime.utc(2026, 7, 17, 12)), + name: 'Score nul', + hasTimeMeasure: false, + hasRepsMeasure: false, + hasScoreMeasure: true, + scoreLabel: 'Score', + scoreUnit: 'pts', + defaultTargetScore: 0, + ); + + await exerciseRepository.save(exercise); + + final restored = await exerciseRepository.findById(exercise.metadata.id); + expect(restored, isNotNull); + expect(restored!.defaultTargetScore, 0); + }); + test('exercise repository round-trips configured steps', () async { final now = DateTime.utc(2026, 7, 17, 12); final exercise = Exercise( From ebc537ad37e3be1d4b38a4d5e5ebdc172760343e Mon Sep 17 00:00:00 2001 From: Blomios Date: Mon, 20 Jul 2026 07:50:32 +0200 Subject: [PATCH 3/3] =?UTF-8?q?docs(ideai):=20met=20=C3=A0=20jour=20le=20t?= =?UTF-8?q?icket=20#69=20apr=C3=A8s=20les=20correctifs=20#71/#74?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .ideai/tickets/69/carnet.md | 6 +++--- .ideai/tickets/69/issue.md | 8 ++++---- .ideai/tickets/index.json | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.ideai/tickets/69/carnet.md b/.ideai/tickets/69/carnet.md index 7e74077..f4e6733 100644 --- a/.ideai/tickets/69/carnet.md +++ b/.ideai/tickets/69/carnet.md @@ -1,6 +1,6 @@ --- issueRef: "#69" -version: 1 -updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"} -updatedAt: 1784488364646 +version: 2 +updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"} +updatedAt: 1784525029743 --- diff --git a/.ideai/tickets/69/issue.md b/.ideai/tickets/69/issue.md index b33e4df..6d101eb 100644 --- a/.ideai/tickets/69/issue.md +++ b/.ideai/tickets/69/issue.md @@ -2,15 +2,15 @@ id: "05482a20-83a6-44da-b0c6-d065a5a1ffba" number: 69 title: "[DevFrontend] Partage sortant et boîte de réception" -status: "open" +status: "qa" priority: "medium" sprint: null links: [{"target":"#63","kind":"relatesTo"},{"target":"#66","kind":"dependsOn"},{"target":"#67","kind":"dependsOn"}] agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}] createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"} -updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"} +updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"} createdAt: 1784488364646 -updatedAt: 1784488364646 -version: 1 +updatedAt: 1784525029743 +version: 2 --- Ajouter l'action `Partager` dans les écrans programme/séance enregistrés, formulaire email destinataire, états déconnecté et messages discrets. Ajouter `Profil > Partages reçus`, liste inbox, accepter/refuser, import local des programmes/séances reçus. Les erreurs réseau restent inline/neutres; accepter un partage rend la copie disponible offline. \ No newline at end of file diff --git a/.ideai/tickets/index.json b/.ideai/tickets/index.json index 0b30752..d3b800f 100644 --- a/.ideai/tickets/index.json +++ b/.ideai/tickets/index.json @@ -775,13 +775,13 @@ "issueRef": "#69", "path": "69", "title": "[DevFrontend] Partage sortant et boîte de réception", - "status": "open", + "status": "qa", "priority": "medium", "sprint": null, "assignedAgentIds": [ "9933c93a-b8a1-4164-a3bb-7063fdad747d" ], - "updatedAt": 1784488364646 + "updatedAt": 1784525029743 }, { "issueRef": "#70",