Skip to content

Integration/d1 d2 d5#652

Open
BrawlerXull wants to merge 20 commits into
CCExtractor:mainfrom
BrawlerXull:integration/d1-d2-d5
Open

Integration/d1 d2 d5#652
BrawlerXull wants to merge 20 commits into
CCExtractor:mainfrom
BrawlerXull:integration/d1-d2-d5

Conversation

@BrawlerXull

@BrawlerXull BrawlerXull commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

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

Summary by CodeRabbit

  • New Features
    • Added replica task search and client-side filtering in Home.
    • Enriched replica task details (dependencies, recurrence, annotations, blocked/blocking) and improved replica sorting.
    • Updated sync-server wording and credential fields to TaskChampion across supported languages.
    • Added safer tutorial/tour display to avoid crashes when screens/targets aren’t ready.
    • Introduced the project website with docs, downloads, features, and nightly build history.
  • Bug Fixes
    • Task complete/delete actions now update local data only.
    • Replica refresh now reloads from local data.
  • Chores
    • Automated nightly signed APK builds and website deployment, including nightly build log updates.

Removes the deprecated HTTP sync path so all synchronization flows through
the native TaskChampion Rust FFI bridge, matching the single-path target
architecture.

- Delete lib/app/v3/net/{fetch,add_task,complete,delete,modify,origin}.dart
  and lib/app/v3/db/update.dart (the HTTP push-sync helper).
- Rewrite saveCredentials() to validate credentials via the native sync_()
  FFI call instead of an HTTP GET /tasks probe; drop the taskReplica branch.
- Remove the taskc-HTTP sync call sites (home_controller.refreshTasks and its
  callers, show_tasks, taskc_details, home_page_app_bar) while preserving all
  local SQLite operations.
- Rebrand the 8 ccsync* localization keys to syncServer* and reword copy from
  "CCSync" to "TaskChampion sync server" across all 9 language files; collapse
  the mode-branched sync-URL label to the single path.
- Drop the obsolete HTTP fetchTasks test group and its generated mocks; realign
  the localization snapshot tests to the reworded strings.

http stays in pubspec (still used by pushNotification_service.dart).
flutter analyze: 0 errors. No test regressions vs main (pre-existing headless
plugin/MethodChannel failures unchanged).
Follow-up hardening after an adversarial review of the CCSync retirement:

- saveCredentials(): validate the entered credentials with sync_() BEFORE
  persisting them via setTaskcCreds(), so invalid credentials are never
  written to the active profile when validation fails. (The prior order —
  which mirrored the proposal's example — persisted first, leaving bad creds
  on disk while telling the user the check failed.) Validation is a live sync,
  so saving now requires connectivity to the sync server.
- Remove the orphaned taskchampionBackendUrl localization getter from the
  abstract Sentences class and all 8 locale implementations; it became dead
  when the credentials view's mode-branched URL label collapsed to the single
  syncServerBackendUrl path.
- Fix a stray non-Urdu token ("używaj") left in syncServerEasySyncTitle on a
  line the rebrand already touched.

flutter analyze: 0 errors. Full suite unchanged at +317 -18 (no new failures).
…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.
Adds a self-contained Hugo site and the CI to publish it (Deliverable 5).

Site (website/)
- Themeless Hugo site (all layouts in-repo, no submodule/theme fetch): landing
  page, downloads page, and a docs skeleton, with a small dark stylesheet.
- A `nightly-builds` shortcode renders the build log; the downloads page shows
  the most recent nightly APKs, newest first, with per-build status.
- Ships a custom-domain CNAME (taskwarrior.ccextractor.org).

Build log
- scripts/update_build_log.py prepends {ts, sha, msg, status, artifact, build}
  to website/data/nightly_builds.json (keeps the 90 most recent). The file ships
  empty ([]) and is populated by CI.

CI
- build-nightly.yml: daily at 02:00 UTC (+ manual) — builds the signed nightly
  APK, records the result via the collector, and commits the log to main.
