diff --git a/.github/workflows/md-naming.yml b/.github/workflows/md-naming.yml new file mode 100644 index 0000000..1bb197c --- /dev/null +++ b/.github/workflows/md-naming.yml @@ -0,0 +1,38 @@ +name: md-naming + +# The four markdown namespaces, enforced on push as well as at commit time. +# The local pre-commit hook can be skipped (a fresh clone that never ran +# `pre-commit install` has no hooks at all), so the convention needs a second +# gate that no one can forget to install. +# +# Deliberately a SEPARATE workflow from any release job: a badly named doc +# should fail lint, not block publishing an artifact. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + md-naming: + name: markdown naming convention + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # The gate and its exemption list live in the shared utils repo so every + # repo enforces the same namespaces. Checking it out beats vendoring a + # copy here, which is exactly how a gate and a survey start disagreeing. + - name: Check out the shared gate + uses: actions/checkout@v4 + with: + repository: kuhyx/utils + path: .utils + + - name: Enforce the naming convention + run: bash .utils/scripts/check_md_naming.sh --all + + # Warns only: no machine can prove a task is finished, so an old TODO + # gets a nudge, never a failure. + - name: Warn about stale TODOs + run: PYTHONPATH=.utils python3 .utils/md_naming/stale.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 918f6f5..036d138 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,3 +50,13 @@ repos: name: file length <= 250 lines entry: scripts/check_file_length.sh language: system + + # 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 + # one repo's idea of a valid name cannot drift from every other repo's. + - id: md-naming + name: markdown naming convention + entry: scripts/check_md_naming.sh + language: system + files: \.(md|markdown)$ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b094ee3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,172 @@ +# CLAUDE.md — todo + +Offline-first, CRDT-synced notes app. Flutter, targets **Android + web**. + +The **desktop app is the web build**, served by a small local wrapper +(`bin/todo_desktop.dart`) and shown in a Chrome `--app` window. Flutter's Linux +embedder was removed: it manages only ~20fps at 4K while the same Dart code in +Chrome sustains ~144fps. See `docs/DOCS-desktop-performance-findings.md` for the +measurements. Do not re-add `linux/` — `flutter create --platforms linux` would +silently bring the slow path back. +Capture an idea instantly (persisted on every keystroke), browse/filter notes, +and sync peer-to-peer through a GitHub repo used as dumb storage. + +- Package name: `todo` (Dart SDK `^3.12.2`). +- Git remote: `origin` → `github.com/kuhyx/todo-app`. +- The note **content** also syncs to a separate private repo `kuhyx/syncs` + (under `todo-sync/`) via the in-app GitHub sync (changeset files); that is + data, not this codebase. + +## Git workflow (repo-specific — overrides global rules) + +- **Never open pull requests. Always commit and work directly on `main` and + `git push` to `origin/main`.** Do not create feature branches for normal work. +- End commit messages with the standard + `Co-Authored-By: Claude Opus 4.8 ` trailer. + +## Commands + +- Run tests + coverage: `flutter test --coverage` (suite runs in under a minute; keep it + quick — see Testing below). +- Coverage summary: `lcov --list coverage/lcov.info`. +- Static analysis: `flutter analyze` (must be clean before committing). +- Format: `dart format lib/ test/`. +- Run the desktop app: `./run.sh` (builds the web bundle, starts the wrapper, + opens Chrome). Mobile: `flutter run` with a connected Android device. +- The wrapper serves on a **fixed** port 8730 with a **fixed** Chrome profile + dir. Both must stay fixed: the GitHub token (`localStorage`) and the notes + (IndexedDB) are keyed by origin and live in that profile, so changing either + silently logs you out and hides your notes. Because the port cannot move, + `lib/desktop/port_guard.dart` decides what to do when it is already taken: + reap a wrapper with no window attached (so an upgrade can't leave you on old + code), join one that still has a window, and refuse to touch anything else. +- Release APK: `flutter build apk --release` (signs with the debug key; debug + builds are janky — always measure smoothness on a release build). +- Sync smoke test (hits real GitHub, needs a token): `dart run tool/sync_smoke.dart`. + +## Architecture + +Three layers under `lib/`, each single-purpose: + +- `data/` — domain + local storage. + - `note.dart`: `Note` model; `Priority` enum (low/medium/high, **default + medium**, no "none"); `Status` enum (todo/inProgress/done/abandoned). + - `note_repository.dart`: CRDT storage over `sqlite_crdt`. Schema is at + **version 3** with `onCreate`/`onUpgrade` migrations (v1→v2 adds the + `status` column; v2→v3 backfills legacy priority `0`→medium). Also defines + `NoteFilter` (query / priorities / statuses / created+updated date ranges, + AND-combined), `NoteSort`, `watchNotes`/`watchCount` streams, and + `importNotes` (safe newer-wins merge by id). +- `sync/` — GitHub-as-storage sync. + - `sync_service.dart`: each device owns one file + `todo-sync/changesets/.json` + holding its full CRDT changeset. No two devices write the same file ⇒ no + git merge conflicts; convergence is the CRDT layer's job (pull every other + device's changeset and `merge()`, which is commutative + idempotent). + - `github_client.dart`: thin GitHub contents-API client (injectable + `http.Client`). `github_device_auth.dart`: OAuth device flow to mint a + token. `sync_settings.dart`: persisted owner/repo/token/clientId. + - `notes_markdown.dart`: round-trippable single-file export/import format + (HTML-comment `` markers). +- `ui/` — screens, all take an injected `NoteRepository`. + - `capture_screen.dart`: landing screen; always-focused text box pre-filled + with the structured template; lazy note creation on first keystroke. + - `notes_list_screen.dart`: list + search + sort + filter sheet + per-note + sheet. **Default view hides Done/Abandoned and shows no filter badge** + (looks unfiltered). + - `settings_screen.dart`: GitHub connect/test, device-flow auth, and the + Export/Import backup actions. +- `main.dart`: bootstrap only (`// coverage:ignore-file`) — wires platform DB + paths and `runApp`. + +### Note template (default content of every new note) + +`lib/data/note_template.dart` is the **single source of truth** — the stepper, +the editor and the tests are all template-driven, so changing that list changes +every surface. A new note pre-fills a title line plus +`what / where / must / done / verify / read first`. + +The set was cut from twelve sections to seven in 2026-07 after auditing 514 real +sessions (`docs/DOCS-llm-design-spec-audit.md`): `estimate`, `ask`, `nice`, `tech`, +`never` and `depends` were measurably never read, and `verify` was added because +"test it yourself, on the phone" was the single most repeated correction in the +corpus. Do not re-add a section without evidence that it gets used. + +`NoteTemplate.retiredLabels` lists the dropped headings. `parse()` reports any +note containing one as **non-conforming** so it opens in the raw editor — without +that, the stepper would fold a retired section into its neighbour on the next +save. `tool/migrate_backlog.dart` converts an old export (Settings → Export → +migrate → Settings → Import). + +## Testing + +- **The suite must stay quick and fully green.** It is currently **313 tests at + 100% line coverage** (2005 lines), running well under a minute on this + machine. Don't regress either. + (The "~5s" figure this file used to quote had drifted well before the port + guard was added; measure before quoting a new one.) +- Widget tests use `test/fake_note_repository.dart` — a `FakeNoteRepository` + built on `StreamController`s, **not** a real DB. A real `sqlite_crdt` DB + schedules timers that never drain under the widget tester's fake clock + ("A Timer is still pending"); the fake avoids that. +- Pitfalls learned the hard way (keep following these): + - **Avoid `pumpAndSettle` when a widget animates forever** — an autofocused + `TextField`'s cursor blink never settles, and an open device-code dialog + keeps a pending poll timer. Use explicit `pump(Duration)` there. + `pumpAndSettle` is fine once nothing is animating (e.g. a route/dialog pop, + or a dialog already showing a static error). + - Inject fakes rather than touching the platform: `MockClient` + (`package:http/testing.dart`) for HTTP; the `file_selector` and + `url_launcher` **platform interfaces** (`MockPlatformInterfaceMixin`) for + the picker/launcher; stub `SystemChannels.platform` for clipboard. + - To exercise the configured sync path, pass a `MockClient` via + `CaptureScreen(httpClient: …)` / `SettingsScreen(httpClient: …)` — the real + `SyncService` then runs without network. + - `getChangeset()` serialises HLCs as `String`; `merge()` needs `Hlc.parse` + and fresh mutable maps (QueryRows are read-only) — see the changeset test. +- Use `// coverage:ignore-line` / `ignore-start`/`ignore-end` only for + genuinely unreachable code (e.g. a private static-only constructor, or a + mobile-only `Platform.isAndroid` branch that can't run on the Linux test + host), with a one-line reason. + +## Conventions + +- Run `dart format` + `flutter analyze` (clean) before every commit. +- Comments explain intent/trade-offs, not syntax. +- Keep the app buttery-smooth and low on CPU/RAM — it's a quick-capture tool. +- **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 + 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 + stashes unstaged changes and restores them with `git apply`; a hook that + rewrites a tracked file inside that window makes the restore conflict, + which aborts the commit *and drops the developer's uncommitted edit*. That + is why `bump_patch_version.sh` runs from the wrapper hook that + `install_hooks.sh` writes, after pre-commit has exited and only when it + passed — see the comments in both files. There is deliberately no + baseline and no allowlist — split the file instead. Exemptions (generated, + vendored, data files) live with the checker so the gate and the survey + cannot disagree. +- Where a split would otherwise break callers, keep the original path as a + barrel that re-exports the parts (`note_template.dart`, + `firebase_backend.dart`, `note_repository.dart` all do this). Where a + widget and its private `State` must stay one library, use `part` files + (`note_editor_*.dart`, `capture_screen_*.dart`). + +## Flutter/Dart AI rules + +Upstream's Flutter/Dart guidance used to be pasted in below this line, which +made 85% of this always-loaded file a document nobody edits. It now lives in +`docs/`, linked rather than inlined so the part loaded on every turn stays +about *this repo*: + +- [Language, style and architecture](docs/DOCS-flutter-rules-language.md) +- [State, routing and data](docs/DOCS-flutter-rules-state-and-data.md) +- [Code generation, testing, theming and assets](docs/DOCS-flutter-rules-testing-and-assets.md) +- [UI: theming, layout and overlays](docs/DOCS-flutter-rules-ui.md) +- [Colour, type, documentation and accessibility](docs/DOCS-flutter-rules-design.md) + +Sourced from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md) +via `~/.claude/CLAUDE.md`'s Flutter AI tooling setup. Re-fetch periodically. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index cc180b2..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,172 +0,0 @@ -# CLAUDE.md — todo - -Offline-first, CRDT-synced notes app. Flutter, targets **Android + web**. - -The **desktop app is the web build**, served by a small local wrapper -(`bin/todo_desktop.dart`) and shown in a Chrome `--app` window. Flutter's Linux -embedder was removed: it manages only ~20fps at 4K while the same Dart code in -Chrome sustains ~144fps. See `docs/desktop-performance-findings.md` for the -measurements. Do not re-add `linux/` — `flutter create --platforms linux` would -silently bring the slow path back. -Capture an idea instantly (persisted on every keystroke), browse/filter notes, -and sync peer-to-peer through a GitHub repo used as dumb storage. - -- Package name: `todo` (Dart SDK `^3.12.2`). -- Git remote: `origin` → `github.com/kuhyx/todo-app`. -- The note **content** also syncs to a separate private repo `kuhyx/syncs` - (under `todo-sync/`) via the in-app GitHub sync (changeset files); that is - data, not this codebase. - -## Git workflow (repo-specific — overrides global rules) - -- **Never open pull requests. Always commit and work directly on `main` and - `git push` to `origin/main`.** Do not create feature branches for normal work. -- End commit messages with the standard - `Co-Authored-By: Claude Opus 4.8 ` trailer. - -## Commands - -- Run tests + coverage: `flutter test --coverage` (suite runs in under a minute; keep it - quick — see Testing below). -- Coverage summary: `lcov --list coverage/lcov.info`. -- Static analysis: `flutter analyze` (must be clean before committing). -- Format: `dart format lib/ test/`. -- Run the desktop app: `./run.sh` (builds the web bundle, starts the wrapper, - opens Chrome). Mobile: `flutter run` with a connected Android device. -- The wrapper serves on a **fixed** port 8730 with a **fixed** Chrome profile - dir. Both must stay fixed: the GitHub token (`localStorage`) and the notes - (IndexedDB) are keyed by origin and live in that profile, so changing either - silently logs you out and hides your notes. Because the port cannot move, - `lib/desktop/port_guard.dart` decides what to do when it is already taken: - reap a wrapper with no window attached (so an upgrade can't leave you on old - code), join one that still has a window, and refuse to touch anything else. -- Release APK: `flutter build apk --release` (signs with the debug key; debug - builds are janky — always measure smoothness on a release build). -- Sync smoke test (hits real GitHub, needs a token): `dart run tool/sync_smoke.dart`. - -## Architecture - -Three layers under `lib/`, each single-purpose: - -- `data/` — domain + local storage. - - `note.dart`: `Note` model; `Priority` enum (low/medium/high, **default - medium**, no "none"); `Status` enum (todo/inProgress/done/abandoned). - - `note_repository.dart`: CRDT storage over `sqlite_crdt`. Schema is at - **version 3** with `onCreate`/`onUpgrade` migrations (v1→v2 adds the - `status` column; v2→v3 backfills legacy priority `0`→medium). Also defines - `NoteFilter` (query / priorities / statuses / created+updated date ranges, - AND-combined), `NoteSort`, `watchNotes`/`watchCount` streams, and - `importNotes` (safe newer-wins merge by id). -- `sync/` — GitHub-as-storage sync. - - `sync_service.dart`: each device owns one file - `todo-sync/changesets/.json` - holding its full CRDT changeset. No two devices write the same file ⇒ no - git merge conflicts; convergence is the CRDT layer's job (pull every other - device's changeset and `merge()`, which is commutative + idempotent). - - `github_client.dart`: thin GitHub contents-API client (injectable - `http.Client`). `github_device_auth.dart`: OAuth device flow to mint a - token. `sync_settings.dart`: persisted owner/repo/token/clientId. - - `notes_markdown.dart`: round-trippable single-file export/import format - (HTML-comment `` markers). -- `ui/` — screens, all take an injected `NoteRepository`. - - `capture_screen.dart`: landing screen; always-focused text box pre-filled - with the structured template; lazy note creation on first keystroke. - - `notes_list_screen.dart`: list + search + sort + filter sheet + per-note - sheet. **Default view hides Done/Abandoned and shows no filter badge** - (looks unfiltered). - - `settings_screen.dart`: GitHub connect/test, device-flow auth, and the - Export/Import backup actions. -- `main.dart`: bootstrap only (`// coverage:ignore-file`) — wires platform DB - paths and `runApp`. - -### Note template (default content of every new note) - -`lib/data/note_template.dart` is the **single source of truth** — the stepper, -the editor and the tests are all template-driven, so changing that list changes -every surface. A new note pre-fills a title line plus -`what / where / must / done / verify / read first`. - -The set was cut from twelve sections to seven in 2026-07 after auditing 514 real -sessions (`docs/llm-design-spec-audit.md`): `estimate`, `ask`, `nice`, `tech`, -`never` and `depends` were measurably never read, and `verify` was added because -"test it yourself, on the phone" was the single most repeated correction in the -corpus. Do not re-add a section without evidence that it gets used. - -`NoteTemplate.retiredLabels` lists the dropped headings. `parse()` reports any -note containing one as **non-conforming** so it opens in the raw editor — without -that, the stepper would fold a retired section into its neighbour on the next -save. `tool/migrate_backlog.dart` converts an old export (Settings → Export → -migrate → Settings → Import). - -## Testing - -- **The suite must stay quick and fully green.** It is currently **313 tests at - 100% line coverage** (2005 lines), running well under a minute on this - machine. Don't regress either. - (The "~5s" figure this file used to quote had drifted well before the port - guard was added; measure before quoting a new one.) -- Widget tests use `test/fake_note_repository.dart` — a `FakeNoteRepository` - built on `StreamController`s, **not** a real DB. A real `sqlite_crdt` DB - schedules timers that never drain under the widget tester's fake clock - ("A Timer is still pending"); the fake avoids that. -- Pitfalls learned the hard way (keep following these): - - **Avoid `pumpAndSettle` when a widget animates forever** — an autofocused - `TextField`'s cursor blink never settles, and an open device-code dialog - keeps a pending poll timer. Use explicit `pump(Duration)` there. - `pumpAndSettle` is fine once nothing is animating (e.g. a route/dialog pop, - or a dialog already showing a static error). - - Inject fakes rather than touching the platform: `MockClient` - (`package:http/testing.dart`) for HTTP; the `file_selector` and - `url_launcher` **platform interfaces** (`MockPlatformInterfaceMixin`) for - the picker/launcher; stub `SystemChannels.platform` for clipboard. - - To exercise the configured sync path, pass a `MockClient` via - `CaptureScreen(httpClient: …)` / `SettingsScreen(httpClient: …)` — the real - `SyncService` then runs without network. - - `getChangeset()` serialises HLCs as `String`; `merge()` needs `Hlc.parse` - and fresh mutable maps (QueryRows are read-only) — see the changeset test. -- Use `// coverage:ignore-line` / `ignore-start`/`ignore-end` only for - genuinely unreachable code (e.g. a private static-only constructor, or a - mobile-only `Platform.isAndroid` branch that can't run on the Linux test - host), with a one-line reason. - -## Conventions - -- Run `dart format` + `flutter analyze` (clean) before every commit. -- Comments explain intent/trade-offs, not syntax. -- Keep the app buttery-smooth and low on CPU/RAM — it's a quick-capture tool. -- **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 - 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 - stashes unstaged changes and restores them with `git apply`; a hook that - rewrites a tracked file inside that window makes the restore conflict, - which aborts the commit *and drops the developer's uncommitted edit*. That - is why `bump_patch_version.sh` runs from the wrapper hook that - `install_hooks.sh` writes, after pre-commit has exited and only when it - passed — see the comments in both files. There is deliberately no - baseline and no allowlist — split the file instead. Exemptions (generated, - vendored, data files) live with the checker so the gate and the survey - cannot disagree. -- Where a split would otherwise break callers, keep the original path as a - barrel that re-exports the parts (`note_template.dart`, - `firebase_backend.dart`, `note_repository.dart` all do this). Where a - widget and its private `State` must stay one library, use `part` files - (`note_editor_*.dart`, `capture_screen_*.dart`). - -## Flutter/Dart AI rules - -Upstream's Flutter/Dart guidance used to be pasted in below this line, which -made 85% of this always-loaded file a document nobody edits. It now lives in -`docs/`, linked rather than inlined so the part loaded on every turn stays -about *this repo*: - -- [Language, style and architecture](docs/flutter-rules-language.md) -- [State, routing and data](docs/flutter-rules-state-and-data.md) -- [Code generation, testing, theming and assets](docs/flutter-rules-testing-and-assets.md) -- [UI: theming, layout and overlays](docs/flutter-rules-ui.md) -- [Colour, type, documentation and accessibility](docs/flutter-rules-design.md) - -Sourced from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md) -via `~/.claude/CLAUDE.md`'s Flutter AI tooling setup. Re-fetch periodically. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 0973419..0000000 --- a/MIGRATION.md +++ /dev/null @@ -1,44 +0,0 @@ -# Migration: `sqlite_crdt` → shared `crdt_sync` - -todo's note storage and sync were moved off `sqlite_crdt` onto the shared -[`crdt_sync`](https://github.com/kuhyx/utils/tree/main/crdt_sync_dart) library -(pinned tag `crdt_sync_dart-v0.3.0`), so every companion app now syncs through -one library. - -## What changed - -- **Storage:** `NoteRepository` is backed by `crdt_sync`'s `LogStore` (a - `Log = Map` persisted as JSON). Each `Note` is stored *as* a - `Record` (per-field last-writer-wins + a sticky tombstone), so the on-disk - log **is** the sync wire format — no separate adapter. Filtering/sorting run - in Dart over the in-memory log. -- **Sync:** `SyncService` pushes each device's `Log` to - `todo-sync/notes/.json` in `kuhyx/syncs` (new dir; the old - `changesets/` format is incompatible, so it's a clean cutover). It uses - `crdt_sync`'s `GitHubClient` + `logToJson`/`logFromJson`. -- **Client:** todo's local `lib/sync/github_client.dart` was deleted; it now - uses `crdt_sync`'s `GitHubClient` (which gained `canAccessRepo()` + - `deleteFile()` in v0.3.0). `putFileText` self-resolves the file sha, so the - old SHA-tracking is gone. - -## The one-time on-device migration - -`NoteRepository.open()` migrates a pre-existing `sqlite_crdt` DB into the log on -first launch, guarded by a persisted `crdt.migratedFromSqlite` flag. Verified -on-device: a real 27-note backlog (+5 tombstones) migrated intact. - -`sqlite_crdt` is **retained as a dependency** only to read that legacy DB during -migration — the old `todo.db` is left untouched as a safety net. Remove the dep -in a later cleanup once every device is known-migrated. - -## Surprises / non-obvious decisions - -- **Seed HLCs from each note's `updatedAt`, not "now".** Two devices migrate - independently; stamping migration-time "now" would let per-field LWW resolve - a pre-cutover edit arbitrarily. Seeding from `updatedAt` keeps - last-real-edit-wins across the cutover. -- **Reuse the legacy `sqlite_crdt` node id** (persisted in prefs) so a device - keeps its `devices/` identity and devices still converge. -- **`watchNotes`/`watchCount` seed in `onListen`**, not lazily in an `async*` - body — otherwise a write between `listen()` and the first generator tick - swallows the initial snapshot. diff --git a/DESIGN_AUDIT_TODO.md b/TODO-design-audit.md similarity index 99% rename from DESIGN_AUDIT_TODO.md rename to TODO-design-audit.md index 70dadc2..ef7e838 100644 --- a/DESIGN_AUDIT_TODO.md +++ b/TODO-design-audit.md @@ -87,3 +87,5 @@ per-file values have no source of truth at all). - The single hardcoded-color violation (`Colors.red`) is also the app's only spot where a raw `Colors.*` constant appears outside `main.dart`'s seed — fixing it removes the last non-theme-derived color reference in the app. + +REMOVE ME AFTER FINISH diff --git a/docs/desktop-performance-findings.md b/docs/DOCS-desktop-performance-findings.md similarity index 100% rename from docs/desktop-performance-findings.md rename to docs/DOCS-desktop-performance-findings.md diff --git a/docs/desktop-performance-interaction-lag.md b/docs/DOCS-desktop-performance-interaction-lag.md similarity index 100% rename from docs/desktop-performance-interaction-lag.md rename to docs/DOCS-desktop-performance-interaction-lag.md diff --git a/docs/flutter-rules-design.md b/docs/DOCS-flutter-rules-design.md similarity index 100% rename from docs/flutter-rules-design.md rename to docs/DOCS-flutter-rules-design.md diff --git a/docs/flutter-rules-language.md b/docs/DOCS-flutter-rules-language.md similarity index 100% rename from docs/flutter-rules-language.md rename to docs/DOCS-flutter-rules-language.md diff --git a/docs/flutter-rules-state-and-data.md b/docs/DOCS-flutter-rules-state-and-data.md similarity index 100% rename from docs/flutter-rules-state-and-data.md rename to docs/DOCS-flutter-rules-state-and-data.md diff --git a/docs/flutter-rules-testing-and-assets.md b/docs/DOCS-flutter-rules-testing-and-assets.md similarity index 100% rename from docs/flutter-rules-testing-and-assets.md rename to docs/DOCS-flutter-rules-testing-and-assets.md diff --git a/docs/flutter-rules-ui.md b/docs/DOCS-flutter-rules-ui.md similarity index 100% rename from docs/flutter-rules-ui.md rename to docs/DOCS-flutter-rules-ui.md diff --git a/docs/initial_design.md b/docs/DOCS-initial-design.md similarity index 100% rename from docs/initial_design.md rename to docs/DOCS-initial-design.md diff --git a/docs/llm-design-spec-audit.md b/docs/DOCS-llm-design-spec-audit.md similarity index 100% rename from docs/llm-design-spec-audit.md rename to docs/DOCS-llm-design-spec-audit.md diff --git a/prompts/dopamine-ux-todo.md b/prompts/TODO-dopamine-ux-todo.md similarity index 99% rename from prompts/dopamine-ux-todo.md rename to prompts/TODO-dopamine-ux-todo.md index 9b4e2bb..44ec1a0 100644 --- a/prompts/dopamine-ux-todo.md +++ b/prompts/TODO-dopamine-ux-todo.md @@ -216,3 +216,5 @@ can go stale independently — but this prompt's done-condition is the phone. - Settings sync cross-device last-writer-wins through Firebase (`settings/advancedMode`). If you add a synced preference, follow `reconcileWithRemote`/`adopt`; if local-only, skip that path entirely. + +REMOVE ME AFTER FINISH diff --git a/pubspec.yaml b/pubspec.yaml index 70bbdc0..a121621 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.26+1 +version: 1.0.27+1 environment: sdk: ^3.12.2 diff --git a/refactor_claude_todo.md b/refactor_claude_todo.md deleted file mode 100644 index c826bd5..0000000 --- a/refactor_claude_todo.md +++ /dev/null @@ -1,104 +0,0 @@ -# Refactor TODO — enforce the 250-line file cap - -> **DONE for this repo (2026-08-14).** Every file is at most 250 lines and -> `bash ~/utils/scripts/check_file_length.sh --all` exits 0. The gate is wired -> into `.pre-commit-config.yaml` and `.github/workflows/file-length.yml`, and -> was verified from a fresh checkout: `scripts/install_hooks.sh` installs it, -> and a staged 260-line file then makes `git commit` fail. 313 tests pass at -> 100% coverage (up from 99.9%). The counts below are the pre-refactor survey, -> kept as the record of what was done — re-run the survey before reusing this -> file here. It is still a valid prompt for **other** repos. - -> **This file is a ready-to-use prompt.** Paste it to Claude, or open this repo -> and say "do refactor_claude_todo". It is self-contained: everything needed to -> execute is below. Generated 2026-08-14 from a measured survey of every repo. - -## Goal - -Every file in this repo must be **at most 250 lines** — source, tests, and -prose (`.md`/`.txt`/`.rst`/`.tex`) alike — and must **stay** that way forever, -enforced by a gate that fails the commit, not by a note anyone can ignore. - -Why: a file that cannot be read in one piece forces re-reads and partial edits, -which is the single largest avoidable cost in an LLM-assisted workflow. Aim by -churn, not size alone — refactoring pays where code is read and changed often -(Fowler, *refactoring economic benefit*). - -## Scope in this repo - -- **19 files** currently exceed 250 lines (of 115 eligible files). -- **8,691 lines** sit in violation; longest file is **925 lines**. - -Exempt (do NOT split these): -- generated files — `*.g.dart`, `*.freezed.dart`, `*.gr.dart`, `**/l10n/generated/**`, - anything with a `GENERATED` header -- markup — `.html`, `.css`, `.scss` -- data files — `.json`, `.yaml`, `.csv`, wordlists and other data-ish `.txt` - (mean line length under 25 chars) - -## Violations, highest ROI first - -ROI = lines x commits in the last year. Work top-down; a long file nobody edits -has near-zero payoff and should not be first. - -| lines | commits/yr | kind | file | -|------:|-----------:|:-----|:-----| -| 739 | 19 | code | `lib/ui/capture_screen.dart` | -| 660 | 17 | code | `test/capture_screen_test.dart` | -| 702 | 10 | code | `lib/ui/note_editor.dart` | -| 677 | 9 | code | `test/note_repository_test.dart` | -| 925 | 6 | prose | `CLAUDE.md` | -| 391 | 13 | code | `test/settings_screen_test.dart` | -| 429 | 8 | code | `lib/data/note_repository.dart` | -| 502 | 6 | code | `test/note_editor_test.dart` | -| 356 | 7 | code | `test/sync_service_test.dart` | -| 402 | 5 | code | `test/wrapper_server_test.dart` | -| 256 | 7 | code | `lib/ui/notes_list_screen.dart` | -| 253 | 7 | code | `bin/todo_desktop.dart` | -| 347 | 4 | code | `test/notes_list_screen_test.dart` | -| 336 | 4 | code | `lib/desktop/wrapper_server.dart` | -| 405 | 1 | code | `test/port_guard_test.dart` | - -_(4 further files over 250 lines not listed — re-run the survey for the full set.)_ - -## How to split - -- **Python** — extract cohesive helpers into sibling modules; keep the public - API and imports stable. -- **Shell** — split into `lib/*.sh` sourced by a thin entry script. Keep - `set -euo pipefail` in each. -- **Dart / TypeScript** — extract widgets/components into their own files. -- **Tests** — split by test-group into sibling files - (`foo.test.ts` -> `foo.parsing.test.ts`, `foo.render.test.ts`). Coverage must - not drop. -- **Docs** — split into topic files under `docs/` with an index. For an - oversized `CLAUDE.md`, move detail into referenced docs so the - always-loaded part shrinks. - -**Do not** game the cap: no one-lining, no deleting tests, no moving code into -an exempt extension, no `# noqa`-style suppressions. - -## Make it permanent (required — this is the point) - -A refactor without a gate silently regrows. Before this task is done: - -1. Wire the shared gate `~/utils/scripts/check_file_length.sh` into this repo's - `.pre-commit-config.yaml` as a local hook. If the repo has no pre-commit - config, add a minimal one. -2. The hook checks **files in the commit** (not the whole tree), so unrelated - commits never break, and it **fails** — exit 1, not a warning. -3. No baseline file and no allowlist. Those are suppressions. -4. If this repo has CI (`.github/workflows`), add the same check there so it - also fails on push. - -## Done condition - -- `bash ~/utils/scripts/check_file_length.sh --all` from this repo root exits 0. -- The repo's own test suite and coverage bar are still green. -- `pre-commit run --files ` passes. -- A deliberately over-250-line test file, staged, makes `git commit` **fail**. -- For a deployed daemon/app: the entry point still actually runs. - -## Verify - -Mobile is the primary platform: verify with an APK on the phone via the `phone-deploy` skill, not on desktop. diff --git a/scripts/check_md_naming.sh b/scripts/check_md_naming.sh new file mode 100755 index 0000000..50deaa0 --- /dev/null +++ b/scripts/check_md_naming.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# ============================================================================ +# Fail if any markdown file breaks the shared naming convention. +# +# Thin delegate to the shared gate in ~/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 +# the shared checker and forwards its arguments. +# +# Usage: +# scripts/check_md_naming.sh [ ...] # pre-commit passes these +# scripts/check_md_naming.sh --all # whole tree, from cwd +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/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 " UTILS_ROOT to where it lives." >&2 + exit 1 + fi + + exec bash "$SHARED_GATE" "$@" +} + +main "$@"