mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-14 21:30:54 +01:00
The desktop app is a browser now, so it cannot write files. This adds the other half: a loopback HTTP server that serves the web build and owns the two files the browser cannot touch -- `~/todo/BACKLOG.md`, which the user's tooling and the todo MCP server read, and a disk copy of the note log so a wiped Chrome profile is not a total-loss event for unsynced notes. Notable details: - Binds to 127.0.0.1 only. The endpoints overwrite files in the user's home with no authentication, so a routable bind would let anything on the network rewrite the backlog. - Serves explicit content types. CanvasKit refuses a `.wasm` labelled anything else and the app then renders nothing at all. - Mirroring to disk is debounced. The store persists on every keystroke, so mirroring eagerly meant an HTTP request plus a whole-log write per character; IndexedDB still gets every keystroke, only the durability copy waits. Separately, this fixes a pre-existing destructive bug found while testing: `settings_screen_test` did real file I/O and overwrote the user's canonical `~/todo/BACKLOG.md` with a single fake note on *every* `flutter test` run. It went unnoticed because the app rewrites the file whenever it launches. The export destination is now injectable and the test redirects it to a temp dir; verified that a full suite run leaves the real file byte-identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44 lines
1.8 KiB
Dart
44 lines
1.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:share_plus/share_plus.dart';
|
|
|
|
/// Resolves the home directory the desktop export writes under.
|
|
///
|
|
/// Overridable **so the test suite never writes to the real `~/todo`**. This is
|
|
/// not hypothetical: before this seam existed, running `flutter test` exported
|
|
/// a fake note over the user's canonical `~/todo/BACKLOG.md` every time.
|
|
String Function() resolveExportHome =
|
|
() => Platform.environment['HOME'] ?? Directory.current.path;
|
|
|
|
/// Writes the Markdown export somewhere the user can get at it, and returns a
|
|
/// human-readable description of where it went.
|
|
///
|
|
/// On mobile this opens the system share sheet; on a `dart:io` desktop host it
|
|
/// writes the canonical `~/todo/BACKLOG.md`.
|
|
Future<String> exportBacklog(String markdown, int noteCount) async {
|
|
// coverage:ignore-start
|
|
// Mobile-only share path: Platform.isAndroid/isIOS are always false on the
|
|
// Linux test host, so these lines are structurally unreachable in CI and
|
|
// excluded from the coverage denominator. Verified on-device.
|
|
if (Platform.isAndroid || Platform.isIOS) {
|
|
final dir = await getTemporaryDirectory();
|
|
final file = File('${dir.path}/todo-backlog.md');
|
|
await file.writeAsString(markdown);
|
|
await SharePlus.instance.share(
|
|
ShareParams(
|
|
files: [XFile(file.path, mimeType: 'text/markdown')],
|
|
subject: 'todo backlog ($noteCount notes)',
|
|
),
|
|
);
|
|
return 'Shared $noteCount notes';
|
|
}
|
|
// coverage:ignore-end
|
|
final home = resolveExportHome();
|
|
final dir = Directory('$home/todo');
|
|
if (!dir.existsSync()) dir.createSync(recursive: true);
|
|
final file = File('${dir.path}/BACKLOG.md');
|
|
await file.writeAsString(markdown);
|
|
return 'Exported $noteCount notes to ${file.path}';
|
|
}
|