wake-alarm/phone_app/test/fake_day_pool_store.dart
Krzysztof kuhy Rudnicki 34037e55c1 phone: sick days — picker, silent ring, stick stand-down, pre-alarm sync
- Sick days screen (own name, sick icon, cat-2 fill): tap today or a later
  day to mark it sick, tap again to clear; past days inert. Writes the
  sickdays wire format (state sick/cleared + actor, one HLC per write) to
  sickdays-sync/devices via crdt_sync syncLog.
- Mutual exclusion in both pickers: each syncs the other pool on open and
  refuses a date it holds, naming the pool; an unreadable pool refuses all.
- Native SickDays cache (fail closed via DayCache); AlarmReceiver skips the
  ring with reason "sick_day" (no markFired, no ring_started); StickCheck
  verdict sick_day, streak skips sick days; stakes card says so.
- Pre-alarm pool sync: allow-while-idle alarm at 21:00 and A-30 (request
  code 1006) runs a headless engine (poolSyncMain) that pulls both pools
  from RTDB and rewrites both native caches, so a PC mark arrives with the
  app closed. The ring decision stays a cache read.
- JVM unit tests (JUnit 4.13.2) for DayCache, RingDecision,
  PoolSyncSchedule, StickCheck; Dart tests; Dart->Python interop check.

pylint hook skipped: verify.py imports sickdays, not in the hook env until
the sickdays-v0.1.0 tag exists; run by hand in a sickdays venv (10.00/10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xfj1sxKH9VBPqpcAni8Jry
2026-10-16 13:22:40 +02:00

95 lines
2.8 KiB
Dart

import 'package:crdt_sync/crdt_sync.dart';
/// An in-memory `LogPersistence`.
class MemPersistence implements LogPersistence {
/// The persisted text, or null before the first write.
String? text;
@override
Future<String?> read() async => text;
@override
Future<void> write(String value) async => text = value;
}
/// An in-memory `RemoteStore`: a flat path -> text map.
class MemRemote implements RemoteStore {
/// Every file, by full path.
final Map<String, String> files = {};
@override
Future<List<String>> listDirectory(String path) async {
final prefix = '$path/';
return files.keys
.where((k) => k.startsWith(prefix))
.map((k) => k.substring(prefix.length).split('/').first)
.toSet()
.toList();
}
@override
Future<String?> getFileText(String path) async => files[path];
@override
Future<void> putFileText(
String path,
String text, {
required String message,
}) async => files[path] = text;
@override
Future<void> deleteFile(String path, {String? message}) async =>
files.remove(path);
@override
Future<bool> canAccessRemote() async => true;
@override
void close() {}
}
/// A remote that is offline for every read and write.
class BrokenRemote implements RemoteStore {
@override
Future<List<String>> listDirectory(String path) async =>
throw RemoteSyncError('offline');
@override
Future<String?> getFileText(String path) async =>
throw RemoteSyncError('offline');
@override
Future<void> putFileText(
String path,
String text, {
required String message,
}) async => throw RemoteSyncError('offline');
@override
Future<void> deleteFile(String path, {String? message}) async {}
@override
Future<bool> canAccessRemote() async => false;
@override
void close() {}
}
/// A log exactly as `sickdays` 0.1.0 (Python) writes it: 2026-10-20 sick,
/// 2026-10-21 marked and then cleared. Generated by `add_sick_day` /
/// `remove_sick_day` on 2026-10-16, not hand-written.
const pythonSickLog =
'{"2026-10-20": {"id": "2026-10-20", "fields": {"state": ["sick", '
'"2026-10-16T11:13:53.005Z-0000-ad78e209-49cd-4989-9235-da5ec179f183"], '
'"actor": ["ad78e209-49cd-4989-9235-da5ec179f183", '
'"2026-10-16T11:13:53.005Z-0000-ad78e209-49cd-4989-9235-da5ec179f183"]}, '
'"deleted": false, "deleted_hlc": null}, "2026-10-21": {"id": '
'"2026-10-21", "fields": {"state": ["cleared", '
'"2026-10-16T11:13:53.007Z-0000-ad78e209-49cd-4989-9235-da5ec179f183"], '
'"actor": ["ad78e209-49cd-4989-9235-da5ec179f183", '
'"2026-10-16T11:13:53.007Z-0000-ad78e209-49cd-4989-9235-da5ec179f183"]}, '
'"deleted": false, "deleted_hlc": null}}';
/// The PC's device directory in [pythonSickLog].
const pythonDeviceId = 'ad78e209-49cd-4989-9235-da5ec179f183';