feat: #36 expose invalidate_forest_cache in fula-flutter + fula-js bindings (0.6.9)#37
Merged
Merged
Conversation
…+ fula-js
The encrypted client caches each bucket forest for the client lifetime,
so a long-lived session never observes another device's uploads -
listings and downloads resolve against the session-stale index
indefinitely (FxFiles repro: a mobile web tab kept a 2-file listing
while desktop had uploaded a 3rd). The dirty-safe escape hatch
(EncryptedClient::invalidate_forest_cache / invalidate_all_forest_caches,
encryption.rs:9015/:9029) existed but was unreachable from Dart and JS.
- fula-flutter: invalidate_forest_cache(client, bucket) +
invalidate_all_forest_caches(client) bridge fns (guard read ->
call through); Dart bindings regenerated with the pinned
flutter_rust_bridge_codegen 2.11.1 (invalidateForestCache /
invalidateAllForestCaches on api/forest); flutter analyze clean.
- fula-js: same two exports (invalidateForestCache /
invalidateAllForestCaches) for npm parity; CI's wasm .d.ts export
check now guards both.
Tests:
- fula-client/tests/issue_36_invalidate_forest_cache.rs pins the
acceptance criteria at the layer both bindings delegate to, against
the stateful conditional-PUT mock master:
* stale listing on an EXISTING client refreshes after invalidate +
re-list (sees another device's upload; no client rebuild) - and
pins the lifetime-pinning premise (without invalidate the listing
stays stale);
* a dirty (unsaved) forest is NOT evicted - pending entry and
has_pending_forest_changes survive; after flush, invalidation
takes effect and the persisted file is still listed;
* invalidate_all drops clean forests (they refresh) and keeps dirty
ones.
- fula-flutter/tests/issue_36_invalidate_bridge_test.rs pins the
binding wiring: fns live on the FRB-scanned crate::api surface and
are safe idempotent no-ops on a client with no loaded forest.
Fixes #36
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ndings) Workspace + fula-js + fula-flutter crate versions, fula_client pubspec/ podspec, changelog entry. The fula-js / fula-wasm npm packages keep their own 0.2.x version line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…36) invalidate_forest_cache had a TOCTOU window between the is_dirty read and the remove(): a concurrent deferred put could dirty the entry in-between and the eviction would silently drop the pending index entry. Reachable in practice now that #36 exposes the call to app refresh paths running concurrently with uploads. DashMap::remove_if holds the shard lock across predicate + removal, closing the window. (invalidate_all_forest_caches already used the atomic retain().) Also adds invalidate_is_per_bucket_isolated: invalidating bucket A refreshes A and must NOT evict bucket B (B stays session-pinned until its own invalidate) - guards the cache keying in both directions. Flagged by external review (Gemini) during pre-merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #36 — the encrypted client caches each bucket's forest for the client lifetime, so a long-lived session never observes another device's uploads:
list_from_forest/get_object_flatkeep resolving against the session-stale index (FxFiles repro: a mobile web tab kept showing a 2-file listing indefinitely while desktop had uploaded a third). The dirty-safe escape hatch —EncryptedClient::invalidate_forest_cache/invalidate_all_forest_caches(encryption.rs:9015/:9029) — existed but was unreachable from Dart and JS.Verified not addressed in 0.6.8 before starting: that release (#35) fixed #34 entirely inside
fula-crypto; grep confirms zeroinvalidatereferences in either binding crate onmain. (0.6.8's two-client reconcile only covers stale writers via the 412 path; pure readers stay pinned forever, exactly as the issue reports.)What this adds
crates/fula-flutter/src/api/forest.rs):invalidate_forest_cache(client, bucket)andinvalidate_all_forest_caches(client)— guard read → call through, exactly the issue's sketch. Docs spell out the dirty-safe contract (pending changes are never evicted; flush first) and the intended app wiring (pull-to-refresh / tab-resume / reconnect / SWR revalidation).flutter_rust_bridge_codegen 2.11.1— same as the hash-consistent release pipeline):invalidateForestCache/invalidateAllForestCachesnow exist onlib/src/api/forest.dart.flutter analyze: no issues. Diff is purely additive (new functions + their generated glue).crates/fula-js/src/lib.rs): same two exports,invalidateForestCache/invalidateAllForestCaches, and CI's wasm.d.tsexport verification now guards both so parity can't silently regress.Tests
crates/fula-client/tests/issue_36_invalidate_forest_cache.rs— acceptance criteria pinned at the layer both bindings delegate to, against the stateful conditional-PUT mock master (3/3 passing):invalidate_forest_cache+ re-list on the same client instance → 3 files, and B's file downloads byte-exactly. No client rebuild.has_pending_forest_changesstays true and the pending entry stays listed; afterflush_forest, invalidation takes effect and the reloaded forest still lists the persisted file.invalidate_all_forest_cachesdrops clean forests (they refresh and see the other device's upload) and keeps dirty ones.crates/fula-flutter/tests/issue_36_invalidate_bridge_test.rs— pins the binding wiring: the functions live on the FRB-scannedcrate::apisurface and are safe, idempotent no-ops on a client with no loaded forest (the "call unconditionally from every revalidation path" pattern).Validation
cargo test -p fula-client --test issue_36_invalidate_forest_cache— 3 passedcargo test -p fula-flutter— all suites green incl. the new bridge testcargo check -p fula-js --target wasm32-unknown-unknown/-p fula-flutter --target wasm32-unknown-unknown/ native — cleanflutter analyzeonpackages/fula_client— no issuesVersion
0.6.8 → 0.6.9(workspace, fula-js + fula-flutter crates, fula_client pubspec/podspec, changelog). The fula-js/fula-wasm npm packages keep their own 0.2.x line.Consumer side: FxFiles'
FulaApiService.invalidateForestCache(bucket)(commit cfaa185) is already wired to call this on every SWR revalidation/force path — it picks the Rust call up as soon as 0.6.9 ships, replacing the interim full-client-rebuild workaround.🤖 Generated with Claude Code