Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
289d1f1
refactor: retire the CCSync HTTP sync layer, unify on TaskChampion FFI
BrawlerXull Jun 13, 2026
bdab680
fix(ccsync): address audit findings on the sync-retirement change
BrawlerXull Jun 20, 2026
09040e1
refactor(rust): modularize tc_helper, add typed errors, surface task …
BrawlerXull Jun 27, 2026
98ee2d9
feat(website): project website with nightly build tracking
BrawlerXull Jul 1, 2026
f013a87
refactor(ccsync): remove residual CCSync references from the profile UI
BrawlerXull Jul 8, 2026
8aac26e
fix(deps): pin flutter_rust_bridge to 2.11.1 to match generated bindings
BrawlerXull Jul 8, 2026
0ded498
test(rust): cover dependency & annotation serialization; pin flutter_…
BrawlerXull Jul 9, 2026
0549580
build(android): rebuild arm64-v8a tc_helper from the overhauled Rust
BrawlerXull Jul 9, 2026
154318b
fix(ui): show replica tasks & surface FFI attributes; harden tour show
BrawlerXull Jul 9, 2026
494c06e
ci: compile tc_helper from source with cargo-ndk (arm64-v8a)
BrawlerXull Jul 9, 2026
78a3163
fix(home): make replica task list actually display tasks
BrawlerXull Jul 9, 2026
279de06
build(android): support armeabi-v7a via target-scoped aws-lc-sys bind…
BrawlerXull Jul 9, 2026
d8d7fb0
refactor(rust): drop unused AWS-S3/GCS sync backends (removes aws-lc)
BrawlerXull Jul 9, 2026
0534000
fix(home): make search filter the TaskChampion replica list
BrawlerXull Jul 9, 2026
a81dc6d
docs(website): add Features page, Docs guides, and content styling
BrawlerXull Jul 15, 2026
f2e0392
Merge branch 'feat/retire-ccsync' into integration/d1-d2-d5
BrawlerXull Jul 15, 2026
feb7d80
Merge branch 'feat/project-website' into integration/d1-d2-d5
BrawlerXull Jul 15, 2026
18ca717
fix(sync): show tasks immediately after saving TaskChampion credentials
BrawlerXull Jul 15, 2026
7b3116c
fix(profile): correct sync-mode snackbar spacing and config label
BrawlerXull Jul 15, 2026
d12b6b0
fix(sort): make all Taskchampion replica sort options work
BrawlerXull Jul 15, 2026
7d6bf48
fix(sort): decouple sort key from localized label
BrawlerXull Jul 16, 2026
5ba2e3d
feat(sort): implement Tags and Urgency sorting for the replica list
BrawlerXull Jul 16, 2026
6887b12
feat(reports): reporting-engine data layer (Issue #418)
BrawlerXull Jul 17, 2026
0937e01
feat(reports): reporting-engine UI + entry point (Issue #418)
BrawlerXull Jul 18, 2026
c1a483d
fix(reports): 3 bugs found in a code + on-device audit of the reporti…
BrawlerXull Jul 22, 2026
81cf76b
fix(reports): surface replica read failures instead of a misleading e…
BrawlerXull Jul 22, 2026
f0bef2e
fix: 4 bugs found in a broader UI/functionality audit
BrawlerXull Jul 22, 2026
a28ff98
fix: 6 more bugs from a broader audit (legacy DB layer, profile switc…
BrawlerXull Jul 22, 2026
85a4191
build(android): auto-compile tc_helper from rust/ on every build
BrawlerXull Jul 29, 2026
1b1dd6a
refactor(modularization): consolidate utils/taskc into utils/taskcham…
BrawlerXull Aug 1, 2026
90b5cd8
refactor(modularization): unify the two task models behind one contract
BrawlerXull Aug 8, 2026
f09d3c9
fix(tasks): make delete a soft delete and add a Deleted status filter
BrawlerXull Aug 11, 2026
f6d27d4
refactor(modularization): collapse the nine burndown charts into one
BrawlerXull Aug 11, 2026
09987dd
refactor(reports): drop the dead burndown bucketing from ReportsContr…
BrawlerXull Aug 11, 2026
b5002a6
build: compile tc_helper from source on iOS, macOS, Linux and Windows…
BrawlerXull Aug 11, 2026
d2bae56
fix(android): build tc_helper for x86_64 so emulator APKs don't crash
BrawlerXull Aug 11, 2026
75f634f
ci: compile tc_helper from source in every APK workflow, and verify t…
BrawlerXull Aug 12, 2026
2b06bf1
fix(ci): call cargo-ndk as `cargo ndk`, and correct a stale workflow …
BrawlerXull Aug 12, 2026
3343485
feat(tasks): let notes (annotations) be added and removed
BrawlerXull Aug 13, 2026
20de16e
fix(tour): guard the remaining 5 coach-mark tours, and repair the fil…
BrawlerXull Aug 13, 2026
4e3b1d9
feat(tasks): let dependencies be added and removed, with the guards t…
BrawlerXull Aug 13, 2026
0e5a13d
feat(reports): build custom reports in the app, not just in a config …
BrawlerXull Aug 15, 2026
b46e673
feat(tasks): allow recurrence to be set, generated by desktop Taskwar…
BrawlerXull Aug 17, 2026
4ce6536
fix(tasks): actually clear recurrence when the repeat is turned off
BrawlerXull Aug 17, 2026
9b9e5d9
ci(deploy): show the deployed commit on the website
BrawlerXull Aug 17, 2026
f9363d2
fix(reports): use DropdownButtonFormField.value, which exists in the …
BrawlerXull Aug 17, 2026
de16eca
chore: move the website out of the app repository
BrawlerXull Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/actions/setup-rust-android/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Setup Rust for Android
description: >
Installs the Android NDK, a Rust toolchain with every Android target the app
ships, and cargo-ndk — everything android/app/build.gradle's cargoBuildTcHelper
task needs to compile libtc_helper.so from rust/ during the Gradle build.

Any workflow that builds an APK must use this. Without it the Gradle hook finds
no cargo, prints "using committed jniLibs", and silently produces an APK whose
native library is stale or (once the committed .so files are purged) absent —
an APK that installs cleanly and then crashes at RustLib.init().

inputs:
ndk-version:
description: NDK version to install. Must satisfy the ring/taskchampion build.
required: false
default: "26.1.10909125"
cargo-ndk-version:
description: cargo-ndk version requirement.
required: false
default: "^4"

runs:
using: composite
steps:
- uses: android-actions/setup-android@v3

- name: Install NDK ${{ inputs.ndk-version }}
shell: bash
run: |
sdkmanager "ndk;${{ inputs.ndk-version }}"
# cargo-ndk locates the clang cross-compilers through this variable.
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/${{ inputs.ndk-version }}" >> "$GITHUB_ENV"

# The three targets correspond to the three ABIs Flutter builds
# (arm64-v8a, armeabi-v7a, x86_64) and to the -t flags in
# android/app/build.gradle. Adding an ABI means changing both.
- name: Install Rust toolchain + Android targets
shell: bash
run: |
rustup toolchain install stable --profile minimal
rustup target add \
aarch64-linux-android \
armv7-linux-androideabi \
x86_64-linux-android

# Compiling taskchampion for three targets is the slow part; cache it.
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
# Distinguishes this cache from any host-only Rust build.
key: android-${{ inputs.ndk-version }}

- name: Install cargo-ndk
shell: bash
run: |
# Note: the binary must be invoked as `cargo ndk`, never as `cargo-ndk` —
# called directly it exits 1 with "This binary may only be called via
# `cargo ndk`". command -v is the safe way to test for its presence.
if command -v cargo-ndk >/dev/null 2>&1; then
echo "cargo-ndk already present: $(cargo ndk --version)"
else
cargo install cargo-ndk --version '${{ inputs.cargo-ndk-version }}' --locked
fi

# Diagnostics only. Guarded with || true so a future CLI change here can never
# be the thing that fails a build — the APK verification is the real gate.
- name: Report toolchain
shell: bash
run: |
cargo --version || true
cargo ndk --version || true
echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME"
rustup target list --installed | grep android || true
69 changes: 69 additions & 0 deletions .github/workflows/build-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Nightly Build & Log

# Scheduled canary: builds the signed nightly APK so a break is caught even on a
# day with no commits. It deliberately publishes nothing — the F-Droid deploy and
# the website build log are handled by nightlydepolyci.yml, which runs on push and
# builds the site from the deploy branch where its source now lives.

on:
schedule:
- cron: '0 2 * * *' # 02:00 UTC daily
workflow_dispatch:

permissions:
contents: read # nothing is committed; this only proves the build still works

jobs:
nightly:
name: Build nightly APK and record build log
runs-on: ubuntu-latest
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: false # keep Android SDKs for Flutter
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17.x'

# Rust + NDK + cargo-ndk, so the Gradle hook compiles libtc_helper.so from
# rust/ instead of falling back to a prebuilt binary.
- uses: ./.github/actions/setup-rust-android

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.29.2'

- name: Get dependencies
run: flutter pub get

- name: Decode signing secrets
env:
NIGHTLY_KEYSTORE_B64: ${{ secrets.NIGHTLY_KEYSTORE_B64 }}
NIGHTLY_PROPERTIES_B64: ${{ secrets.NIGHTLY_PROPERTIES_B64 }}
run: |
echo "$NIGHTLY_KEYSTORE_B64" | base64 --decode > android/nightly.jks
echo "$NIGHTLY_PROPERTIES_B64" | base64 --decode > android/key_nightly.properties

- name: Build nightly APK
id: build
run: flutter build apk --flavor nightly --build-number=${{ github.run_number }} --release

# A missing native library produces an APK that installs and then crashes
# at RustLib.init(), so fail the canary on it.
- name: Verify native libraries are in the APK
run: ./scripts/verify_apk_native_libs.sh build/app/outputs/flutter-apk/app-nightly-release.apk
80 changes: 80 additions & 0 deletions .github/workflows/build-tc-helper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Compiles the tc_helper Rust native library from source (instead of relying on
# the pre-built .so committed under android/app/src/main/jniLibs/) and then
# builds the production APK against the freshly-compiled library.
#
# This is the "proper" fix for stale committed binaries: the .so is regenerated
# from rust/ on every run, so it can never drift from the Rust source. Once this
# is green, the committed jniLibs/*.so can be git-ignored and purged from history.
#
# This workflow has run green on CI (ubuntu-latest), so the NDK version and the
# cargo-ndk invocation are known to work on a runner and not just locally. The
# NDK + Rust setup has since moved to .github/actions/setup-rust-android, shared
# with every workflow that builds an APK.
#
# All three ABIs Flutter builds are covered: arm64-v8a, armeabi-v7a and x86_64.
# The list must stay in sync with android/app/build.gradle's cargoBuildTcHelper —
# an ABI missing here ships an APK without libtc_helper.so that installs fine and
# then crashes at RustLib.init(). taskchampion is configured with only
# the server-sync backend (see rust/Cargo.toml), so there's no aws-lc-sys — hence
# no cmake/ninja/bindgen/libclang toolchain needed; ring builds with just the
# Rust + NDK toolchain.
name: Build tc_helper (compile from source)

on:
workflow_dispatch:
pull_request:
branches: [main, reports]
paths:
- "rust/**"
- "android/**"
- ".github/workflows/build-tc-helper.yml"

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"

# NDK + Rust + Android targets + cargo-ndk. Shared with the APK-building
# workflows so the target list can't drift between them.
- uses: ./.github/actions/setup-rust-android

- name: Compile tc_helper (arm64-v8a + armeabi-v7a + x86_64) from source
working-directory: rust
run: |
cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 \
-o ../android/app/src/main/jniLibs \
build --release

- name: Assert every ABI Flutter builds got a library
run: |
for abi in arm64-v8a armeabi-v7a x86_64; do
f="android/app/src/main/jniLibs/$abi/libtc_helper.so"
[ -f "$f" ] || { echo "::error::missing $f — the APK for $abi would crash at RustLib.init()"; exit 1; }
echo "ok: $f"
done

- name: Show freshly-built libs
run: ls -lhR android/app/src/main/jniLibs/

- uses: subosito/flutter-action@v2
with:
flutter-version: "3.44.5"

- run: flutter pub get
- run: flutter build apk --release --flavor production

# The step above asserts the libraries were *built*; this asserts they were
# *packaged*. Both are needed — Gradle can silently drop a library it has.
- name: Verify native libraries are in the APK
run: ./scripts/verify_apk_native_libs.sh build/app/outputs/flutter-apk/app-production-release.apk

- uses: actions/upload-artifact@v4
with:
name: production-apk-from-source
path: build/app/outputs/flutter-apk/app-production-release.apk
10 changes: 10 additions & 0 deletions .github/workflows/flutterci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jobs:
distribution: "temurin"
java-version: "17.x"

# Rust + NDK + cargo-ndk, so the Gradle hook compiles libtc_helper.so from
# rust/ instead of falling back to a prebuilt binary. Must come before any
# `flutter build apk`.
- uses: ./.github/actions/setup-rust-android

# Step 3: Setup Flutter with version 3.7.11
- uses: subosito/flutter-action@v1
with:
Expand All @@ -40,6 +45,11 @@ jobs:
# Step 8: Build APK using flutter build apk
- run: flutter build apk --release --flavor production

# The APK must carry libtc_helper.so for every ABI, or it installs fine and
# crashes at RustLib.init(). Fails the build rather than uploading a dud.
- name: Verify native libraries are in the APK
run: ./scripts/verify_apk_native_libs.sh build/app/outputs/flutter-apk/app-production-release.apk

# Step 9: Upload the built APK as an artifact
- uses: actions/upload-artifact@v4
with:
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/nightlydepolyci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
push:
branches:
- main
# The website lives on the deploy branch now, so nothing here needs to be
# excluded to avoid rebuilding the APK for a site-only change.
paths-ignore:
- '.github/workflows/build-nightly.yml'

jobs:
build-and-deploy:
Expand Down Expand Up @@ -35,6 +39,10 @@ jobs:
distribution: "temurin"
java-version: "17.x"

# Step 2b: Rust + NDK + cargo-ndk, so the Gradle hook compiles
# libtc_helper.so from rust/ instead of falling back to a prebuilt binary.
- uses: ./.github/actions/setup-rust-android

# Step 3: Setup Flutter
- name: Setup Flutter
uses: subosito/flutter-action@v2
Expand All @@ -58,10 +66,26 @@ jobs:
- name: Build APK
run: flutter build apk --flavor nightly --build-number=${{ github.run_number }} --release

# Step 6b: An APK missing its native library installs fine and then crashes
# at RustLib.init() — fail here rather than deploying it to F-Droid.
- name: Verify native libraries are in the APK
run: ./scripts/verify_apk_native_libs.sh build/app/outputs/flutter-apk/app-nightly-release.apk

# Step 7: Verify the APK is signed
- name: Verify sign
run: keytool -printcert -jarfile build/app/outputs/flutter-apk/app-nightly-release.apk

# Step 7b: Capture which commit is being released.
#
# It has to be captured now: the job switches this work tree to the deploy
# branch further down, and from that point git HEAD is that branch's own
# orphan commit rather than the app commit. The site source lives on the
# deploy branch, so the site itself is built after the switch.
- name: Capture the deployed commit
run: |
echo "DEPLOY_SHA=${{ github.sha }}" >> "$GITHUB_ENV"
echo "DEPLOY_MSG=$(git log -1 --pretty=%s)" >> "$GITHUB_ENV"

# Step 8: Configure git, fetch existing repo, and place new APK
# We fetch the repo so we can see existing APKs to prune them in the next step.
- name: Configure and Prepare Files
Expand Down Expand Up @@ -109,6 +133,36 @@ jobs:
# Run the update command
fdroid update -c

# Step 10b: Record the deploy and rebuild the site from this branch's own
# source, then place it at the root. Additions only — repo/, metadata/ and
# assets/ are untouched, so F-Droid clients are unaffected.
- name: Record deploy and rebuild the website
run: |
set -e
# website/ and scripts/ live on this branch, so they are on disk now.
python3 scripts/update_build_log.py success \
"https://github.com/${{ github.repository }}/raw/fdroid-repo/repo/nightly.${{ github.run_number }}.apk" \
"${{ github.run_number }}" "$DEPLOY_SHA" "$DEPLOY_MSG"

# Pinned Hugo rather than a floating action, so a site build cannot
# start failing because an upstream default moved.
HUGO_VERSION=0.164.0
curl -sSL -o /tmp/hugo.tar.gz \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
tar -xzf /tmp/hugo.tar.gz -C /tmp hugo

/tmp/hugo --source website --destination /tmp/site --gc --minify \
--baseURL "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"

# The generated CNAME is dropped on purpose: no custom domain is
# configured, and committing one would make Pages claim a domain with
# no DNS behind it — taking the site down and breaking the F-Droid repo
# URL that clients already poll.
rm -f /tmp/site/CNAME

cp -R /tmp/site/. .
touch .nojekyll

# Step 11: Push a fresh history with NO bloat
# This creates a temporary orphan branch (no parents) containing ONLY
# the files currently on disk, then force pushes it to replace fdroid-repo.
Expand Down
58 changes: 58 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,61 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}

// ---------------------------------------------------------------------------
// Auto-compile the tc_helper Rust native library on every Android build.
//
// When a Rust toolchain is available, this task cross-compiles rust/ with
// cargo-ndk straight into src/main/jniLibs/<abi>/, so `flutter run` /
// `flutter build` always package a library that matches the current Rust
// source instead of a stale pre-built binary. It is wired into `preBuild`, so
// it runs before the APK's native libraries are merged.
//
// If the toolchain is NOT present (rustup/cargo/cargo-ndk missing), the task
// skips with a warning and the build falls back to the committed .so — so a
// checkout without Rust still builds. (Once every build environment, including
// CI, has the toolchain, the committed jniLibs can be git-ignored and removed.)
//
// The Android std targets are added on demand, so a fresh toolchain self-heals.
// ---------------------------------------------------------------------------
def rustCrateDir = file("${projectDir}/../../rust")
def rustJniOut = "${projectDir}/src/main/jniLibs"
def cargoBinDir = "${System.getenv('HOME')}/.cargo/bin"

def resolveNdkDir = {
try {
if (android.ndkDirectory != null) return android.ndkDirectory.absolutePath
} catch (ignored) { /* no ndkVersion configured; fall through */ }
def ndkRoot = new File(android.sdkDirectory, "ndk")
if (ndkRoot.isDirectory()) {
def versions = ndkRoot.listFiles()?.findAll { it.isDirectory() }?.sort { it.name }
if (versions) return versions.last().absolutePath
}
return System.getenv("ANDROID_NDK_HOME")
}

tasks.register("cargoBuildTcHelper", Exec) {
workingDir rustCrateDir
def ndkDir = resolveNdkDir()
def cargoPath = "${cargoBinDir}:/opt/homebrew/bin:/usr/local/bin:${System.getenv('PATH')}"
environment "PATH", cargoPath
if (ndkDir != null) environment "ANDROID_NDK_HOME", ndkDir
doFirst { new File(rustJniOut).mkdirs() }
// If cargo/cargo-ndk aren't on PATH, skip (the committed .so is used as a
// fallback) rather than failing the whole build. When they are present, a
// real compile error DOES fail the build, so problems surface loudly.
commandLine "sh", "-c",
"if ! command -v cargo >/dev/null 2>&1 || ! command -v cargo-ndk >/dev/null 2>&1; then " +
" echo 'cargoBuildTcHelper: Rust toolchain (cargo/cargo-ndk) not found — using committed jniLibs'; exit 0; " +
"fi; " +
"set -e; " +
// Must cover every ABI Flutter builds (arm64-v8a, armeabi-v7a, x86_64),
// or the APK for a missing one ships without libtc_helper.so and dies at
// RustLib.init(). x86_64 is what the standard Android emulator runs.
"rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android >/dev/null 2>&1 || true; " +
"cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -o \"${rustJniOut}\" build --release"
}

tasks.matching { it.name == "preBuild" }.configureEach {
dependsOn "cargoBuildTcHelper"
}

Binary file not shown.
Binary file modified android/app/src/main/jniLibs/arm64-v8a/libtc_helper.so
Binary file not shown.
Binary file not shown.
Binary file modified android/app/src/main/jniLibs/armeabi-v7a/libtc_helper.so
Binary file not shown.
Binary file not shown.
Loading
Loading