feat(todo): the person's projects, sealed and synced, the Inbox made once (ADR 0033, D5, D6)

TodoPerson opens the person's own sync database in their folder,
halkyon_app's SyncService behind AppCompatibility, their key (TodoKeys,
Train's copied with Todo's own key IDs) and their projects, and closes
them in that order, the key last. A project is sealed for its row; a
readable, foreign or moved row shows locked, and a device without the
account's keys reads and writes nothing. The Inbox is the row under an
ID keyed by the account, so every device makes the same one and the
instance can't tell which it is; it can't be deleted. Settings shows
Sync's card first and the cog its dot while sync fails, as Learn's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-01 01:05:31 +01:00
commit 60a8f5b617
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 1285 additions and 36 deletions

View file

@ -0,0 +1,19 @@
import 'dart:typed_data';
import 'package:halkyon_todo_flutter/src/core/privacy/content_key_gateway.dart';
/// [ContentKeyGateway] in memory, which tests share between "devices" as the
/// sync engine would, and change as an instance could.
class MemoryContentKeyGateway() implements ContentKeyGateway {
/// The rows, as the instance holds them.
final rows = <ContentKeyRow>[];
@override
Future<List<ContentKeyRow>> all() async => List.of(rows);
@override
Future<void> insert({
required String dataKeyId,
required Uint8List sealedKey,
}) async => rows.add((dataKeyId: dataKeyId, sealedKey: sealedKey));
}

View file

@ -0,0 +1,36 @@
import 'dart:async';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_todo_flutter/src/feature/project/data/project_gateway.dart';
/// [ProjectGateway] in memory, whose rows a test changes as an instance could.
class MemoryProjectGateway() implements ProjectGateway {
final rows = <String, Envelope>{};
final _remote = StreamController<void>.broadcast();
/// As a sync bringing others' changes.
void bringRemoteChanges() => _remote.add(null);
@override
Future<List<ProjectRow>> projects() async => [
for (final MapEntry(:key, :value) in rows.entries)
(id: key, envelope: value),
];
@override
Future<void> insert(ProjectRow project) async =>
rows[project.id] = project.envelope;
@override
Future<void> update(String id, Envelope envelope) async {
if (rows.containsKey(id)) {
rows[id] = envelope;
}
}
@override
Future<void> delete(String id) async => rows.remove(id);
@override
Stream<void> get remoteChanges => _remote.stream;
}