- deploy-website.yml: on pushes touching website/** — builds with Hugo and
  deploys to GitHub Pages via actions/deploy-pages.
- nightlydepolyci.yml: ignore website/** and scripts/** so a build-log commit
  does not trigger a redundant APK rebuild.

Verified locally: warning-free `hugo --minify` build (Hugo 0.164.0), and the
collector + shortcode round-trip (entry renders on the downloads page; empty
state renders when the log is []).

Deviation from the proposal, documented in website/README.md: uses the modern
GitHub Actions Pages deployment instead of renaming fdroid-repo -> public-site,
so the site source stays in website/ on main with no dedicated deploy branch.
Manual infra steps (enable Pages, DNS, branch protection) are listed there.
Follow-up to the CCSync retirement: the profile page still surfaced the
"CCSync" brand -- the "changed profile mode to CCSync" snackbar label and a
commented-out "CCSync (v3)" mode radio. Rebrand the label to "Taskchampion"
(matching the retained TW3C mode) and delete the dead commented block.
pubspec used a caret constraint, so `flutter pub get` could resolve the newer
2.12.0 runtime while the committed FFI bindings were generated with codegen
2.11.1. That mismatch crashed the app at RustLib.init() on startup. Pinning the
exact 2.11.1 (matching rust/Cargo.toml's =2.11.1) keeps the Dart package,
generated bindings, and native library in lockstep.
…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.
Expand the site beyond the initial skeleton:
- New Features page (wired into the nav) covering the full task model,
  offline-first behaviour, native TaskChampion sync, and cross-platform reach.
- New Docs guides that auto-list on /docs/: getting-started, sync-setup,
  architecture (with a data-flow diagram), and an FAQ.
- Home page: three more feature cards + an "Explore all features" link.
- CSS: style markdown tables, code blocks, and blockquotes on content pages.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83e04b7b-3ee8-4d1d-a73b-6f6d6789c5ef

📥 Commits

Reviewing files that changed from the base of the PR and between 7b3116c and d12b6b0.

📒 Files selected for processing (2)
  • lib/app/modules/home/views/show_tasks_replica.dart
  • lib/app/v3/champion/models/task_for_replica.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/app/v3/champion/models/task_for_replica.dart

📝 Walkthrough

Walkthrough

The change migrates TaskChampion synchronization into the Rust bridge, exposes enriched replica task fields, updates Flutter sync and task views, renames sync-server localization, adds safer tours, and introduces nightly APK plus Hugo website automation.

Changes

TaskChampion synchronization and Flutter integration

Layer / File(s) Summary
Rust TaskChampion core and bridge
rust/*, lib/rust_bridge/*, pubspec.yaml
Adds centralized replica access, structured errors, enriched task serialization, Result-based operations, and updated Flutter Rust Bridge codecs.
Enriched replica task data
lib/app/v3/champion/*, lib/app/modules/taskc_details/*
Adds dependencies, recurrence, annotations, blocked state, model serialization, and task-detail rendering.
Local replica home flow
lib/app/modules/home/*
Uses local replica reloads, filters replica tasks through reactive search state, initializes persisted filters, and removes legacy remote actions.
Sync-server credentials and terminology
lib/app/modules/manageTaskChampionCreds/*, lib/app/utils/language/*, test/*
Validates credentials through native sync, renames controllers and localization getters, updates translations, and aligns tests.
Safe tours and profile modes
lib/app/tour/*, lib/app/modules/profile/*
Adds guarded tour display and updates profile mode labels and deprecated CCSync UI.

Build automation and website

Layer / File(s) Summary
Native and nightly build automation
.github/workflows/*, scripts/update_build_log.py
Adds signed nightly APK logging, source-built Android native artifacts, production APK uploads, and workflow path exclusions.
Hugo website platform
website/hugo.toml, website/README.md, website/data/*, website/static/CNAME, .github/workflows/deploy-website.yml
Adds Hugo configuration, Pages deployment, custom-domain metadata, build-log storage, and website operating documentation.
Website content and presentation
website/content/*, website/layouts/*, website/static/css/*
Adds homepage, documentation, downloads, feature pages, layouts, nightly-build rendering, and dark responsive styling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ManageTaskChampionCredsController
  participant RustBridge
  participant TaskChampionReplica
  User->>ManageTaskChampionCredsController: submit sync credentials
  ManageTaskChampionCredsController->>RustBridge: call sync_
  RustBridge->>TaskChampionReplica: open replica and synchronize
  TaskChampionReplica-->>RustBridge: success or string error
  RustBridge-->>ManageTaskChampionCredsController: complete or throw
  ManageTaskChampionCredsController-->>User: persist credentials or report failure
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is just the unfilled template and lacks an actual change summary, issue number, dependencies, screenshots, and checklist updates. Replace the template text with a real summary, add the fixed issue number, list any dependencies, and mark relevant checklist items completed.
Title check ❓ Inconclusive The title is too generic to describe the main change and doesn't indicate the replica task sorting work. Use a specific title like "Improve TaskChampion replica task sorting" to reflect the primary change.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (9)
scripts/update_build_log.py (2)

45-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify UTF-8 encoding for file operations.

Relying on the system's default encoding can lead to UnicodeDecodeError or corrupted data on platforms where the default encoding is not UTF-8 (e.g., Windows), especially when handling JSON.

Explicitly specify encoding="utf-8" for both read and write operations.

♻️ Proposed fix
-            entries = json.loads(LOG.read_text() or "[]")
+            entries = json.loads(LOG.read_text(encoding="utf-8") or "[]")
         except json.JSONDecodeError:
             entries = []
 
     entries.insert(0, entry)
     entries = entries[:MAX_ENTRIES]
 
     LOG.parent.mkdir(parents=True, exist_ok=True)
-    LOG.write_text(json.dumps(entries, indent=2) + "\n")
+    LOG.write_text(json.dumps(entries, indent=2) + "\n", encoding="utf-8")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/update_build_log.py` around lines 45 - 53, Update the file operations
in the script’s read and write paths to explicitly use UTF-8 encoding by passing
encoding="utf-8" to each open call, including JSON handling.

60-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Safeguard argument unpacking.

Unpacking sys.argv[1:] directly into main will raise a TypeError if the script receives more than 3 arguments.

Consider slicing the arguments to a maximum of 3, or using the standard argparse module for robustness.

♻️ Proposed fix
 if __name__ == "__main__":
-    main(*sys.argv[1:])
+    main(*sys.argv[1:4])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/update_build_log.py` around lines 60 - 61, Update the script’s
entry-point invocation of main to limit sys.argv[1:] to the maximum three
arguments accepted by main, preventing excess command-line arguments from
causing a TypeError while preserving the existing argument behavior.
website/static/css/style.css (2)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Include pseudo-elements in the box-sizing reset.

Standard practice is to include *::before and *::after to ensure pseudo-elements also respect the border-box sizing model, avoiding unexpected sizing behaviors.

♻️ Proposed refactor
-* { box-sizing: border-box; }
+*, *::before, *::after { box-sizing: border-box; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/static/css/style.css` at line 15, Update the global box-sizing reset
in the stylesheet to include the *::before and *::after pseudo-elements
alongside the universal selector, ensuring all elements use border-box sizing.

17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Include the standard text-size-adjust property.

For broader browser compatibility, it is recommended to include the standard, unprefixed text-size-adjust property alongside the -webkit- prefixed version.

♻️ Proposed refactor
-html { -webkit-text-size-adjust: 100%; }
+html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/static/css/style.css` at line 17, Update the text sizing rule in
style.css to include the standard unprefixed text-size-adjust property alongside
the existing -webkit-text-size-adjust declaration, preserving the current value.
.github/workflows/build-nightly.yml (1)

22-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pin third-party GitHub Actions to a specific version or commit SHA.

Using @main for third-party actions can introduce unexpected breaking changes or security risks. Consider pinning jlumbroso/free-disk-space to a stable release tag (e.g., @v1) or a specific commit SHA.

🛠️ Proposed fix to use a stable version tag
-      - uses: jlumbroso/free-disk-space@main
+      - uses: jlumbroso/free-disk-space@v1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-nightly.yml around lines 22 - 23, Update the “Free
Disk Space (Ubuntu)” workflow step to pin jlumbroso/free-disk-space to a stable
release tag or immutable commit SHA instead of the moving `@main` reference.
lib/app/v3/champion/models/task_for_replica.dart (1)

43-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use full null-aware chaining to prevent potential runtime errors.

In Dart, method cascades do not implicitly short-circuit. If value is ever null, value?.toString() evaluates to null, and calling .toLowerCase() directly on it will throw a NoSuchMethodError.

Although the current implementation ensures value is not null before calling this method, using full null-aware chaining (?.) provides safer future-proofing.

♻️ Proposed refactor
   static bool _parseBool(dynamic value) {
     if (value is bool) return value;
-    return value?.toString().toLowerCase() == 'true';
+    return value?.toString()?.toLowerCase() == 'true';
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/v3/champion/models/task_for_replica.dart` around lines 43 - 46,
Update the value normalization expression in the relevant task model to use
null-aware chaining through the string conversion and lowercase call, so a null
value remains null instead of invoking toLowerCase on it. Preserve the existing
behavior for non-null values.
lib/app/modules/profile/controllers/profile_controller.dart (1)

49-61: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Check if context is mounted after async gaps.

BuildContext is used here after multiple async gaps (a 500ms delay followed by awaiting getProfileTourStatus). If the user navigates away from the profile page before this completes, passing an unmounted context to safeShowTour may cause a runtime error or trigger a Flutter lint warning for using BuildContext across async gaps.

Consider adding a context.mounted check before using the context.

♻️ Proposed fix
       () async {
         final seen = await SaveTourStatus.getProfileTourStatus();
         if (seen) return;
+        if (!context.mounted) return;
         await safeShowTour(
           tutorialCoachMark: tutorialCoachMark,
           context: context,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/modules/profile/controllers/profile_controller.dart` around lines 49
- 61, In the profile tour flow, add a context.mounted check after the delay and
getProfileTourStatus await operations and before passing context to
safeShowTour. Return or skip the tour when the context is unmounted, while
preserving the existing behavior for mounted contexts.
lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart (1)

81-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optimize the URL text field for better user experience.

Consider adding keyboardType: TextInputType.url and disabling autocorrect for this text field. This will present a URL-optimized keyboard on mobile devices and prevent the OS from attempting to auto-correct domain names or paths.

💡 Proposed UX improvement
                   TextField(
                     style: TextStyle(color: tColors.primaryTextColor),
                     controller: controller.syncServerUrlController,
+                    keyboardType: TextInputType.url,
+                    autocorrect: false,
                     decoration: InputDecoration(
                       labelText: SentenceManager(
                               currentLanguage: AppSettings.selectedLanguage)
                           .sentences
                           .syncServerBackendUrl,
                       labelStyle: TextStyle(color: tColors.primaryTextColor),
                       border: const OutlineInputBorder(),
                     ),
                   ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart`
around lines 81 - 92, Update the URL text field in the manage task champion
credentials view to use TextInputType.url and disable autocorrect, while
preserving its existing controller, validation, and other behavior.
rust/src/utils/error.rs (1)

12-25: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Do not expose raw paths and backend errors through the FFI message.

These Display implementations forward filesystem paths and arbitrary backend error text to Dart. That can leak local paths or internal service details through UI, logs, or exception telemetry. Return sanitized user-facing messages and retain diagnostic details only in controlled Rust-side logging; please verify the downstream Dart error handling before merging.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/utils/error.rs` around lines 12 - 25, Sanitize the Display
implementations in rust/src/utils/error.rs so FFI-facing messages do not include
raw filesystem paths or backend error text. Replace interpolated diagnostic
details with stable user-facing messages, while retaining full details only in
controlled Rust-side logging. Verify downstream Dart error handling continues to
receive and process the sanitized messages correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/app/modules/home/views/home_page_app_bar.dart`:
- Line 189: Update the refresh flow around fetchTasksFromDB so the local
database reload executes regardless of whether sync credentials c and e are
present. Keep credential validation limited to synchronization, and only report
“Sync Completed” when an actual synchronization occurred, not for local-only
refreshes.

In
`@lib/app/modules/manage_task_champion_creds/controllers/manage_task_champion_creds_controller.dart`:
- Around line 35-57: Update saveCredentials so credential validation does not
call sync_ against the active replicaPath. Create and use a temporary empty
replica for the validation sync, then discard it after completion, or use an
available non-mutating native credential-check API; only persist credentials
after validation succeeds and ensure temporary resources are cleaned up on
success or failure.

In
`@lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart`:
- Around line 225-238: Update the async callback around
SaveTourStatus.getManageTaskServerTourStatus to verify the widget context is
still mounted after the await and before calling safeShowTour. Return early when
context is unmounted, while preserving the existing seen-status guard and tour
behavior.

In `@lib/app/modules/taskc_details/views/taskc_details_view.dart`:
- Around line 120-150: Localize the detail labels and fallback values used by
the replica-task view, including Blocked, Blocking, Depends, Recur, Annotations,
Yes, No, and None. Add corresponding entries to the app’s localization resources
and update the view’s display logic to retrieve them through the existing
localization mechanism instead of hardcoded English strings.

In `@lib/app/tour/safe_tour.dart`:
- Around line 27-34: Update safeShowTour’s markSeen callback to use a
best-effort error-handling helper so persistence failures are caught and cannot
escape the method. Preserve the tour display flow, and separately log or report
markSeen failures as appropriate.

In `@lib/app/utils/language/german_sentences.dart`:
- Around line 12-13: Correct the German spelling in the relevant sentence or
localization entry by replacing “erhälst” with “erhältst”; leave all surrounding
text and behavior unchanged.

In `@lib/app/v3/champion/models/task_for_replica.dart`:
- Around line 174-178: Update TaskForReplica’s equality operator to compare
annotations and project in addition to the already included fields. Update
TaskForReplica.hashCode to incorporate the same fields, ensuring equality and
hashing remain consistent.

In `@rust/src/api.rs`:
- Around line 79-82: Update the mutation paths at the referenced locations so a
missing update target is converted from None into a TaskNotFound error before
applying operations. Do not skip the mutation block and return Ok(()) when the
target is absent; preserve successful updates for existing targets.

In `@rust/src/serialize.rs`:
- Around line 40-60: Update the serialization flow in rust/src/serialize.rs to
sort tags, dependencies, and annotations before assigning them to the JSON
object. Ensure each collection produced by task.get_taskmap(), get_dependencies,
or get_annotations has a stable ordering while preserving the existing
serialized values and structure.

---

Nitpick comments:
In @.github/workflows/build-nightly.yml:
- Around line 22-23: Update the “Free Disk Space (Ubuntu)” workflow step to pin
jlumbroso/free-disk-space to a stable release tag or immutable commit SHA
instead of the moving `@main` reference.

In
`@lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart`:
- Around line 81-92: Update the URL text field in the manage task champion
credentials view to use TextInputType.url and disable autocorrect, while
preserving its existing controller, validation, and other behavior.

In `@lib/app/modules/profile/controllers/profile_controller.dart`:
- Around line 49-61: In the profile tour flow, add a context.mounted check after
the delay and getProfileTourStatus await operations and before passing context
to safeShowTour. Return or skip the tour when the context is unmounted, while
preserving the existing behavior for mounted contexts.

In `@lib/app/v3/champion/models/task_for_replica.dart`:
- Around line 43-46: Update the value normalization expression in the relevant
task model to use null-aware chaining through the string conversion and
lowercase call, so a null value remains null instead of invoking toLowerCase on
it. Preserve the existing behavior for non-null values.

In `@rust/src/utils/error.rs`:
- Around line 12-25: Sanitize the Display implementations in
rust/src/utils/error.rs so FFI-facing messages do not include raw filesystem
paths or backend error text. Replace interpolated diagnostic details with stable
user-facing messages, while retaining full details only in controlled Rust-side
logging. Verify downstream Dart error handling continues to receive and process
the sanitized messages correctly.

In `@scripts/update_build_log.py`:
- Around line 45-53: Update the file operations in the script’s read and write
paths to explicitly use UTF-8 encoding by passing encoding="utf-8" to each open
call, including JSON handling.
- Around line 60-61: Update the script’s entry-point invocation of main to limit
sys.argv[1:] to the maximum three arguments accepted by main, preventing excess
command-line arguments from causing a TypeError while preserving the existing
argument behavior.

In `@website/static/css/style.css`:
- Line 15: Update the global box-sizing reset in the stylesheet to include the
*::before and *::after pseudo-elements alongside the universal selector,
ensuring all elements use border-box sizing.
- Line 17: Update the text sizing rule in style.css to include the standard
unprefixed text-size-adjust property alongside the existing
-webkit-text-size-adjust declaration, preserving the current value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e1b35bd-e393-47d8-8832-f3d41ab86bc1

📥 Commits

Reviewing files that changed from the base of the PR and between f058b4a and feb7d80.

⛔ Files ignored due to path filters (6)
  • android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b3182f249ae653b7.so is excluded by !**/*.so
  • android/app/src/main/jniLibs/arm64-v8a/libtc_helper.so is excluded by !**/*.so
  • android/app/src/main/jniLibs/armeabi-v7a/libcrc_fast-c8bd19aec5c73f3a.so is excluded by !**/*.so
  • android/app/src/main/jniLibs/armeabi-v7a/libtc_helper.so is excluded by !**/*.so
  • pubspec.lock is excluded by !**/*.lock
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (82)
  • .github/workflows/build-nightly.yml
  • .github/workflows/build-tc-helper.yml
  • .github/workflows/deploy-website.yml
  • .github/workflows/nightlydepolyci.yml
  • ios/tc_helper.xcframework/Info.plist
  • ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helper
  • ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/tc_helper
  • lib/app/modules/home/controllers/home_controller.dart
  • lib/app/modules/home/views/home_page_app_bar.dart
  • lib/app/modules/home/views/home_page_body.dart
  • lib/app/modules/home/views/show_tasks.dart
  • lib/app/modules/home/views/show_tasks_replica.dart
  • lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart
  • lib/app/modules/manage_task_champion_creds/controllers/manage_task_champion_creds_controller.dart
  • lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart
  • lib/app/modules/profile/controllers/profile_controller.dart
  • lib/app/modules/profile/views/profile_view.dart
  • lib/app/modules/taskc_details/controllers/taskc_details_controller.dart
  • lib/app/modules/taskc_details/views/taskc_details_view.dart
  • lib/app/tour/safe_tour.dart
  • lib/app/utils/language/bengali_sentences.dart
  • lib/app/utils/language/english_sentences.dart
  • lib/app/utils/language/french_sentences.dart
  • lib/app/utils/language/german_sentences.dart
  • lib/app/utils/language/hindi_sentences.dart
  • lib/app/utils/language/marathi_sentences.dart
  • lib/app/utils/language/sentences.dart
  • lib/app/utils/language/spanish_sentences.dart
  • lib/app/utils/language/urdu_sentences.dart
  • lib/app/v3/champion/models/task_for_replica.dart
  • lib/app/v3/db/update.dart
  • lib/app/v3/models/task.dart
  • lib/app/v3/net/add_task.dart
  • lib/app/v3/net/complete.dart
  • lib/app/v3/net/delete.dart
  • lib/app/v3/net/fetch.dart
  • lib/app/v3/net/modify.dart
  • lib/app/v3/net/origin.dart
  • lib/rust_bridge/api.dart
  • lib/rust_bridge/frb_generated.dart
  • lib/rust_bridge/frb_generated.io.dart
  • lib/rust_bridge/frb_generated.web.dart
  • pubspec.yaml
  • rust/Cargo.toml
  • rust/build.rs
  • rust/src/api.rs
  • rust/src/frb_generated.rs
  • rust/src/lib.rs
  • rust/src/serialize.rs
  • rust/src/storage.rs
  • rust/src/utils/error.rs
  • rust/src/utils/mod.rs
  • scripts/update_build_log.py
  • test/api_service_test.dart
  • test/api_service_test.mocks.dart
  • test/utils/language/bengali_sentences_test.dart
  • test/utils/language/english_sentences_test.dart
  • test/utils/language/french_sentences_test.dart
  • test/utils/language/hindi_sentences_test.dart
  • test/utils/language/marathi_sentences_test.dart
  • test/utils/language/sentences_test.dart
  • test/utils/language/spanish_sentences_test.dart
  • test/utils/taskchampion/taskchampion_test.dart
  • website/.gitignore
  • website/README.md
  • website/content/_index.md
  • website/content/docs/_index.md
  • website/content/docs/architecture.md
  • website/content/docs/faq.md
  • website/content/docs/getting-started.md
  • website/content/docs/sync-setup.md
  • website/content/downloads.md
  • website/content/features.md
  • website/data/nightly_builds.json
  • website/hugo.toml
  • website/layouts/_default/baseof.html
  • website/layouts/_default/list.html
  • website/layouts/_default/single.html
  • website/layouts/index.html
  • website/layouts/shortcodes/nightly-builds.html
  • website/static/CNAME
  • website/static/css/style.css
