From a0d37c4df82fcf670c50c3e7ddc18d09c8f7f8bb Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 11 Sep 2026 19:49:03 +0200 Subject: [PATCH] Re-point paths for the ~ reorganisation (home-tidy) Also bumps Flutter 3.47.3, flutter_secure_storage 11.1.0, idb_shim 2.9.9, sqflite_common_ffi 2.4.3, very_good_analysis 11 and mcp 2.2.0 so the freshness gate lets this through; v11 adds async_return_with_no_await, so the 18 flagged return sites now await explicitly. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF --- .fvmrc | 2 +- .github/workflows/release-apk.yml | 2 +- .pre-commit-config.yaml | 4 ++-- AGENTS.md | 2 +- desktop/install_desktop_entry.sh | 2 +- docs/DOCS-llm-design-spec-audit.md | 6 ++--- ...7-11-explanation-per-keystroke-backup.html | 2 +- lib/data/desktop_backup_client.dart | 2 +- lib/data/repository_factory_io.dart | 7 ++++-- lib/data/repository_factory_web.dart | 5 +++- lib/data/web_log_persistence.dart | 2 +- lib/desktop/port_guard.dart | 2 +- lib/desktop/wrapper_server.dart | 14 +++++------ lib/sync/backlog_export.dart | 2 +- lib/sync/backlog_export_io.dart | 6 ++--- lib/sync/backlog_export_web.dart | 4 ++-- lib/sync/firebase_account_store.dart | 2 +- lib/sync/firebase_client.dart | 4 ++-- lib/sync/google_sign_in_backend.dart | 2 +- lib/sync/local_backup_factory_io.dart | 5 ++-- lib/sync/local_backup_factory_web.dart | 2 +- lib/sync/sync_service.dart | 2 +- lib/sync/sync_state_factory_web.dart | 2 +- lib/ui/capture_draft.dart | 2 +- lib/ui/settings_screen.dart | 2 +- mcp/README.md | 2 +- mcp/pyproject.toml | 2 +- mcp/todo_mcp/_mcp.py | 4 ++-- mcp/todo_mcp/tests/test_mcp.py | 2 +- packaging/aur/README.md | 6 ++--- prompts/TODO-dopamine-ux-todo.md | 24 +++++++++---------- pubspec.lock | 22 ++++++++--------- pubspec.yaml | 12 +++++----- scripts/check_dependency_freshness.sh | 6 ++--- scripts/check_file_length.sh | 6 ++--- scripts/check_md_naming.sh | 6 ++--- scripts/check_no_crdt_sync_override.sh | 2 +- test/capture_screen_harness.dart | 2 +- test/firebase_backend_test.dart | 2 +- test/settings_screen_backup_test.dart | 2 +- 40 files changed, 97 insertions(+), 90 deletions(-) diff --git a/.fvmrc b/.fvmrc index 16a96ef..d7f4b8c 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.47.2" + "flutter": "3.47.3" } diff --git a/.github/workflows/release-apk.yml b/.github/workflows/release-apk.yml index e6bbfe9..37b100a 100644 --- a/.github/workflows/release-apk.yml +++ b/.github/workflows/release-apk.yml @@ -47,7 +47,7 @@ jobs: - uses: subosito/flutter-action@v2 with: - flutter-version: 3.47.2 + flutter-version: 3.47.3 channel: stable cache: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f105ada..9d99e26 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: # unrelated commit never breaks; exits 1 rather than warning. No # baseline and no allowlist -- those are suppressions. The exemption # list (generated/vendored/data files) lives with the checker in - # ~/utils/file_length/config.py so the gate and the survey cannot + # ~/src/utils/file_length/config.py so the gate and the survey cannot # disagree. - id: file-length name: file length <= 250 lines @@ -53,7 +53,7 @@ repos: # Four markdown namespaces (README / CLAUDE* / DOCS* / TODO*), plus the # rule that a TODO carries "REMOVE ME AFTER FINISH" and nothing else may. - # Shared gate in ~/utils owns the namespaces and the exemption list, so + # Shared gate in ~/src/utils owns the namespaces and the exemption list, so # one repo's idea of a valid name cannot drift from every other repo's. - id: md-naming name: markdown naming convention diff --git a/AGENTS.md b/AGENTS.md index b094ee3..1a10ad8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -136,7 +136,7 @@ migrate → Settings → Import). - Keep the app buttery-smooth and low on CPU/RAM — it's a quick-capture tool. - **Every file is at most 250 lines**, source and prose alike. Enforced by a pre-commit hook (`scripts/check_file_length.sh`, delegating to the shared - gate in `~/utils`) and by `.github/workflows/file-length.yml`, so it fails + gate in `~/src/utils`) and by `.github/workflows/file-length.yml`, so it fails the commit rather than being a note anyone can ignore. Install the hook on a fresh clone with `scripts/install_hooks.sh`. - **Only non-mutating checks go in `.pre-commit-config.yaml`.** pre-commit diff --git a/desktop/install_desktop_entry.sh b/desktop/install_desktop_entry.sh index 869843e..e4e22c2 100755 --- a/desktop/install_desktop_entry.sh +++ b/desktop/install_desktop_entry.sh @@ -10,7 +10,7 @@ # # Icon PNGs are pre-rendered and committed under desktop/icons/, so this script # needs no image tooling at run time. Regenerate them with: -# PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons \ +# PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons \ # generate --app todo --linux-out desktop/icons # ============================================================================ diff --git a/docs/DOCS-llm-design-spec-audit.md b/docs/DOCS-llm-design-spec-audit.md index e5495a9..98d6baa 100644 --- a/docs/DOCS-llm-design-spec-audit.md +++ b/docs/DOCS-llm-design-spec-audit.md @@ -2,7 +2,7 @@ ## Context -`~/todo`'s default note template (`lib/data/note_template.dart`, `NoteTemplate.llmDesignSpec`) +`~/src/todo`'s default note template (`lib/data/note_template.dart`, `NoteTemplate.llmDesignSpec`) has 12 sections: title, what, where, tech, must, ask, nice, never, done, depends, estimate, refs. It was designed top-down from the `` format in `~/.claude/CLAUDE.md`, never validated against what actually gets written or what @@ -19,7 +19,7 @@ read, and did leaving it out cause a failure?* | Source | Size | | --- | --- | | `~/.claude/projects/**/*.jsonl` | 514 sessions, 495 with real user prompts, **1503 prompts** | -| `~/todo/BACKLOG.md` | 49 notes — **25 structured, 24 freeform** | +| `~/src/todo/BACKLOG.md` | 49 notes — **25 structured, 24 freeform** | | note → session join (title match) | **22 note/session pairs**, 21 of 25 notes traced | | `~/.claude/memories/mistakes.md` + `projects/-home-kuhy/memory/feedback-*.md` | 13 dated mistakes, 33 feedback memories | @@ -93,7 +93,7 @@ add fields that cannot work. | --- | --- | --- | | `title` | **keep** | 25/25 | | `what` | **keep** | 25/25 — the load-bearing section | -| `where` | **keep, widen** | 25/25. Widen to allow "the fix belongs in another repo" — two corrections were exactly that (*"our INSTALLER, installing files in a WRONG FOLDER"*, *"The real fix is in ~/screen-locker (a different repo)"*) | +| `where` | **keep, widen** | 25/25. Widen to allow "the fix belongs in another repo" — two corrections were exactly that (*"our INSTALLER, installing files in a WRONG FOLDER"*, *"The real fix is in ~/src/screen-locker (a different repo)"*) | | `tech` | **merge into `where`** | Claude reads the manifest anyway in 54% of sessions. Load-bearing only for `where — new app: `, which is where it should live | | `must` | **keep** | 25/25 | | `ask` | **remove** | Provably inert: 92% ask rate without it vs 100% with it. Global rules already own this | diff --git a/docs/explanations/2026-07-11-explanation-per-keystroke-backup.html b/docs/explanations/2026-07-11-explanation-per-keystroke-backup.html index 291e072..94d0550 100644 --- a/docs/explanations/2026-07-11-explanation-per-keystroke-backup.html +++ b/docs/explanations/2026-07-11-explanation-per-keystroke-backup.html @@ -120,7 +120,7 @@
  • watchNotes() — a live stream: it re-runs its SELECT * and re-emits every time the table changes, so the UI can stay in sync automatically.
  • -
  • A plain-text Markdown backup on disk (~/todo/BACKLOG.md), managed by LocalBackup. It's a human-readable third copy you (or an LLM) can read directly. LocalBackup debounces so a burst of typing produces a single file write.
  • +
  • A plain-text Markdown backup on disk (~/src/todo/BACKLOG.md), managed by LocalBackup. It's a human-readable third copy you (or an LLM) can read directly. LocalBackup debounces so a burst of typing produces a single file write.
  • The subtle part is how those two were wired together. To keep the backup current, the capture screen subscribed to watchNotes() and handed each emission to the backup:

    diff --git a/lib/data/desktop_backup_client.dart b/lib/data/desktop_backup_client.dart index 7563677..59dc764 100644 --- a/lib/data/desktop_backup_client.dart +++ b/lib/data/desktop_backup_client.dart @@ -5,7 +5,7 @@ import 'package:http/http.dart' as http; /// 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 +/// * `~/src/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 diff --git a/lib/data/repository_factory_io.dart b/lib/data/repository_factory_io.dart index b870980..3d4d0f4 100644 --- a/lib/data/repository_factory_io.dart +++ b/lib/data/repository_factory_io.dart @@ -19,7 +19,7 @@ import 'package:uuid/uuid.dart'; // run under test; [openRepositoryIn] holds all the logic and is covered. Future openRepository() async { final dir = await getApplicationSupportDirectory(); - return openRepositoryIn(dir.path); + return await openRepositoryIn(dir.path); } // coverage:ignore-end @@ -60,5 +60,8 @@ Future openRepositoryIn(String dirPath) async { nodeId = const Uuid().v4(); await prefs.setString(NoteRepository.kNodeId, nodeId); } - return NoteRepository.openWith(persistence: persistence, nodeId: nodeId); + return await NoteRepository.openWith( + persistence: persistence, + nodeId: nodeId, + ); } diff --git a/lib/data/repository_factory_web.dart b/lib/data/repository_factory_web.dart index 5461e32..b3569c5 100644 --- a/lib/data/repository_factory_web.dart +++ b/lib/data/repository_factory_web.dart @@ -23,5 +23,8 @@ Future openRepository() async { nodeId = const Uuid().v4(); await prefs.setString(NoteRepository.kNodeId, nodeId); } - return NoteRepository.openWith(persistence: persistence, nodeId: nodeId); + return await NoteRepository.openWith( + persistence: persistence, + nodeId: nodeId, + ); } diff --git a/lib/data/web_log_persistence.dart b/lib/data/web_log_persistence.dart index 9d6d4c7..7de5ac4 100644 --- a/lib/data/web_log_persistence.dart +++ b/lib/data/web_log_persistence.dart @@ -69,7 +69,7 @@ class WebLogPersistence implements LogPersistence { // Nothing local yet (fresh profile, or the browser evicted the database). // Fall back to the wrapper's on-disk copy so a cleared profile recovers // instead of silently starting from an empty backlog. - return backup?.readLog(); + return await backup?.readLog(); } @override diff --git a/lib/desktop/port_guard.dart b/lib/desktop/port_guard.dart index 16e2a4c..cee350f 100644 --- a/lib/desktop/port_guard.dart +++ b/lib/desktop/port_guard.dart @@ -214,7 +214,7 @@ class PortGuard { probe.signal(pid, ProcessSignal.sigterm); if (await _awaitFreePort(port, termWait, pollInterval)) return true; probe.signal(pid, ProcessSignal.sigkill); - return _awaitFreePort(port, killWait, pollInterval); + return await _awaitFreePort(port, killWait, pollInterval); } Future _awaitFreePort( diff --git a/lib/desktop/wrapper_server.dart b/lib/desktop/wrapper_server.dart index fcaaf33..8b8921b 100644 --- a/lib/desktop/wrapper_server.dart +++ b/lib/desktop/wrapper_server.dart @@ -24,7 +24,7 @@ const kSyncCredentialsPath = '/sync-credentials'; /// of the desktop app: it serves the build and owns the two files a browser /// cannot write. /// -/// * `~/todo/BACKLOG.md` — the canonical backlog path the user's tooling and +/// * `~/src/todo/BACKLOG.md` — the canonical backlog path the user's tooling and /// the `todo` MCP server read. /// * a copy of the note log — so a wiped Chrome profile is not a total-loss /// event for notes that have not yet synced to GitHub. @@ -112,7 +112,7 @@ class WrapperServer { } /// Stops serving and releases the port. - Future stop() async => _server?.close(force: true); + Future stop() async => await _server?.close(force: true); Future _serve(HttpServer server) async { await for (final request in server) { @@ -128,18 +128,18 @@ class WrapperServer { Future _handle(HttpRequest request) async { final path = request.uri.path; if (path == '/backup/backlog') { - return _file(request, backlogPath); + return await _file(request, backlogPath); } if (path == '/backup/log') { - return _file(request, logPath); + return await _file(request, logPath); } if (path == kSyncAccountPath) { - return _syncAccount(request); + return await _syncAccount(request); } if (path == kSyncCredentialsPath) { - return _syncCredentials(request); + return await _syncCredentials(request); } - return _static(request, path); + return await _static(request, path); } /// GET returns the file's contents (404 when absent); POST overwrites it. diff --git a/lib/sync/backlog_export.dart b/lib/sync/backlog_export.dart index 064f186..52a76d7 100644 --- a/lib/sync/backlog_export.dart +++ b/lib/sync/backlog_export.dart @@ -1,7 +1,7 @@ /// Platform entry point for the explicit "Export notes" action. /// /// Conditional export because `dart:io` is unusable in a browser: mobile shares -/// a temp file, io desktop writes `~/todo/BACKLOG.md` directly, and the web +/// a temp file, io desktop writes `~/src/todo/BACKLOG.md` directly, and the web /// desktop app asks the local wrapper to write that same path. library; diff --git a/lib/sync/backlog_export_io.dart b/lib/sync/backlog_export_io.dart index a42710c..c2e3b88 100644 --- a/lib/sync/backlog_export_io.dart +++ b/lib/sync/backlog_export_io.dart @@ -5,9 +5,9 @@ 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 +/// Overridable **so the test suite never writes to the real `~/src/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. +/// a fake note over the user's canonical `~/src/todo/BACKLOG.md` every time. String Function() resolveExportHome = () => Platform.environment['HOME'] ?? Directory.current.path; @@ -15,7 +15,7 @@ String Function() resolveExportHome = () => /// 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`. +/// writes the canonical `~/src/todo/BACKLOG.md`. Future exportBacklog(String markdown, int noteCount) async { // coverage:ignore-start // Mobile-only share path: Platform.isAndroid/isIOS are always false on the diff --git a/lib/sync/backlog_export_web.dart b/lib/sync/backlog_export_web.dart index 2717eab..cba0ef2 100644 --- a/lib/sync/backlog_export_web.dart +++ b/lib/sync/backlog_export_web.dart @@ -2,10 +2,10 @@ import 'package:todo/data/desktop_backup_client.dart'; import 'package:todo/sync/desktop_wrapper.dart'; /// Writes the Markdown export through the desktop wrapper, so an explicit -/// Export still lands at the canonical `~/todo/BACKLOG.md` path the user's +/// Export still lands at the canonical `~/src/todo/BACKLOG.md` path the user's /// tooling reads — the browser itself cannot write there. Future exportBacklog(String markdown, int noteCount) async { final client = DesktopBackupClient(baseUrl: desktopWrapperOrigin); await client.writeBacklog(markdown); - return 'Exported $noteCount notes to ~/todo/BACKLOG.md'; + return 'Exported $noteCount notes to ~/src/todo/BACKLOG.md'; } diff --git a/lib/sync/firebase_account_store.dart b/lib/sync/firebase_account_store.dart index adb39d6..b63c66f 100644 --- a/lib/sync/firebase_account_store.dart +++ b/lib/sync/firebase_account_store.dart @@ -41,7 +41,7 @@ SecureCredentialStore credentialStore() => SecureCredentialStore( /// `~/.config/todo/firebase_auth.json` via `seed_session.py --app todo`) -- /// the only thing that actually authenticates, since the shared account's /// password grant is retired fleet-wide (see `link_google.py`/ -/// `seed_session.py` in `~/utils/crdt-sync`) -- then the legacy +/// `seed_session.py` in `~/src/utils/crdt-sync`) -- then the legacy /// email/password route (`/sync-account`, `~/.config/crdt-sync/`) as a /// fallback for a machine that has not been re-seeded yet. Whichever /// succeeds is written to the store on first success, so the route is diff --git a/lib/sync/firebase_client.dart b/lib/sync/firebase_client.dart index 55acd3c..74a564d 100644 --- a/lib/sync/firebase_client.dart +++ b/lib/sync/firebase_client.dart @@ -27,9 +27,9 @@ Future openFirebase() async { // marker beside it. Treating the marker as the source of truth is what // made a phone with a live session sync over GitHub and 401 forever -- // the credential was in the keystore the whole time, unused. - return _clientFromStoredSession(); + return await _clientFromStoredSession(); } - return firebaseClientFor( + return await firebaseClientFor( config: kProject.configFor(account.email), store: credentialStore(), // A Google-provisioned account stores an empty password. Passing '' would diff --git a/lib/sync/google_sign_in_backend.dart b/lib/sync/google_sign_in_backend.dart index 543de1c..f1f9e65 100644 --- a/lib/sync/google_sign_in_backend.dart +++ b/lib/sync/google_sign_in_backend.dart @@ -75,7 +75,7 @@ Future googleIdToken({ Future Function()? signInFn, String serverClientId = kServerClientId, }) async { - if (signInFn != null) return signInFn(); + if (signInFn != null) return await signInFn(); if (serverClientId.isEmpty) { // A build without --dart-define=GOOGLE_SERVER_CLIENT_ID would otherwise // fail inside the plugin with a far less obvious message. diff --git a/lib/sync/local_backup_factory_io.dart b/lib/sync/local_backup_factory_io.dart index 2e91cf1..09e5c8a 100644 --- a/lib/sync/local_backup_factory_io.dart +++ b/lib/sync/local_backup_factory_io.dart @@ -29,8 +29,9 @@ LocalBackup createLocalBackup(NoteRepository repository) { fetch: repository.listNotes, reader: () async { final file = await backupFile(); - return file.existsSync() ? file.readAsString() : null; + return file.existsSync() ? await file.readAsString() : null; }, - writer: (markdown) async => (await backupFile()).writeAsString(markdown), + writer: (markdown) async => + await (await backupFile()).writeAsString(markdown), ); } diff --git a/lib/sync/local_backup_factory_web.dart b/lib/sync/local_backup_factory_web.dart index 11824a9..bc2f282 100644 --- a/lib/sync/local_backup_factory_web.dart +++ b/lib/sync/local_backup_factory_web.dart @@ -6,7 +6,7 @@ import 'package:todo/sync/local_backup.dart'; /// Builds the [LocalBackup] for the browser-hosted desktop app. /// /// A browser cannot write to the filesystem, so the wrapper that serves the -/// build owns the file. This keeps `~/todo/BACKLOG.md` current — the user's +/// build owns the file. This keeps `~/src/todo/BACKLOG.md` current — the user's /// tooling and the `todo` MCP server read it, and without this the move to a /// web build would let it silently go stale. LocalBackup createLocalBackup(NoteRepository repository) { diff --git a/lib/sync/sync_service.dart b/lib/sync/sync_service.dart index dacb4ec..8b8b885 100644 --- a/lib/sync/sync_service.dart +++ b/lib/sync/sync_service.dart @@ -182,7 +182,7 @@ class SyncService { /// depends on the optimisation being available. Future> _revisions(RemoteStore remote) async { if (remote is! BulkMapReader) return const {}; - return (remote as BulkMapReader).getStringMap(revsDir); + return await (remote as BulkMapReader).getStringMap(revsDir); } /// Parses a remote note log, returning `null` for a corrupt or wrong-shape diff --git a/lib/sync/sync_state_factory_web.dart b/lib/sync/sync_state_factory_web.dart index ba072e9..72919a2 100644 --- a/lib/sync/sync_state_factory_web.dart +++ b/lib/sync/sync_state_factory_web.dart @@ -23,5 +23,5 @@ class _PrefsPersistence implements LogPersistence { @override Future write(String text) async => - _prefs.setString(kSyncStateKey, text); + await _prefs.setString(kSyncStateKey, text); } diff --git a/lib/ui/capture_draft.dart b/lib/ui/capture_draft.dart index 0d9a848..29e16a4 100644 --- a/lib/ui/capture_draft.dart +++ b/lib/ui/capture_draft.dart @@ -101,7 +101,7 @@ class CaptureDraft { } // coverage:ignore-start -// Platform file IO for the local backup: BACKLOG.md under ~/todo on desktop +// Platform file IO for the local backup: BACKLOG.md under ~/src/todo on desktop // (the path the user's workflow already reads), or the app documents dir on // mobile (which Android Auto Backup includes). Exercised by running the app; // tests inject an in-memory LocalBackup instead. diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index c4c7923..69fd2a9 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -139,7 +139,7 @@ class _SettingsScreenState extends State { } /// Exports every note to a single Markdown file. On mobile this opens the - /// system share sheet; on desktop it writes the canonical `~/todo/ + /// system share sheet; on desktop it writes the canonical `~/src/todo/ /// BACKLOG.md` so a future tool/agent has a stable path to read. Future _export() async { final notes = await widget.repository.listNotes(); diff --git a/mcp/README.md b/mcp/README.md index e2404ad..471f018 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -36,7 +36,7 @@ This creates `~/.venvs/todo-mcp`, installs this package (which pulls in the `mcp` SDK), and verifies the imports. The server is registered via the repo-root `.mcp.json`; restart Claude Code in this repo and approve the project MCP server prompt. Override the backlog location with `TODO_BACKLOG_PATH` -(defaults to `~/todo/BACKLOG.md`). +(defaults to `~/src/todo/BACKLOG.md`). ## Development diff --git a/mcp/pyproject.toml b/mcp/pyproject.toml index 00129af..e64b53f 100644 --- a/mcp/pyproject.toml +++ b/mcp/pyproject.toml @@ -7,7 +7,7 @@ dependencies = [ # The MCP SDK is the only runtime dependency: FastMCP powers the stdio # server. It lives in a dedicated venv (see scripts/setup_mcp.sh / # ../.mcp.json), kept entirely separate from the Dart/Flutter app. - "mcp==2.1.1", + "mcp==2.2.0", ] # Explicit package discovery so ``pip install -e .`` picks up the package and diff --git a/mcp/todo_mcp/_mcp.py b/mcp/todo_mcp/_mcp.py index 97f9d02..4a9928c 100644 --- a/mcp/todo_mcp/_mcp.py +++ b/mcp/todo_mcp/_mcp.py @@ -54,12 +54,12 @@ def _backlog_path() -> Path: """Resolve the ``BACKLOG.md`` path (env override, else the default). Returns: - ``$TODO_BACKLOG_PATH`` when set, otherwise ``~/todo/BACKLOG.md``. + ``$TODO_BACKLOG_PATH`` when set, otherwise ``~/src/todo/BACKLOG.md``. """ override = os.environ.get("TODO_BACKLOG_PATH") if override: return Path(override) - return Path.home() / "todo" / "BACKLOG.md" + return Path.home() / "src/todo" / "BACKLOG.md" def _missing_message() -> str: diff --git a/mcp/todo_mcp/tests/test_mcp.py b/mcp/todo_mcp/tests/test_mcp.py index 8a900e7..bf16666 100644 --- a/mcp/todo_mcp/tests/test_mcp.py +++ b/mcp/todo_mcp/tests/test_mcp.py @@ -48,7 +48,7 @@ def missing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: class TestBacklogPath: def test_default_path_when_env_unset(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("TODO_BACKLOG_PATH", raising=False) - assert _mcp._backlog_path() == Path.home() / "todo" / "BACKLOG.md" + assert _mcp._backlog_path() == Path.home() / "src/todo" / "BACKLOG.md" class TestListBacklog: diff --git a/packaging/aur/README.md b/packaging/aur/README.md index cd33744..9338708 100644 --- a/packaging/aur/README.md +++ b/packaging/aur/README.md @@ -1,7 +1,7 @@ # AUR packaging `PKGBUILD` here is the source of truth for the `todo-flutter` AUR package. The -AUR checkout (`~/aur/todo-flutter`) is a copy of this file plus a generated +AUR checkout (`~/sdk/aur/todo-flutter`) is a copy of this file plus a generated `.SRCINFO`. ## Why the checksum can go stale @@ -20,8 +20,8 @@ AUR checkout to fill in the real hash, and copy the result back here. git tag v1.2.3 && git push origin v1.2.3 # 3. sync + checksum + build + verify -cp packaging/aur/PKGBUILD ~/aur/todo-flutter/PKGBUILD -cd ~/aur/todo-flutter +cp packaging/aur/PKGBUILD ~/sdk/aur/todo-flutter/PKGBUILD +cd ~/sdk/aur/todo-flutter # bump pkgver to match the tag, then: updpkgsums makepkg -Cf diff --git a/prompts/TODO-dopamine-ux-todo.md b/prompts/TODO-dopamine-ux-todo.md index 44ec1a0..c839e90 100644 --- a/prompts/TODO-dopamine-ux-todo.md +++ b/prompts/TODO-dopamine-ux-todo.md @@ -2,8 +2,8 @@ > "do dopamine-ux-todo". It is self-contained -- it needs no context from > any other session. > -> Canonical copy: `~/utils/dopamine-ux/06-todo.md` (source of truth; this is a -> distributed copy). Running order and cross-prompt rules: `~/utils/dopamine-ux/00-INDEX.md`. +> Canonical copy: `~/src/utils/dopamine-ux/06-todo.md` (source of truth; this is a +> distributed copy). Running order and cross-prompt rules: `~/src/utils/dopamine-ux/00-INDEX.md`. > Generated 2026-08-16 from a survey of this repo -- line numbers are accurate > as of that date; **anchor on the symbol names, not the line numbers.** @@ -19,10 +19,10 @@ Do **both**, in the same session that completes the work: Do not leave a finished prompt lying in the repo -- a stale prompt is indistinguishable from a pending one, and the next session will re-run it. -2. **Log completion in the canonical index**, in `~/utils` (a *different* repo, +2. **Log completion in the canonical index**, in `~/src/utils` (a *different* repo, so it needs its own commit): ```bash - cd ~/utils + cd ~/src/utils # append to the "Completion log" table in dopamine-ux/00-INDEX.md: # | 06-todo.md | DONE | | | git add dopamine-ux/00-INDEX.md @@ -67,7 +67,7 @@ things; one that celebrates *capturing* keeps the backlog honest. ## where -Repo: `~/todo`. +Repo: `~/src/todo`. **First step — bump the dependency.** `pubspec.yaml` pins `design_system` at git ref `design_system-v0.1.0`, which predates the motion tokens. Bump it to @@ -101,7 +101,7 @@ pattern the other prompts copy**: Theme: no local file. `lib/main.dart` ~:4 imports `design_system`; ~:60-61 wires `theme: buildLightTheme(), darkTheme: buildDarkTheme()`. Source of truth is -`~/utils/design_system/lib/src/theme.dart`. +`~/src/utils/design_system/lib/src/theme.dart`. ## must @@ -162,8 +162,8 @@ Theme: no local file. `lib/main.dart` ~:4 imports `design_system`; ~:60-61 wires 3. Wizard step transitions and page navigation use shared motion tokens; no inline `Duration` remains for animation purposes (the 250ms search debounce and 5s auto-sync debounce stay as they are — they are not motion). -4. `cd ~/todo && flutter analyze` is clean. -5. `cd ~/todo && flutter test` passes. +4. `cd ~/src/todo && flutter analyze` is clean. +5. `cd ~/src/todo && flutter test` passes. 6. With OS "remove animations" enabled, the app works and durations are zero. 7. Step 2: the sound toggle flips, persists across restart, and silences the cue. @@ -173,7 +173,7 @@ Theme: no local file. `lib/main.dart` ~:4 imports `design_system`; ~:60-61 wires ``` adb devices # confirm 23181JEGR08034 -cd ~/todo +cd ~/src/todo flutter build apk --release adb install -r build/app/outputs/flutter-apk/app-release.apk ``` @@ -194,11 +194,11 @@ can go stale independently — but this prompt's done-condition is the phone. toggle pattern, in full, before writing the sound switch. - `lib/ui/capture_screen_sync.dart` ~:76-78 — how `showToast`/`showError` are used today; the shared helpers live in - `~/utils/design_system/lib/src/feedback.dart`. -- `~/utils/design_system/lib/src/feedback.dart` — `showToast`/`showError`, `_show` + `~/src/utils/design_system/lib/src/feedback.dart`. +- `~/src/utils/design_system/lib/src/feedback.dart` — `showToast`/`showError`, `_show` ~:50. `confirm.dart` beside it **already imports `flutter/services.dart`**, so if prompt 01 added a shared haptic helper it lives near here. -- `~/utils/unified-design-system/motion.md` — the vocabulary from prompt 01. +- `~/src/utils/unified-design-system/motion.md` — the vocabulary from prompt 01. **Prompt 01 must have run first, and its tag must be cut.** - `pubspec.yaml` — read the comment explaining why there is no local theme. diff --git a/pubspec.lock b/pubspec.lock index 007f4ca..3579217 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -268,10 +268,10 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "15e8c8fe269fdf7d469b23008ab3df521c8b826ed345820532364c31bdebace6" + sha256: "4540927d8b37e6cc47aaa2d17b6035a2d49049e2797cb19d2bdd299bff602c67" url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.1.0" flutter_secure_storage_darwin: dependency: transitive description: @@ -292,10 +292,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" + sha256: "4bc033841169d07f690d46d89dbc3f5305b6562820822445384c82e0866e2719" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" flutter_secure_storage_web: dependency: transitive description: @@ -415,10 +415,10 @@ packages: dependency: "direct main" description: name: idb_shim - sha256: e36a748165663a165e1f292e9d85af6e3aa087f6e2291d16f86b2035103b2950 + sha256: "6a478bf0928fa954512f2a1e7e56f09e2fb2e3e7d183eb051a59b5d6b39ec16f" url: "https://pub.dev" source: hosted - version: "2.9.8" + version: "2.9.9" image: dependency: transitive description: @@ -764,10 +764,10 @@ packages: dependency: "direct main" description: name: sqflite_common_ffi - sha256: d5564f1308cafbf064be498f2beb1e993e742985a7cca9e2e228d6cbccd84a05 + sha256: "4ec920a01788641fa1c9922e757c108427e7f8634cce3ce8a1073d573060f4a3" url: "https://pub.dev" source: hosted - version: "2.4.2+1" + version: "2.4.3" sqflite_common_ffi_web: dependency: transitive description: @@ -957,10 +957,10 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d" + sha256: "0bd5eb8d6be76aa0d71e70cbdae6cac8f784f761170568e3c2214a6948c7f6d3" url: "https://pub.dev" source: hosted - version: "10.3.0" + version: "11.0.0" vm_service: dependency: transitive description: @@ -1010,5 +1010,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.12.2 <4.0.0" + dart: ">=3.13.0 <4.0.0" flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2cafb1b..aed4e35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.32+1 +version: 1.0.33+1 environment: sdk: ^3.12.2 @@ -53,7 +53,7 @@ dependencies: # Stores the GitHub sync token in the OS keystore (Android Keystore / # libsecret) instead of plaintext SharedPreferences. - flutter_secure_storage: 11.0.0 + flutter_secure_storage: 11.1.0 github_device_auth: git: @@ -65,12 +65,12 @@ dependencies: # this plugin does not exist. See lib/sync/google_sign_in_backend.dart. google_sign_in: 7.2.0 http: 1.6.0 - idb_shim: 2.9.8 + idb_shim: 2.9.9 path: 1.9.1 path_provider: 2.1.6 share_plus: 13.3.0 shared_preferences: 2.5.5 - sqflite_common_ffi: 2.4.2+1 + sqflite_common_ffi: 2.4.3 # Retained only to read the legacy on-device DB during the one-time # migration in NoteRepository.open; remove once all devices are migrated. sqlite_crdt: 3.0.4 @@ -105,7 +105,7 @@ dev_dependencies: # Stricter than flutter_lints -- standardized across this ecosystem's # Flutter apps (see project memory: all Flutter apps use # very_good_analysis, not flutter_lints). - very_good_analysis: ^10.3.0 + very_good_analysis: ^11.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -150,7 +150,7 @@ flutter: # see https://flutter.dev/to/font-from-package # Launcher icon generation. Source art is produced by the shared generator: -# PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --app +# PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --app # then run: dart run flutter_launcher_icons flutter_launcher_icons: android: true diff --git a/scripts/check_dependency_freshness.sh b/scripts/check_dependency_freshness.sh index 5cb0d78..7715d3c 100755 --- a/scripts/check_dependency_freshness.sh +++ b/scripts/check_dependency_freshness.sh @@ -3,7 +3,7 @@ # ============================================================================ # Fail if any dependency in the commit is behind its ecosystem's newest stable. # -# Thin delegate to the shared gate in ~/utils, which owns the registry +# Thin delegate to the shared gate in ~/src/utils, which owns the registry # adapters, the pre-release rules and the allowlist semantics. Copying that # logic here is what lets one repo's idea of "current" drift from every other # repo's -- so this script only locates the shared checker and forwards its @@ -18,12 +18,12 @@ set -euo pipefail -readonly SHARED_GATE="${UTILS_ROOT:-$HOME/utils}/scripts/check_dependency_freshness.sh" +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_dependency_freshness.sh" main() { if [[ ! -x "$SHARED_GATE" ]]; then echo "Error: shared dependency-freshness gate not found at $SHARED_GATE" >&2 - echo " Clone github.com/kuhyx/utils to ~/utils, or set" >&2 + echo " Clone github.com/kuhyx/utils to ~/src/utils, or set" >&2 echo " UTILS_ROOT to where it lives." >&2 exit 1 fi diff --git a/scripts/check_file_length.sh b/scripts/check_file_length.sh index 45d8370..943f67a 100755 --- a/scripts/check_file_length.sh +++ b/scripts/check_file_length.sh @@ -3,7 +3,7 @@ # ============================================================================ # Fail if any file in the commit exceeds the shared 250-line cap. # -# Thin delegate to the shared gate in ~/utils, which owns the cap and the +# Thin delegate to the shared gate in ~/src/utils, which owns the cap and the # exemption list (generated / vendored / data files). Copying that logic here # is what lets one repo's idea of "too long" drift from every other repo's -- # so this script only locates the shared checker and forwards its arguments. @@ -15,12 +15,12 @@ set -euo pipefail -readonly SHARED_GATE="${UTILS_ROOT:-$HOME/utils}/scripts/check_file_length.sh" +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_file_length.sh" main() { if [[ ! -x "$SHARED_GATE" ]]; then echo "Error: shared file-length gate not found at $SHARED_GATE" >&2 - echo " Clone github.com/kuhyx/utils to ~/utils, or set" >&2 + echo " Clone github.com/kuhyx/utils to ~/src/utils, or set" >&2 echo " UTILS_ROOT to where it lives." >&2 exit 1 fi diff --git a/scripts/check_md_naming.sh b/scripts/check_md_naming.sh index 50deaa0..7847c56 100755 --- a/scripts/check_md_naming.sh +++ b/scripts/check_md_naming.sh @@ -3,7 +3,7 @@ # ============================================================================ # Fail if any markdown file breaks the shared naming convention. # -# Thin delegate to the shared gate in ~/utils, which owns the four namespaces +# Thin delegate to the shared gate in ~/src/utils, which owns the four namespaces # (README / CLAUDE* / DOCS* / TODO*), the "REMOVE ME AFTER FINISH" marker rule # and the exemption list. Copying that logic here is what lets one repo's idea # of a valid name drift from every other repo's -- so this script only locates @@ -16,12 +16,12 @@ set -euo pipefail -readonly SHARED_GATE="${UTILS_ROOT:-$HOME/utils}/scripts/check_md_naming.sh" +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_md_naming.sh" main() { if [[ ! -x "$SHARED_GATE" ]]; then echo "Error: shared markdown-naming gate not found at $SHARED_GATE" >&2 - echo " Clone github.com/kuhyx/utils to ~/utils, or set" >&2 + echo " Clone github.com/kuhyx/utils to ~/src/utils, or set" >&2 echo " UTILS_ROOT to where it lives." >&2 exit 1 fi diff --git a/scripts/check_no_crdt_sync_override.sh b/scripts/check_no_crdt_sync_override.sh index 967e6c8..e945a0d 100755 --- a/scripts/check_no_crdt_sync_override.sh +++ b/scripts/check_no_crdt_sync_override.sh @@ -5,7 +5,7 @@ # # The apps depend on crdt_sync as a *git dependency pinned to a tag*, so what # CI builds is whatever that tag points at. A `dependency_overrides:` entry -# pointing at ~/utils/crdt_sync_dart is invaluable while developing an unreleased +# pointing at ~/src/utils/crdt_sync_dart is invaluable while developing an unreleased # library change -- and catastrophic if it survives the commit: local tests go # green against code CI has never seen, and the failure is silent in both # directions. diff --git a/test/capture_screen_harness.dart b/test/capture_screen_harness.dart index 4b3bf4f..797bfdc 100644 --- a/test/capture_screen_harness.dart +++ b/test/capture_screen_harness.dart @@ -58,7 +58,7 @@ Future pumpCapture( final repo = repository ?? FakeNoteRepository(seed); addTearDown(repo.close); // Default to an in-memory, no-op backup so tests never touch real disk - // (the production backup writes ~/todo/BACKLOG.md on the Linux test host). + // (the production backup writes ~/src/todo/BACKLOG.md on the Linux test host). final backup = localBackup ?? LocalBackup( diff --git a/test/firebase_backend_test.dart b/test/firebase_backend_test.dart index 9904cd8..d924144 100644 --- a/test/firebase_backend_test.dart +++ b/test/firebase_backend_test.dart @@ -121,7 +121,7 @@ void main() { group('loadAccount', () { // The account's password grant is retired fleet-wide (see - // link_google.py/seed_session.py in ~/utils/crdt-sync), so the wrapper's + // link_google.py/seed_session.py in ~/src/utils/crdt-sync), so the wrapper's // seeded-credentials route is the only fallback that can actually // authenticate a fresh desktop install. These exercise loadAccount()'s // adoption of that route directly, since Uri.base in a test is diff --git a/test/settings_screen_backup_test.dart b/test/settings_screen_backup_test.dart index cdda987..ec75d61 100644 --- a/test/settings_screen_backup_test.dart +++ b/test/settings_screen_backup_test.dart @@ -72,7 +72,7 @@ void main() { tester, ) async { // Redirect HOME: this test does real file I/O, and without the - // override it overwrites the user's canonical ~/todo/BACKLOG.md with + // override it overwrites the user's canonical ~/src/todo/BACKLOG.md with // the fake note below on every test run. final home = Directory.systemTemp.createTempSync('todo_export_home'); resolveExportHome = () => home.path;