This cycle is all about pressure-testing the foundation with three separate audits - performance,
security, and accessibility. It's rounded out with a top-to-bottom rewrite of the Rust FFI layer
for the modern flutter_rust_bridge v2 idioms.
On the Flutter side, the Riverpod 3.x graph had quietly grown bigger than we were tracking, and a lot
of widgets were watching whole notifiers when they only cared about one field. We swept through with
fine-grained .select(...) selectors wherever it mattered - sync indicators, the editor's
status badges, the lock screen - and the difference in profiler frames is honest. The pending-changes
provider and the current-branch provider now also ref.watch the active repo path, so they
reset automatically when users switch repos instead of leaking stale data into the new session.
On the Rust side, we threw out every catch_unwind in favor of typed
Result<T, GitError> errors via thiserror, with a
scrub_credentials pass that strips tokens out of any error message before it crosses the
FFI boundary. The SSL init path is now guarded by OnceLock so the
unsafe { set_var } calls only ever run on a single thread, exactly once per process.
Clone progress events are throttled to ~10 Hz and abort libgit2 immediately if the Dart stream closes
- no more multi-GB downloads continuing in the background after users navigate away. And
git_checkout_branch now refuses to clobber a dirty working tree unless the caller
explicitly passes force = true.
Security got the biggest list. We pulled git service client IDs out of source in favor of
String.fromEnvironment defines, swapped OAuth from the older plain-PKCE method to
S256 with full state validation, and migrated subscription state out of
SharedPreferences into hardened FlutterSecureStorage. Subscription receipts
are now HMAC-signed in the local cache so a tampered file fails closed instead of silently unlocking
Pro subscription. We also closed a path-traversal hole in the file repository: any path containing
.. or pointing outside the repo root is rejected before it ever reaches the filesystem.
Accessibility was less dramatic but probably the most important for real users. Every
GestureDetector and bare IconButton now lives inside a
Semantics widget with a real label, the lock screen wraps everything beneath it in
ExcludeSemantics so screen readers can't read through the lock, and the editor's font
size respects the system text scaler with a sensible 2.0× cap. Status transitions like "Saved" and
"Synced" get announced exactly once via SemanticsService.announce instead of being
shouted by a noisy liveRegion on every animation tick.
The biggest under-the-hood change is a process-wide repository cache. Every Git operation used to
re-open the libgit2 Repository from disk, which on Android meant walking
.git/, parsing config, and mapping the index on every single call. The cache keeps one
Arc<Mutex<Repository>> per canonicalized repo path for the lifetime of the
process. Same repo serializes (which is libgit2's contract anyway), different repos run fully in
parallel. The repository list loads visibly faster - back-to-back status +
log + remoteHeadSha calls per repo now share a single handle instead of
paying the open cost three times. We also exposed git_show_file_bytes returning a
Vec<u8> that maps zero-copy onto Dart's Uint8List, so binary previews
(PNG, PDF, anything non-text) can land in the next release without a second FFI round-trip.