From 2bae704e902e39c1d91f8bea89643194b02e60f5 Mon Sep 17 00:00:00 2001 From: Andrew Steinmetz Date: Fri, 31 Jul 2026 07:22:41 -0700 Subject: [PATCH 1/3] fix(desktop): make the Linux client work inside a Flatpak sandbox Four behaviors the desktop app owns on a normal Linux install belong to the sandbox (or are outright broken by it) in a Flatpak build: - The database directory hardcoded ~/.local/share/chef-mate, which the sandbox does not mount. Honor XDG_DATA_HOME, which Flatpak points at the app's private ~/.var/app//data. Outside a sandbox the two resolve to the same path. - The in-app updater downloads a .deb and runs it. The sandbox is read-only and Flathub publishes updates, so disable it there for the same reason it is already disabled on the two app-store platforms. - SchemeRegistrar self-registers chefmate:// by writing a handler .desktop file. Inside the sandbox that points at a sandbox-internal launcher path the host session cannot run; the exported .desktop declares the scheme instead. - X11 derived WM_CLASS from the main class name, so the window would not match the .desktop file's StartupWMClass and showed up as a second unnamed dock entry. Pin awtAppClassName to the app id before AWT initializes. Adds platform/Flatpak.kt as the shared FLATPAK_ID / /.flatpak-info probe. Co-Authored-By: Claude Opus 5 --- .../chefmate/deeplink/SchemeRegistrar.kt | 6 ++++++ .../kotlin/com/plusmobileapps/chefmate/main.kt | 7 +++++++ .../plusmobileapps/chefmate/platform/Flatpak.kt | 16 ++++++++++++++++ .../chefmate/update/DesktopUpdater.kt | 9 +++++++-- .../client/database/DriverFactory.jvm.kt | 11 ++++++++++- 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/platform/Flatpak.kt diff --git a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/deeplink/SchemeRegistrar.kt b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/deeplink/SchemeRegistrar.kt index ff5e7670d..3d39472cf 100644 --- a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/deeplink/SchemeRegistrar.kt +++ b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/deeplink/SchemeRegistrar.kt @@ -2,6 +2,7 @@ package com.plusmobileapps.chefmate.deeplink import co.touchlab.kermit.Logger import com.plusmobileapps.chefmate.ChefMateUrls +import com.plusmobileapps.chefmate.platform.isRunningInFlatpak import java.io.File /** @@ -25,6 +26,11 @@ object SchemeRegistrar { * `--args` instead). */ fun registerIfPackaged() { + // The Flathub build declares x-scheme-handler/chefmate in its exported .desktop file, so + // the sandbox already routes the scheme. Self-registering there would only write a handler + // inside the sandbox pointing at the sandbox-internal launcher path, which the host session + // can neither see nor run. + if (isRunningInFlatpak) return val launcher = launcherPath() ?: return runCatching { when (desktopOs()) { diff --git a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/main.kt b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/main.kt index 17798899c..4a5c9c88f 100644 --- a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/main.kt +++ b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/main.kt @@ -50,6 +50,13 @@ private const val KEY_WINDOW_PLACEMENT = "window.placement" @OptIn(ExperimentalTime::class, FlowPreview::class) fun main(args: Array) { + // X11 derives a window's WM_CLASS from the main class name unless this is set, which would make + // it `com-plusmobileapps-chefmate-MainKt`. Desktop shells match a running window to its + // launcher by comparing WM_CLASS against the .desktop file's StartupWMClass, so pin it to the + // app id the Linux .desktop files declare — otherwise the window shows up as a second, + // unnamed entry in the dock instead of the Chef Mate icon. Must run before AWT initializes. + System.setProperty("sun.awt.X11.XToolkit.awtAppClassName", "com.plusmobileapps.chefmate") + // Windows/Linux spawn a fresh process for every `chefmate://…` open. If another instance is // already running, forward this launch's link to it and exit without opening a second window. if (!SingleInstance.acquireOrForward(args)) return diff --git a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/platform/Flatpak.kt b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/platform/Flatpak.kt new file mode 100644 index 000000000..b6bcda3f2 --- /dev/null +++ b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/platform/Flatpak.kt @@ -0,0 +1,16 @@ +package com.plusmobileapps.chefmate.platform + +/** + * Whether this process is running inside the Flatpak sandbox (the Flathub build of the Linux app). + * + * Flatpak exports `FLATPAK_ID` into every sandboxed process, and also creates `/.flatpak-info` + * inside the sandbox; either is a reliable signal, and both are checked because `FLATPAK_ID` can be + * dropped by a wrapper that scrubs the environment. + * + * Behavior that the sandbox owns rather than the app must be skipped when this is true — the app + * cannot replace its own installation (Flathub publishes updates), and it cannot register OS-level + * handlers itself (the exported `.desktop` file does that declaratively). + */ +val isRunningInFlatpak: Boolean by lazy { + !System.getenv("FLATPAK_ID").isNullOrBlank() || java.io.File("/.flatpak-info").exists() +} diff --git a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/update/DesktopUpdater.kt b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/update/DesktopUpdater.kt index a2bc408cd..ffab95fb8 100644 --- a/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/update/DesktopUpdater.kt +++ b/client/composeApp/src/jvmMain/kotlin/com/plusmobileapps/chefmate/update/DesktopUpdater.kt @@ -2,6 +2,7 @@ package com.plusmobileapps.chefmate.update import co.touchlab.kermit.Logger import com.plusmobileapps.chefmate.buildconfig.BuildConfig +import com.plusmobileapps.chefmate.platform.isRunningInFlatpak import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.request.get @@ -50,8 +51,12 @@ class DesktopUpdater( private var available: UpdateState.Available? = null - /** In-app updates run on Linux only; macOS and Windows defer to their respective app stores. */ - private val isSupported: Boolean = platformKey() == "linux" + /** + * In-app updates run on Linux only; macOS and Windows defer to their respective app stores. The + * Flathub build is excluded for the same reason as the stores: Flatpak owns the install and the + * sandbox is read-only, so the app can neither replace itself nor run a downloaded .deb. + */ + private val isSupported: Boolean = platformKey() == "linux" && !isRunningInFlatpak fun checkForUpdates() { if (!isSupported) return diff --git a/client/database/core/src/jvmMain/kotlin/com/plusmobileapps/chefmate/client/database/DriverFactory.jvm.kt b/client/database/core/src/jvmMain/kotlin/com/plusmobileapps/chefmate/client/database/DriverFactory.jvm.kt index 4bf8299d4..d61d5d148 100644 --- a/client/database/core/src/jvmMain/kotlin/com/plusmobileapps/chefmate/client/database/DriverFactory.jvm.kt +++ b/client/database/core/src/jvmMain/kotlin/com/plusmobileapps/chefmate/client/database/DriverFactory.jvm.kt @@ -50,7 +50,16 @@ actual class DriverFactory { return when { os.contains("mac") -> File(userHome, "Library/Application Support/Chef Mate") os.contains("win") -> File(System.getenv("APPDATA") ?: userHome, "Chef Mate") - else -> File(userHome, ".local/share/chef-mate") // Linux + // Linux: honor XDG_DATA_HOME, falling back to its spec default of ~/.local/share. + // Outside a sandbox the two are the same path, but the Flatpak build has no access to + // the real ~/.local/share — Flatpak points XDG_DATA_HOME at the app's private + // ~/.var/app//data instead, and writing the hardcoded path there fails. + else -> { + val dataHome = + System.getenv("XDG_DATA_HOME")?.takeIf { it.isNotBlank() } + ?: File(userHome, ".local/share").path + File(dataHome, "chef-mate") + } } } } From 7a7f833405817686f519dd801413c54fa73da106 Mon Sep 17 00:00:00 2001 From: Andrew Steinmetz Date: Fri, 31 Jul 2026 07:22:55 -0700 Subject: [PATCH 2/3] feat(linux): add the Flathub manifest and its release tarball The Flatpak manifest, AppStream metainfo, desktop entry, and launcher shim that get committed to the Flathub repository. They live here so they are reviewed and version-bumped alongside the code they package. The manifest consumes a jpackage app image tarball rather than building from source: every client/* module applies the Android Gradle plugin, so even the JVM target needs the Android SDK to configure, and the SDK cannot be a Flatpak source. Desktop Release now stages that tarball (app image with its bundled jlink runtime, hicolor icon sizes resized from the 584x584 source, and the license) and attaches it to the GitHub Release, where x-checker-data picks it up. Runtime choices worth noting: org.gnome.Platform because JavaFX links against GTK 3, plain --socket=x11 because neither AWT/Skiko nor JavaFX has a Wayland backend, and xdg-pictures/documents/download because the file pickers are java.awt.FileDialog and never reach the document portal. Co-Authored-By: Claude Opus 5 --- .github/workflows/desktop-release.yml | 53 +++++++++++ packaging/linux/chef-mate.sh | 11 +++ .../linux/com.plusmobileapps.chefmate.desktop | 13 +++ .../com.plusmobileapps.chefmate.metainfo.xml | 94 +++++++++++++++++++ .../linux/com.plusmobileapps.chefmate.yml | 79 ++++++++++++++++ 5 files changed, 250 insertions(+) create mode 100755 packaging/linux/chef-mate.sh create mode 100644 packaging/linux/com.plusmobileapps.chefmate.desktop create mode 100644 packaging/linux/com.plusmobileapps.chefmate.metainfo.xml create mode 100644 packaging/linux/com.plusmobileapps.chefmate.yml diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 1acce1bde..89be68503 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -39,6 +39,59 @@ jobs: BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} run: ./gradlew :client:composeApp:${{ matrix.task }} + # The Flathub manifest (packaging/linux/com.plusmobileapps.chefmate.yml) consumes this tarball + # rather than the .deb: it is the jpackage app image — bundled jlink runtime included, so the + # flatpak needs no JDK extension — plus the hicolor icon sizes and the license file that the + # manifest installs into the sandbox prefix. + - name: Package Linux app image tarball (Flathub) + if: runner.os == 'Linux' + # Same reason the Windows MSIX step re-declares these: BuildKonfig reads them via + # System.getenv at Gradle configuration time, and GitHub Actions env vars are per-step. + # Without them this step reconfigures with placeholder Supabase keys and rebuilds the app + # image with broken auth. + env: + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} + run: | + set -euo pipefail + ./gradlew :client:composeApp:createReleaseDistributable + + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + VERSION="${GITHUB_REF_NAME#v}" + else + VERSION=$(sed -n 's/.*versionName = "\(.*\)".*/\1/p' client/composeApp/build.gradle.kts | head -n1) + fi + + # jpackage names the app image directory after the platform packageName, so glob for it + # instead of hardcoding a name the Gradle config could change out from under us. + APP_DIR=$(find client/composeApp/build/compose/binaries/main-release/app \ + -mindepth 1 -maxdepth 1 -type d | head -n1) + if [ -z "$APP_DIR" ]; then echo "No app image produced" >&2; exit 1; fi + + STAGE="${RUNNER_TEMP}/chef-mate-${VERSION}" + mkdir -p "$STAGE/icons" + cp -a "$APP_DIR" "$STAGE/app" + cp LICENSE "$STAGE/LICENSE" + + # Flathub wants icons at the standard hicolor sizes; the source is 584x584, which is not + # one of them, so resize rather than shipping a mis-binned icon. + IM=$(command -v magick || command -v convert) + for size in 64 128 256 512; do + "$IM" client/composeApp/src/jvmMain/resources/app-icon.png \ + -resize "${size}x${size}" "$STAGE/icons/${size}.png" + done + + tar -czf "${RUNNER_TEMP}/chef-mate-${VERSION}-linux-x86_64.tar.gz" \ + -C "${RUNNER_TEMP}" "chef-mate-${VERSION}" + + - name: Upload Flathub tarball (Linux) + if: runner.os == 'Linux' + uses: actions/upload-artifact@v4 + with: + name: desktop-linux-flatpak-tarball + path: ${{ runner.temp }}/chef-mate-*-linux-x86_64.tar.gz + - name: Build app image for MSIX (Windows) if: runner.os == 'Windows' # BuildKonfig reads these at Gradle configuration time via System.getenv, and GitHub diff --git a/packaging/linux/chef-mate.sh b/packaging/linux/chef-mate.sh new file mode 100755 index 000000000..2fedf454c --- /dev/null +++ b/packaging/linux/chef-mate.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Flatpak launcher shim. The jpackage app image lives at /app/chef-mate; its bin/chef-mate launcher +# resolves the bundled jlink runtime relative to itself, so it must be exec'd in place rather than +# symlinked into /app/bin. +set -e + +# JavaFX (the in-app recipe browser's WebView) has no Wayland backend and aborts if GDK picks one, +# so pin GDK to X11 — the manifest grants --socket=x11, which gives XWayland in Wayland sessions. +export GDK_BACKEND=x11 + +exec /app/chef-mate/bin/chef-mate "$@" diff --git a/packaging/linux/com.plusmobileapps.chefmate.desktop b/packaging/linux/com.plusmobileapps.chefmate.desktop new file mode 100644 index 000000000..d5a8519eb --- /dev/null +++ b/packaging/linux/com.plusmobileapps.chefmate.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name=Chef Mate +GenericName=Recipe Manager +Comment=Save, organize, and cook your recipes with an AI kitchen assistant +Exec=chef-mate %u +Icon=com.plusmobileapps.chefmate +Terminal=false +Categories=Utility; +Keywords=recipe;cooking;kitchen;food;meal;grocery; +StartupNotify=true +StartupWMClass=com.plusmobileapps.chefmate +MimeType=x-scheme-handler/chefmate; diff --git a/packaging/linux/com.plusmobileapps.chefmate.metainfo.xml b/packaging/linux/com.plusmobileapps.chefmate.metainfo.xml new file mode 100644 index 000000000..acd898d8d --- /dev/null +++ b/packaging/linux/com.plusmobileapps.chefmate.metainfo.xml @@ -0,0 +1,94 @@ + + + com.plusmobileapps.chefmate + + Chef Mate + Save, organize, and cook your recipes + + CC0-1.0 + Apache-2.0 + + + Plus Mobile Apps + + + +

+ Chef Mate is a recipe manager and cooking companion. Import recipes from the web, write your + own, organize them into recipe books, and follow them step by step while you cook. +

+

Features:

+
    +
  • Import recipes from any website with the built-in browser
  • +
  • Organize recipes into books and share them with other people
  • +
  • Build grocery lists from the recipes you plan to cook
  • +
  • Ask an AI kitchen assistant about substitutions, scaling, and technique
  • +
  • Sync your library across desktop, Android, and iOS with a free account
  • +
+

+ An account is optional — recipes are stored locally and the app works offline. Signing in + enables sync and sharing. The AI assistant sends your question to a remote service to answer + it. +

+
+ + com.plusmobileapps.chefmate.desktop + + https://chefmate.plusmobileapps.com + https://github.com/Plus-Mobile-Apps/chef-mate/issues + https://github.com/Plus-Mobile-Apps/chef-mate + + + + + https://chefmate.plusmobileapps.com/flathub/screenshot-recipe-list.png + Your recipe library + + + https://chefmate.plusmobileapps.com/flathub/screenshot-recipe-detail.png + Cook mode with ingredients and steps side by side + + + https://chefmate.plusmobileapps.com/flathub/screenshot-grocery-list.png + Grocery lists built from the recipes you plan to cook + + + + + Utility + + + + recipe + cooking + kitchen + grocery + meal + + + + + mild + + + + pointing + keyboard + + + + 768 + + + support_AT_plusmobileapps.com + + + + https://github.com/Plus-Mobile-Apps/chef-mate/releases/tag/v1.9.40 + + +
diff --git a/packaging/linux/com.plusmobileapps.chefmate.yml b/packaging/linux/com.plusmobileapps.chefmate.yml new file mode 100644 index 000000000..b5dafd8a8 --- /dev/null +++ b/packaging/linux/com.plusmobileapps.chefmate.yml @@ -0,0 +1,79 @@ +# Flatpak manifest for Chef Mate (Linux desktop). +# +# This is the file that gets submitted to https://github.com/flathub/flathub — it lives here so it +# can be reviewed and version-bumped alongside the app. See README.md in this directory for the +# build/test/submit workflow. +# +# The module consumes the jpackage app image tarball published by the Desktop Release workflow +# (`chef-mate--linux-x86_64.tar.gz` on the GitHub Release). That tarball already contains a +# jlink'd JRE, so the flatpak needs no JDK extension — see README.md for why this isn't built from +# source. +id: com.plusmobileapps.chefmate + +# GNOME rather than Freedesktop: JavaFX (used by the in-app recipe browser's WebView) links against +# GTK 3, which org.freedesktop.Platform does not ship. +runtime: org.gnome.Platform +runtime-version: "50" +sdk: org.gnome.Sdk + +command: chef-mate + +finish-args: + - --share=ipc + # Plain x11, not fallback-x11: Compose Desktop renders through AWT/Skiko and JavaFX renders + # through GTK, and neither has a Wayland backend — both need XWayland even in a Wayland session. + - --socket=x11 + - --device=dri + # Supabase sync, AI chat, and the in-app recipe browser. + - --share=network + # The file pickers are java.awt.FileDialog (ImagePickerUtil / ZipPickerUtil / ZipSaveUtil), which + # browses the sandbox filesystem directly instead of going through the portal, so the user-facing + # directories have to be mounted for import/export to work at all. + - --filesystem=xdg-pictures + - --filesystem=xdg-documents + - --filesystem=xdg-download + +modules: + - name: chef-mate + buildsystem: simple + build-commands: + - mkdir -p /app/chef-mate + - cp -r chefmate/app/. /app/chef-mate/ + - chmod +x /app/chef-mate/bin/chef-mate + - install -Dm755 chef-mate.sh /app/bin/chef-mate + - install -Dm644 com.plusmobileapps.chefmate.desktop -t /app/share/applications + - install -Dm644 com.plusmobileapps.chefmate.metainfo.xml -t /app/share/metainfo + - install -Dm644 chefmate/icons/64.png + /app/share/icons/hicolor/64x64/apps/com.plusmobileapps.chefmate.png + - install -Dm644 chefmate/icons/128.png + /app/share/icons/hicolor/128x128/apps/com.plusmobileapps.chefmate.png + - install -Dm644 chefmate/icons/256.png + /app/share/icons/hicolor/256x256/apps/com.plusmobileapps.chefmate.png + - install -Dm644 chefmate/icons/512.png + /app/share/icons/hicolor/512x512/apps/com.plusmobileapps.chefmate.png + - install -Dm644 chefmate/LICENSE + /app/share/licenses/com.plusmobileapps.chefmate/LICENSE + sources: + - type: archive + url: https://github.com/Plus-Mobile-Apps/chef-mate/releases/download/v1.9.40/chef-mate-1.9.40-linux-x86_64.tar.gz + # Replace on every release — `sha256sum` of the uploaded tarball. + sha256: 0000000000000000000000000000000000000000000000000000000000000000 + # The tarball root is `chef-mate-/`; strip it so the paths below are stable + # across releases. + strip-components: 1 + dest: chefmate + only-arches: + - x86_64 + # Lets Flathub's external-data-checker open the version-bump PR automatically when a new + # GitHub Release lands, instead of hand-editing the url/sha256 above. + x-checker-data: + type: json + url: https://api.github.com/repos/Plus-Mobile-Apps/chef-mate/releases/latest + version-query: .tag_name | sub("^v"; "") + url-query: .assets[] | select(.name | endswith("-linux-x86_64.tar.gz")) | .browser_download_url + - type: file + path: chef-mate.sh + - type: file + path: com.plusmobileapps.chefmate.desktop + - type: file + path: com.plusmobileapps.chefmate.metainfo.xml From ca6058d32c0fe3770afb40cef45f686368e389f4 Mon Sep 17 00:00:00 2001 From: Andrew Steinmetz Date: Fri, 31 Jul 2026 07:22:55 -0700 Subject: [PATCH 3/3] docs: document the Flathub packaging and submission runbook packaging/linux/README.md covers building and linting the flatpak locally, the pre-submission checklist (screenshots and the tarball sha256 are still open), the submission flow against flathub/flathub's new-pr branch, and why the manifest is not a source build. Cross-linked from the deployment guide next to the Windows and macOS store sections. Co-Authored-By: Claude Opus 5 --- docs/deployment.md | 19 ++++++ packaging/linux/README.md | 122 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 packaging/linux/README.md diff --git a/docs/deployment.md b/docs/deployment.md index f5d018a41..35622760a 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -205,6 +205,8 @@ GitHub always serves `releases/latest/download/` from the newest non-prer This is a *signed-installer* model (download + run the new signed package), **not** in-place jar patching. +It is also a no-op in the Flathub build (`isRunningInFlatpak`), where Flatpak owns updates and the sandbox is read-only. See [Linux: Flathub](#linux-flathub). + **`latest.json` shape:** ```json @@ -219,6 +221,23 @@ This is a *signed-installer* model (download + run the new signed package), **no --- +## Linux: Flathub + +Linux ships two ways: the `.deb` on the GitHub Release (self-updating via the feed above), and a +Flatpak on Flathub. + +The Flatpak manifest and its AppStream/desktop metadata live in [`packaging/linux/`](../packaging/linux/README.md), +which is also the runbook for building, linting, submitting, and maintaining it. In short: + +- App id `com.plusmobileapps.chefmate`, runtime `org.gnome.Platform//50` (GTK 3 is needed by JavaFX). +- The manifest consumes `chef-mate--linux-x86_64.tar.gz` — the jpackage app image, bundled + jlink runtime included — which the `Desktop Release` workflow builds and attaches to the release. + It is **not** built from source on Flathub infrastructure; the reasons are in the packaging README. +- Once submitted, Flathub's external-data-checker opens the version-bump PR when a new release + publishes a matching tarball. Nothing in this repo pushes to Flathub. + +--- + ## Windows: Microsoft Store (MSIX) Windows ships through the **Microsoft Store** as an MSIX package. This is the recommended Windows path because: diff --git a/packaging/linux/README.md b/packaging/linux/README.md new file mode 100644 index 000000000..2d7a58816 --- /dev/null +++ b/packaging/linux/README.md @@ -0,0 +1,122 @@ +# Flathub packaging + +Flatpak/Flathub packaging for the Linux desktop client. The files here are the ones that get +committed to the app's Flathub repository; they live in this repo so they are reviewed and +version-bumped alongside the code they package. + +| File | Purpose | +| ---------------------------------------- | ------------------------------------------------ | +| `com.plusmobileapps.chefmate.yml` | Flatpak manifest (the submission entry point) | +| `com.plusmobileapps.chefmate.metainfo.xml`| AppStream metadata — store listing, OARS, releases| +| `com.plusmobileapps.chefmate.desktop` | Desktop entry exported by the sandbox | +| `chef-mate.sh` | Launcher shim placed at `/app/bin/chef-mate` | + +The app id is `com.plusmobileapps.chefmate`, which Flathub allows because `plusmobileapps.com` is +under our control and serves the app's site at `https://chefmate.plusmobileapps.com`. + +## Why this is not built from source + +Flathub prefers manifests that compile the app inside the build sandbox, with no network access. +That is not achievable for this app today: + +- Every `client/*` module applies the Android Gradle plugin, so the whole Gradle graph — including + the JVM target — requires the Android SDK to configure. The SDK is not redistributable and cannot + be a Flatpak source, so even a JVM-only entry point cannot resolve. +- Flathub builds are offline. A Gradle build of this size would need every Maven artifact, the + Gradle distribution, and the Kotlin compiler pinned as manifest sources (via something like + `flatpak-gradle-generator`), regenerated on each dependency change. + +So the manifest consumes the jpackage app image tarball published by the `Desktop Release` workflow, +the same approach Flathub accepts for other large JVM apps (for example IntelliJ IDEA Community, +which is Apache-2.0 and still ships an upstream-built tarball). **Expect reviewers to ask about +this** — the answer is the two bullets above. If they insist on a source build, the prerequisite is +splitting a JVM-only variant of the module graph that never applies the Android plugin. + +The tarball already contains a jlink'd JRE produced by jpackage, so the manifest needs no OpenJDK +SDK extension. + +## Runtime choices + +- **`org.gnome.Platform`, not `org.freedesktop.Platform`** — JavaFX (the in-app recipe browser's + WebView) links against GTK 3, which the Freedesktop runtime does not ship. +- **`--socket=x11`, not `--socket=fallback-x11`** — Compose Desktop renders through AWT/Skiko and + JavaFX through GTK; neither has a Wayland backend, so both need XWayland even in a Wayland + session. `chef-mate.sh` pins `GDK_BACKEND=x11` so GDK does not pick Wayland out from under JavaFX. +- **`--filesystem=xdg-pictures`, `xdg-documents`, `xdg-download`** — the pickers are + `java.awt.FileDialog`, which browses the sandbox filesystem directly instead of going through the + document portal. Without these, photo import and recipe archive import/export cannot see anything. + +## Code paths that are Flatpak-aware + +- `platform/Flatpak.kt` — `isRunningInFlatpak`, detected from `FLATPAK_ID` / `/.flatpak-info`. +- `update/DesktopUpdater.kt` — the in-app updater is disabled under Flatpak. Flathub owns updates, + and the sandbox is read-only, so downloading and running a `.deb` cannot work. +- `deeplink/SchemeRegistrar.kt` — self-registration of `chefmate://` is skipped; the exported + `.desktop` file declares `x-scheme-handler/chefmate` instead. +- `database/DriverFactory.jvm.kt` — the Linux data directory honors `XDG_DATA_HOME`, which Flatpak + points at `~/.var/app/com.plusmobileapps.chefmate/data`. The hardcoded `~/.local/share` is not + writable inside the sandbox. +- `main.kt` — sets `sun.awt.X11.XToolkit.awtAppClassName` so `WM_CLASS` matches the `.desktop` + file's `StartupWMClass` and the window binds to the right dock icon. + +## Build and test locally (needs a Linux machine) + +```bash +flatpak install -y flathub org.flatpak.Builder org.gnome.Platform//50 org.gnome.Sdk//50 +``` + +```bash +flatpak run --command=flathub-build org.flatpak.Builder --install packaging/linux/com.plusmobileapps.chefmate.yml +``` + +```bash +flatpak run com.plusmobileapps.chefmate +``` + +```bash +flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest packaging/linux/com.plusmobileapps.chefmate.yml +``` + +```bash +flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream packaging/linux/com.plusmobileapps.chefmate.metainfo.xml +``` + +Both linters must pass clean before submitting. + +## Before the first submission + +1. **Screenshots.** `com.plusmobileapps.chefmate.metainfo.xml` points at + `https://chefmate.plusmobileapps.com/flathub/*.png`, which do not exist yet. Capture them from + the Linux build and host them at those URLs (or edit the URLs to wherever they land). At least + one is required, and the first is the hero image on the store page. +2. **Tarball on the release.** Cut a release so + `chef-mate--linux-x86_64.tar.gz` exists as a GitHub Release asset, then put its real + `sha256sum` in the manifest — the checked-in value is a placeholder. +3. **Verify the runtime branch.** The manifest pins GNOME 50; confirm it is still current with + `flatpak remote-ls flathub --runtime | grep org.gnome.Platform`. +4. **Read the Flathub requirements**, in particular the generative AI policy at + — it applies to the app, the + manifest, the PR description, and the review replies. + +## Submitting + +Per : + +1. Fork with "Copy the master branch only" **unchecked**. +2. Clone the `new-pr` branch and create a branch named `com.plusmobileapps.chefmate`. +3. Add the four files from this directory at the repository root. +4. Open a PR against the `new-pr` base branch — **not** `master`. +5. Do not close the PR while addressing feedback, and do not merge `master` into the branch. + +After approval Flathub creates `flathub/com.plusmobileapps.chefmate`. Accept the repo invite within +a week and enable 2FA on the GitHub account. + +## Maintaining + +The manifest carries `x-checker-data`, so Flathub's external-data-checker opens a version-bump PR +against the app repo when a new GitHub Release publishes a matching tarball. Merging that PR is what +ships the update — nothing in this repository publishes to Flathub directly. + +To claim the app as verified, follow + (a token under +`https://plusmobileapps.com/.well-known/org.flathub.VerifiedApps.txt`).