💤 Files with no reviewable changes (10)
  • lib/app/v3/net/delete.dart
  • lib/app/v3/net/add_task.dart
  • lib/app/v3/net/complete.dart
  • lib/app/v3/net/origin.dart
  • test/api_service_test.mocks.dart
  • lib/app/v3/net/fetch.dart
  • lib/app/v3/net/modify.dart
  • lib/app/modules/home/views/show_tasks.dart
  • lib/app/v3/db/update.dart
  • lib/rust_bridge/frb_generated.io.dart
👮 Files not reviewed due to content moderation or server errors (1)
  • ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helper

.homePageFetchingTasks);

await controller.refreshTasks(c, e);
await controller.fetchTasksFromDB();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not require sync credentials for this local refresh.

fetchTasksFromDB() only reloads the local TaskChampion database, but this call remains inside the c != null && e != null branch. Consequently, users without sync credentials cannot refresh local tasks. Move the local reload outside that credential check; also avoid reporting “Sync Completed” when no synchronization occurred.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/modules/home/views/home_page_app_bar.dart` at line 189, Update the
refresh flow around fetchTasksFromDB so the local database reload executes
regardless of whether sync credentials c and e are present. Keep credential
validation limited to synchronization, and only report “Sync Completed” when an
actual synchronization occurred, not for local-only refreshes.

Comment on lines +35 to +57
/// The entered credentials are validated with a real [sync_] *before* they
/// are persisted — a successful sync confirms they are valid, while invalid
/// credentials raise a Rust-level exception that surfaces here as a thrown
/// error. This ordering guarantees we never write unverified credentials into
/// the active profile. (Because validation is a live sync, saving requires
/// connectivity to the sync server.)
Future<int> saveCredentials() async {
if (taskReplica.value) {
profilesWidget.setTaskcCreds(
profilesWidget.currentProfile.value,
clientIdController.text,
encryptionSecretController.text,
ccsyncBackendUrlController.text);
return 0;
}
isCheckingCreds.value = true;
String baseUrl = ccsyncBackendUrlController.text;
String uuid = clientIdController.text;
String encryptionSecret = encryptionSecretController.text;
try {
String url =
'$baseUrl/tasks?email=email&origin=$origin&UUID=$uuid&encryptionSecret=$encryptionSecret';

var response = await http.get(Uri.parse(url), headers: {
"Content-Type": "application/json",
}).timeout(const Duration(seconds: 10000));
debugPrint("Fetch tasks response: ${response.statusCode}");
debugPrint("Fetch tasks body: ${response.body}");
if (response.statusCode == 200) {
List<dynamic> allTasks = jsonDecode(response.body);
debugPrint(allTasks.toString());
profilesWidget.setTaskcCreds(
profilesWidget.currentProfile.value,
clientIdController.text,
encryptionSecretController.text,
ccsyncBackendUrlController.text);

isCheckingCreds.value = false;
return 0;
} else {
throw Exception('Failed to load tasks');
}
} catch (e, s) {
debugPrint('Error fetching tasks: $e\n $s');
final String replicaPath = await Replica.getReplicaPath();
await sync_(
taskdbDirPath: replicaPath,
url: syncServerUrlController.text,
clientId: clientIdController.text,
encryptionSecret: encryptionSecretController.text,
);
// Only persist after the sync has confirmed the credentials are valid.
profilesWidget.setTaskcCreds(
profilesWidget.currentProfile.value,
clientIdController.text,
encryptionSecretController.text,
syncServerUrlController.text,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not validate credentials by syncing the active replica.

sync_ is stateful: using the production replicaPath can merge another valid account into the current replica or upload existing local tasks before credentials are saved. Validate through a temporary empty replica that is discarded afterward, or expose a non-mutating native credential check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/app/modules/manage_task_champion_creds/controllers/manage_task_champion_creds_controller.dart`
around lines 35 - 57, Update saveCredentials so credential validation does not
call sync_ against the active replicaPath. Create and use a temporary empty
replica for the validation sync, then discard it after completion, or use an
available non-mutating native credential-check API; only persist credentials
after validation succeeds and ensure temporary resources are cleaned up on
success or failure.

