Skip to content

refactor(rust): modularize tc_helper, add typed errors, surface task … - #2

Open
BrawlerXull wants to merge 9 commits into
mainfrom
feat/rust-ffi-overhaul
Open

refactor(rust): modularize tc_helper, add typed errors, surface task …#2
BrawlerXull wants to merge 9 commits into
mainfrom
feat/rust-ffi-overhaul

Conversation

@BrawlerXull

Copy link
Copy Markdown
Owner

…attributes

Overhauls the Rust FFI bridge (Deliverable 2):

Build config

  • Pin Cargo.toml to edition 2021 for broader Rust/flutter_rust_bridge compatibility; add the thiserror dependency.
  • Add rust/build.rs: rerun-on-source-change tracking, target-platform detection (exposed via the TC_HELPER_PLATFORM compile-time env), and an opt-in (FRB_CODEGEN=1) binding-regeneration trigger.

Modularization & typed errors

  • Extract the duplicated storage-open boilerplate into storage.rs, the task serializer into serialize.rs, and a thiserror-based TcHelperError into utils/error.rs. api.rs is now a thin FFI layer of delegators.
  • Remove every .unwrap() panic; each FFI entry point returns Result<_, String> so flutter_rust_bridge surfaces failures to Dart as catchable exceptions with a real message, instead of relying on panics.

Surfaced task attributes

  • The serializer now emits annotations, dependencies (depends), is_blocked, is_blocking, and recur, which the Dart TaskForReplica model previously anticipated but never received. TaskForReplica gains those fields + parsing (reusing the existing Annotation model).
  • urgency is intentionally NOT surfaced: TaskChampion 2.0.3 does not compute or store an urgency value on Task, so there is nothing authoritative to expose.

Regenerated the flutter_rust_bridge bindings for the new signatures (getAllTasksJson stays Future; the mutating calls are now Future, throwing on error — all existing Dart callers ignored the old int return).

Deferred (needs mentor coordination + CI cross-compilation, not doable here): purging the tracked android jniLibs *.so binaries. There is currently no Android cargo/cargo-ndk integration, so Gradle bundles the committed .so directly; removing them before automated cross-compilation exists would break Android builds.

cargo build + cargo test: pass. flutter analyze: 0 errors. Full test suite unchanged at +317 -20 (pre-existing headless failures only). Runtime FFI behaviour still needs validation via an on-device build.

Description

Please include a summary of the change and which issue is fixed. List any dependencies that are required for this change.

Fixes #(issue_no)

Replace issue_no with the issue number which is fixed in this PR

Screenshots

Checklist

  • Tests have been added or updated to cover the changes
  • Documentation has been updated to reflect the changes
  • Code follows the established coding style guidelines
  • All tests are passing

…attributes

Overhauls the Rust FFI bridge (Deliverable 2):

Build config
- Pin Cargo.toml to edition 2021 for broader Rust/flutter_rust_bridge
  compatibility; add the thiserror dependency.
- Add rust/build.rs: rerun-on-source-change tracking, target-platform
  detection (exposed via the TC_HELPER_PLATFORM compile-time env), and an
  opt-in (FRB_CODEGEN=1) binding-regeneration trigger.

Modularization & typed errors
- Extract the duplicated storage-open boilerplate into storage.rs, the task
  serializer into serialize.rs, and a thiserror-based TcHelperError into
  utils/error.rs. api.rs is now a thin FFI layer of delegators.
- Remove every .unwrap() panic; each FFI entry point returns Result<_, String>
  so flutter_rust_bridge surfaces failures to Dart as catchable exceptions with
  a real message, instead of relying on panics.

Surfaced task attributes
- The serializer now emits annotations, dependencies (depends), is_blocked,
  is_blocking, and recur, which the Dart TaskForReplica model previously
  anticipated but never received. TaskForReplica gains those fields + parsing
  (reusing the existing Annotation model).
- urgency is intentionally NOT surfaced: TaskChampion 2.0.3 does not compute or
  store an urgency value on Task, so there is nothing authoritative to expose.

Regenerated the flutter_rust_bridge bindings for the new signatures
(getAllTasksJson stays Future<String>; the mutating calls are now Future<void>,
throwing on error — all existing Dart callers ignored the old int return).

