Integration/d1 d2 d5#652
Conversation
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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe 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. ChangesTaskChampion synchronization and Flutter integration
Build automation and website
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (9)
scripts/update_build_log.py (2)
45-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSpecify UTF-8 encoding for file operations.
Relying on the system's default encoding can lead to
UnicodeDecodeErroror 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 valueSafeguard argument unpacking.
Unpacking
sys.argv[1:]directly intomainwill raise aTypeErrorif the script receives more than 3 arguments.Consider slicing the arguments to a maximum of 3, or using the standard
argparsemodule 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 valueInclude pseudo-elements in the box-sizing reset.
Standard practice is to include
*::beforeand*::afterto ensure pseudo-elements also respect theborder-boxsizing 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 valueInclude the standard
text-size-adjustproperty.For broader browser compatibility, it is recommended to include the standard, unprefixed
text-size-adjustproperty 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 valuePin third-party GitHub Actions to a specific version or commit SHA.
Using
@mainfor third-party actions can introduce unexpected breaking changes or security risks. Consider pinningjlumbroso/free-disk-spaceto 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 valueUse full null-aware chaining to prevent potential runtime errors.
In Dart, method cascades do not implicitly short-circuit. If
valueis evernull,value?.toString()evaluates tonull, and calling.toLowerCase()directly on it will throw aNoSuchMethodError.Although the current implementation ensures
valueis notnullbefore 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 winCheck if context is mounted after async gaps.
BuildContextis used here after multiple async gaps (a 500ms delay followed by awaitinggetProfileTourStatus). If the user navigates away from the profile page before this completes, passing an unmounted context tosafeShowTourmay cause a runtime error or trigger a Flutter lint warning for usingBuildContextacross async gaps.Consider adding a
context.mountedcheck 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 winOptimize the URL text field for better user experience.
Consider adding
keyboardType: TextInputType.urland disablingautocorrectfor 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 winDo not expose raw paths and backend errors through the FFI message.
These
Displayimplementations 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
⛔ Files ignored due to path filters (6)
android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b3182f249ae653b7.sois excluded by!**/*.soandroid/app/src/main/jniLibs/arm64-v8a/libtc_helper.sois excluded by!**/*.soandroid/app/src/main/jniLibs/armeabi-v7a/libcrc_fast-c8bd19aec5c73f3a.sois excluded by!**/*.soandroid/app/src/main/jniLibs/armeabi-v7a/libtc_helper.sois excluded by!**/*.sopubspec.lockis excluded by!**/*.lockrust/Cargo.lockis 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.ymlios/tc_helper.xcframework/Info.plistios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helperios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/tc_helperlib/app/modules/home/controllers/home_controller.dartlib/app/modules/home/views/home_page_app_bar.dartlib/app/modules/home/views/home_page_body.dartlib/app/modules/home/views/show_tasks.dartlib/app/modules/home/views/show_tasks_replica.dartlib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dartlib/app/modules/manage_task_champion_creds/controllers/manage_task_champion_creds_controller.dartlib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dartlib/app/modules/profile/controllers/profile_controller.dartlib/app/modules/profile/views/profile_view.dartlib/app/modules/taskc_details/controllers/taskc_details_controller.dartlib/app/modules/taskc_details/views/taskc_details_view.dartlib/app/tour/safe_tour.dartlib/app/utils/language/bengali_sentences.dartlib/app/utils/language/english_sentences.dartlib/app/utils/language/french_sentences.dartlib/app/utils/language/german_sentences.dartlib/app/utils/language/hindi_sentences.dartlib/app/utils/language/marathi_sentences.dartlib/app/utils/language/sentences.dartlib/app/utils/language/spanish_sentences.dartlib/app/utils/language/urdu_sentences.dartlib/app/v3/champion/models/task_for_replica.dartlib/app/v3/db/update.dartlib/app/v3/models/task.dartlib/app/v3/net/add_task.dartlib/app/v3/net/complete.dartlib/app/v3/net/delete.dartlib/app/v3/net/fetch.dartlib/app/v3/net/modify.dartlib/app/v3/net/origin.dartlib/rust_bridge/api.dartlib/rust_bridge/frb_generated.dartlib/rust_bridge/frb_generated.io.dartlib/rust_bridge/frb_generated.web.dartpubspec.yamlrust/Cargo.tomlrust/build.rsrust/src/api.rsrust/src/frb_generated.rsrust/src/lib.rsrust/src/serialize.rsrust/src/storage.rsrust/src/utils/error.rsrust/src/utils/mod.rsscripts/update_build_log.pytest/api_service_test.darttest/api_service_test.mocks.darttest/utils/language/bengali_sentences_test.darttest/utils/language/english_sentences_test.darttest/utils/language/french_sentences_test.darttest/utils/language/hindi_sentences_test.darttest/utils/language/marathi_sentences_test.darttest/utils/language/sentences_test.darttest/utils/language/spanish_sentences_test.darttest/utils/taskchampion/taskchampion_test.dartwebsite/.gitignorewebsite/README.mdwebsite/content/_index.mdwebsite/content/docs/_index.mdwebsite/content/docs/architecture.mdwebsite/content/docs/faq.mdwebsite/content/docs/getting-started.mdwebsite/content/docs/sync-setup.mdwebsite/content/downloads.mdwebsite/content/features.mdwebsite/data/nightly_builds.jsonwebsite/hugo.tomlwebsite/layouts/_default/baseof.htmlwebsite/layouts/_default/list.htmlwebsite/layouts/_default/single.htmlwebsite/layouts/index.htmlwebsite/layouts/shortcodes/nightly-builds.htmlwebsite/static/CNAMEwebsite/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(); |
There was a problem hiding this comment.
🎯 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.
| /// 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, | ||
| ); |
There was a problem hiding this comment.
🗄️ 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.
| () async { | ||
| final seen = await SaveTourStatus.getManageTaskServerTourStatus(); | ||
| if (seen) return; | ||
| await safeShowTour( | ||
| tutorialCoachMark: tutorialCoachMark, | ||
| context: context, | ||
| targetKeys: [ | ||
| configureTaskRC, | ||
| configureServerCertificate, | ||
| configureTaskServerKey, | ||
| configureYourCertificate, | ||
| ], | ||
| markSeen: () => SaveTourStatus.saveManageTaskServerTourStatus(true), | ||
| ); |
There was a problem hiding this comment.
🩺 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.
| () 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.
| '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'), |
There was a problem hiding this comment.
🎯 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.
| await markSeen?.call(); | ||
| return; | ||
| } | ||
| try { | ||
| tutorialCoachMark.show(context: context); | ||
| } catch (_) { | ||
| // Defensive: never let a tour failure bubble up or repeat. | ||
| await markSeen?.call(); |
There was a problem hiding this comment.
🩺 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.
| 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.'; |
There was a problem hiding this comment.
📐 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.
| other.priority == priority && | ||
| other.isBlocked == isBlocked && | ||
| other.isBlocking == isBlocking && | ||
| _listEquals(other.depends, depends) && | ||
| other.recur == recur; |
There was a problem hiding this comment.
🎯 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.
| if let Some(mut t) = replica | ||
| .get_task(uuid) | ||
| .map_err(|e| TcHelperError::Champion(e.to_string()))? | ||
| { |
There was a problem hiding this comment.
🗄️ 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.
| 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)); |
There was a problem hiding this comment.
🎯 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.
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_nowith the issue number which is fixed in this PRScreenshots
Checklist
Summary by CodeRabbit