From cb175228b3e447eac729b3c8013b1875113c2a61 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Thu, 5 Nov 2026 02:35:53 +0000 Subject: [PATCH 1/2] refactor: one directory per app store under appstores/ Every store this ships to gets its own directory holding the whole build for that store: source, signing instructions, the art its listing needs, and the copy for its fields. firetv/ moves to appstores/firetv/ unchanged; android/, ios/, appletv/ and roku/ are stubs that say what each store will take and the rules that catch people on it (Play wants an .aab and moves its targetSdk floor every August; Apple rejects bare web wrappers under 4.2; tvOS has no WKWebView at all, so it cannot be a shell; Roku only accepts a .pkg signed on a physical device). Signing keys stay in their team vaults, never in the tree. Co-Authored-By: Claude Opus 5 (1M context) --- appstores/README.md | 20 ++++++++++++++++++ appstores/android/README.md | 19 +++++++++++++++++ appstores/appletv/README.md | 14 ++++++++++++ {firetv => appstores/firetv}/.gitignore | 0 {firetv => appstores/firetv}/README.md | 4 ++-- {firetv => appstores/firetv}/app/build.gradle | 0 .../firetv}/app/src/main/AndroidManifest.xml | 0 .../firetv}/app/src/main/assets/offline.html | 0 .../firetv}/app/src/main/assets/tv.js | 0 .../main/java/com/pairux/tv/MainActivity.java | 0 .../src/main/res/drawable-xhdpi/banner.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../app/src/main/res/values/strings.xml | 0 .../app/src/main/res/values/styles.xml | 0 {firetv => appstores/firetv}/build.gradle | 0 {firetv => appstores/firetv}/build.sh | 0 {firetv => appstores/firetv}/emulator-test.sh | 0 .../firetv}/gradle.properties | 0 {firetv => appstores/firetv}/new-key.sh | 0 {firetv => appstores/firetv}/settings.gradle | 0 {firetv => appstores/firetv}/store/listing.md | 0 {firetv => appstores/firetv}/store/render.sh | 2 +- .../firetv}/store/shots/01-home.png | Bin .../firetv}/store/shots/03-keyboard.png | Bin .../firetv}/store/shots/04-results.png | Bin .../firetv}/store/shots/05-results-nav.png | Bin .../firetv}/store/shots/05b-results-right.png | Bin .../firetv}/store/shots/06-back.png | Bin .../firetv}/store/shots/07-menu-search.png | Bin .../firetv}/store/shots/08-code-entered.png | Bin .../firetv}/store/src/background.svg | 0 .../firetv}/store/src/banner.svg | 0 .../firetv}/store/src/icon.svg | 0 .../firetv}/store/src/logo-light.svg | 0 appstores/ios/README.md | 15 +++++++++++++ appstores/roku/README.md | 13 ++++++++++++ 37 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 appstores/README.md create mode 100644 appstores/android/README.md create mode 100644 appstores/appletv/README.md rename {firetv => appstores/firetv}/.gitignore (100%) rename {firetv => appstores/firetv}/README.md (94%) rename {firetv => appstores/firetv}/app/build.gradle (100%) rename {firetv => appstores/firetv}/app/src/main/AndroidManifest.xml (100%) rename {firetv => appstores/firetv}/app/src/main/assets/offline.html (100%) rename {firetv => appstores/firetv}/app/src/main/assets/tv.js (100%) rename {firetv => appstores/firetv}/app/src/main/java/com/pairux/tv/MainActivity.java (100%) rename {firetv => appstores/firetv}/app/src/main/res/drawable-xhdpi/banner.png (100%) rename {firetv => appstores/firetv}/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {firetv => appstores/firetv}/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {firetv => appstores/firetv}/app/src/main/res/values/strings.xml (100%) rename {firetv => appstores/firetv}/app/src/main/res/values/styles.xml (100%) rename {firetv => appstores/firetv}/build.gradle (100%) rename {firetv => appstores/firetv}/build.sh (100%) rename {firetv => appstores/firetv}/emulator-test.sh (100%) rename {firetv => appstores/firetv}/gradle.properties (100%) rename {firetv => appstores/firetv}/new-key.sh (100%) rename {firetv => appstores/firetv}/settings.gradle (100%) rename {firetv => appstores/firetv}/store/listing.md (100%) rename {firetv => appstores/firetv}/store/render.sh (93%) rename {firetv => appstores/firetv}/store/shots/01-home.png (100%) rename {firetv => appstores/firetv}/store/shots/03-keyboard.png (100%) rename {firetv => appstores/firetv}/store/shots/04-results.png (100%) rename {firetv => appstores/firetv}/store/shots/05-results-nav.png (100%) rename {firetv => appstores/firetv}/store/shots/05b-results-right.png (100%) rename {firetv => appstores/firetv}/store/shots/06-back.png (100%) rename {firetv => appstores/firetv}/store/shots/07-menu-search.png (100%) rename {firetv => appstores/firetv}/store/shots/08-code-entered.png (100%) rename {firetv => appstores/firetv}/store/src/background.svg (100%) rename {firetv => appstores/firetv}/store/src/banner.svg (100%) rename {firetv => appstores/firetv}/store/src/icon.svg (100%) rename {firetv => appstores/firetv}/store/src/logo-light.svg (100%) create mode 100644 appstores/ios/README.md create mode 100644 appstores/roku/README.md diff --git a/appstores/README.md b/appstores/README.md new file mode 100644 index 00000000..bf5c06cd --- /dev/null +++ b/appstores/README.md @@ -0,0 +1,20 @@ +# appstores + +One directory per store pairux.com ships to. Each holds the whole build for that +store: source, signing instructions, the art the listing needs, and the copy +that goes in the store's own fields. + +| Directory | Store | State | +| --- | --- | --- | +| `firetv/` | Amazon Appstore (Fire TV) | built, submitted to Amazon | +| `android/` | Google Play | not built | +| `ios/` | Apple App Store (iPhone, iPad) | not built | +| `appletv/` | Apple App Store (tvOS) | not built | +| `roku/` | Roku Channel Store | not built | + +The web app at https://pairux.com is the product; these are shells and native +builds that put it in front of people who look for apps in a store. + +**Signing keys never live here.** Each build's key is in its own team vault +(`profullstack/---signing`) and the build scripts read it from the +environment. Losing a key means that store's users can never update in place. diff --git a/appstores/android/README.md b/appstores/android/README.md new file mode 100644 index 00000000..30be9e8b --- /dev/null +++ b/appstores/android/README.md @@ -0,0 +1,19 @@ +# Google Play (pairux.com) + +Not built yet. + +**What it takes:** an Android app (the Fire TV shell in `../firetv` is already a +plain Android app and is the obvious starting point — same WebView, same +`tv.js`, minus the leanback intent filter and plus a phone layout), a Play +Console developer account, an upload key, and a Play App Signing enrolment. + +**Before the first upload** +- Play requires an **App Bundle** (`.aab`), not an APK, and a `targetSdk` that + meets Play's current floor. The Fire TV build targets 34 for Fire OS; Play + moves its floor every August, so check it rather than assuming. +- `applicationId` must differ from nothing else on Play, and once published it + can never change. +- Play wants a privacy policy URL, a Data safety form, and content rating + answers — the same facts as the Amazon listing, asked differently. +- The listing copy and art in `../firetv/store` mostly carries over; Play's + screenshot sizes and the 1024x500 feature graphic do not. diff --git a/appstores/appletv/README.md b/appstores/appletv/README.md new file mode 100644 index 00000000..a076ea37 --- /dev/null +++ b/appstores/appletv/README.md @@ -0,0 +1,14 @@ +# Apple App Store, tvOS (pairux.com) + +Not built yet. + +**What it takes:** a separate tvOS target (tvOS has **no WKWebView** — this is +the one platform where the web app cannot simply be wrapped, and the screens +have to be built natively in SwiftUI or UIKit against the same API the web app +uses), plus the same Apple Developer membership and signing as `../ios`. + +**Before the first upload** +- tvOS apps are driven by the Siri Remote: focus engine first, no pointer, no + keyboard beyond the on-screen one. +- The top shelf image and the layered app icon are required and are unlike any + other store's art. diff --git a/firetv/.gitignore b/appstores/firetv/.gitignore similarity index 100% rename from firetv/.gitignore rename to appstores/firetv/.gitignore diff --git a/firetv/README.md b/appstores/firetv/README.md similarity index 94% rename from firetv/README.md rename to appstores/firetv/README.md index 5fd3a368..1af8e2d0 100644 --- a/firetv/README.md +++ b/appstores/firetv/README.md @@ -26,7 +26,7 @@ sed -n 's/^PX_TV_KEYSTORE_B64=//p' keys/release.env | tr -d '"' | base64 -d > ke docker run --rm -e OWNER=$(id -u):$(id -g) -e GRADLE_USER_HOME=/gh \ -v ~/scratch/gradle-home:/gh -v ~/scratch/android-sdk:/sdk -v "$PWD/keys:/keys:ro" \ - -v "$PWD/firetv:/w" -w /w gradle:8.7-jdk17 sh build.sh + -v "$PWD/appstores/firetv:/w" -w /w gradle:8.7-jdk17 sh build.sh # -> firetv/dist/pairux.com-firetv.apk (lint + signed release) ``` @@ -38,7 +38,7 @@ again or installed copies cannot update. ```sh docker run --rm --device /dev/kvm -e OWNER=$(id -u):$(id -g) -v ~/scratch/android-sdk:/sdk \ - -v "$PWD/firetv:/w" -w /w gradle:8.7-jdk17 bash emulator-test.sh + -v "$PWD/appstores/firetv:/w" -w /w gradle:8.7-jdk17 bash emulator-test.sh ``` Boots API 30 Android TV (the base of Fire OS 8), installs the APK, drives it with key diff --git a/firetv/app/build.gradle b/appstores/firetv/app/build.gradle similarity index 100% rename from firetv/app/build.gradle rename to appstores/firetv/app/build.gradle diff --git a/firetv/app/src/main/AndroidManifest.xml b/appstores/firetv/app/src/main/AndroidManifest.xml similarity index 100% rename from firetv/app/src/main/AndroidManifest.xml rename to appstores/firetv/app/src/main/AndroidManifest.xml diff --git a/firetv/app/src/main/assets/offline.html b/appstores/firetv/app/src/main/assets/offline.html similarity index 100% rename from firetv/app/src/main/assets/offline.html rename to appstores/firetv/app/src/main/assets/offline.html diff --git a/firetv/app/src/main/assets/tv.js b/appstores/firetv/app/src/main/assets/tv.js similarity index 100% rename from firetv/app/src/main/assets/tv.js rename to appstores/firetv/app/src/main/assets/tv.js diff --git a/firetv/app/src/main/java/com/pairux/tv/MainActivity.java b/appstores/firetv/app/src/main/java/com/pairux/tv/MainActivity.java similarity index 100% rename from firetv/app/src/main/java/com/pairux/tv/MainActivity.java rename to appstores/firetv/app/src/main/java/com/pairux/tv/MainActivity.java diff --git a/firetv/app/src/main/res/drawable-xhdpi/banner.png b/appstores/firetv/app/src/main/res/drawable-xhdpi/banner.png similarity index 100% rename from firetv/app/src/main/res/drawable-xhdpi/banner.png rename to appstores/firetv/app/src/main/res/drawable-xhdpi/banner.png diff --git a/firetv/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/appstores/firetv/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from firetv/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to appstores/firetv/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/firetv/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/appstores/firetv/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from firetv/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to appstores/firetv/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/firetv/app/src/main/res/values/strings.xml b/appstores/firetv/app/src/main/res/values/strings.xml similarity index 100% rename from firetv/app/src/main/res/values/strings.xml rename to appstores/firetv/app/src/main/res/values/strings.xml diff --git a/firetv/app/src/main/res/values/styles.xml b/appstores/firetv/app/src/main/res/values/styles.xml similarity index 100% rename from firetv/app/src/main/res/values/styles.xml rename to appstores/firetv/app/src/main/res/values/styles.xml diff --git a/firetv/build.gradle b/appstores/firetv/build.gradle similarity index 100% rename from firetv/build.gradle rename to appstores/firetv/build.gradle diff --git a/firetv/build.sh b/appstores/firetv/build.sh similarity index 100% rename from firetv/build.sh rename to appstores/firetv/build.sh diff --git a/firetv/emulator-test.sh b/appstores/firetv/emulator-test.sh similarity index 100% rename from firetv/emulator-test.sh rename to appstores/firetv/emulator-test.sh diff --git a/firetv/gradle.properties b/appstores/firetv/gradle.properties similarity index 100% rename from firetv/gradle.properties rename to appstores/firetv/gradle.properties diff --git a/firetv/new-key.sh b/appstores/firetv/new-key.sh similarity index 100% rename from firetv/new-key.sh rename to appstores/firetv/new-key.sh diff --git a/firetv/settings.gradle b/appstores/firetv/settings.gradle similarity index 100% rename from firetv/settings.gradle rename to appstores/firetv/settings.gradle diff --git a/firetv/store/listing.md b/appstores/firetv/store/listing.md similarity index 100% rename from firetv/store/listing.md rename to appstores/firetv/store/listing.md diff --git a/firetv/store/render.sh b/appstores/firetv/store/render.sh similarity index 93% rename from firetv/store/render.sh rename to appstores/firetv/store/render.sh index 8681249b..6e80668e 100644 --- a/firetv/store/render.sh +++ b/appstores/firetv/store/render.sh @@ -1,7 +1,7 @@ #!/bin/sh # Renders the launcher icons, TV banner and Amazon Appstore art. # icon.svg and logo-light.svg are PairUX's own artwork, copied from apps/web/public. -# Runs inside alpine: docker run --rm -v "$PWD/firetv:/w" -w /w alpine:3.20 sh store/render.sh +# Runs inside alpine: docker run --rm -v "$PWD/appstores/firetv:/w" -w /w alpine:3.20 sh store/render.sh set -eu apk add --no-cache rsvg-convert imagemagick font-dejavu >/dev/null R=app/src/main/res diff --git a/firetv/store/shots/01-home.png b/appstores/firetv/store/shots/01-home.png similarity index 100% rename from firetv/store/shots/01-home.png rename to appstores/firetv/store/shots/01-home.png diff --git a/firetv/store/shots/03-keyboard.png b/appstores/firetv/store/shots/03-keyboard.png similarity index 100% rename from firetv/store/shots/03-keyboard.png rename to appstores/firetv/store/shots/03-keyboard.png diff --git a/firetv/store/shots/04-results.png b/appstores/firetv/store/shots/04-results.png similarity index 100% rename from firetv/store/shots/04-results.png rename to appstores/firetv/store/shots/04-results.png diff --git a/firetv/store/shots/05-results-nav.png b/appstores/firetv/store/shots/05-results-nav.png similarity index 100% rename from firetv/store/shots/05-results-nav.png rename to appstores/firetv/store/shots/05-results-nav.png diff --git a/firetv/store/shots/05b-results-right.png b/appstores/firetv/store/shots/05b-results-right.png similarity index 100% rename from firetv/store/shots/05b-results-right.png rename to appstores/firetv/store/shots/05b-results-right.png diff --git a/firetv/store/shots/06-back.png b/appstores/firetv/store/shots/06-back.png similarity index 100% rename from firetv/store/shots/06-back.png rename to appstores/firetv/store/shots/06-back.png diff --git a/firetv/store/shots/07-menu-search.png b/appstores/firetv/store/shots/07-menu-search.png similarity index 100% rename from firetv/store/shots/07-menu-search.png rename to appstores/firetv/store/shots/07-menu-search.png diff --git a/firetv/store/shots/08-code-entered.png b/appstores/firetv/store/shots/08-code-entered.png similarity index 100% rename from firetv/store/shots/08-code-entered.png rename to appstores/firetv/store/shots/08-code-entered.png diff --git a/firetv/store/src/background.svg b/appstores/firetv/store/src/background.svg similarity index 100% rename from firetv/store/src/background.svg rename to appstores/firetv/store/src/background.svg diff --git a/firetv/store/src/banner.svg b/appstores/firetv/store/src/banner.svg similarity index 100% rename from firetv/store/src/banner.svg rename to appstores/firetv/store/src/banner.svg diff --git a/firetv/store/src/icon.svg b/appstores/firetv/store/src/icon.svg similarity index 100% rename from firetv/store/src/icon.svg rename to appstores/firetv/store/src/icon.svg diff --git a/firetv/store/src/logo-light.svg b/appstores/firetv/store/src/logo-light.svg similarity index 100% rename from firetv/store/src/logo-light.svg rename to appstores/firetv/store/src/logo-light.svg diff --git a/appstores/ios/README.md b/appstores/ios/README.md new file mode 100644 index 00000000..0b61429e --- /dev/null +++ b/appstores/ios/README.md @@ -0,0 +1,15 @@ +# Apple App Store (pairux.com) + +Not built yet. + +**What it takes:** an Xcode project (a WKWebView shell is the equivalent of the +Fire TV build), an Apple Developer Program membership, a signing certificate and +provisioning profile, and a Mac or a hosted macOS runner to build and notarise. + +**Before the first upload** +- Apple rejects apps that are only a website in a shell unless they offer + something a browser tab does not: offline, notifications, Handoff, widgets, + share extensions. Guideline 4.2 is the one that catches these. +- An app that takes an account needs **Sign in with Apple** when it offers any + other third-party sign-in, and a working account-deletion path inside the app. +- App Privacy answers are per data type and are published on the listing. diff --git a/appstores/roku/README.md b/appstores/roku/README.md new file mode 100644 index 00000000..9695df9c --- /dev/null +++ b/appstores/roku/README.md @@ -0,0 +1,13 @@ +# Roku Channel Store (pairux.com) + +Not built yet. + +**What it takes:** a SceneGraph/BrightScript channel, a developer account at +developer.roku.com, and — the part that catches people — a **physical Roku in +developer mode**: the store only accepts a `.pkg` signed on a device, and a +plain zip is refused with "Package file has an invalid header". + +**Before the first upload** +- Roku reviews against its own channel guidelines, and asks for test credentials + that work from a cold start. +- The listing needs its own poster art sizes, unrelated to the other stores'. From c6692a2d510f92028c3d193c1ad8071e4a857a1a Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Thu, 5 Nov 2026 02:46:17 +0000 Subject: [PATCH 2/2] chore: prettier on the new store READMEs (format:check is repo-wide) Co-Authored-By: Claude Opus 5 (1M context) --- appstores/README.md | 14 +++++++------- appstores/android/README.md | 1 + appstores/appletv/README.md | 1 + appstores/ios/README.md | 1 + appstores/roku/README.md | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/appstores/README.md b/appstores/README.md index bf5c06cd..341c263e 100644 --- a/appstores/README.md +++ b/appstores/README.md @@ -4,13 +4,13 @@ One directory per store pairux.com ships to. Each holds the whole build for that store: source, signing instructions, the art the listing needs, and the copy that goes in the store's own fields. -| Directory | Store | State | -| --- | --- | --- | -| `firetv/` | Amazon Appstore (Fire TV) | built, submitted to Amazon | -| `android/` | Google Play | not built | -| `ios/` | Apple App Store (iPhone, iPad) | not built | -| `appletv/` | Apple App Store (tvOS) | not built | -| `roku/` | Roku Channel Store | not built | +| Directory | Store | State | +| ---------- | ------------------------------ | -------------------------- | +| `firetv/` | Amazon Appstore (Fire TV) | built, submitted to Amazon | +| `android/` | Google Play | not built | +| `ios/` | Apple App Store (iPhone, iPad) | not built | +| `appletv/` | Apple App Store (tvOS) | not built | +| `roku/` | Roku Channel Store | not built | The web app at https://pairux.com is the product; these are shells and native builds that put it in front of people who look for apps in a store. diff --git a/appstores/android/README.md b/appstores/android/README.md index 30be9e8b..3c5fa4b4 100644 --- a/appstores/android/README.md +++ b/appstores/android/README.md @@ -8,6 +8,7 @@ plain Android app and is the obvious starting point — same WebView, same Console developer account, an upload key, and a Play App Signing enrolment. **Before the first upload** + - Play requires an **App Bundle** (`.aab`), not an APK, and a `targetSdk` that meets Play's current floor. The Fire TV build targets 34 for Fire OS; Play moves its floor every August, so check it rather than assuming. diff --git a/appstores/appletv/README.md b/appstores/appletv/README.md index a076ea37..bd9dbc4b 100644 --- a/appstores/appletv/README.md +++ b/appstores/appletv/README.md @@ -8,6 +8,7 @@ have to be built natively in SwiftUI or UIKit against the same API the web app uses), plus the same Apple Developer membership and signing as `../ios`. **Before the first upload** + - tvOS apps are driven by the Siri Remote: focus engine first, no pointer, no keyboard beyond the on-screen one. - The top shelf image and the layered app icon are required and are unlike any diff --git a/appstores/ios/README.md b/appstores/ios/README.md index 0b61429e..8ba9e153 100644 --- a/appstores/ios/README.md +++ b/appstores/ios/README.md @@ -7,6 +7,7 @@ Fire TV build), an Apple Developer Program membership, a signing certificate and provisioning profile, and a Mac or a hosted macOS runner to build and notarise. **Before the first upload** + - Apple rejects apps that are only a website in a shell unless they offer something a browser tab does not: offline, notifications, Handoff, widgets, share extensions. Guideline 4.2 is the one that catches these. diff --git a/appstores/roku/README.md b/appstores/roku/README.md index 9695df9c..885e5a4e 100644 --- a/appstores/roku/README.md +++ b/appstores/roku/README.md @@ -8,6 +8,7 @@ developer mode**: the store only accepts a `.pkg` signed on a device, and a plain zip is refused with "Package file has an invalid header". **Before the first upload** + - Roku reviews against its own channel guidelines, and asks for test credentials that work from a cold start. - The listing needs its own poster art sizes, unrelated to the other stores'.