Deferred (needs mentor coordination + CI cross-compilation, not doable here):
purging the tracked android jniLibs *.so binaries. There is currently no
Android cargo/cargo-ndk integration, so Gradle bundles the committed .so
directly; removing them before automated cross-compilation exists would break
Android builds.

cargo build + cargo test: pass. flutter analyze: 0 errors. Full test suite
unchanged at +317 -20 (pre-existing headless failures only). Runtime FFI
behaviour still needs validation via an on-device build.
…rust_bridge to exact 2.11.1

Adds test_dependencies_and_annotations_surface asserting depends[]/is_blocked/is_blocking and annotation entry(RFC3339)+description. Pins flutter_rust_bridge (was ^2.11.1) to avoid resolving 2.12.0, which crashes at RustLib.init().
The committed .so predated the serializer overhaul (Feb build vs June source), so the app ran stale native code. Rebuilt via cargo-ndk. Stopgap until CI compiles from source (see build-tc-helper.yml).
- home_page_body: reactively read tasksFromReplica in the Obx so the replica list rebuilds when the async FFI fetch completes (was empty: taskReplica flips true before the list is populated).
- task detail: surface annotations/depends/is_blocked/is_blocking/recur (read-only) for replica tasks.
- safe_tour: guard tutorial_coach_mark.show() against unmounted target keys (fixes recurring 'obtain target position (null)' FormatException); applied to profile + manage-task-server tours.
Regenerates the native lib from rust/ on each run so it can't go stale; builds the APK against it. Unverified on CI. armeabi-v7a left as a documented TODO (needs aws-lc-sys bindgen).
The Taskchampion (replica) home never showed tasks despite the FFI returning them. Four compounding bugs:
- show_tasks_replica wrapped its body in an Obx that read no observable -> ObxError -> blank error widget. Reactivity now lives in the parent (home_page_body) Obx, so this is a plain build.
- home_controller.onInit never initialized pendingFilter/waitingFilter from their persisted values, so pendingFilter defaulted false and the list filtered for completed tasks only, hiding all pending ones.
- the project filter treated an empty-string projectFilter ('') as an active filter, dropping every task whose project is null.
Verified on-device: replica home lists tasks; detail view surfaces annotations/depends/is_blocked/is_blocking/recur.
…gen; rebuild both ABIs

32-bit ARM has no pre-generated aws-lc-sys bindings, so it needs the crate's 'bindgen' feature (libclang). Scoped that feature to ONLY [target.armv7-linux-androideabi] so arm64/iOS/host keep using pre-generated bindings (forcing bindgen globally broke the iOS link). Rebuilt both Android ABIs from source; CI installs libclang + builds both. Host cargo test green.
tc_helper only ever uses the remote TaskChampion sync server (ServerConfig::Remote). taskchampion's default 'sync' feature also pulled server-aws + server-gcp, dragging in the entire AWS + Google-Cloud SDKs and aws-lc-rs/aws-lc-sys. Switched to default-features=false + [server-sync, bundled] (ureq + ring).

Wins:
- iOS xcframework now builds (aws-lc-sys 0.30.0 failed to cross-compile PQ/kyber for iOS); rebuilt device arm64 + fat simulator from the new Rust.
- No more bindgen hack for 32-bit ARM (aws-lc gone); dropped libcrc_fast entirely.
- libtc_helper.so 29M->6.2M (arm64), 20M->4.7M (v7a); release APK 60M->36M.
- Smaller dependency/attack surface; faster builds; simpler CI (no cmake/ninja/libclang).

Verified: host cargo test (2) pass; both Android ABIs + all 3 iOS targets build; real-server sync returns Ok() (ureq+ring); on-device the replica home lists tasks + detail rows render with the new lib, no RustLib version crash.
The search box only fed the local-taskc list (searchedTasks); the replica
view (TaskReplicaViewBuilder) read tasksFromReplica directly and ignored the
query, so typing in search did nothing in TaskChampion mode.

Add a reactive searchQuery (kept in sync by search()/toggleSearch()) and
filter the replica snapshot by task.description in home_page_body's Obx, so
the list narrows on each keystroke and restores when the query is cleared.
Matching is case-insensitive substring, consistent with the taskc path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant