Re-point paths for the ~ reorganisation (home-tidy)
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
This commit is contained in:
Krzysztof Rudnicki 2026-09-11 19:49:03 +02:00
parent 951b00e263
commit a0d37c4df8
40 changed files with 97 additions and 90 deletions

2
.fvmrc
View File

@ -1,3 +1,3 @@
{ {
"flutter": "3.47.2" "flutter": "3.47.3"
} }

View File

@ -47,7 +47,7 @@ jobs:
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: 3.47.2 flutter-version: 3.47.3
channel: stable channel: stable
cache: true cache: true

View File

@ -44,7 +44,7 @@ repos:
# unrelated commit never breaks; exits 1 rather than warning. No # unrelated commit never breaks; exits 1 rather than warning. No
# baseline and no allowlist -- those are suppressions. The exemption # baseline and no allowlist -- those are suppressions. The exemption
# list (generated/vendored/data files) lives with the checker in # 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. # disagree.
- id: file-length - id: file-length
name: file length <= 250 lines name: file length <= 250 lines
@ -53,7 +53,7 @@ repos:
# Four markdown namespaces (README / CLAUDE* / DOCS* / TODO*), plus the # Four markdown namespaces (README / CLAUDE* / DOCS* / TODO*), plus the
# rule that a TODO carries "REMOVE ME AFTER FINISH" and nothing else may. # 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. # one repo's idea of a valid name cannot drift from every other repo's.
- id: md-naming - id: md-naming
name: markdown naming convention name: markdown naming convention

View File

@ -136,7 +136,7 @@ migrate → Settings → Import).
- Keep the app buttery-smooth and low on CPU/RAM — it's a quick-capture tool. - 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 - **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 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 the commit rather than being a note anyone can ignore. Install the hook on
a fresh clone with `scripts/install_hooks.sh`. a fresh clone with `scripts/install_hooks.sh`.
- **Only non-mutating checks go in `.pre-commit-config.yaml`.** pre-commit - **Only non-mutating checks go in `.pre-commit-config.yaml`.** pre-commit

View File

@ -10,7 +10,7 @@
# #
# Icon PNGs are pre-rendered and committed under desktop/icons/, so this script # Icon PNGs are pre-rendered and committed under desktop/icons/, so this script
# needs no image tooling at run time. Regenerate them with: # 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 # generate --app todo --linux-out desktop/icons
# ============================================================================ # ============================================================================

View File

