Commit Graph

9 Commits

Author SHA1 Message Date
9f2ffdb8a9 Attach any file to a note, of any size, not only images
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
file-length / file length <= 250 lines (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
release-apk / release (APK) (push) Has been cancelled
📎 now picks any file (Android: Camera / Gallery / File; desktop: an
unfiltered dialog) and the desktop page accepts any dropped file. Photo and
screenshot formats up to 64 MB are still re-encoded and shown inline;
everything else is stored byte for byte on dufs beside the images and linked
as [name](url), shown as a row with its size that opens in the platform's
app (Android) or a new browser tab (desktop). BACKLOG.md rewrites file links
to ~/data/cloud/todo-images/ like image links.

There is no size cap, so no layer holds a file in memory: uploads and
downloads stream through DufsImageClient, writes land in a .tmp renamed when
whole (an interrupted download no longer leaves a truncated file that looks
done), the desktop page POSTs the browser's own Blob with fetch, and Android's
"File" uses file_picker, because file_selector's Android side reads the whole
file into memory on the UI thread. Verified with a 1 GB file end to end on
desktop (peak RSS +36 MB) and on an API 36 emulator.

The wrapper serves attachments from the origin that holds the GitHub token
and the notes, so only script-free types open inline (nosniff), the rest
download; writes carrying another site's Origin are refused (this also
closes cross-site overwrites of the backup files); and attachment routes run
concurrently so a long upload or a playing video cannot stall the page.

Also: cupertino_icons 2.0.0 (unused, but the freshness gate blocks the
pubspec otherwise), open_file 4.0.0 and file_picker 13.1.0 pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:17:06 +02:00
0798f7b1d0 Attach images to notes, stored on dufs and kept offline
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 14:11:15 +02:00
753e410fb4 Export the backlog to ~/src/todo, not the pre-reorganisation ~/todo
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
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 17:13:21 +02:00
a0d37c4df8 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
2026-09-11 19:49:03 +02:00
381afd1d77 Split the last three oversized lib/bin files
github_mirror_screen.dart  368 -> 232 + device_code_dialog.dart (109) and
                             a widget part
  wrapper_server.dart        336 -> 223 + wrapper_sync_routes.dart, the two
                             credential-provisioning routes
  bin/todo_desktop.dart      253 -> 199 + lib/desktop/browser_launcher.dart

The fixed port moved into browser_launcher.dart as kWrapperPort, with the
entry point aliasing it, so the "must not move" reason is stated once next
to the code that acts on it.

lib/ and bin/ are now entirely under the cap. The two remaining analyze
infos (bin/todo_desktop.dart cascade_invocations,
capture_screen_sync.dart unnecessary_unawaited) are pre-existing --
verified by analyzing e0eb8b9 in a worktree, which reports the same two.

Verified: 313 tests pass, coverage unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 16:29:35 +02:00
d316563015 Fix desktop Firebase sync: seed a Google-derived session, not a dead password
Some checks are pending
release-apk / release (APK) (push) Waiting to run
The account's password grant is retired fleet-wide (see link_google.py /
seed_session.py in ~/utils/crdt-sync), so desktop's Firebase form could never
have connected regardless of provisioning. Desktop-as-web can't use the
in-browser Google Sign-In button either (google_platform_web.dart hard-codes
googleSignInSupported = false), so it needs the same session-seeding path the
Python daemons already use: seed_session.py now includes todo, and the
wrapper's new /sync-credentials route serves that seeded session to
loadAccount() once per launch, then closes for the rest of the process.

Also stops the status line reporting a bare "Synced" while running over the
GitHub mirror alone with Firebase disconnected -- SyncResult now carries
firebaseConnected so the UI can say "GitHub-only (Firebase not connected)".

Includes the prior session's Google Sign-In one-tap addition
(google_sign_in_backend.dart and friends), which this builds on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPsyKQRX8ZwJpUqg37jp83
2026-08-12 19:35:18 +02:00
be376fd240 Make the sync-account route testable, and cover every branch
Some checks are pending
release-apk / release (APK) (push) Waiting to run
The route read its own configuration from Platform.environment and $HOME, so
the only branch a test could reach was "disabled" -- the serving path, the
missing-config path and the malformed-email path were all unreachable without
mutating the process environment.

Both are now constructor parameters defaulting to the environment, so
production behaviour is unchanged and tests drive all four outcomes: 404 when
disabled, 200 with the account when enabled, 404 when ~/.config/crdt-sync/ is
incomplete, and 404 when firebase.json has no usable email. The password's
trailing newline is stripped, which the test pins.

256 tests, analyzer clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9
2026-08-09 21:49:16 +02:00
3c6c071513 Let the desktop provision its sync account from the wrapper
The desktop app is a Flutter web build in a Chrome --app window, so
FlutterSecureStorage resolves to the web backend: AES-encrypted localStorage
keyed inside the browser profile. That left the sync account settable only by
typing it into the settings screen — nothing outside the page can write that
store (verified: `secret-tool` targets libsecret, which only a Linux build
would read, and a Dart CLI cannot even import flutter_secure_storage since it
is a Flutter plugin that fails to resolve on the VM).

So the account now arrives the same way the GitHub token already does: from
the wrapper, which owns the filesystem and serves the page. loadAccount()
falls back to GET /sync-account when the store is empty and persists the
result, so the route is consulted once per install rather than being
load-bearing forever.

The route reads ~/.config/crdt-sync/{firebase.json,password} — the same pair
the Python daemons already use — so there is one source of truth per machine
instead of a per-app copy.

Off unless CRDT_SYNC_SERVE_ACCOUNT is set, because it hands a credential with
database write access to anything that can reach the port. Verified both
ways against the running wrapper: 404 without the env var, 200 with it, and
the app serves normally either way. It answers 404 rather than 403 when
disabled, so a probe cannot tell the route exists.

clearAccount() now writes an opt-out marker that the fallback checks —
without it, Disconnect would delete the account and the next launch would
silently re-adopt it, making the button look broken.

252 tests, analyzer clean. Pinned to crdt_sync_dart-v0.8.0 for the shared
path/env-var constants and accountFromWrapper().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9
2026-08-09 21:39:16 +02:00
c37a9ef118 Add the desktop wrapper, and stop the test suite eating BACKLOG.md
The desktop app is a browser now, so it cannot write files. This adds the other
half: a loopback HTTP server that serves the web build and owns the two files
the browser cannot touch -- `~/todo/BACKLOG.md`, which the user's tooling and
the todo MCP server read, and a disk copy of the note log so a wiped Chrome
profile is not a total-loss event for unsynced notes.

Notable details:

- Binds to 127.0.0.1 only. The endpoints overwrite files in the user's home
  with no authentication, so a routable bind would let anything on the network
  rewrite the backlog.
- Serves explicit content types. CanvasKit refuses a `.wasm` labelled anything
  else and the app then renders nothing at all.
- Mirroring to disk is debounced. The store persists on every keystroke, so
  mirroring eagerly meant an HTTP request plus a whole-log write per character;
  IndexedDB still gets every keystroke, only the durability copy waits.

Separately, this fixes a pre-existing destructive bug found while testing:
`settings_screen_test` did real file I/O and overwrote the user's canonical
`~/todo/BACKLOG.md` with a single fake note on *every* `flutter test` run. It
went unnoticed because the app rewrites the file whenever it launches. The
export destination is now injectable and the test redirects it to a temp dir;
verified that a full suite run leaves the real file byte-identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:33:25 +02:00