// lib/editor/persistence/sidecar_flush_observer.dart // // Phase 6 / §F.3 of the file-based storage plan (docs/plans/2026-06-24-file- // based-storage.md): app-lifecycle flush hardening. // // The per-file SidecarRepository debounces writes by 800 ms. That window is the // data-loss gap on a Windows tablet: if the OS suspends or closes the app // before the timer fires, the last strokes never reach disk. This observer // listens for the app leaving the foreground and DRAINS every open repo's // pending write before the process can be frozen, so "never lose the last // strokes on app close" holds even when the editor's own dispose() doesn't run. // // Registered once in BadNoteApp; it delegates to // [SidecarRepositoryRegistry.flushAll], which awaits every repo's flush(). import 'package:flutter/widgets.dart'; import 'sidecar_repository.dart'; /// A [WidgetsBindingObserver] that flushes all open sidecar repositories when /// the app leaves the foreground (`inactive`/`paused`/`detached`/`hidden`). class SidecarFlushObserver with WidgetsBindingObserver { /// Whether the observer is currently registered with the binding. bool get isAttached => _attached; bool _attached = false; /// Register with [WidgetsBinding.instance] so lifecycle changes are observed. void attach() { if (_attached) return; WidgetsBinding.instance.addObserver(this); _attached = true; } /// Stop observing lifecycle changes. void detach() { if (!_attached) return; WidgetsBinding.instance.removeObserver(this); _attached = false; } @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { // Any transition out of the foreground is a potential suspend/kill point: // drain pending sidecar writes now (the editors' own dispose() may never // run when the OS freezes the process). case AppLifecycleState.inactive: case AppLifecycleState.hidden: case AppLifecycleState.paused: case AppLifecycleState.detached: // Fire-and-forget at the framework boundary, but each write is atomic // and awaited inside flushAll, so a half-written sidecar is impossible. SidecarRepositoryRegistry.flushAll(); case AppLifecycleState.resumed: break; } } }