Skip to content

Releases: DocGerd/pantry-tracker

v1.4.0 — German localization & DocGerdSoft brand identity

02 Jun 11:22
Immutable release. Only release title and notes can be modified.
e601c6c

Choose a tag to compare

[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
    UiText resolvable-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 #4F7942 background.
  • 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). The AddGreen / RemoveRed verb accents are retained as
    brand constants outside the scheme.
  • Backed the ≥80% statement-coverage gate by promoting the emulator-backed
    androidTest CI job (which runs :app:jacocoTestCoverageVerification at 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 Silver test_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 the AddGreen / RemoveRed fills 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

29 May 14:52
Immutable release. Only release title and notes can be modified.
b9e277a

Choose a tag to compare

[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. The v1.3.0 tag 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

28 May 11:19
Immutable release. Only release title and notes can be modified.
b3d4ef0

Choose a tag to compare

[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 -keep rules in app/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
    AppDatabase and Converters defensively). (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 ErrorToneRule extracted to a standalone
      :detekt-rules Gradle module with a proof test. Retires UAT §14
      (configuration change row) and §15 (error tone).
  • CI emulator job (#79 / SR-79) on
    reactivecircus/android-emulator-runner@v2.37.0, API 35, PR-gating
    connectedDebugAndroidTest runs. ~5-8 min added per PR.
  • R8 static-inspection script (#80 / SR-80) verifies @Serializable +
    @Entity classes 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.isCameraGranted test seam so revoking a held
    runtime permission no longer kills the shared instrumentation
    process.
  • Room schema baseline committed under app/schemas/ (#57 / SR-17),
    unlocking MigrationTestHelper-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)

  1. Download app-release.apk from this release.
  2. Enable "Install unknown apps" for your browser or file manager (Android: Settings → Apps → Special access).
  3. 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

19 May 10:34
b3e9202

Choose a tag to compare

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 in NonCancellable so 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 KB before parse. Known limitation: OFF's CDN uses chunked transfer encoding and omits Content-Length on real responses, so chunked / no-Content-Length responses 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 404 walks the chain (5xx / timeout / network error / contract violation fail fast). Every request still carries only the scanned barcode plus the static PantryTracker/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

18 May 14:47

Choose a tag to compare

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 M3 primary slot; 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_RESUME permission re-check.
  • Settings-intent fallback: on devices where ACTION_APPLICATION_DETAILS_SETTINGS is 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.logging so 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 static PantryTracker/<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 a cct backend that uploads SDK-usage events to firebaselogging.googleapis.com via a JobScheduler. We remove the three components that make that pipeline live — TransportBackendDiscovery, JobInfoSchedulerService, and AlarmManagerSchedulerBroadcastReceiver — via tools:node="remove" in our AndroidManifest.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 standard firebase_data_collection_default_enabled=false flag is not sufficient in standalone (no-Firebase-project) mode — it is only honoured by FirebaseInitProvider, 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), INTERNET and ACCESS_NETWORK_STATE (install-time). No location, contacts, storage, microphone, or sensor permissions. ACCESS_NETWORK_STATE is 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.md covers 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.xml extraction 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.md for the three install paths (adb install of 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.