mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 16:38:36 +01:00
Re-point paths for the ~ reorganisation (home-tidy)
Some checks are pending
Some checks are pending
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:
parent
951b00e263
commit
a0d37c4df8
2
.github/workflows/release-apk.yml
vendored
2
.github/workflows/release-apk.yml
vendored
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@ -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 `<work_backlog>` 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: <name>`, 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 |
|
||||
|
||||
@ -120,7 +120,7 @@
|
||||
<li><code>watchNotes()</code> — a <em>live stream</em>: it re-runs its <code>SELECT *</code> and re-emits <strong>every time the table changes</strong>, so the UI can stay in sync automatically.</li>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -19,7 +19,7 @@ import 'package:uuid/uuid.dart';
|
||||
// run under test; [openRepositoryIn] holds all the logic and is covered.
|
||||
Future<NoteRepository> openRepository() async {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
return openRepositoryIn(dir.path);
|
||||
return await openRepositoryIn(dir.path);
|
||||
}
|
||||
// coverage:ignore-end
|
||||
|
||||
@ -60,5 +60,8 @@ Future<NoteRepository> 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,
|
||||
);
|
||||
}
|
||||
|
||||
@ -23,5 +23,8 @@ Future<NoteRepository> 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,
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<bool> _awaitFreePort(
|
||||
|
||||
@ -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<void> stop() async => _server?.close(force: true);
|
||||
Future<void> stop() async => await _server?.close(force: true);
|
||||
|
||||
Future<void> _serve(HttpServer server) async {
|
||||
await for (final request in server) {
|
||||
@ -128,18 +128,18 @@ class WrapperServer {
|
||||
Future<void> _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.
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<String> exportBacklog(String markdown, int noteCount) async {
|
||||
// coverage:ignore-start
|
||||
// Mobile-only share path: Platform.isAndroid/isIOS are always false on the
|
||||
|
||||
@ -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<String> 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';
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -27,9 +27,9 @@ Future<FirebaseRestClient?> 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
|
||||
|
||||
@ -75,7 +75,7 @@ Future<String?> googleIdToken({
|
||||
Future<String?> 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.
|
||||
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -182,7 +182,7 @@ class SyncService {
|
||||
/// depends on the optimisation being available.
|
||||
Future<Map<String, String>> _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
|
||||
|
||||
@ -23,5 +23,5 @@ class _PrefsPersistence implements LogPersistence {
|
||||
|
||||
@override
|
||||
Future<void> write(String text) async =>
|
||||
_prefs.setString(kSyncStateKey, text);
|
||||
await _prefs.setString(kSyncStateKey, text);
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -139,7 +139,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
|
||||
/// 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<void> _export() async {
|
||||
final notes = await widget.repository.listNotes();
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <YYYY-MM-DD> | <impl commit sha> | <one-line note> |
|
||||
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.
|
||||
|
||||
|
||||
22
pubspec.lock
22
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"
|
||||
|
||||
12
pubspec.yaml
12
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 <key>
|
||||
# PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --app <key>
|
||||
# then run: dart run flutter_launcher_icons
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -58,7 +58,7 @@ Future<FakeNoteRepository> 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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user