diff --git a/README.md b/README.md index 0c411659..7fc6ed33 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ telemetry. - Official example collection: [PyBLE-dev/examples](https://github.com/PyBLE-dev/examples) - iPad external beta: [join with TestFlight](https://testflight.apple.com/join/yU4e8s6d) -- Android invited testing: [see the app page](https://pyble.dev/app) +- Android 0.2.0 published for open testing: + [get PyBLE on Google Play](https://play.google.com/store/apps/details?id=dev.pyble.pyble) - License: [MIT](LICENSE)

@@ -75,13 +76,20 @@ and asks separately before overwriting existing files. Import never creates a remote folder hierarchy, opens an editor document, or runs downloaded code. The current App source version is `0.2.0+8`; App and firmware versions are -independent. iPad testing uses TestFlight, and Android distribution follows -the testing access shown on the [App page](https://pyble.dev/app). The +independent. The published Android version is `0.2.0`, available through +[Google Play open testing](https://play.google.com/store/apps/details?id=dev.pyble.pyble). +iPad testing continues through TestFlight. The +[App page](https://pyble.dev/app) provides both installation links and QR codes. +Google Play determines account, country, device, and testing availability; +open testing is a beta channel, not a production-track release. + +The [publication review](docs/testing/website/android-open-testing-2026-09-25.md) +records the current Android availability. The earlier [Google Play build-8 handoff](docs/testing/google-play/0.2.0-build-8.md) -records prepared artifacts and their exact source; preparation does not -establish a live store release or mean those earlier artifacts contain later -connection-lifecycle fixes. Both platforms share the same Flutter source and -app test gates. The iOS/iPadOS project minimum is 15. +records prepared artifacts and their exact source. The store's public version +does not establish its installed build/source identity or mean those earlier +artifacts contain later connection-lifecycle fixes. Both platforms share the +same Flutter source and app test gates. The iOS/iPadOS project minimum is 15. ### Firmware @@ -168,9 +176,12 @@ shared conformance corpus, documentation, and CI atomically. ## Try PyBLE -1. Install the iPad beta from - [TestFlight](https://testflight.apple.com/join/yU4e8s6d), or build the - Flutter app locally. +1. Install PyBLE 0.2.0 for Android open testing from + [Google Play](https://play.google.com/store/apps/details?id=dev.pyble.pyble), + or install the iPad external beta from + [TestFlight](https://testflight.apple.com/join/yU4e8s6d). The + [App page](https://pyble.dev/app) has links and QR codes for both platforms. + You can also build the Flutter app locally. 2. Open [pyble.dev/flash](https://pyble.dev/flash) in a supported desktop Chromium browser. Confirm that the published v0.6.1 release is selected, then choose only the exact profile matching the board and memory topology. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 06c5f9fb..f4a79c72 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -11,6 +11,9 @@ v0.7.0 remains non-normative and requires separate maintainer approval. ## Available now - iPad external beta through TestFlight +- Android PyBLE 0.2.0 published for + [Google Play open testing](https://play.google.com/store/apps/details?id=dev.pyble.pyble), + subject to Play's account, country, device, and testing availability - PBLE/1 editing, run/stop, console, and file workflows over BLE - Offline Blockly with beginner GPIO and NeoPixel examples - The published firmware v0.6.1 covers five exact profiles: four ESP @@ -18,7 +21,9 @@ v0.7.0 remains non-normative and requires separate maintainer approval. Publication follows the owner's qualification confirmation, recorded in the [owner confirmation](https://pyble.dev/firmware-v0.6.1-owner-confirmation.md). Incomplete automated evidence is preserved unchanged; the qualified v0.6.0 - release remains a separate historical baseline. + release remains a separate historical baseline. The original qualification + tracker [#24](https://github.com/PyBLE-dev/PyBLE/issues/24) was closed as + superseded on 2026-09-13; its incomplete checklist is not marked completed. - Guided [tutorials](https://pyble.dev/learn) with reviewed Lenovo captures, the [firmware functional reference](https://pyble.dev/features), and the separate [official example collection](https://github.com/PyBLE-dev/examples) @@ -27,14 +32,15 @@ v0.7.0 remains non-normative and requires separate maintainer approval. ## Near term - Continue the app 0.2.0 beta train; source is currently `0.2.0+8`. The - [prepared build-8 handoff](testing/google-play/0.2.0-build-8.md) is not proof - of store publication or validation of subsequent source changes. -- Reconcile the original automated qualification tracker - [#24](https://github.com/PyBLE-dev/PyBLE/issues/24) with the recorded - owner-confirmed v0.6.1 publication without inventing missing measurements. + [Android publication review](testing/website/android-open-testing-2026-09-25.md) + records live open-testing availability. The + [prepared build-8 handoff](testing/google-play/0.2.0-build-8.md) remains a + source-specific historical record, not validation of subsequent source + changes in the installed store app. - Review the proposed v0.7.0 Reliable Projects scope before implementation. - Expand user-facing setup, recovery, and board-specific wiring guidance -- Open and document the Android beta distribution path +- Gather Android open-testing feedback and improve installation and recovery + guidance - Convert remaining pre-public planning references into focused GitHub issues - Improve automated app-to-board integration coverage diff --git a/docs/specifications/google-play-release.md b/docs/specifications/google-play-release.md index 66e6b302..cc9af1d2 100644 --- a/docs/specifications/google-play-release.md +++ b/docs/specifications/google-play-release.md @@ -2,18 +2,40 @@ # Google Play open-testing preparation -Status: candidate preparation, 10 September 2026. This contract supplements +Status: historical candidate preparation, 10 September 2026; publication status +updated 25 September 2026. This contract supplements the [app distribution requirements](App/specs.md#9-build-versioning--distribution--bld) and [ADR-0044](../decisions/0044-start-app-v020-beta-train-at-build-5.md). -The owner has reported that open testing is available for the existing Play -application. Preparation does not imply a submitted or approved release. +At preparation time, the owner reported that the open-testing track was +available for the existing Play application. That preparation alone did not +establish a submitted or approved release. + +## Published status — 25 September 2026 + +The owner confirms that PyBLE **0.2.0** is now published on Google Play for +Android **open testing**. The official +[listing](https://play.google.com/store/apps/details?id=dev.pyble.pyble) +identifies SciLabPro and describes open testing for this version. The +[publication review](../testing/website/android-open-testing-2026-09-25.md) +records the primary-source check. + +Current README and website copy MUST reflect this live open-testing status, +as specified by [website §3.1](website.md#31-app-beta-distribution). Google Play +determines account, country, device, and testing availability. Do not infer a +production-track release, worldwide availability, or the installed artifact's +exact version code/source from the public version alone. Keep the official +listing as the public link; no signed-in opt-in workflow is asserted verified. + +The preparation, artifact identities, and remaining historical checklists +below retain their original scope. Publication does not retroactively certify +unrecorded Console decisions, hardware evidence, or later source changes. ## Candidate identity -The next candidate is **0.2.0 (8)**, owned by `app/pubspec.yaml`, for production -package `dev.pyble.pyble`. Build 5 remains a historical handoff; retained local -builds 6 and 7 must not be relabelled or replaced. The automated app-version +The 10 September candidate is **0.2.0 (8)**, owned by `app/pubspec.yaml`, for +production package `dev.pyble.pyble`. Build 5 remains a historical handoff; +retained local builds 6 and 7 must not be relabelled or replaced. The automated app-version contract must bind build 8 to base version 0.2.0, PBLE/1 HELLO, and the existing GitHub User-Agent. If Play Console has already consumed code 8 or higher, increase this declaration and the version contract before rebuilding. @@ -58,10 +80,12 @@ any stale golden requiring a reviewed update. Physical release-candidate BLE checks and iOS distribution parity remain separate release gates; an emulator or historical engineering report does not close them. -Keep the public website's existing testing status until open testing is -actually available. Console declarations, reviewer arrangements, upload, -review submission and rollout remain explicit handoff steps. This preparation -does not authorize sending tester messages or publishing a production release. +At the preparation handoff, the public website's testing status was to remain +unchanged until open testing became available. The dated publication status +above now governs current public copy. Console declarations, reviewer +arrangements, upload, review submission and rollout remain explicit handoff +steps. This preparation does not authorize sending tester messages or +publishing a production release. ## Official references diff --git a/docs/specifications/website.md b/docs/specifications/website.md index c3128a77..f7635e7f 100644 --- a/docs/specifications/website.md +++ b/docs/specifications/website.md @@ -1,7 +1,7 @@ # PyBLE — Public Website Specification Status: **FROZEN (pre-v1 and v1 launch surface)** · Owner: project maintainer · Last updated: -2026-09-01 +2026-09-25 This document is the source of truth for the first public website at `pyble.dev`. It specifies only the public site; the Flutter app, PBLE/1, and @@ -127,23 +127,31 @@ or promise that every eligible board has a firmware image today. ### 3.1 App beta distribution -The currently approved app-testing channels are: +The current app channels, verified on 2026-09-25, are: - the public Apple external-testing invitation at `https://testflight.apple.com/join/yU4e8s6d`; and -- the restricted Android internal-testing listing at - `https://play.google.com/store/apps/details?id=dev.pyble.pyble`, available - only to approved testers signed in with the Google account that was invited. +- PyBLE **0.2.0** published on Google Play for Android **open testing**, at + `https://play.google.com/store/apps/details?id=dev.pyble.pyble`. + +The project owner confirms the Android publication. The official Google Play +listing identifies SciLabPro and describes open testing for PyBLE 0.2.0. This +supersedes the former invited internal-testing status. It does not establish a +production-track release, worldwide availability, a verified opt-in URL, or +the exact source/build identity of the installed store artifact. While either channel is active, the home page and `/app` MUST: -- distinguish the iPad external beta from the Android internal test, without - implying a production App Store or public Google Play release; +- distinguish the iPad external beta from the published Android Google Play + open test, without implying a production-track release on either store; - summarize both channels in the home-page hero and link its primary app action to `/app`; the hero MUST NOT present the iPad channel as the only available app test; -- explain that an unapproved or signed-out visitor may be unable to open the - restricted Google Play listing; +- describe Android as Google Play open testing, with no invited-account or + approved-internal-tester requirement; installation remains subject to Google + Play's account, country, device compatibility, and testing availability; +- keep visible copy, page descriptions, Open Graph/Twitter descriptions, and + setup guidance consistent with these channel states; - provide a normal HTTPS link that works on the device displaying the page; - provide a high-contrast QR code encoding each channel's exact HTTPS destination for a user viewing the site on another screen; and @@ -160,7 +168,7 @@ normal navigation. The two home-page invitations MUST appear in one labelled **Choose your tablet beta** comparison group. Within that group the iPad external-beta and Android -invited-internal-test cards MUST be peer siblings, render side by side when the +open-testing cards MUST be peer siblings, render side by side when the viewport permits, and stack without changing order on narrower screens. Each card retains its own accessible heading, exact link, local QR, direct-device instructions, and channel-specific availability warning. @@ -173,14 +181,23 @@ contract, and corresponding QR asset to change together before deployment. `https://pyble.dev/app` is the stable first-party app-discovery destination encoded by exact-board firmware. It MUST be a complete statically rendered document, not an external redirect. It MUST have its own canonical metadata, -identify the iPad external beta and restricted Android internal test (not -production store releases), reuse `siteConfig.testFlightUrl` and -`siteConfig.googlePlayInternalTestUrl` as the approved destination sources, +identify the iPad external beta and published Android Google Play open test, +reuse `siteConfig.testFlightUrl` and `siteConfig.googlePlayUrl` as the approved +destination sources, present both local authored QR codes plus visible direct-URL fallbacks, and link to `/flash` and `/support`. Its firmware wording MUST remain release-state-neutral or derive from the same build-selected state as the home page; it MUST NOT copy a version/profile claim that can become stale. +The Android QR asset is `/google-play/pyble-google-play-qr.svg` and MUST encode +the exact listing URL above. Its name and accessible description MUST NOT +retain the superseded internal-testing restriction. The existing listing URL +remains the direct installation destination; an inferred or unverified opt-in +URL MUST NOT replace it. Repository documentation MUST distinguish app source +version `0.2.0+8`, published app version `0.2.0`, and independent firmware +versions. A public listing does not prove that later source fixes are included +in the store artifact. + `/app/` MUST permanently normalize to `/app` while preserving the query. Exact document routing MUST coexist with the existing `/app/*` real-app screenshot asset namespace: adding or redirecting the document MUST NOT capture, rename, @@ -1126,9 +1143,10 @@ The v1 site is releasable when: - the hero uses a reviewed real-app capture with meaningful alternative text, a visible actual-app caption, and a responsive crop that never obscures the pictured Blocks workspace or generated Python; -- the current external iPad beta and restricted Android internal test are each - exposed as an operable link and locally served, exact-URL QR code without - claiming production availability; +- the current external iPad beta and published Android Google Play open test + are each exposed as an operable link and locally served, exact-URL QR code, + with matching page/social descriptions and setup guidance; no current copy + requires an Android invitation or claims production-track availability; - `/app` is canonical, statically rendered, uses both approved testing-channel destination/link/QR contracts, links setup and support, and its slash normalization does not break an existing nested `/app/*` screenshot asset; diff --git a/docs/testing/website/android-open-testing-2026-09-25.md b/docs/testing/website/android-open-testing-2026-09-25.md new file mode 100644 index 00000000..83c08e77 --- /dev/null +++ b/docs/testing/website/android-open-testing-2026-09-25.md @@ -0,0 +1,53 @@ + + + +# Android open-testing publication review — 2026-09-25 + +## Observed status + +The project owner confirms that PyBLE is published on Google Play. Current +first-party README and website copy still described an invited Android +internal test, while the public store listing described open testing. + +The official [Google Play listing](https://play.google.com/store/apps/details?id=dev.pyble.pyble) +was retrieved over HTTPS on 2026-09-25 with English/US presentation parameters +(`hl=en&gl=US`). It returned HTTP 200 and identified **SciLabPro**, app version +**0.2.0**, and release notes stating **“Open testing for PyBLE 0.2.0.”** The +package is `dev.pyble.pyble`. This primary-source result supports the +owner-confirmed publication and supersedes the invited internal-testing copy. + +The [AppAgg entry](https://appagg.com/android/education/pyble-micropython-ide-44135975.html?hl=en) +also lists version 0.2.0 and the same open-testing release notes, with a +2026-09-16 listing date. It is corroborating third-party indexing, not proof of +the exact rollout date or Play Console configuration. + +## Current publication contract + +- Describe Android as **PyBLE 0.2.0 published on Google Play for open testing**. +- Keep iPad described as the external TestFlight beta. +- Link directly to the existing official Google Play listing and encode the + same destination in the neutral `/google-play/pyble-google-play-qr.svg` asset. +- Keep README, home, `/app`, metadata, setup guidance, and current distribution + documentation consistent. Remove the invitation requirement from current + Android installation guidance. +- Google Play determines account, country, device, and testing availability. + Open testing does not establish a production-track release or universal + availability. + +The candidate opt-in endpoint redirected to Google sign-in. No signed-in +enrollment or on-device installation was verified in this review. The public +listing remains the link; no inferred opt-in destination is introduced. + +## Source and evidence boundaries + +The repository's App source version remains `0.2.0+8`. The public app version +`0.2.0` does not establish the installed store artifact's version code, source +commit, or inclusion of later connection-lifecycle fixes. The +[build-8 handoff](../google-play/0.2.0-build-8.md) and original submission +materials remain historical preparation records. Firmware versions and +qualification evidence remain independent of the app distribution channel. + +This record documents availability and the coordinated content contract. It +does not attest a deployment, signed-in Play Console state, installed app +binary, or hardware-validation result. Search engines control when indexed +snippets refresh after updated pages are published. diff --git a/tests/publication/test_public_claims.py b/tests/publication/test_public_claims.py index 4a04cf33..a7519240 100644 --- a/tests/publication/test_public_claims.py +++ b/tests/publication/test_public_claims.py @@ -182,13 +182,22 @@ def test_current_public_surfaces_agree_on_v061_publication(self) -> None: "v0.7.0", near_term, ) - self.assertIn("automated qualification", near_term) + self.assertIn("closed as superseded on 2026-09-13", available) + self.assertIn("incomplete checklist is not marked completed", available) def test_readme_distinguishes_app_source_from_store_availability(self) -> None: firmware = markdown_section(self.readme, "What works") normalized = " ".join(firmware.split()) self.assertIn("0.2.0+8", firmware) - self.assertIn("does not establish a live store release", normalized) + self.assertIn("Google Play open testing", normalized) + self.assertIn("published Android version is `0.2.0`", normalized) + self.assertIn("exact source", normalized) + self.assertIn("later connection-lifecycle fixes", normalized) + self.assertIn( + "https://play.google.com/store/apps/details?id=dev.pyble.pyble", + self.readme, + ) + self.assertNotRegex(self.readme, r"(?i)Android (?:invited|internal) test") self.assertIn("offline privacy policy", normalized) self.assertIn("multi-file deletion", normalized) diff --git a/tools/web/DEPLOYMENT.md b/tools/web/DEPLOYMENT.md index 4a0f201d..ad5da8ef 100644 --- a/tools/web/DEPLOYMENT.md +++ b/tools/web/DEPLOYMENT.md @@ -39,9 +39,11 @@ Review at least: - home at phone, portrait-tablet, landscape-tablet, and desktop widths; - home and metadata clearly separate the capability-defined MicroPython + BLE platform vision from the targets validated by the current release; -- `/app`, including the current iPad external-beta and Android internal-test - destinations, scannable local QR images, visible fallback addresses, - invited-account restriction, and links to firmware and support; +- home, `/app`, and setup guidance identify the iPad external beta and published + Android PyBLE 0.2.0 Google Play open test consistently, including page and + social descriptions; verify the official `dev.pyble.pyble` listing link, + scannable local QR images, visible fallback addresses, Play availability + guidance, and links to firmware and support; - `/privacy`, including the effective date and public contact address; - `/support`, including the public contact address; - `/flash`, confirming the install button is still disabled unless the diff --git a/tools/web/README.md b/tools/web/README.md index 295d1aea..0e68e22f 100644 --- a/tools/web/README.md +++ b/tools/web/README.md @@ -34,13 +34,20 @@ introduce a second page source or an application backend. | Route | Purpose | | ----------- | --------------------------------------------------------------------------- | | `/` | Product story, verified capabilities, platform vision, and current targets | -| `/app` | Stable app landing page for iPad and Android testing channels | +| `/app` | iPad TestFlight beta and published Android Google Play open-testing links | | `/privacy` | Separate app and website privacy disclosures | | `/support` | Beta quick start, troubleshooting, and report checklist | | `/flash` | Fail-closed Web Serial installer, exact profiles, and recovery | | `/features` | Release-bound firmware architecture, operations, profiles, limits, evidence | | `/learn` | Ordered tutorial hub, compatibility legend, and learning tracks | +Android PyBLE 0.2.0 is published for Google Play open testing. Home, `/app`, +their metadata, and setup guidance share this status; iPad remains an external +TestFlight beta. The official Android listing uses package `dev.pyble.pyble`. +Both the direct link and local `/google-play/pyble-google-play-qr.svg` QR code +use `https://play.google.com/store/apps/details?id=dev.pyble.pyble`. Play +determines account, country, device, and testing availability. + The ten statically authored tutorials are `/learn/setup`, `/learn/first-program`, `/learn/files`, `/learn/github-import`, `/learn/blocks`, `/learn/examples`, `/learn/hardware`, diff --git a/tools/web/public/google-play/pyble-google-play-internal-test-qr.svg b/tools/web/public/google-play/pyble-google-play-qr.svg similarity index 100% rename from tools/web/public/google-play/pyble-google-play-internal-test-qr.svg rename to tools/web/public/google-play/pyble-google-play-qr.svg diff --git a/tools/web/public/manifest.webmanifest b/tools/web/public/manifest.webmanifest index 1b6c8ad8..6a9be7f3 100644 --- a/tools/web/public/manifest.webmanifest +++ b/tools/web/public/manifest.webmanifest @@ -1,7 +1,7 @@ { "name": "PyBLE — Python over Bluetooth Low Energy", "short_name": "PyBLE", - "description": "A free, open-source, tablet-first IDE for MicroPython boards with compatible Bluetooth Low Energy agent firmware.", + "description": "PyBLE is a free, open-source MicroPython IDE over Bluetooth Low Energy. Available on Google Play open testing for Android and TestFlight for iPad.", "start_url": "/", "display": "standalone", "background_color": "#F4F7FF", diff --git a/tools/web/public/sitemap.xml b/tools/web/public/sitemap.xml index d6e1a112..be087c33 100644 --- a/tools/web/public/sitemap.xml +++ b/tools/web/public/sitemap.xml @@ -2,13 +2,13 @@ https://pyble.dev/ - 2026-09-11T00:00:00.000Z + 2026-09-25T00:00:00.000Z weekly 1 https://pyble.dev/app - 2026-09-11T00:00:00.000Z + 2026-09-25T00:00:00.000Z monthly 0.9 @@ -44,7 +44,7 @@ https://pyble.dev/learn/setup - 2026-09-11T00:00:00.000Z + 2026-09-25T00:00:00.000Z monthly 0.8 diff --git a/tools/web/src/app/app/page.tsx b/tools/web/src/app/app/page.tsx index 2ce32838..ad182239 100644 --- a/tools/web/src/app/app/page.tsx +++ b/tools/web/src/app/app/page.tsx @@ -12,7 +12,7 @@ import { pageMetadata, siteConfig } from "@/lib/site"; export const metadata: Metadata = pageMetadata({ title: "PyBLE for iPad and Android", description: - "Join the PyBLE iPad external beta on Apple TestFlight or the invited Android internal test on Google Play, then code compatible MicroPython boards over Bluetooth Low Energy.", + "Get PyBLE 0.2.0 for Android on Google Play open testing, or join the iPad external beta on TestFlight. Code MicroPython boards over Bluetooth Low Energy.", path: "/app", }); @@ -20,7 +20,7 @@ const gettingStartedSteps = [ { number: "01", title: "Install a beta", - body: "Use Apple TestFlight on iPad, or the invited-only Google Play internal test on Android.", + body: "Use Apple TestFlight on iPad, or Google Play open testing on Android.", }, { number: "02", @@ -42,9 +42,10 @@ export default function AppPage() { title="Install PyBLE on iPad or Android." >

- Choose the public iPad external beta or the invited Android internal - test. PyBLE is an open-source, tablet-first MicroPython IDE that - connects to compatible boards over Bluetooth Low Energy. + Get PyBLE 0.2.0 for Android on Google Play open testing, or join the + iPad external beta on TestFlight. PyBLE is an open-source, + tablet-first MicroPython IDE that connects to compatible boards over + Bluetooth Low Energy.

@@ -54,8 +55,8 @@ export default function AppPage() {

Current app testing

Choose your testing channel.

- These are beta channels, not production App Store or public Google - Play releases. + PyBLE for Android is published on Google Play through open + testing. The iPad beta is available through Apple TestFlight.

@@ -123,50 +124,48 @@ export default function AppPage() {

Android · Google Play

-

- Join the Android internal test. -

+

Get PyBLE on Google Play.

- Only approved internal testers signed in with an invited - Google account can access this listing. An unapproved or - signed-out visitor may find it unavailable. This is not a - public Google Play release. + PyBLE 0.2.0 is available on Google Play open testing. Open the + listing on your Android device and follow Google Play’s + instructions to join the beta and install the app. Google Play + determines availability for your account, country, and device.

- Open Android internal test + Get PyBLE on Google Play

- Open this address on the invited Android device: - {siteConfig.googlePlayInternalTestUrl} + Open this address on your Android device: + {siteConfig.googlePlayUrl}

QR code for the PyBLE Android internal test on Google Play
Scan with your Android camera - Sign in with the Google account that was invited. + The code opens the same Google Play listing.
diff --git a/tools/web/src/app/learn/setup/page.tsx b/tools/web/src/app/learn/setup/page.tsx index 03f3982b..2e302bed 100644 --- a/tools/web/src/app/learn/setup/page.tsx +++ b/tools/web/src/app/learn/setup/page.tsx @@ -93,9 +93,9 @@ export default function SetupTutorial() {

Before you begin

Have the exact board, a reliable data-capable USB cable, a desktop or - laptop that can perform the wired install, stable power, and an iPad - or invited Android test device with PyBLE installed. Stock MicroPython - alone does not provide PBLE/1. + laptop that can perform the wired install, stable power, and PyBLE + installed on your tablet. Use TestFlight for iPad or Google Play open + testing for Android. Stock MicroPython alone does not provide PBLE/1.

diff --git a/tools/web/src/app/page.tsx b/tools/web/src/app/page.tsx index be0901f6..4fffc967 100644 --- a/tools/web/src/app/page.tsx +++ b/tools/web/src/app/page.tsx @@ -131,7 +131,7 @@ export default function HomePage() {

Python over Bluetooth Low Energy @@ -482,8 +482,8 @@ export default function HomePage() {

Choose your tablet beta.

Both builds come from the same open-source Flutter app. Choose the - testing channel for your tablet; neither is a production app store - release. + iPad external beta on TestFlight or Android open testing on Google + Play.

@@ -545,28 +545,29 @@ export default function HomePage() {
-

Android internal testing

-

- Join the PyBLE Android internal test. +

Android · Google Play open testing

+

+ Get PyBLE on Google Play.

- The Android build is available only to approved internal - testers signed in with an invited Google account. An - unapproved or signed-out visitor may find the listing - unavailable. This is not a public Google Play release. + PyBLE 0.2.0 is available on Google Play open testing. Install + the Android beta from Google Play and follow any enrollment + prompts. Google Play determines availability for your account, + country, and device. Firmware availability depends on your + exact board; check the firmware installer before provisioning.

- Open Android internal test + Get PyBLE on Google Play @@ -577,22 +578,22 @@ export default function HomePage() {
QR code for the PyBLE Android internal test on Google Play
Scan with your Android camera - Sign in with the Google account that was invited: + Or open Google Play on your Android device: - {siteConfig.googlePlayInternalTestUrl} + {siteConfig.googlePlayUrl}
diff --git a/tools/web/src/lib/site.ts b/tools/web/src/lib/site.ts index 144a15a5..e4adc455 100644 --- a/tools/web/src/lib/site.ts +++ b/tools/web/src/lib/site.ts @@ -20,10 +20,10 @@ export const siteConfig = { origin: "https://pyble.dev", alternateOrigin: "https://pyble.org", description: - "A free, open-source, tablet-first IDE for MicroPython boards with compatible Bluetooth Low Energy agent firmware.", + "PyBLE is a free, open-source MicroPython IDE over Bluetooth Low Energy. Available on Google Play open testing for Android and TestFlight for iPad.", supportEmail: "viwat.v@chula.ac.th", testFlightUrl: "https://testflight.apple.com/join/yU4e8s6d", - googlePlayInternalTestUrl: + googlePlayUrl: "https://play.google.com/store/apps/details?id=dev.pyble.pyble", repositoryUrl: "https://github.com/PyBLE-dev/PyBLE", bugReportUrl: diff --git a/tools/web/src/test/site-contract.test.tsx b/tools/web/src/test/site-contract.test.tsx index e5bec403..7210d30b 100644 --- a/tools/web/src/test/site-contract.test.tsx +++ b/tools/web/src/test/site-contract.test.tsx @@ -10,6 +10,7 @@ import { render, screen, within } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import AppPage, { metadata as appMetadata } from "@/app/app/page"; +import SetupPage from "@/app/learn/setup/page"; import FlashPage, { metadata as flashMetadata } from "@/app/flash/page"; import { metadata as rootMetadata } from "@/app/layout"; import HomePage, { metadata as homeMetadata } from "@/app/page"; @@ -135,6 +136,32 @@ function jpegDimensions(bytes: Buffer): { width: number; height: number } { } describe("public-site contract", () => { + it.each([ + ["home", HomePage], + ["app", AppPage], + ["setup", SetupPage], + ])( + "keeps %s installation copy consistent with public Android open testing", + (_name, Page) => { + const { container } = render(); + expect(container).toHaveTextContent(/Google Play open testing/i); + expect(container).not.toHaveTextContent( + /invited|invitation.only|internal test|approved testers|unapproved|not (?:a )?public Google Play release/i, + ); + }, + ); + + it("describes Android open testing in search and social metadata", () => { + for (const metadata of [rootMetadata, homeMetadata, appMetadata]) { + expect(metadata.description).toMatch(/Google Play open testing/i); + expect(JSON.stringify(metadata)).not.toMatch(/invited|internal test/i); + } + for (const metadata of [rootMetadata, appMetadata]) { + expect(metadata.openGraph?.description).toBe(metadata.description); + expect(metadata.twitter?.description).toBe(metadata.description); + } + }); + it("keeps pyble.dev canonical and presents Features and Learn in the primary routes", () => { expect(siteConfig.origin).toBe("https://pyble.dev"); expect(siteConfig.alternateOrigin).toBe("https://pyble.org"); @@ -148,7 +175,7 @@ describe("public-site contract", () => { it("publishes canonical metadata and static discovery files", async () => { expect(siteConfig.description).toBe( - "A free, open-source, tablet-first IDE for MicroPython boards with compatible Bluetooth Low Energy agent firmware.", + "PyBLE is a free, open-source MicroPython IDE over Bluetooth Low Energy. Available on Google Play open testing for Android and TestFlight for iPad.", ); expect(homeMetadata.title).toEqual({ absolute: "PyBLE — Python over Bluetooth Low Energy", @@ -159,7 +186,7 @@ describe("public-site contract", () => { }); expect(appMetadata.title).toBe("PyBLE for iPad and Android"); expect(appMetadata.description).toMatch( - /ipad external beta.*android internal test/i, + /google play open testing.*ipad external beta/i, ); expect(privacyMetadata.alternates).toEqual({ canonical: "https://pyble.dev/privacy", @@ -207,10 +234,10 @@ describe("public-site contract", () => { /https:\/\/pyble\.dev\/privacy<\/loc>\s*2026-09-11T00:00:00\.000Z<\/lastmod>/, ); expect(sitemap).toMatch( - /https:\/\/pyble\.dev\/<\/loc>\s*2026-09-11T00:00:00\.000Z<\/lastmod>/, + /https:\/\/pyble\.dev\/<\/loc>\s*2026-09-25T00:00:00\.000Z<\/lastmod>/, ); expect(sitemap).toMatch( - /https:\/\/pyble\.dev\/app<\/loc>\s*2026-09-11T00:00:00\.000Z<\/lastmod>/, + /https:\/\/pyble\.dev\/app<\/loc>\s*2026-09-25T00:00:00\.000Z<\/lastmod>/, ); expect(sitemap).toMatch( /https:\/\/pyble\.dev\/support<\/loc>\s*2026-09-11T00:00:00\.000Z<\/lastmod>/, @@ -218,9 +245,11 @@ describe("public-site contract", () => { expect(sitemap).toContain( "https://pyble.dev/features\n 2026-09-11T00:00:00.000Z", ); + expect(sitemap).toContain( + "https://pyble.dev/learn/setup\n 2026-09-25T00:00:00.000Z", + ); for (const path of [ "/learn", - "/learn/setup", "/learn/first-program", "/learn/files", "/learn/github-import", @@ -240,7 +269,7 @@ describe("public-site contract", () => { expect(manifest).toMatchObject({ short_name: "PyBLE", description: - "A free, open-source, tablet-first IDE for MicroPython boards with compatible Bluetooth Low Energy agent firmware.", + "PyBLE is a free, open-source MicroPython IDE over Bluetooth Low Energy. Available on Google Play open testing for Android and TestFlight for iPad.", start_url: "/", theme_color: "#081B35", }); @@ -404,7 +433,7 @@ describe("public-site contract", () => { screen.getByText("Python over Bluetooth Low Energy"), ).toBeInTheDocument(); expect( - screen.getByText("iPad external beta + Android internal test"), + screen.getByText("iPad external beta + Android open testing"), ).toBeInTheDocument(); expect( screen.getByRole("link", { @@ -556,22 +585,19 @@ describe("public-site contract", () => { ); const androidSection = screen.getByRole("region", { - name: "Join the PyBLE Android internal test.", + name: "Get PyBLE on Google Play.", }); expect(channelGroup).toContainElement(testFlightSection); expect(channelGroup).toContainElement(androidSection); expect(testFlightSection.parentElement).toBe(androidSection.parentElement); expect(testFlightSection.parentElement).toHaveClass("beta-channel-grid"); expect(testFlightSection.nextElementSibling).toBe(androidSection); - expect( - within(androidSection).getByText(/approved internal testers/i), - ).toBeInTheDocument(); - expect( - within(androidSection).getByText(/not a public Google Play release/i), - ).toBeInTheDocument(); - expect( - within(androidSection).getByText(/unapproved.*may.*unavailable/i), - ).toBeInTheDocument(); + expect(androidSection).toHaveTextContent( + /PyBLE 0\.2\.0 is available on Google Play open testing/i, + ); + expect(androidSection).not.toHaveTextContent( + /internal test|invited|approved testers|not a public Google Play release/i, + ); const testFlightLink = within(testFlightSection).getByRole("link", { name: "Open in TestFlight", @@ -581,7 +607,7 @@ describe("public-site contract", () => { expect(testFlightLink).toHaveAttribute("rel", "noopener noreferrer"); const googlePlayLink = within(androidSection).getByRole("link", { - name: "Open Android internal test", + name: "Get PyBLE on Google Play", }); expect(googlePlayLink).toHaveAttribute("href", googlePlayUrl); expect(googlePlayLink).toHaveAttribute("target", "_blank"); @@ -602,7 +628,7 @@ describe("public-site contract", () => { ).toHaveAttribute("src", "/testflight/pyble-testflight-qr.svg"); const googlePlayQrDescription = - "QR code for the PyBLE Android internal test on Google Play"; + "QR code for PyBLE on Google Play open testing"; const googlePlayQrLink = within(androidSection).getByRole("link", { name: googlePlayQrDescription, }); @@ -613,10 +639,7 @@ describe("public-site contract", () => { within(androidSection).getByRole("img", { name: googlePlayQrDescription, }), - ).toHaveAttribute( - "src", - "/google-play/pyble-google-play-internal-test-qr.svg", - ); + ).toHaveAttribute("src", "/google-play/pyble-google-play-qr.svg"); expect( within(testFlightSection).getByText("testflight.apple.com/join/yU4e8s6d"), @@ -654,7 +677,7 @@ describe("public-site contract", () => { process.cwd(), "public", "google-play", - "pyble-google-play-internal-test-qr.svg", + "pyble-google-play-qr.svg", ), sha256: "fbd84d11d773346d0a2cd23f0e3193d02ff02e6c9780298294431d95dd07f32d", @@ -684,7 +707,7 @@ describe("public-site contract", () => { expect(siteConfig.testFlightUrl).toBe( "https://testflight.apple.com/join/yU4e8s6d", ); - expect(siteConfig.googlePlayInternalTestUrl).toBe( + expect(siteConfig.googlePlayUrl).toBe( "https://play.google.com/store/apps/details?id=dev.pyble.pyble", ); @@ -696,12 +719,9 @@ describe("public-site contract", () => { expect(testFlightLink).toHaveAttribute("rel", "noopener noreferrer"); const googlePlayLink = screen.getByRole("link", { - name: "Open Android internal test", + name: "Get PyBLE on Google Play", }); - expect(googlePlayLink).toHaveAttribute( - "href", - siteConfig.googlePlayInternalTestUrl, - ); + expect(googlePlayLink).toHaveAttribute("href", siteConfig.googlePlayUrl); expect(googlePlayLink).toHaveAttribute("target", "_blank"); expect(googlePlayLink).toHaveAttribute("rel", "noopener noreferrer"); @@ -717,23 +737,18 @@ describe("public-site contract", () => { ).toBeInTheDocument(); expect( screen.getByRole("img", { - name: "QR code for the PyBLE Android internal test on Google Play", + name: "QR code for PyBLE on Google Play open testing", }), - ).toHaveAttribute( - "src", - "/google-play/pyble-google-play-internal-test-qr.svg", - ); + ).toHaveAttribute("src", "/google-play/pyble-google-play-qr.svg"); expect( - screen.getByText(siteConfig.googlePlayInternalTestUrl, { + screen.getByText(siteConfig.googlePlayUrl, { selector: ".app-install__direct span", }), ).toBeInTheDocument(); - expect(screen.getByText(/approved internal testers/i)).toBeInTheDocument(); - expect( - screen.getByText(/not a public Google Play release/i), - ).toBeInTheDocument(); expect( - screen.getByText(/unapproved.*may.*unavailable/i), + screen.getByText( + /PyBLE 0\.2\.0 is available on Google Play open testing/i, + ), ).toBeInTheDocument(); expect( screen.getByRole("link", { name: /open firmware installer/i }), diff --git a/tools/web/src/test/sites-output.test.ts b/tools/web/src/test/sites-output.test.ts index 5f50be1f..ccbdb8ed 100644 --- a/tools/web/src/test/sites-output.test.ts +++ b/tools/web/src/test/sites-output.test.ts @@ -266,7 +266,7 @@ describe("Sites vinext-output adapter", () => { ); const googlePlayQr = await worker.fetch( new Request( - "https://pyble.dev/google-play/pyble-google-play-internal-test-qr.svg", + "https://pyble.dev/google-play/pyble-google-play-qr.svg", ), {}, context, @@ -384,7 +384,7 @@ describe("Sites vinext-output adapter", () => { }, googlePlayQr: { status: 200, - body: "vinext:/google-play/pyble-google-play-internal-test-qr.svg:execution-context", + body: "vinext:/google-play/pyble-google-play-qr.svg:execution-context", }, firmwareManifest: { status: 200,