Fusionne feature/bugfix-71-74-critical — pub get OK, build_runner OK (migration schemaVersion 12→13), analyze propre, 133/133 tests verts, build APK debug validé. Fix #74 reproduit et confirmé manuellement en isolant le scénario Docker exact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -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
|
||||
---
|
||||
|
||||
@ -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.
|
||||
@ -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",
|
||||
|
||||
@ -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<void> _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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)',
|
||||
];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
Reference in New Issue
Block a user