@ -2,7 +2,7 @@
## Context ## 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, has 12 sections: title, what, where, tech, must, ask, nice, never, done, depends,
estimate, refs. It was designed top-down from the `<work_backlog>` format in estimate, refs. It was designed top-down from the `<work_backlog>` format in
`~/.claude/CLAUDE.md`, never validated against what actually gets written or what `~/.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 | | Source | Size |
| --- | --- | | --- | --- |
| `~/.claude/projects/**/*.jsonl` | 514 sessions, 495 with real user prompts, **1503 prompts** | | `~/.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 | | 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 | | `~/.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 | | `title` | **keep** | 25/25 |
| `what` | **keep** | 25/25 — the load-bearing section | | `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: <name>`, which is where it should live | | `tech` | **merge into `where`** | Claude reads the manifest anyway in 54% of sessions. Load-bearing only for `where — new app: <name>`, which is where it should live |
| `must` | **keep** | 25/25 | | `must` | **keep** | 25/25 |
| `ask` | **remove** | Provably inert: 92% ask rate without it vs 100% with it. Global rules already own this | | `ask` | **remove** | Provably inert: 92% ask rate without it vs 100% with it. Global rules already own this |

View File

@ -120,7 +120,7 @@
<li><code>watchNotes()</code> — a <em>live stream</em>: it re-runs its <code>SELECT&nbsp;*</code> and re-emits <strong>every time the table changes</strong>, so the UI can stay in sync automatically.</li> <li><code>watchNotes()</code> — a <em>live stream</em>: it re-runs its <code>SELECT&nbsp;*</code> and re-emits <strong>every time the table changes</strong>, so the UI can stay in sync automatically.</li>
</ul> </ul>
</li> </li>
<li>A plain-text <strong>Markdown backup</strong> on disk (<code>~/todo/BACKLOG.md</code>), managed by <code>LocalBackup</code>. It's a human-readable third copy you (or an LLM) can read directly. <code>LocalBackup</code> debounces so a burst of typing produces a single file write.</li> <li>A plain-text <strong>Markdown backup</strong> on disk (<code>~/src/todo/BACKLOG.md</code>), managed by <code>LocalBackup</code>. It's a human-readable third copy you (or an LLM) can read directly. <code>LocalBackup</code> debounces so a burst of typing produces a single file write.</li>
</ul> </ul>
<p>The subtle part is how those two were wired together. To keep the backup current, the capture screen subscribed to <code>watchNotes()</code> and handed each emission to the backup:</p> <p>The subtle part is how those two were wired together. To keep the backup current, the capture screen subscribed to <code>watchNotes()</code> and handed each emission to the backup:</p>

View File

@ -5,7 +5,7 @@ import 'package:http/http.dart' as http;
/// The wrapper exists because a browser cannot write to the filesystem, and two /// The wrapper exists because a browser cannot write to the filesystem, and two
/// things depend on it: /// 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 /// server. Without this it would silently go stale after the move to a web
/// build. /// build.
/// * an on-disk copy of the note log, so a wiped Chrome profile is not a /// * an on-disk copy of the note log, so a wiped Chrome profile is not a

View File

@ -19,7 +19,7 @@ import 'package:uuid/uuid.dart';
// run under test; [openRepositoryIn] holds all the logic and is covered. // run under test; [openRepositoryIn] holds all the logic and is covered.
Future<NoteRepository> openRepository() async { Future<NoteRepository> openRepository() async {
final dir = await getApplicationSupportDirectory(); final dir = await getApplicationSupportDirectory();
return openRepositoryIn(dir.path); return await openRepositoryIn(dir.path);
} }
// coverage:ignore-end // coverage:ignore-end
@ -60,5 +60,8 @@ Future<NoteRepository> openRepositoryIn(String dirPath) async {
nodeId = const Uuid().v4(); nodeId = const Uuid().v4();
await prefs.setString(NoteRepository.kNodeId, nodeId); await prefs.setString(NoteRepository.kNodeId, nodeId);
} }
return NoteRepository.openWith(persistence: persistence, nodeId: nodeId); return await NoteRepository.openWith(
persistence: persistence,
nodeId: nodeId,
);
} }

View File

@ -23,5 +23,8 @@ Future<NoteRepository> openRepository() async {
nodeId = const Uuid().v4(); nodeId = const Uuid().v4();
await prefs.setString(NoteRepository.kNodeId, nodeId); await prefs.setString(NoteRepository.kNodeId, nodeId);
} }
return NoteRepository.openWith(persistence: persistence, nodeId: nodeId); return await NoteRepository.openWith(
persistence: persistence,
nodeId: nodeId,
);
} }

View File

@ -69,7 +69,7 @@ class WebLogPersistence implements LogPersistence {
// Nothing local yet (fresh profile, or the browser evicted the database). // 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 // Fall back to the wrapper's on-disk copy so a cleared profile recovers
// instead of silently starting from an empty backlog. // instead of silently starting from an empty backlog.
return backup?.readLog(); return await backup?.readLog();
} }
@override @override

View File

@ -214,7 +214,7 @@ class PortGuard {
probe.signal(pid, ProcessSignal.sigterm); probe.signal(pid, ProcessSignal.sigterm);
if (await _awaitFreePort(port, termWait, pollInterval)) return true; if (await _awaitFreePort(port, termWait, pollInterval)) return true;
probe.signal(pid, ProcessSignal.sigkill); probe.signal(pid, ProcessSignal.sigkill);
return _awaitFreePort(port, killWait, pollInterval); return await _awaitFreePort(port, killWait, pollInterval);
} }
Future<bool> _awaitFreePort( Future<bool> _awaitFreePort(

View File

@ -24,7 +24,7 @@ const kSyncCredentialsPath = '/sync-credentials';
/// of the desktop app: it serves the build and owns the two files a browser /// of the desktop app: it serves the build and owns the two files a browser
/// cannot write. /// 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. /// the `todo` MCP server read.
/// * a copy of the note log — so a wiped Chrome profile is not a total-loss /// * 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. /// event for notes that have not yet synced to GitHub.
@ -112,7 +112,7 @@ class WrapperServer {
} }
/// Stops serving and releases the port. /// Stops serving and releases the port.
Future<void> stop() async => _server?.close(force: true); Future<void> stop() async => await _server?.close(force: true);
Future<void> _serve(HttpServer server) async { Future<void> _serve(HttpServer server) async {
await for (final request in server) { await for (final request in server) {
@ -128,18 +128,18 @@ class WrapperServer {
Future<void> _handle(HttpRequest request) async { Future<void> _handle(HttpRequest request) async {
final path = request.uri.path; final path = request.uri.path;
if (path == '/backup/backlog') { if (path == '/backup/backlog') {
return _file(request, backlogPath); return await _file(request, backlogPath);
} }
if (path == '/backup/log') { if (path == '/backup/log') {
return _file(request, logPath); return await _file(request, logPath);
} }
if (path == kSyncAccountPath) { if (path == kSyncAccountPath) {
return _syncAccount(request); return await _syncAccount(request);
} }
if (path == kSyncCredentialsPath) { 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. /// GET returns the file's contents (404 when absent); POST overwrites it.

View File

@ -1,7 +1,7 @@
/// Platform entry point for the explicit "Export notes" action. /// Platform entry point for the explicit "Export notes" action.
/// ///
/// Conditional export because `dart:io` is unusable in a browser: mobile shares /// 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. /// desktop app asks the local wrapper to write that same path.
library; library;

View File

@ -5,9 +5,9 @@ import 'package:share_plus/share_plus.dart';
/// Resolves the home directory the desktop export writes under. /// 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 /// 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 = () => String Function() resolveExportHome = () =>
Platform.environment['HOME'] ?? Directory.current.path; Platform.environment['HOME'] ?? Directory.current.path;
@ -15,7 +15,7 @@ String Function() resolveExportHome = () =>
/// human-readable description of where it went. /// human-readable description of where it went.
/// ///
/// On mobile this opens the system share sheet; on a `dart:io` desktop host it /// 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<String> exportBacklog(String markdown, int noteCount) async { Future<String> exportBacklog(String markdown, int noteCount) async {
// coverage:ignore-start // coverage:ignore-start
// Mobile-only share path: Platform.isAndroid/isIOS are always false on the // Mobile-only share path: Platform.isAndroid/isIOS are always false on the

View File

@ -2,10 +2,10 @@ import 'package:todo/data/desktop_backup_client.dart';
import 'package:todo/sync/desktop_wrapper.dart'; import 'package:todo/sync/desktop_wrapper.dart';
/// Writes the Markdown export through the desktop wrapper, so an explicit /// 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. /// tooling reads — the browser itself cannot write there.
Future<String> exportBacklog(String markdown, int noteCount) async { Future<String> exportBacklog(String markdown, int noteCount) async {
final client = DesktopBackupClient(baseUrl: desktopWrapperOrigin); final client = DesktopBackupClient(baseUrl: desktopWrapperOrigin);
await client.writeBacklog(markdown); await client.writeBacklog(markdown);
return 'Exported $noteCount notes to ~/todo/BACKLOG.md'; return 'Exported $noteCount notes to ~/src/todo/BACKLOG.md';
} }

View File

@ -41,7 +41,7 @@ SecureCredentialStore credentialStore() => SecureCredentialStore(
/// `~/.config/todo/firebase_auth.json` via `seed_session.py --app todo`) -- /// `~/.config/todo/firebase_auth.json` via `seed_session.py --app todo`) --
/// the only thing that actually authenticates, since the shared account's /// the only thing that actually authenticates, since the shared account's
/// password grant is retired fleet-wide (see `link_google.py`/ /// 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 /// email/password route (`/sync-account`, `~/.config/crdt-sync/`) as a
/// fallback for a machine that has not been re-seeded yet. Whichever /// 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 /// succeeds is written to the store on first success, so the route is

View File

@ -27,9 +27,9 @@ Future<FirebaseRestClient?> openFirebase() async {
// marker beside it. Treating the marker as the source of truth is what // 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 -- // made a phone with a live session sync over GitHub and 401 forever --
// the credential was in the keystore the whole time, unused. // 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), config: kProject.configFor(account.email),
store: credentialStore(), store: credentialStore(),
// A Google-provisioned account stores an empty password. Passing '' would // A Google-provisioned account stores an empty password. Passing '' would

View File

@ -75,7 +75,7 @@ Future<String?> googleIdToken({
Future<String?> Function()? signInFn, Future<String?> Function()? signInFn,
String serverClientId = kServerClientId, String serverClientId = kServerClientId,
}) async { }) async {
if (signInFn != null) return signInFn(); if (signInFn != null) return await signInFn();
if (serverClientId.isEmpty) { if (serverClientId.isEmpty) {
// A build without --dart-define=GOOGLE_SERVER_CLIENT_ID would otherwise // A build without --dart-define=GOOGLE_SERVER_CLIENT_ID would otherwise
// fail inside the plugin with a far less obvious message. // fail inside the plugin with a far less obvious message.

View File

@ -29,8 +29,9 @@ LocalBackup createLocalBackup(NoteRepository repository) {
fetch: repository.listNotes, fetch: repository.listNotes,
reader: () async { reader: () async {
final file = await backupFile(); 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),
); );
} }

View File

@ -6,7 +6,7 @@ import 'package:todo/sync/local_backup.dart';
/// Builds the [LocalBackup] for the browser-hosted desktop app. /// Builds the [LocalBackup] for the browser-hosted desktop app.
/// ///
/// A browser cannot write to the filesystem, so the wrapper that serves the /// 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 /// tooling and the `todo` MCP server read it, and without this the move to a
/// web build would let it silently go stale. /// web build would let it silently go stale.
LocalBackup createLocalBackup(NoteRepository repository) { LocalBackup createLocalBackup(NoteRepository repository) {

View File

@ -182,7 +182,7 @@ class SyncService {
/// depends on the optimisation being available. /// depends on the optimisation being available.
Future<Map<String, String>> _revisions(RemoteStore remote) async { Future<Map<String, String>> _revisions(RemoteStore remote) async {
if (remote is! BulkMapReader) return const {}; 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 /// Parses a remote note log, returning `null` for a corrupt or wrong-shape

View File

@ -23,5 +23,5 @@ class _PrefsPersistence implements LogPersistence {
@override @override
Future<void> write(String text) async => Future<void> write(String text) async =>
_prefs.setString(kSyncStateKey, text); await _prefs.setString(kSyncStateKey, text);
} }

View File

@ -101,7 +101,7 @@ class CaptureDraft {
} }
// coverage:ignore-start // 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 // (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; // mobile (which Android Auto Backup includes). Exercised by running the app;
// tests inject an in-memory LocalBackup instead. // tests inject an in-memory LocalBackup instead.

View File

@ -139,7 +139,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
/// Exports every note to a single Markdown file. On mobile this opens the /// 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. /// BACKLOG.md` so a future tool/agent has a stable path to read.
Future<void> _export() async { Future<void> _export() async {
final notes = await widget.repository.listNotes(); final notes = await widget.repository.listNotes();

View File

@ -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 `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 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` MCP server prompt. Override the backlog location with `TODO_BACKLOG_PATH`
(defaults to `~/todo/BACKLOG.md`). (defaults to `~/src/todo/BACKLOG.md`).
## Development ## Development

View File

@ -7,7 +7,7 @@ dependencies = [
# The MCP SDK is the only runtime dependency: FastMCP powers the stdio # The MCP SDK is the only runtime dependency: FastMCP powers the stdio
# server. It lives in a dedicated venv (see scripts/setup_mcp.sh / # server. It lives in a dedicated venv (see scripts/setup_mcp.sh /
# ../.mcp.json), kept entirely separate from the Dart/Flutter app. # ../.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 # Explicit package discovery so ``pip install -e .`` picks up the package and

View File

@ -54,12 +54,12 @@ def _backlog_path() -> Path:
"""Resolve the ``BACKLOG.md`` path (env override, else the default). """Resolve the ``BACKLOG.md`` path (env override, else the default).
Returns: 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") override = os.environ.get("TODO_BACKLOG_PATH")
if override: if override:
return Path(override) return Path(override)
return Path.home() / "todo" / "BACKLOG.md" return Path.home() / "src/todo" / "BACKLOG.md"
def _missing_message() -> str: def _missing_message() -> str:

View File

@ -48,7 +48,7 @@ def missing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
class TestBacklogPath: class TestBacklogPath:
def test_default_path_when_env_unset(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_default_path_when_env_unset(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("TODO_BACKLOG_PATH", raising=False) 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: class TestListBacklog:

View File

@ -1,7 +1,7 @@
# AUR packaging # AUR packaging
`PKGBUILD` here is the source of truth for the `todo-flutter` AUR package. The `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`. `.SRCINFO`.
## Why the checksum can go stale ## 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 git tag v1.2.3 && git push origin v1.2.3
# 3. sync + checksum + build + verify # 3. sync + checksum + build + verify
cp packaging/aur/PKGBUILD ~/aur/todo-flutter/PKGBUILD cp packaging/aur/PKGBUILD ~/sdk/aur/todo-flutter/PKGBUILD
cd ~/aur/todo-flutter cd ~/sdk/aur/todo-flutter
# bump pkgver to match the tag, then: # bump pkgver to match the tag, then:
updpkgsums updpkgsums
makepkg -Cf makepkg -Cf

View File

@ -2,8 +2,8 @@
> "do dopamine-ux-todo". It is self-contained -- it needs no context from > "do dopamine-ux-todo". It is self-contained -- it needs no context from
> any other session. > any other session.
> >
> Canonical copy: `~/utils/dopamine-ux/06-todo.md` (source of truth; this is a > Canonical copy: `~/src/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`. > 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 > 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.** > 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 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. 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): so it needs its own commit):
```bash ```bash
cd ~/utils cd ~/src/utils
# append to the "Completion log" table in dopamine-ux/00-INDEX.md: # append to the "Completion log" table in dopamine-ux/00-INDEX.md:
# | 06-todo.md | DONE <YYYY-MM-DD> | <impl commit sha> | <one-line note> | # | 06-todo.md | DONE <YYYY-MM-DD> | <impl commit sha> | <one-line note> |
git add dopamine-ux/00-INDEX.md git add dopamine-ux/00-INDEX.md
@ -67,7 +67,7 @@ things; one that celebrates *capturing* keeps the backlog honest.
## where ## where
Repo: `~/todo`. Repo: `~/src/todo`.
**First step — bump the dependency.** `pubspec.yaml` pins `design_system` at git **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 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: no local file. `lib/main.dart` ~:4 imports `design_system`; ~:60-61 wires
`theme: buildLightTheme(), darkTheme: buildDarkTheme()`. Source of truth is `theme: buildLightTheme(), darkTheme: buildDarkTheme()`. Source of truth is
`~/utils/design_system/lib/src/theme.dart`. `~/src/utils/design_system/lib/src/theme.dart`.
## must ## 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 3. Wizard step transitions and page navigation use shared motion tokens; no
inline `Duration` remains for animation purposes (the 250ms search debounce inline `Duration` remains for animation purposes (the 250ms search debounce
and 5s auto-sync debounce stay as they are — they are not motion). and 5s auto-sync debounce stay as they are — they are not motion).
4. `cd ~/todo && flutter analyze` is clean. 4. `cd ~/src/todo && flutter analyze` is clean.
5. `cd ~/todo && flutter test` passes. 5. `cd ~/src/todo && flutter test` passes.
6. With OS "remove animations" enabled, the app works and durations are zero. 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. 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 adb devices # confirm 23181JEGR08034
cd ~/todo cd ~/src/todo
flutter build apk --release flutter build apk --release
adb install -r build/app/outputs/flutter-apk/app-release.apk 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. toggle pattern, in full, before writing the sound switch.
- `lib/ui/capture_screen_sync.dart` ~:76-78 — how `showToast`/`showError` are - `lib/ui/capture_screen_sync.dart` ~:76-78 — how `showToast`/`showError` are
used today; the shared helpers live in used today; the shared helpers live in
`~/utils/design_system/lib/src/feedback.dart`. `~/src/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` — `showToast`/`showError`, `_show`
~:50. `confirm.dart` beside it **already imports `flutter/services.dart`**, so ~:50. `confirm.dart` beside it **already imports `flutter/services.dart`**, so
if prompt 01 added a shared haptic helper it lives near here. 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.** **Prompt 01 must have run first, and its tag must be cut.**
- `pubspec.yaml` — read the comment explaining why there is no local theme. - `pubspec.yaml` — read the comment explaining why there is no local theme.

View File

@ -268,10 +268,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_secure_storage name: flutter_secure_storage
sha256: "15e8c8fe269fdf7d469b23008ab3df521c8b826ed345820532364c31bdebace6" sha256: "4540927d8b37e6cc47aaa2d17b6035a2d49049e2797cb19d2bdd299bff602c67"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.0.0" version: "11.1.0"
flutter_secure_storage_darwin: flutter_secure_storage_darwin:
dependency: transitive dependency: transitive
description: description:
@ -292,10 +292,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_platform_interface name: flutter_secure_storage_platform_interface
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" sha256: "4bc033841169d07f690d46d89dbc3f5305b6562820822445384c82e0866e2719"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
flutter_secure_storage_web: flutter_secure_storage_web:
dependency: transitive dependency: transitive
description: description:
@ -415,10 +415,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: idb_shim name: idb_shim
sha256: e36a748165663a165e1f292e9d85af6e3aa087f6e2291d16f86b2035103b2950 sha256: "6a478bf0928fa954512f2a1e7e56f09e2fb2e3e7d183eb051a59b5d6b39ec16f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.9.8" version: "2.9.9"
image: image:
dependency: transitive dependency: transitive
description: description:
@ -764,10 +764,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite_common_ffi name: sqflite_common_ffi
sha256: d5564f1308cafbf064be498f2beb1e993e742985a7cca9e2e228d6cbccd84a05 sha256: "4ec920a01788641fa1c9922e757c108427e7f8634cce3ce8a1073d573060f4a3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2+1" version: "2.4.3"
sqflite_common_ffi_web: sqflite_common_ffi_web:
dependency: transitive dependency: transitive
description: description:
@ -957,10 +957,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: very_good_analysis name: very_good_analysis
sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d" sha256: "0bd5eb8d6be76aa0d71e70cbdae6cac8f784f761170568e3c2214a6948c7f6d3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.3.0" version: "11.0.0"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@ -1010,5 +1010,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.12.2 <4.0.0" dart: ">=3.13.0 <4.0.0"
flutter: ">=3.44.0" flutter: ">=3.44.0"

View File

@ -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 # 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 # 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. # 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: environment:
sdk: ^3.12.2 sdk: ^3.12.2
@ -53,7 +53,7 @@ dependencies:
# Stores the GitHub sync token in the OS keystore (Android Keystore / # Stores the GitHub sync token in the OS keystore (Android Keystore /
# libsecret) instead of plaintext SharedPreferences. # libsecret) instead of plaintext SharedPreferences.
flutter_secure_storage: 11.0.0 flutter_secure_storage: 11.1.0
github_device_auth: github_device_auth:
git: git:
@ -65,12 +65,12 @@ dependencies:
# this plugin does not exist. See lib/sync/google_sign_in_backend.dart. # this plugin does not exist. See lib/sync/google_sign_in_backend.dart.
google_sign_in: 7.2.0 google_sign_in: 7.2.0
http: 1.6.0 http: 1.6.0
idb_shim: 2.9.8 idb_shim: 2.9.9
path: 1.9.1 path: 1.9.1
path_provider: 2.1.6 path_provider: 2.1.6
share_plus: 13.3.0 share_plus: 13.3.0
shared_preferences: 2.5.5 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 # Retained only to read the legacy on-device DB during the one-time
# migration in NoteRepository.open; remove once all devices are migrated. # migration in NoteRepository.open; remove once all devices are migrated.
sqlite_crdt: 3.0.4 sqlite_crdt: 3.0.4
@ -105,7 +105,7 @@ dev_dependencies:
# Stricter than flutter_lints -- standardized across this ecosystem's # Stricter than flutter_lints -- standardized across this ecosystem's
# Flutter apps (see project memory: all Flutter apps use # Flutter apps (see project memory: all Flutter apps use
# very_good_analysis, not flutter_lints). # 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 # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@ -150,7 +150,7 @@ flutter:
# see https://flutter.dev/to/font-from-package # see https://flutter.dev/to/font-from-package
# Launcher icon generation. Source art is produced by the shared generator: # Launcher icon generation. Source art is produced by the shared generator:
# PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --app <key> # PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --app <key>
# then run: dart run flutter_launcher_icons # then run: dart run flutter_launcher_icons
flutter_launcher_icons: flutter_launcher_icons:
android: true android: true

View File

@ -3,7 +3,7 @@
# ============================================================================ # ============================================================================
# Fail if any dependency in the commit is behind its ecosystem's newest stable. # 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 # 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 # 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 # repo's -- so this script only locates the shared checker and forwards its
@ -18,12 +18,12 @@
set -euo pipefail 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() { main() {
if [[ ! -x "$SHARED_GATE" ]]; then if [[ ! -x "$SHARED_GATE" ]]; then
echo "Error: shared dependency-freshness gate not found at $SHARED_GATE" >&2 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 echo " UTILS_ROOT to where it lives." >&2
exit 1 exit 1
fi fi

View File

@ -3,7 +3,7 @@
# ============================================================================ # ============================================================================
# Fail if any file in the commit exceeds the shared 250-line cap. # 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 # 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 -- # 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. # so this script only locates the shared checker and forwards its arguments.
@ -15,12 +15,12 @@
set -euo pipefail 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() { main() {
if [[ ! -x "$SHARED_GATE" ]]; then if [[ ! -x "$SHARED_GATE" ]]; then
echo "Error: shared file-length gate not found at $SHARED_GATE" >&2 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 echo " UTILS_ROOT to where it lives." >&2
exit 1 exit 1
fi fi

View File

@ -3,7 +3,7 @@
# ============================================================================ # ============================================================================
# Fail if any markdown file breaks the shared naming convention. # 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 # (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 # 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 # of a valid name drift from every other repo's -- so this script only locates
@ -16,12 +16,12 @@
set -euo pipefail 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() { main() {
if [[ ! -x "$SHARED_GATE" ]]; then if [[ ! -x "$SHARED_GATE" ]]; then
echo "Error: shared markdown-naming gate not found at $SHARED_GATE" >&2 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 echo " UTILS_ROOT to where it lives." >&2
exit 1 exit 1
fi fi

View File

@ -5,7 +5,7 @@
# #
# The apps depend on crdt_sync as a *git dependency pinned to a tag*, so what # 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 # 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 # 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 # green against code CI has never seen, and the failure is silent in both
# directions. # directions.

View File

@ -58,7 +58,7 @@ Future<FakeNoteRepository> pumpCapture(
final repo = repository ?? FakeNoteRepository(seed); final repo = repository ?? FakeNoteRepository(seed);
addTearDown(repo.close); addTearDown(repo.close);
// Default to an in-memory, no-op backup so tests never touch real disk // 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 = final backup =
localBackup ?? localBackup ??
LocalBackup( LocalBackup(

View File

@ -121,7 +121,7 @@ void main() {
group('loadAccount', () { group('loadAccount', () {
// The account's password grant is retired fleet-wide (see // 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 // seeded-credentials route is the only fallback that can actually
// authenticate a fresh desktop install. These exercise loadAccount()'s // authenticate a fresh desktop install. These exercise loadAccount()'s
// adoption of that route directly, since Uri.base in a test is // adoption of that route directly, since Uri.base in a test is

View File

@ -72,7 +72,7 @@ void main() {
tester, tester,
) async { ) async {
// Redirect HOME: this test does real file I/O, and without the // 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. // the fake note below on every test run.
final home = Directory.systemTemp.createTempSync('todo_export_home'); final home = Directory.systemTemp.createTempSync('todo_export_home');
resolveExportHome = () => home.path; resolveExportHome = () => home.path;