The earlier submodule wiring left a mis-indented `with:` / `submodules:
recursive` block under `uses: actions/checkout@v4` (and, in ci.yaml, a second
stray `with:`), which made both workflows fail to parse
("mapping values are not allowed here"). Merge submodules into the single
correctly-indented `with:` block so CI actually checks out floret-kit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 of the shared-kit plan: Calendula becomes the second core-time consumer.
floret-kit is embedded as a git submodule and wired with includeBuild, so the
app depends on de.jeanlucmakiola.floret:core-time and Gradle substitutes the
local source module (built from source).
- Remove the app's TimeBridge (Instant <-> epoch millis) + its test; the helpers
now live in core-time. Repoint the 7 call sites to de.jeanlucmakiola.floret.time.
- Calendula's richer time formatting (TimeFormat, 24h CompositionLocal) stays
app-local by design.
Reproducible-build handling (Calendula publishes via official F-Droid repro):
- Pin the submodule to a foojay-free kit commit (the resolver would let a build
fetch a JDK -> non-reproducible; the kit never used a toolchain block).
- Harden scripts/check_reproducible_release.sh to scan the included build's
Gradle scripts for the toolchain-resolver invariant.
- Draft fdroiddata recipe: submodules: true (else F-Droid checks out an empty
floret-kit/ and the from-source build fails).
- CI build checkouts fetch submodules recursively.
Clean composite build + unit tests + lintDebug + repro guard all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make fastlane/metadata/android/ the single source of truth for store
listing metadata, consumed directly by the official F-Droid repo and
transformed into the self-hosted repo's localized layout at release time.
- Move fdroid-metadata/<appid>/<locale>/ -> fastlane/metadata/android/<locale>/
(git-tracked renames: summary->short_description, description->full_description,
+ title.txt, images/icon.png, images/phoneScreenshots/); keep the app-level
.yml control file for the self-hosted `fdroid update`.
- Add scripts/fastlane_to_fdroid_localized.sh (fastlane -> F-Droid localized,
incl. changelogs) and scripts/sync_changelog_to_fastlane.sh (CHANGELOG.md ->
fastlane changelog); verified byte-identical to the previous metadata.
- release.yaml: build self-hosted metadata from the fastlane tree and sync the
per-version changelog before the transform (one changelog source for both
channels).
- Disable AGP VCS-info embedding on release builds (vcsInfo { include = false })
so builds reproduce byte-for-byte vs the distributed APK — the only file that
otherwise differed (META-INF/version-control-info.textproto). Effective from
the next release.
- Add docs/fdroid-official/ (draft fdroiddata recipe: reproducible build +
AllowedAPKSigningKeys + Binaries + notes).
- Repoint README screenshots/icon, update docs/README + RELEASING, and skip the
Android build on fastlane-only changes (ci.yaml).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworks the pipeline so a change is built once and a release is driven by the
merge, not a manual tag push.
- ci.yaml now runs on pull_request (one gate per PR) instead of every branch
push, so there's no CI-on-push + CI-on-merge double run. A single `ci` job
with a docs-only fast-path keeps the required "CI" check always reporting
(docs/metadata-only PRs skip the Android build but still go green).
- release.yaml triggers on push to main. A cheap `detect` job reads versionName
from build.gradle; only when no tag for it exists does the `release` job run:
tests on the merged commit, build + sign, publish to F-Droid, then create the
vX.Y.Z tag + Gitea release via the API (target_commitish = the merged sha).
The tag is now an OUTPUT of a successful release, not its trigger — a failure
before publish leaves no tag, so re-running safely retries. No more separate
tag-triggered run or duplicate ci job.
- The committed versionName/versionCode are now the source of truth (pipeline
pins versionCode from versionName); updated the build.gradle comment.
- translations.yaml switched to pull_request (same path filter).
- docs/RELEASING.md: release-by-merge flow, no manual git tag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P1.3 Versioning: the git tag is already the de-facto single source of truth
(every published versionCode uses MAJOR*10000+MINOR*100+PATCH; committed 13
was a stale outlier). Align the committed default to 20000 and document the
scheme in a comment + docs/RELEASING.md.
P1.4 F-Droid changelogs: a tag-only step extracts the tag's CHANGELOG section
into metadata/.../en-US/changelogs/<versionCode>.txt so clients show a
per-version "What's New". Also upload metadata/ (non-secret, never web-served)
alongside repo/ so changelog history survives across releases.
P1.5 R8 mapping: attach mapping-<version>.txt.gz to the Gitea release
(best-effort, continue-on-error) so user crash stacktraces stay
deobfuscatable. The gitea-release notes step is now an upsert (PATCH if the
release already exists) so it composes with the mapping step creating the
release first.
P1.6 docs/RELEASING.md: release ritual, versioning scheme, secrets inventory,
key custody/recovery, manual re-sign path, F-Droid repo details.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The release job assumed the ref is a version tag (Set version from git tag →
versionCode). A manual workflow_dispatch from a branch yielded versionCode 0
and Gradle aborted assembleRelease before the F-Droid steps ran.
Gate the tag-only steps (version, app keystore, assembleRelease, copy APK)
on refs/tags/*. On a manual dispatch the job now skips the APK build and just
re-signs the existing index with the configured repo key and re-uploads —
exactly what a repo-key rotation or recovery needs, no new release required.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The F-Droid repo signing key (keystore.p12) and its config.yml — including
the keystore passwords in cleartext — were publicly downloadable at
apps.dev.jeanlucmakiola.de/dev/fdroid/ because the release workflow uploaded
the entire fdroid/ working dir into the web-served path. The webserver has
since been locked down to repo/ only; this rotates the now-compromised key
and removes the root cause.
- release.yaml: restore the repo key + config from new CI secrets
(FDROID_KEYSTORE_BASE64, FDROID_CONFIG_BASE64) instead of the box; upload
ONLY repo/ so the key never re-enters the served tree.
- release.yaml: fail loudly when the repo key secrets are unset, replacing
`fdroid update --create-key`, which silently minted a NEW repo key on a
wiped server and would have broken every user's pinned fingerprint.
- README: publish the new repo fingerprint (C2C0…3425). Existing users must
remove and re-add the repo.
- .gitignore: ignore *.p12 and the whole /fdroid/ working dir.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documentation pass after the 2.0 milestone:
- docs/ARCHITECTURE.md — principles (provider as single source of truth,
observer-driven UI, JVM-first tests, no network), layer + reminder
mermaid diagrams, navigation (overlay/held-key, no nav lib), and the
provider lessons (recurring-write invariants, conflict snapshots)
- docs/README.md — map of what documentation lives where, incl. the
convention that superpowers/ plans are historical artifacts while
.planning/ stays current
- README.md — showcase layout (centered header, badges, screenshot
gallery from the fastlane assets, grouped features, install/build/
architecture/roadmap sections); renders on Gitea
- .planning/{PROJECT,REQUIREMENTS,STATE}.md unstaled: read-only-V1 talk
removed, V1/V2 checklists marked shipped, state points at v3 + the
Locations & People go/no-go
release.yaml gains a gitea-release job: on every tag push it extracts the
tag's CHANGELOG section and creates a Gitea release with it as the notes.
No APK assets — distribution stays with the F-Droid repo. Idempotent
(skips an existing release), gated on the test job only so notes appear
even when the F-Droid upload hiccups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- skip setup-android's default packages (pulled the ~300 MB emulator every run)
- drop unused platforms;android-36 and the dead jq install step
- cache /opt/android-sdk and ~/.gradle (release.yaml had no cache at all)
- drop --no-daemon so lint/test/assemble reuse one warm daemon per job
- Trivy scan only on main (advisory-only; was ~25s tax on every branch push)
- concurrency group cancels superseded runs; drop duplicate pull_request trigger
Companion to the act_runner fix on the CI host: job containers now join the
runner's network so the actions/cache server is reachable (saves previously
failed with reserveCache timeouts, so no cache was ever stored).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ci.yaml: ./gradlew lint -> lintDebug, test -> testDebugUnitTest.
Default lint task runs for BOTH debug and release variants which
doubles the scan work; AGP's lint catalog is identical between
variants for our scope so debug-only is sufficient. Same for test:
testDebugUnitTest avoids running release-variant test compilation.
- release.yaml: drop lint step from ci-sanity job. Lint is enforced
on every push to main via ci.yaml; by the time a tag exists at a
main commit, lint has already passed. Release-sanity keeps test +
assembleDebug to catch any tag-resolved drift (e.g. version code
substitution issues).
Expected CI run time reduction: ~30% (lint accounts for the largest
single block of cold-cache work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The release workflow's ci-sanity job ran 'lint test assembleDebug' as
a single gradle invocation, which combined all three phases in one
JVM and exceeded the 2GB heap inside the gitea-actions docker
container ("Gradle build daemon disappeared unexpectedly"). Split
into three separate invocations matching ci.yaml - each gradle call
gets its own fresh 2GB JVM, well under the container's memory ceiling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Triggers on git tags. Runs CI sanity (lint+test+assembleDebug), then
in build-and-deploy job: writes version from tag into app/build.gradle.kts
(versionCode = MAJOR*10000 + MINOR*100 + PATCH, HouseHoldKeaper
convention), drops keystore + key.properties from secrets, runs
assembleRelease, pulls existing F-Droid repo from Hetzner, drops the
new APK + metadata, regenerates index with 'fdroid update -c', and
SCPs the whole tree back to Hetzner.
Required secrets: KEYSTORE_BASE64, KEY_PASSWORD, KEY_ALIAS,
HETZNER_HOST, HETZNER_USER, HETZNER_PASS. Configure these in Gitea
repo settings before pushing the first tag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>