mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-11 22:13:59 +01:00
First step of moving the desktop app off Flutter's Linux embedder, which tops
out at ~20fps at 4K (see docs/desktop-performance-findings.md). Android is
unaffected and keeps every existing code path.
The blocker was that `dart:io` was imported by note_repository, capture_screen
and settings_screen. On web it compiles to a stub and then throws at runtime --
the first web build rendered a blank white window. So each `dart:io` use now
sits behind a conditional export, with the shared logic hoisted into pure Dart:
- note_repository loses `dart:io`/sqlite entirely. Platform wiring moves to
repository_factory{,_io,_web}, and the legacy sqlite import moves to its own
io-only library. The HLC-from-updated_at seeding is preserved verbatim, so
independently-migrating devices still resolve pre-cutover edits by real edit
time.
- Notes persist to IndexedDB on web, not localStorage: the log is the primary
copy of the backlog and localStorage caps at ~5-10MB and is evicted eagerly.
- BACKLOG.md and the explicit Export keep working through a local wrapper
(added next). A browser cannot write `~/todo/BACKLOG.md`, and the user's
tooling plus the todo MCP server read that path, so leaving it to rot was not
an option. Writes are best-effort so the app still works with no wrapper.
- Notes are also mirrored to the wrapper's on-disk copy, so a wiped Chrome
profile is not a total-loss event for notes that have not reached GitHub yet.
`openRepository` is split so `openRepositoryIn` keeps the migration tests; the
suite stays at 174 green and 100% coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
58 lines
2.0 KiB
Dart
58 lines
2.0 KiB
Dart
import 'package:http/http.dart' as http;
|
|
|
|
/// Talks to the local desktop wrapper that serves the web build.
|
|
///
|
|
/// The wrapper exists because a browser cannot write to the filesystem, and two
|
|
/// things depend on it:
|
|
///
|
|
/// * `~/todo/BACKLOG.md` — read by the user's tooling and the `todo` MCP
|
|
/// server. Without this it would silently go stale after the move to a web
|
|
/// build.
|
|
/// * an on-disk copy of the note log, so a wiped Chrome profile is not a
|
|
/// total-loss event for notes that have not reached GitHub yet.
|
|
///
|
|
/// Every method is best-effort and swallows failures: the app must remain fully
|
|
/// usable when the wrapper is absent, e.g. when the build is opened in a plain
|
|
/// browser tab or on mobile.
|
|
class DesktopBackupClient {
|
|
/// Creates a client targeting [baseUrl] (the wrapper's origin).
|
|
DesktopBackupClient({required this.baseUrl, http.Client? httpClient})
|
|
: _client = httpClient ?? http.Client();
|
|
|
|
/// Origin the wrapper listens on, e.g. `http://localhost:8730`.
|
|
final String baseUrl;
|
|
|
|
final http.Client _client;
|
|
|
|
/// Writes the Markdown backlog export to disk via the wrapper.
|
|
Future<void> writeBacklog(String markdown) =>
|
|
_post('/backup/backlog', markdown);
|
|
|
|
/// Writes the serialised note log to disk via the wrapper.
|
|
Future<void> writeLog(String json) => _post('/backup/log', json);
|
|
|
|
/// Reads the wrapper's on-disk copy of the log, or null if unavailable.
|
|
Future<String?> readLog() => _get('/backup/log');
|
|
|
|
/// Reads the on-disk Markdown backlog, or null if unavailable.
|
|
Future<String?> readBacklog() => _get('/backup/backlog');
|
|
|
|
Future<String?> _get(String path) async {
|
|
try {
|
|
final response = await _client.get(Uri.parse('$baseUrl$path'));
|
|
if (response.statusCode != 200 || response.body.isEmpty) return null;
|
|
return response.body;
|
|
} on Exception {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _post(String path, String body) async {
|
|
try {
|
|
await _client.post(Uri.parse('$baseUrl$path'), body: body);
|
|
} on Exception {
|
|
// Best-effort by design; see the class docs.
|
|
}
|
|
}
|
|
}
|