mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-07-04 15:23:02 +02:00
Initialised from this session: documents the data/sync/ui layers, the CRDT sync model, the structured note template, build/test commands, the repo-specific git rule (always push to main, never open PRs), and the widget-testing patterns that keep the suite fast and at 100% coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.5 KiB
5.5 KiB
CLAUDE.md — todo
Offline-first, CRDT-synced notes app. Flutter, targets Android + Linux. Capture an idea instantly (persisted on every keystroke), browse/filter notes, and sync peer-to-peer through a GitHub repo used as dumb storage.
- Package name:
todo(Dart SDK^3.12.2). - Git remote:
origin→github.com/kuhyx/todo-app. - The note content also syncs to a separate private repo
kuhyx/todo-syncvia the in-app GitHub sync (changeset files); that is data, not this codebase.
Git workflow (repo-specific — overrides global rules)
- Never open pull requests. Always commit and work directly on
mainandgit pushtoorigin/main. Do not create feature branches for normal work. - End commit messages with the standard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>trailer.
Commands
- Run tests + coverage:
flutter test --coverage(suite runs in ~5s; keep it that fast — see Testing below). - Coverage summary:
lcov --list coverage/lcov.info. - Static analysis:
flutter analyze(must be clean before committing). - Format:
dart format lib/ test/. - Run the app:
flutter run(Linux desktop or a connected Android device). - Release APK:
flutter build apk --release(signs with the debug key; debug builds are janky — always measure smoothness on a release build). - Sync smoke test (hits real GitHub, needs a token):
dart run tool/sync_smoke.dart.
Architecture
Three layers under lib/, each single-purpose:
data/— domain + local storage.note.dart:Notemodel;Priorityenum (low/medium/high, default medium, no "none");Statusenum (todo/inProgress/done/abandoned).note_repository.dart: CRDT storage oversqlite_crdt. Schema is at version 3 withonCreate/onUpgrademigrations (v1→v2 adds thestatuscolumn; v2→v3 backfills legacy priority0→medium). Also definesNoteFilter(query / priorities / statuses / created+updated date ranges, AND-combined),NoteSort,watchNotes/watchCountstreams, andimportNotes(safe newer-wins merge by id).
sync/— GitHub-as-storage sync.sync_service.dart: each device owns one filechangesets/<nodeId>.jsonholding its full CRDT changeset. No two devices write the same file ⇒ no git merge conflicts; convergence is the CRDT layer's job (pull every other device's changeset andmerge(), which is commutative + idempotent).github_client.dart: thin GitHub contents-API client (injectablehttp.Client).github_device_auth.dart: OAuth device flow to mint a token.sync_settings.dart: persisted owner/repo/token/clientId.notes_markdown.dart: round-trippable single-file export/import format (HTML-comment<!-- @note id=… priority=… status=… … -->markers).
ui/— screens, all take an injectedNoteRepository.capture_screen.dart: landing screen; always-focused text box pre-filled with the structured template; lazy note creation on first keystroke.notes_list_screen.dart: list + search + sort + filter sheet + per-note sheet. Default view hides Done/Abandoned and shows no filter badge (looks unfiltered).settings_screen.dart: GitHub connect/test, device-flow auth, and the Export/Import backup actions.
main.dart: bootstrap only (// coverage:ignore-file) — wires platform DB paths andrunApp.
Note template (default content of every new note)
Every new note pre-fills this scaffold (matches the user's <work_backlog>
format): a title line plus what / where / must / nice / out / done / depends / estimate / refs sections.
Testing
- The suite must stay fast (~5s) and fully green. It is currently 101 tests at 100% line coverage. Don't regress either.
- Widget tests use
test/fake_note_repository.dart— aFakeNoteRepositorybuilt onStreamControllers, not a real DB. A realsqlite_crdtDB schedules timers that never drain under the widget tester's fake clock ("A Timer is still pending"); the fake avoids that. - Pitfalls learned the hard way (keep following these):
- Avoid
pumpAndSettlewhen a widget animates forever — an autofocusedTextField's cursor blink never settles, and an open device-code dialog keeps a pending poll timer. Use explicitpump(Duration)there.pumpAndSettleis fine once nothing is animating (e.g. a route/dialog pop, or a dialog already showing a static error). - Inject fakes rather than touching the platform:
MockClient(package:http/testing.dart) for HTTP; thefile_selectorandurl_launcherplatform interfaces (MockPlatformInterfaceMixin) for the picker/launcher; stubSystemChannels.platformfor clipboard. - To exercise the configured sync path, pass a
MockClientviaCaptureScreen(httpClient: …)/SettingsScreen(httpClient: …)— the realSyncServicethen runs without network. getChangeset()serialises HLCs asString;merge()needsHlc.parseand fresh mutable maps (QueryRows are read-only) — see the changeset test.
- Avoid
- Use
// coverage:ignore-line/ignore-start/ignore-endonly for genuinely unreachable code (e.g. a private static-only constructor, or a mobile-onlyPlatform.isAndroidbranch that can't run on the Linux test host), with a one-line reason.
Conventions
- Run
dart format+flutter analyze(clean) before every commit. - Comments explain intent/trade-offs, not syntax.
- Keep the app buttery-smooth and low on CPU/RAM — it's a quick-capture tool.