Releases: DocGerd/pantry-tracker
v1.4.0 — German localization & DocGerdSoft brand identity
[1.4.0] — 2026-06-02
Added
- German (
de) translation of the Compose UI layer, with the user-facing
strings externalized to resources and relative-time labels using
locale-correct plurals (#168). - ViewModel-layer error messages (Scan / Detail / Buying-list / camera-settings
Toast) externalized to string resources with German translations via a new
UiTextresolvable-text type, completing the internationalization sweep
(#218, #168). - Monochrome / themed-icon layer (Android 13+) on the adaptive launcher icon, so
the OS can tint the canisters-and-shelf foreground to the user's monochrome theme. - README hero banner (
docs/brand/hero.svg) and store-style screenshots; the
crisp 2560×1120 PNG export of the hero is deferred to the maintainer.
Changed
- Refined the adaptive launcher-icon foreground geometry: the three flat
rectangles become rounded three-height pantry canisters with recessed lids,
and the shelf is redrawn as the DocGerdSoft datum stroke — all white on the
unchanged Fern#4F7942background. - Expanded the Material 3 theme from the previous primary-only override into a
full hand-built light/dark colour scheme seeded from Fern#4F7942(the
primary/secondary/tertiary/error/surface/outline/inverse roles mapped to hex;
the surface-tint tonal variants — surfaceContainerLowest/Low/High/Highest,
surfaceBright, surfaceDim, scrim, surfaceTint — seeded from Fern's neutral
tonal palette via material-color-utilities (HCT), so ModalBottomSheets and
other surface-tinted components no longer fall back to the M3 Baseline-purple
tint; #236/#240). TheAddGreen/RemoveRedverb accents are retained as
brand constants outside the scheme. - Backed the ≥80% statement-coverage gate by promoting the emulator-backed
androidTestCI job (which runs:app:jacocoTestCoverageVerificationat 0.80
LINE on the merged unit + instrumented JaCoCo report; baseline ~85.93% line)
to a required check on the develop ruleset, and closed the delete/undo
snackbar render path + an EN/DE format-template drift guard, satisfying
OpenSSF Silvertest_statement_coverage80. (#219)
Fixed
- Verb-action buttons (Home "Scan to Add" / "Scan to Remove", the Scan top app
bar title + back arrow, and the scan result-sheet confirm / switch buttons)
now use a fixed white foreground on theAddGreen/RemoveRedfills instead
of the theme-derived M3 default content colour, which rendered the labels +
icons at ~2:1 contrast. White reads on both fills in light and dark
(~6.6:1 on AddGreen, ~8.6:1 on RemoveRed), clearing WCAG 2.1 AA. (#241)
APK: app-release.apk — SHA-256 724c0cf9cf7d7f8215c3ad860a13d8b787fa1a0b6d86b9ef961cd591a857b669 (24,326,201 bytes)
Signing: apksigner with the project lifetime keystore — certificate SHA-256 ec9a4bb8…b3d9 (unchanged since v1.0.0). Verify with apksigner verify --print-certs app-release.apk and compare the SHA-256 above.
Provenance: GitHub automatically generates a Sigstore artifact attestation for this asset — verify with gh attestation verify app-release.apk -R DocGerd/pantry-tracker (gh ≥ 2.49). The former cosign + SLSA-generator release.yml was retired (#210/#211) as broken under cosign v4 and incompatible with the immutable-releases policy; see SECURITY.md.
No Room schema change since v1.3.1 — upgrade-install over a prior version preserves all pantry data.
v1.3.1 — Buying list & one-tap restock
[1.3.1] — 2026-05-29
Changed
- Re-cut of v1.3.0 with no functional change — everything listed under
1.3.0 below, re-released under a new version. Thev1.3.0tag was consumed
by a release-tooling error under the repo's immutable-releases policy (assets
can't be added to a published release after the fact, and deleting it burns
the tag name), so v1.3 ships as v1.3.1. See
docs/release/SHIPPING.md"Common gotchas".
[1.3.0] — 2026-05-29
Added
- Per-item low-stock limits with an auto-generated buying list and one-tap restock (#191).
Changed
- Home list rows now show the manufacturer beneath the product name (#190).
Security
- Bound OFF response JSON nesting depth (#59) and type-encode the OFF
host invariant as an enum (#61).
APK: app-release.apk — SHA-256 81e1fcd3291c6cdbfec79f5f9ca2cf8943eee7402c286b94b2048502840e2e15
Signing: jarsigner/apksigner with the project lifetime keystore — certificate SHA-256 ec9a4bb8…b3d9 (unchanged since v1.0.0). Verify: apksigner verify --print-certs app-release.apk and compare the SHA-256 above. Note: this release is jarsigner-signed only (as v1.0–v1.2 were); cosign signature + SLSA provenance are NOT attached — the release.yml cosign step is being repaired separately.
v1.2.0 — OFF cache + body-cap streaming + R8 minify
[1.2.0] — 2026-05-28
Added
- Offline cache for OFF lookups of non-pantry barcodes. Re-scanning a
product that was previewed (but not added) now resolves locally —
no network call, no 4-host fallback walk. 30-day TTL; cache row is
deleted when the barcode is committed to the pantry. (#48)
Changed
- Release builds now use R8 minify +
shrinkResources. APK size
reduced from 40,523,977 bytes (~40.5 MB) at v1.1.0 to 24,140,473
bytes (~24.1 MB) at v1.2.0 — a 40.4% reduction. (SR-9, refs #36)
Security
- Replace header-only OFF response body cap with streamed enforcement.
The 256 KB cap now fires on chunked responses regardless of
Content-Length advertisement, closing the gap left by the v1.1.0
hotfix. (#52) - R8 strips unused code from the release artifact, reducing attack
surface. Belt-and-braces-keeprules inapp/proguard-rules.pro
preempt the "first-time R8 enable strips reflection target"
symptom for kotlinx.serialization @serializable types
(OffProduct,OffApiEnvelope) and Room entities/DAOs (Product,
OffLookupCacheEntry,ProductDao,OffLookupCacheDao, plus
AppDatabaseandConvertersdefensively). (SR-9, refs #36)
Tests / quality
- 194 → 218 JVM unit tests.
- RNG screenshot harness added (#74 / SR-74) using Robolectric
Native Graphics + golden-PNG diff. Covers icon variants, theme,
font-scale, and Coil image rendering — retires UAT §0 rows 2-4,
§2 rows 1, 2, and 4, §11 last row (greyed-row 45% opacity check),
and v1.2 §11 Coil row. - Scan-flow Compose UI tests (#75 / SR-75) cover OFF-hit, OFF-miss
timeout fallback, in-inventory remove, and not-in-inventory +
switch-to-Add — retires UAT §7 (8 of 9), §8 (5 of 6), §11 (5), §12 (2). - Search UI test (#76 / SR-76) — retires UAT §9 (5 rows).
- Camera permission deep-link + onResume tests (#77 / SR-77) —
retires UAT §4 (5 of 6), §5 (5 of 7), §6 (3 of 8). - Rotation + error-tone tests + detekt rule (#78 / SR-78) — config-change
- error tone; the
ErrorToneRuleextracted to a standalone
:detekt-rulesGradle module with a proof test. Retires UAT §14
(configuration change row) and §15 (error tone).
- error tone; the
- CI emulator job (#79 / SR-79) on
reactivecircus/android-emulator-runner@v2.37.0, API 35, PR-gating
connectedDebugAndroidTestruns. ~5-8 min added per PR. - R8 static-inspection script (#80 / SR-80) verifies
@Serializable+
@Entityclasses survive minification in the release APK. - MIGRATION_1_2 emulator-drive runbook (#81 / SR-81) — script + docs
for on-device migration smoke; retires UAT v1.2 §1 upgrade-install row. - OFF resilience tests (#82 / SR-82) — fallback-chain matrix +
cache offline replay; retires UAT v1.2 §3-5 (3) + §12 (1). - androidTest permission-revoke fix (SR-117 / PR #118) — adds
CameraPermissionGate.isCameraGrantedtest seam so revoking a held
runtime permission no longer kills the shared instrumentation
process. - Room schema baseline committed under
app/schemas/(#57 / SR-17),
unlockingMigrationTestHelper-based migration tests against the
v1 → v2 schema delta. - UAT checklist umbrella closed (#73) — every retired row annotated
with[automated by SR-N]; new "Stays human-only" appendix
enumerates the irreducibly-physical items + the v1.2 OFF CDN chunked
encoding rule. - CLAUDE.md lessons fold (#119) — 6 new entries in "Things that
have bitten past sessions" covering revoke-of-held-permission,
custom AndroidJUnitRunner.newApplication, detekt custom rules in
standalone module, post-tag lockfile untrack, on-device CI catches
what static review can't, GitFlow ruleset constraints.
Install (sideload)
- Download
app-release.apkfrom this release. - Enable "Install unknown apps" for your browser or file manager (Android: Settings → Apps → Special access).
- Open the downloaded APK. The installer will request permission to install; the app updates over v1.0.x / v1.1.x in place (no data loss).
Verify the APK
SHA-256 8be3e781c4837dc433fc6b11b70baa02916e44d9ac4533df4712c2286c8ad366 app-release.apk
size 24,271,545 bytes (~24.27 MB)
Signing certificate (lifetime identity for all v1.x updates):
Subject: CN=Patrick Kuhn, OU=DocGerdSoft, O=DocGerdSoft, L=Schwetzingen, ST=BW, C=DE
SHA-256: ec9a4bb80ccbe56b3aeccf73eb220418be87ac10b1128bdd868b3d0cd87cb3d9
SHA-1: fbd758914b3abeb71321c03b429716dae9e212cc
If you have v1.0.x or v1.1.x installed, the signing cert matches and Android will accept the upgrade-install.
Source
Built reproducibly from tag v1.2.0 at commit b3d4ef02e6bcbb8516c765cd197834244e2500d5.
v1.1.0 — Fallbacks & undo
Fallbacks & undo milestone. All three feature items shipped.
Added
- Non-food product coverage — OFF lookup now walks Open Food Facts → Open Beauty Facts → Open Pet Food Facts → Open Products Facts on
404, so cosmetics, pet food, and household products that aren't on the food endpoint still resolve through their sibling databases (#44). - Delete UNDO — after confirming a delete, a snackbar offers UNDO for a few seconds. Restored items preserve their original id and every column;
restore()is wrapped inNonCancellableso a backgrounded VM can't lose the row mid-write (#46). - Surfaced failure feedback — delete and undo paths now show "Could not delete X" / "Could not undo delete of X" instead of silently swallowing exceptions.
Security
- OFF response body cap (SR-24) — HTTP client rejects responses advertising
Content-Length > 256 KBbefore parse. Known limitation: OFF's CDN uses chunked transfer encoding and omitsContent-Lengthon real responses, so chunked / no-Content-Lengthresponses pass through. A streaming-bounded body read is tracked at #52 for v1.2. - CancellationException contract preserved across all new suspend code.
Privacy
- OFF chain walks up to four sister-project hosts on 404. Happy path is still a single request to OFF; only
404walks the chain (5xx / timeout / network error / contract violation fail fast). Every request still carries only the scanned barcode plus the staticPantryTracker/1.1.0 (<repo URL>)User-Agent — no user identifier, no cookie, no device fingerprint.
Install
Sideload-only. Min Android 8.0 (API 26), targets Android 16 (API 36). See docs/release/SHIPPING.md for install paths.
Signing identity unchanged from v1.0 (SHA-256 ec9a4bb8…b3d9) — this APK installs as an update over v1.0.x.
Full changelog: CHANGELOG.md
v1.0.0 — first public-ready release
Changelog
All notable changes to Pantry Tracker are documented in this file.
The format is based on Keep a Changelog 1.1.0,
and the project follows Semantic Versioning.
For install instructions see docs/release/SHIPPING.md.
For architecture documentation see docs/architecture/.
1.0.0 — 2026-05-18
First public-ready release. Single-user, sideloaded — no Play Store presence.
Added — inventory
- Local pantry stored on-device via Room SQLite. Survives force-stop, reboot, and app updates. A schema mismatch crashes the app rather than silently wiping pantry data (no destructive migration).
- Browse and search from the Home screen — alphabetical list, instant substring search, out-of-stock rows greyed (kept visible for quick re-add).
- Two empty-state layouts depending on context:
- Empty pantry + blank query → two-CTA layout (Scan to Add / Add manually).
- Empty pantry + active query → small "No matches for …" hint, no CTAs.
Added — adding products
- Scan-to-Add: tap Scan to Add, point at an EAN-13 / EAN-8 / UPC-A / UPC-E barcode, the app resolves it against Open Food Facts. On a hit, a preview sheet shows name + brand + product image + quantity stepper; confirm adds the row.
- OFF-miss fallback drops the user into manual entry pre-filled with the scanned barcode — no error, no retry storm.
- Manual entry from the FAB (Home) or the "Add manually" CTA (empty state) — type the name and quantity, no barcode required.
Added — removing products
- Scan-to-Remove: tap Scan to Remove, scan a barcode of something in inventory, the preview sheet's quantity stepper is clamped to the current quantity; confirm applies a negative delta.
- Not-in-inventory detection: scanning an unknown barcode (or a known one already at quantity 0) shows a "Not in inventory" sheet with a Switch to Add button that flips mode and re-resolves the same barcode through the Add flow.
- Detail-screen stepper for ±1 adjustments without scanning.
- Long-press → confirm → Delete on a Home row, or the trash-can icon on the detail screen.
Added — item detail
- Tap any row → detail screen with product image (if available), inline-editable name (commits on focus loss or IME Done), brand, barcode, quantity stepper, last-updated timestamp.
- Stale nav-arg handling: opening a detail screen for a product that no longer exists auto-pops back to Home instead of leaving a stuck screen.
Added — theme + icon
- Material 3 theme with Fern (
#4F7942) as the M3primaryslot; remaining roles (secondary, tertiary, surface, error, …) come from M3's Baseline palette (no seed-derived tonal expansion). Light + dark scheme follow the system setting. - Adaptive launcher icon — three white jars on a horizontal shelf, white-on-fern. Adapts to round / squircle / teardrop launcher masks; the foreground vector stays inside the 66dp safe zone.
Added — camera-permission UX
- In-context rationale dialog before the system permission prompt — explains why we need the camera and that nothing leaves the device.
- SoftDenied recovery (denied without "don't ask again"): "Try again" affordance re-triggers the system prompt.
- HardDenied recovery (denied with "don't ask again"): "Open settings" deep-links to the app's permission page; after granting, returning to the app auto-resumes to the scan flow via an
ON_RESUMEpermission re-check. - Settings-intent fallback: on devices where
ACTION_APPLICATION_DETAILS_SETTINGSis disabled (some MDM-locked or stripped builds), the user gets a "Couldn't open settings on this device" Toast instead of a silent dead button.
Added — error UX
- Every failure surfaces a user-visible message in the canonical
"Couldn't <verb>: <reason>"tone (Couldn't open camera: …,Couldn't rename: …, etc.). No raw stack-trace strings, no silent failures. - Repository operations log via
java.util.loggingso logcat keeps a stack trace for diagnosis without leaking the message into the UI twice.
Privacy
- No accounts, no analytics, no crash reporter. The app makes no outbound network calls of its own except the single OFF lookup (
GET world.openfoodfacts.org/api/v2/product/<barcode>.json). The request carries only the scanned barcode plus a staticPantryTracker/<version> (<repo URL>)User-Agent — no user identifier, no cookie, no device fingerprint. - ML Kit telemetry is disarmed at the manifest level. Google's ML Kit barcode-scanning artifact transitively pulls in
google-datatransport, which by default registers acctbackend that uploads SDK-usage events tofirebaselogging.googleapis.comvia a JobScheduler. We remove the three components that make that pipeline live —TransportBackendDiscovery,JobInfoSchedulerService, andAlarmManagerSchedulerBroadcastReceiver— viatools:node="remove"in ourAndroidManifest.xml. The barcode detector itself does not depend on the transport; events queued by ML Kit fail backend-discovery and are silently dropped, so nothing leaves the device. The standardfirebase_data_collection_default_enabled=falseflag is not sufficient in standalone (no-Firebase-project) mode — it is only honoured byFirebaseInitProvider, which ML Kit does not register. - No cleartext network traffic. OFF is HTTPS-only; no certificate pinning (intentional — pinning would break the app on routine cert rotation).
android:allowBackup = false— Google Backup does NOT auto-restore the pantry on a fresh install. Trade-off documented in arc42 §8.9.- Permissions:
CAMERA(runtime),INTERNETandACCESS_NETWORK_STATE(install-time). No location, contacts, storage, microphone, or sensor permissions.ACCESS_NETWORK_STATEis pulled in transitively by the HTTP stack to let it check connectivity before retrying; it does not query SSIDs and is not user-personally-identifying.
Build / quality
- CI runs on every PR:
assembleDebug,testDebugUnitTest,lintDebug,assembleDebugAndroidTest(compile-only — emulator runs are local), Detekt static analysis, Gitleaks secret scan, OSV-Scanner against the Gradle runtime classpath lockfile. - End-to-end UAT test (
HappyPathUatTest) walks the user-visible state machine (add → list → detail → rename → stepper → delete) through the real navigation graph with an in-memory repository. - Manual UAT checklist at
docs/uat/v1-uat-checklist.mdcovers the visual + device-specific paths that can't be automated (theme, icon rendering, OEM permission flows, real-barcode scanning). - arc42 architecture docs at
docs/architecture/covering all 12 standard sections.
Out of scope for v1.0 — planned for v1.1+
- Localization (UI strings are inline English in Kotlin; no
strings.xmlextraction yet). - Crash reporter (Sentry / Firebase Crashlytics).
- Background work of any kind (no
WorkManager, no foreground service). - Pantry sync / multi-device support — would require an account system that contradicts the privacy goal.
- Non-food product auto-resolution — v1.0 queries Open Food Facts only; non-food items (cleaning supplies, beauty, pet food) typically OFF-miss and fall through to the manual-entry sheet with the barcode pre-filled. Querying the sister Open Products Facts / Open Beauty Facts / Open Pet Food Facts endpoints would close the gap.
- Expiry-date tracking on
Product. - CSV / JSON export for pantry backup (paired with
allowBackup = false). - Batch-scan mode for unloading a full grocery bag in one camera session.
- Wear OS companion.
- Animations polish — default Compose transitions only.
- Tablet / foldable adaptive layouts.
Install / distribution
- Sideload-only for v1.0. Signed APK distributed via GitHub Releases. See
docs/release/SHIPPING.mdfor the three install paths (adb installof debug APK, sideload of release APK, optional Firebase App Distribution). - Minimum Android version: 8.0 (API 26).
- Target Android version: 16 (API 36).
Acknowledgements
- Product data from Open Food Facts (Open Database License).
- Barcode decoding via Google ML Kit.
- Built on Jetpack Compose, Material 3, Room, CameraX, Ktor, Coil, Detekt.