mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 18:07:05 +01:00
- 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
95 lines
2.8 KiB
Dart
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';
|