core: extract the store and sync engine into a shared crate (M12 step 1)
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 48s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 1m50s
Desktop (Tauri) / Update manifest (push) Skipped

Android becomes a native Kotlin client over this same code (Scribe note 2730), so
the local store and sync engine stop being modules of the desktop app and become
`thoughtsync-core`, a crate with no UI framework in it at all.

This is a move, not a rewrite, and the measurement is why: every file in local/
and sync/ already carried ZERO Tauri references — 4,980 of 6,372 lines. The
coupling was 473 lines of command shim, which stays behind in the desktop crate
as src/commands/. Kept as git renames so history follows the files.

The desktop imports them under their old names (`use thoughtsync_core::{local,
sync}`) so every call site reads exactly as before. What moved is where they
live, not what they are.

Two things a workspace changes that are easy to miss, both caught before pushing:

[profile.release] now lives at the workspace ROOT. Cargo silently ignores
profiles declared by a non-root member — leaving it in the desktop crate would
have dropped lto/strip/opt-level from every release build with only a warning.

And a workspace shares ONE target dir, so the bundles moved from
desktop/src-tauri/target to target/. Thirteen references across publish-release,
debundle-graphics, verify.sh, package-prebuilt and the workflow now point there.
Pinning target-dir back would have been the smaller diff, but the Android lane
also produces Rust artifacts and they do not belong under desktop/.

Also retires the Tauri Android lane in the same push rather than leaving a path
that is being replaced: gen/android, android.yml and docs/android-dev.md are
gone, the mobile_entry_point attribute with them, and the lib drops to rlib —
staticlib/cdylib existed for Tauri mobile, and the .so Android loads will be
built from the core crate instead. Rule 22, no parallel path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 23:12:26 -04:00
co-authored by Claude Opus 5
parent c28f2bc00e
commit 0a7480cf9b
75 changed files with 246 additions and 1346 deletions
-97
View File
@@ -1,97 +0,0 @@
# Android (Tauri v2 mobile) — SEPARATE from desktop.yml because it is a different
# surface with a different toolchain, not a different bundle of the same one. The
# Rust core is cross-compiled with the NDK and then packaged by the Gradle project
# in desktop/src-tauri/gen/android.
#
# Toolchain comes from the ci-tauri-android image (Rust + the Android ABIs +
# SDK/NDK + JDK 17 + tauri-cli); runs-on is only a scheduling label (Label Model B).
#
# Android, desktop and web are peer surfaces on one quality bar, so this triggers on
# the same shared frontend that desktop.yml does — the frontend is compiled INTO the
# app by generate_context!, so a frontend commit that skipped this lane would ship a
# stale phone build.
name: Android (Tauri)
on:
push:
branches: [dev, main]
tags: ["v*"]
paths:
- "desktop/**"
- "frontend/**"
- ".forgejo/workflows/android.yml"
workflow_dispatch:
concurrency:
group: android-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
permissions:
contents: read
jobs:
build:
name: Android APK (debug)
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-tauri-android:1.97
steps:
- uses: actions/checkout@v6
# Same reason as the desktop lanes: generate_context! embeds the built
# frontend at compile time, so it must exist before cargo runs.
- name: Build the shared frontend
run: npm ci && npm run build
working-directory: frontend
# The lockfile gate, matching the desktop lanes (issue 2102). Fetching for the
# Android target also pre-warms exactly the crates the build will want.
- name: Verify the lockfile and fetch dependencies
run: cargo fetch --locked --target aarch64-linux-android
working-directory: desktop/src-tauri
# DEBUG, and arm64 only, deliberately.
#
# Debug because a release APK has to be signed, and the release keystore must
# be generated by the operator and never pass through CI logs or an agent
# session (the constraint recorded on task 2136 for the updater key applies
# identically here). Gradle's throwaway debug keystore needs nothing from
# anyone, so this lane can prove the app COMPILES and PACKAGES today and grow
# a signed release job when a keystore exists.
#
# arm64 + x86_64. arm64 is every real device; x86_64 is what a desktop
# emulator runs, and an arm64-only APK installs there and then dies unable to
# load its native library — so a build nobody can try on an emulator is a
# build nobody checks. armv7 and i686 are left out: 32-bit hardware we do not
# target, and the image carries all four if that changes.
- name: Tauri build (Android APK)
run: |
version="$(sh ../packaging/build-version.sh)"
echo "Building version $version"
cargo tauri android build \
--debug \
--target aarch64 \
--target x86_64 \
--config '{"build":{"beforeBuildCommand":""}}' \
--config "{\"version\":\"$version\"}"
working-directory: desktop/src-tauri
# Located rather than hardcoded: the output path depends on the target and
# build type, and a wrong literal here would fail as "no files found" long
# after the expensive step succeeded.
- name: Locate the APK
run: |
apk="$(find desktop/src-tauri/gen/android/app/build/outputs/apk -name '*.apk' -type f | head -1)"
[ -n "$apk" ] || { echo "ERROR: no APK produced" >&2; exit 1; }
echo "Built $apk ($(du -h "$apk" | cut -f1))"
echo "APK_PATH=$apk" >> "$GITHUB_ENV"
# Mirrored action, never actions/upload-artifact — see desktop.yml's Upload
# bundles step for the full reasoning. Pinned by SHA because the mirror
# auto-syncs.
- name: Upload the APK
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
with:
name: thoughtsync-android-debug
path: ${{ env.APK_PATH }}
if-no-files-found: error
+11 -5
View File
@@ -20,6 +20,13 @@ on:
tags: ["v*"]
paths:
- "desktop/**"
# The shared client core (store + sync engine) the desktop wraps. Its own
# crate since the Android client binds the same code, so a change there is a
# change to this app even though nothing under desktop/ moved.
- "core/**"
# The workspace manifest and lockfile, which now live at the repo root.
- "Cargo.toml"
- "Cargo.lock"
# The whole frontend, not just the adapter/bridge seam: it is compiled INTO
# the desktop binary, so any part of it changing means the shipped app is out
# of date. Config and lockfile included — a dependency bump changes the bundle
@@ -142,7 +149,6 @@ jobs:
cargo tauri signer sign "$appimage"
[ -s "$appimage.sig" ] || { echo "ERROR: re-signing produced no .sig" >&2; exit 1; }
echo "Re-signed $(basename "$appimage")"
working-directory: desktop/src-tauri
# install.sh hands the .deb to every Debian/Ubuntu user, so the package's
# Depends must be right BEFORE a release exists. Prints the generated
@@ -180,9 +186,9 @@ jobs:
with:
name: thoughtsync-linux
path: |
desktop/src-tauri/target/release/bundle/appimage/*.AppImage
desktop/src-tauri/target/release/bundle/deb/*.deb
desktop/src-tauri/target/release/bundle/arch/*.pkg.tar.*
target/release/bundle/appimage/*.AppImage
target/release/bundle/deb/*.deb
target/release/bundle/arch/*.pkg.tar.*
# error, not warn: a build that bundles nothing should report as a
# failure, not as a green run with an empty artifact.
if-no-files-found: error
@@ -296,7 +302,7 @@ jobs:
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
with:
name: thoughtsync-windows
path: desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
path: target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
if-no-files-found: error
# Publishes to the SAME release as the Linux job. Safe to run twice: the