diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d99e26..14c7bb7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,6 +55,18 @@ repos: # rule that a TODO carries "REMOVE ME AFTER FINISH" and nothing else may. # 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. + # Since the 2026-09-11 reorganisation ~ holds eleven visible entries and + # home-tidy sweeps anything else to inbox/ within the hour. The migration's + # rewriter matched *literal* /home/kuhy/ strings, so paths assembled + # at run time survived it -- the todo wrapper exported to a dead ~/todo for + # a day while its MCP read ~/src/todo. This gate matches the shape, so it + # stays correct as repos come and go. + - id: home-path-layout + name: home-relative paths stay inside the ~ layout + entry: bash scripts/check_home_paths.sh + language: system + pass_filenames: true + - id: md-naming name: markdown naming convention entry: scripts/check_md_naming.sh diff --git a/bin/todo_desktop.dart b/bin/todo_desktop.dart index bf01118..feda1b2 100644 --- a/bin/todo_desktop.dart +++ b/bin/todo_desktop.dart @@ -39,10 +39,12 @@ Future main(List args) async { // Overridable so a verification run cannot point at the real backlog: an // app that starts with an empty store would export over it. + // + // The default comes from [defaultBacklogPath] so it is unit-covered; this + // file is coverage:ignore-file and the path silently broke once already. final server = WrapperServer( webRoot: webRoot, - backlogPath: - _argValue(args, '--backlog-path') ?? p.join(home, 'todo', 'BACKLOG.md'), + backlogPath: _argValue(args, '--backlog-path') ?? defaultBacklogPath(home), logPath: _argValue(args, '--log-path') ?? p.join(home, '.local', 'share', 'todo-desktop', 'todo_notes.json'), diff --git a/lib/desktop/wrapper_server.dart b/lib/desktop/wrapper_server.dart index 8b8921b..1608229 100644 --- a/lib/desktop/wrapper_server.dart +++ b/lib/desktop/wrapper_server.dart @@ -16,6 +16,19 @@ part 'wrapper_sync_routes.dart'; /// [kSyncAccountEnvVar] as [kSyncAccountPath]. const kSyncCredentialsPath = '/sync-credentials'; +/// The canonical backlog file for [home], i.e. `~/src/todo/BACKLOG.md`. +/// +/// Lives here rather than in `bin/todo_desktop.dart` so it is covered: the +/// entry point is `coverage:ignore-file` thin wiring, and this path silently +/// broke once already. The 2026-09-11 `~` reorganisation moved every repo +/// under `~/src`, but the migration's rewriter only matched *literal* +/// `/home/kuhy/todo` strings — a path assembled from segments was invisible +/// to it, so the wrapper went on exporting to the dead `~/todo` while the +/// `todo` MCP read `~/src/todo`. Every backlog read between 2026-09-11 and +/// 2026-09-12 got a stale file. +String defaultBacklogPath(String home) => + p.join(home, 'src', 'todo', 'BACKLOG.md'); + /// Local HTTP server backing the desktop app. /// /// The desktop app is a Flutter **web** build (Flutter's Linux embedder manages diff --git a/pubspec.yaml b/pubspec.yaml index 8aa245a..0db29ea 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.34+1 +version: 1.0.35+1 environment: sdk: ^3.12.2 diff --git a/run.sh b/run.sh index 4a7d740..266dc93 100755 --- a/run.sh +++ b/run.sh @@ -78,7 +78,9 @@ install_flutter_debian() { return fi log "no apt package named 'flutter'; cloning the Flutter SDK instead" - local install_dir="${HOME}/development/flutter" + # ~/sdk is the toolchain bucket since the 2026-09-11 ~ reorganisation; + # anything else at ~ root is swept to inbox/ within the hour. + local install_dir="${HOME}/sdk/flutter" if [[ ! -d "${install_dir}" ]]; then sudo apt-get install -y git curl git clone --depth 1 -b stable https://github.com/flutter/flutter.git "${install_dir}" diff --git a/scripts/check_home_paths.sh b/scripts/check_home_paths.sh new file mode 100755 index 0000000..5b658d1 --- /dev/null +++ b/scripts/check_home_paths.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# ============================================================================ +# Fail if code builds a ~/ path that ~ is not allowed to contain. +# +# Thin delegate to the shared gate in ~/src/utils, which owns the eleven +# allowed root entries and the exclusion list. Copying that logic here is what +# lets one repo's idea of the layout drift from every other repo's -- so this +# script only locates the shared checker and forwards its arguments. +# +# Why it exists: the 2026-09-11 reorganisation's rewriter matched *literal* +# /home/kuhy/ strings, so a path assembled at run time was invisible to +# it. The todo desktop wrapper went on exporting to a dead ~/todo for a day +# while its MCP read ~/src/todo, and every backlog read in between was stale. +# +# Usage: +# scripts/check_home_paths.sh [ ...] # pre-commit passes these +# scripts/check_home_paths.sh --all [] # whole tree, default cwd +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_home_paths.sh" + +main() { + if [[ ! -x "$SHARED_GATE" ]]; then + echo "Error: shared gate not found at $SHARED_GATE" >&2 + echo "Clone kuhyx/utils to ~/src/utils, or set UTILS_ROOT." >&2 + exit 1 + fi + exec "$SHARED_GATE" "$@" +} + +main "$@" diff --git a/test/wrapper_server_static_test.dart b/test/wrapper_server_static_test.dart index b21b86f..523719f 100644 --- a/test/wrapper_server_static_test.dart +++ b/test/wrapper_server_static_test.dart @@ -131,4 +131,17 @@ void main() { expect(kind('a.bin'), 'application/octet-stream'); expect(kind('a.unknown'), 'application/octet-stream'); }); + + // Regression: the 2026-09-11 `~` reorganisation moved every repo under + // `~/src`, and this path — assembled from segments — was invisible to the + // migration's literal-string rewriter. The wrapper exported to the dead + // `~/todo` while the `todo` MCP read `~/src/todo`, so every backlog read + // for a day came back stale. Assert the `src` segment, not just the tail. + test('default backlog path is the canonical ~/src/todo/BACKLOG.md', () { + expect( + defaultBacklogPath('/home/someone'), + '/home/someone/src/todo/BACKLOG.md', + ); + expect(p.split(defaultBacklogPath('/h')), contains('src')); + }); }