feat: learning paths in studysets, synced, with progress and tags

Server: five synced tables, only additions (migration
20261002211346701-learning-paths):
- `learning_path`: its studyset, a fractional position, and its own
  content in the payload {title, secondaryTitle, description}, the last
  writer winning, as a studyset's;
- `learning_path_step`: its path, a position, and its kind ('info',
  'singleChoice', 'multipleChoice', 'dragAndDrop', 'fillInTheBlank'),
  fixed once created;
- `learning_path_step_revision`: a step's content as append-only
  revisions, as a card's: {title, body, exercise, rightFeedback,
  wrongFeedback, sources};
- `learning_path_answer`: an append-only log (right, wrong, continued,
  reset), as reviews are, so answers from two devices never conflict;
- `learning_path_tag`: one row per assignment, as `card_tag`.

App, `feature/learning_path`: the five exercises and their JSON, grading
that marks only the learner's own wrong picks, progress worked out from
the log (a step is done once answered right since its last fresh start),
`LearningPathRepository` (steps saved on what they started from, merged
per field) and `PathProgressRepository` (answers, resets with Undo, the
times steps were done). Paths share the person's tags; deleting a path
or a step can be undone for a moment, and a studyset's deletion takes
its paths. Tests: models, codecs, repositories, tags, the device
migration guard, and sync tests against the server (four).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-02 23:31:20 +02:00
commit 91f6376b93
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -9,6 +9,10 @@ import 'package:halkyon_learn_flutter/src/core/sync/sync_service.dart';
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/card/data/sync_card_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/card/model/card.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/path_progress_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/sync_learning_path_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/sync_path_answer_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/note/data/note_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/note/data/sync_note_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/note/model/note.dart';
@ -127,6 +131,12 @@ final class SyncDevice(
/// The app's notes on this device's database.
final NoteRepository notes,
/// The app's learning paths on this device's database.
final LearningPathRepository paths,
/// The app's answers to steps of paths on this device's database.
final PathProgressRepository pathProgress,
/// The app's reviews on this device's database.
final ReviewRepository reviews,
@ -163,6 +173,9 @@ final class SyncDevice(
final notes = NoteRepository(
SyncNoteGateway(db, syncRequests, person.authUserId.uuid),
);
final paths = LearningPathRepository(
SyncLearningPathGateway(db, syncRequests, person.authUserId.uuid),
);
return SyncDevice(
client,
db,
@ -170,11 +183,14 @@ final class SyncDevice(
StudysetRepository(SyncStudysetGateway(db, syncRequests)),
cards,
notes,
paths,
PathProgressRepository(SyncPathAnswerGateway(db, syncRequests)),
ReviewRepository(SyncReviewGateway(db, syncRequests)),
TagRepository(
SyncTagGateway(db, syncRequests),
cardChanges: cards.changes,
noteChanges: notes.changes,
pathChanges: paths.changes,
),
SettingsRepository(SyncSettingsGateway(db, syncRequests)),
AssetRepository(
@ -226,6 +242,8 @@ final class SyncDevice(
await reviews.dispose();
await cards.dispose();
await notes.dispose();
await paths.dispose();
await pathProgress.dispose();
await assets.dispose();
await _syncRequests.dispose();
await _db.close();