Comment on lines +225 to +238
() async {
final seen = await SaveTourStatus.getManageTaskServerTourStatus();
if (seen) return;
await safeShowTour(
tutorialCoachMark: tutorialCoachMark,
context: context,
targetKeys: [
configureTaskRC,
configureServerCertificate,
configureTaskServerKey,
configureYourCertificate,
],
markSeen: () => SaveTourStatus.saveManageTaskServerTourStatus(true),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard BuildContext usage across asynchronous gaps.

Using context after an await without verifying if the widget is still in the tree can lead to runtime exceptions. If the user navigates away before the SaveTourStatus check completes, passing an unmounted context to safeShowTour will cause a crash.

🛡️ Proposed fix
       () async {
         final seen = await SaveTourStatus.getManageTaskServerTourStatus();
-        if (seen) return;
+        if (seen || !context.mounted) return;
         await safeShowTour(
           tutorialCoachMark: tutorialCoachMark,
           context: context,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
() async {
final seen = await SaveTourStatus.getManageTaskServerTourStatus();
if (seen) return;
await safeShowTour(
tutorialCoachMark: tutorialCoachMark,
context: context,
targetKeys: [
configureTaskRC,
configureServerCertificate,
configureTaskServerKey,
configureYourCertificate,
],
markSeen: () => SaveTourStatus.saveManageTaskServerTourStatus(true),
);
() async {
final seen = await SaveTourStatus.getManageTaskServerTourStatus();
if (seen || !context.mounted) return;
await safeShowTour(
tutorialCoachMark: tutorialCoachMark,
context: context,
targetKeys: [
configureTaskRC,
configureServerCertificate,
configureTaskServerKey,
configureYourCertificate,
],
markSeen: () => SaveTourStatus.saveManageTaskServerTourStatus(true),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart`
around lines 225 - 238, Update the async callback around
SaveTourStatus.getManageTaskServerTourStatus to verify the widget context is
still mounted after the await and before calling safeShowTour. Return early when
context is unmounted, while preserving the existing seen-status guard and tour
behavior.

Comment on lines +120 to +150
'Blocked:',
controller.isBlocked.value ? 'Yes' : 'No',
),
_buildDetail(
context,
'Blocking:',
controller.isBlocking.value ? 'Yes' : 'No',
),
_buildDetail(
context,
'Depends:',
controller.depends.isEmpty
? 'None'
: controller.depends.join(', '),
),
_buildDetail(
context,
'Recur:',
controller.recur.value.isEmpty
? 'None'
: controller.recur.value,
),
_buildDetail(
context,
'Annotations:',
controller.annotations.isEmpty
? 'None'
: controller.annotations
.map((a) =>
'• ${a.description ?? ''}${(a.entry != null && a.entry!.isNotEmpty) ? ' (${a.entry})' : ''}')
.join('\n'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the new detail labels and fallback values.

Blocked:, Blocking:, Depends:, Recur:, Annotations:, Yes, No, and None are hardcoded, so this replica-task view will display English regardless of the selected app language. Add localization keys and use them here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/modules/taskc_details/views/taskc_details_view.dart` around lines 120
- 150, Localize the detail labels and fallback values used by the replica-task
view, including Blocked, Blocking, Depends, Recur, Annotations, Yes, No, and
None. Add corresponding entries to the app’s localization resources and update
the view’s display logic to retrieve them through the existing localization
mechanism instead of hardcoded English strings.

Comment on lines +27 to +34
await markSeen?.call();
return;
}
try {
tutorialCoachMark.show(context: context);
} catch (_) {
// Defensive: never let a tour failure bubble up or repeat.
await markSeen?.call();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Prevent markSeen failures from escaping.

markSeen is awaited without protection, so a persistence error can still make safeShowTour complete with an uncaught exception. Wrap this callback in a best-effort helper, while logging/reporting persistence failures separately if needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/tour/safe_tour.dart` around lines 27 - 34, Update safeShowTour’s
markSeen callback to use a best-effort error-handling helper so persistence
failures are caught and cannot escape the method. Preserve the tour display
flow, and separately log or report markSeen failures as appropriate.

Comment on lines +12 to +13
String get syncServerIntro =>
'TaskChampion synchronisiert deine Aufgaben nahtlos über mehrere Geräte hinweg. Außerdem erhälst du ein Web-Dashboard, über das du deine Aufgaben von jedem Browser aus verwalten kannst.';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the German spelling error.

erhälst should be erhältst.

Proposed fix
-      '... Außerdem erhälst du ein Web-Dashboard ...';
+      '... Außerdem erhältst du ein Web-Dashboard ...';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/utils/language/german_sentences.dart` around lines 12 - 13, Correct
the German spelling in the relevant sentence or localization entry by replacing
“erhälst” with “erhältst”; leave all surrounding text and behavior unchanged.

Comment on lines +174 to +178
other.priority == priority &&
other.isBlocked == isBlocked &&
other.isBlocking == isBlocking &&
_listEquals(other.depends, depends) &&
other.recur == recur;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include annotations in the equality operator.

The == operator was updated with the new fields, but annotations was omitted. This will cause instances with different annotations to evaluate as equal, which can lead to missed UI updates or caching bugs.

(Note: Ensure that other.project == project is also included if it was previously omitted, and remember to update hashCode for the new fields as well).

🐛 Proposed fix
         other.priority == priority &&
         other.isBlocked == isBlocked &&
         other.isBlocking == isBlocking &&
-        _listEquals(other.depends, depends) &&
-        other.recur == recur;
+        _listEquals(other.depends, depends) &&
+        other.recur == recur &&
+        _listEquals(other.annotations, annotations);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/app/v3/champion/models/task_for_replica.dart` around lines 174 - 178,
Update TaskForReplica’s equality operator to compare annotations and project in
addition to the already included fields. Update TaskForReplica.hashCode to
incorporate the same fields, ensuring equality and hashing remain consistent.

Comment thread rust/src/api.rs
Comment on lines +79 to +82
if let Some(mut t) = replica
.get_task(uuid)
.map_err(|e| TcHelperError::Champion(e.to_string()))?
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return an error when the update target does not exist.

None skips the mutation block and reaches Ok(()), so callers treat a lost update as persisted. Convert the Option to a TaskNotFound error before applying operations.

Also applies to: 137-141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/api.rs` around lines 79 - 82, Update the mutation paths at the
referenced locations so a missing update target is converted from None into a
TaskNotFound error before applying operations. Do not skip the mutation block
and return Ok(()) when the target is absent; preserve successful updates for
existing targets.

Comment thread rust/src/serialize.rs
Comment on lines +40 to +60
let annotations: Vec<Value> = task
.get_annotations()
.map(|a| {
json!({
// RFC 3339 / ISO-8601 so consumers get an unambiguous,
// directly-parseable timestamp (not a bare epoch number).
"entry": a.entry.to_rfc3339(),
"description": a.description,
})
})
.collect();

let depends: Vec<Value> = task
.get_dependencies()
.map(|uuid| Value::String(uuid.to_string()))
.collect();

map.insert("uuid".into(), Value::String(task.get_uuid().to_string()));
map.insert("tags".into(), Value::String(tags.join(" ")));
map.insert("annotations".into(), Value::Array(annotations));
map.insert("depends".into(), Value::Array(depends));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Sort collections to guarantee deterministic JSON serialization.

Because task.get_taskmap() iterates over an internal HashMap, the tags are collected in a pseudo-random order. Likewise, if get_dependencies or get_annotations are driven by hash collections, they will also yield items unpredictably.

This non-determinism cascades into the serialized JSON output, meaning the exact same task can produce slightly different JSON strings on subsequent accesses. When passed to the Flutter layer, this will break object equality checks, potentially causing spurious UI rebuilds, invalidating state caches, and causing intermittent test failures.

Please sort these collections before assigning them to ensure a stable output.

🛠️ Proposed fix to sort arrays before JSON construction
-    let annotations: Vec<Value> = task
-        .get_annotations()
-        .map(|a| {
-            json!({
-                // RFC 3339 / ISO-8601 so consumers get an unambiguous,
-                // directly-parseable timestamp (not a bare epoch number).
-                "entry": a.entry.to_rfc3339(),
-                "description": a.description,
-            })
-        })
-        .collect();
+    let mut annotations_list: Vec<_> = task.get_annotations().collect();
+    annotations_list.sort_unstable_by_key(|a| a.entry);
+    let annotations: Vec<Value> = annotations_list
+        .into_iter()
+        .map(|a| {
+            json!({
+                // RFC 3339 / ISO-8601 so consumers get an unambiguous,
+                // directly-parseable timestamp (not a bare epoch number).
+                "entry": a.entry.to_rfc3339(),
+                "description": a.description,
+            })
+        })
+        .collect();

-    let depends: Vec<Value> = task
-        .get_dependencies()
-        .map(|uuid| Value::String(uuid.to_string()))
-        .collect();
+    let mut depends_list: Vec<_> = task.get_dependencies().collect();
+    depends_list.sort_unstable();
+    let depends: Vec<Value> = depends_list
+        .into_iter()
+        .map(|uuid| Value::String(uuid.to_string()))
+        .collect();

     map.insert("uuid".into(), Value::String(task.get_uuid().to_string()));
+    tags.sort_unstable();
     map.insert("tags".into(), Value::String(tags.join(" ")));
     map.insert("annotations".into(), Value::Array(annotations));
     map.insert("depends".into(), Value::Array(depends));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/serialize.rs` around lines 40 - 60, Update the serialization flow in
rust/src/serialize.rs to sort tags, dependencies, and annotations before
assigning them to the JSON object. Ensure each collection produced by
task.get_taskmap(), get_dependencies, or get_annotations has a stable ordering
while preserving the existing serialized values and structure.

Saving TaskChampion credentials validated and synced the replica, but the
home task list was only ever populated by an explicit refresh. So right
after configuring sync for the first time the user saw an empty list (until
a manual refresh or an app restart) even though the sync had succeeded —
which reads as "sync is broken".

After a successful save, reload the sync-mode flag and run a sync+refresh on
the HomeController so the freshly-synced tasks appear at once. Guarded with
Get.isRegistered and a try/catch so a transient refresh failure can never
flip an already-successful credential save.

Verified on-device against a live TaskChampion server: configuring a fresh
profile now lands on a populated task list without a manual refresh.
Two cosmetic issues in the profile sync UI:
- The "changed profile mode to<Mode>" snackbar was missing a space before
  the mode name (rendered "...mode toTaskchampion"). Add the separator.
- The per-profile config action was hard-coded to "Configure Taskserver"
  even for Taskchampion (v3) profiles. Label it from the profile's actual
  mode so a v3 profile reads "Configure Taskchampion" (the destination
  screen was already correct; only the label was wrong).

Both verified on-device across TW2<->TW3C mode switches.
The right-drawer Sort By offers 8 columns, but the replica task list only
handled Modified / Due till / Priority — Created, Start Time, and Project fell
through to `default: return 0`, so those buttons did nothing. Priority also
sorted alphabetically ('H' < 'L' < 'M'), which is the wrong severity order.

- Add `entry` to TaskForReplica (the Rust serializer already emits it; the
  model just dropped it) so "Created" can sort.
- Handle Created (entry), Start Time (start), and Project in the replica sort
  switch, alongside the existing Modified / Due till.
- Sort Priority by severity rank (H > M > L > none) instead of alphabetically.
- Drop the redundant pre-sort; unsorted/unsupported columns (Tags, Urgency —
  Urgency isn't surfaced by taskchampion 2.0.3) fall back to newest-modified.

Filters (Status Pending/Completed, Project) were already correct — verified.
Sort verified on-device against a live server: Created- matched the CLI's
entry order exactly; Priority- correctly floated H tasks to the top.
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