Skip to content

Merge apps into main#740

Merged
microbit-matt-hillsdon merged 174 commits into
mainfrom
apps
May 7, 2026
Merged

Merge apps into main#740
microbit-matt-hillsdon merged 174 commits into
mainfrom
apps

Conversation

@microbit-matt-hillsdon
Copy link
Copy Markdown

@microbit-matt-hillsdon microbit-matt-hillsdon commented Feb 13, 2026

The apps are still pre-release but this work is now in a good enough state to merge back.

There's quite a bit of change with some degree of regression risk for web-based CreateAI but we've done quite a bit of testing and will monitor carefully when this rolls out.

We're still running with a beta of the connection library but we'll continue to do that for a while longer in case we need to make breaking changes in response to issues arising.

Apps releases still require the -apps tag suffix and the web release is still skipped for -apps tags, so we can continue to release a separate stream of apps releases for testing until they unify in future (if that proves practical).

microbit-matt-hillsdon and others added 30 commits November 26, 2025 15:03
Android should have signing. We'll revisit iOS.
No signing for iOS
Top padding to respect the safe area on mobile.
* Add icon/splash - just a quick starting point.
* Call the apps CreateAI - micro:bit CreateAI doesn't fit - on iPad the space is smooshed and on Android phone I just see "micro:bit..."
* Assets cap plugin pulls in prettier xml formatting so ignore the existing SVGs in the app
Uses alpha apps release microbit-connection for native Bluetooth support.

This change also uses capacitor for web BLE which we need to evaluate.
It seems to roughly work but there are a bunch of notes in microbit-connection
that we'll follow up on.

This integration is partial but is sufficient to use a single micro:bit to
flash data collection, record data and flash a MakeCode program.

There's lots about this that's WIP but I think it's a good base for further
fixes. Note it targets the apps branch.

I plan to merge this and then get the iOS app testable in non-dev environments
(e.g. deal with signing, internal distribution).

Known issues we're going to merge with anyway:

- Not tested on Android. The underlying connection code used to work there before recent fixes, likely still does.
- Only superficial testing on web (Mac only, V1 + V2).
  - Seems OK but we lost many subtle cases from microbit-connection that will need to be investigated there.
- No or wrong flow's error and reconnection (after 2 x fail) UX.
- Poor experience reconnecting after flashing a MakeCode hex.

Non-obvious changes:
- Switches to MakeCode programs for the data collection hex
- Upgrades Playwright and Node versions to get local dev working again
- Very basic CLAUDE.md
Eventually we'll align with Web CreateAI's minor version at least, but for now
let's start low.
* Update data collection hex files with correct bluetooth pattern

- Added note on generating hex files with configured Bluetooth mode.
- Minor correction in local develoment doc

Co-authored-by: Matt Hillsdon <matt.hillsdon@microbit.org>

---------

Co-authored-by: Matt Hillsdon <matt.hillsdon@microbit.org>
- Move lower-level connection code to ConnectionService (might eliminate later)
- Update UI code to fire simple state machine events or use hooks for data
  subscriptions.
- Unit test the state machines
- Add e2e tests covering key connection/reconnection scenarios.

UI changes for web/radio flows:

- Don't show a dialog behind bluetooth requestDevice (we didn't for USB already)
- Don't talk about Web Bluetooth quite so in the dialog flow, after the first dialog just talk about connecting to the data connection micro:bit (will help with less text for native/web scenarios and is more user oriented)

This change relies on the introduction of the PAUSED state in the apps branch of the connection library.
It's daft to talk about tablets and the other mobile OS when they've clearly
installed an app not just stumbled on a website.
This only deals with Bluetooth permissions in the data connection flow.

They also apply to the download flow (especially if you skip past connecting, but all the time in principle) but I want to refactor in a separate PR to enable better sharing between the flows before implementing it there.

I've also added in the missing A+B reset dialog.

I regenerated all the translated strings via `find lang -type f -not -name ui.en.json | while read n; do git checkout main -- $n; done  && npm run i18n:compile` as they were a mess and no actual translation has happened on the apps branch.

Includes these changes in the microbit-connection upgrade:

microbit-foundation/microbit-connection#73
Still just manually uploading to TestFlight etc. but that should be easy to
automate via Fastlane with a bit more work.

I've left Debug builds using automatic signing.

Use job run number for the numeric versions so we can just upload them without
faff. We'll worry about marketing version numbers later once we tag releases.
Keeping them on the apps branch for now for two reasons:

1. We're always using the -apps version of microbit-connection which has a
different BLE implementation that hasn't been adequately tested (especially on
Windows).

2. More generally aiming to reduce risk to main until changes stabilize.

Includes logic to ensure we don't release the web app from the apps branch and
a temporary backstop for the first tag in case I screwed that up.

See README.md changes for the plan.
- Remove ConnectionService class in favour of more direct connection access via Connections interface that tracks which is the current data connection and exposes a listener API for it for the data connection state machine to use to get events.                                                                                
- Unify connection actions (connectBluetooth, connectMicrobits, reconnect) into single   
connectData action
After this change, if you either skip straight to MakeCode (because you previously collected data) or remove BT permissions after a data connection was made, then you will be re-prompted for permissions in the MakeCode download flow.
Move it to be a setting. This might cause a minor conflict with Rob's work to
add IndexDB storage but should mean it ultimately gets stored in the right
place not still in localStorage.

Closes #672
This removes a gap where there was briefly no dialog.
Upload releases as internal builds to TestFlight (iOS) and Google Play
internal testing track (Android). Upload steps only run on release events.

Use Workload Identity Federation for Google Play uploads.
There doesn't seem to be an equivalent for App Store Connect.
* Shorten delay post-flash to 1 sec for reboot
* Upgrade microbit-connection library to retry connection multiple times and to ensure services are discovered
microbit-matt-hillsdon and others added 25 commits May 1, 2026 15:45
`device_flash` was tagging the data-connection type, so flashing a
radio-remote micro:bit while the data connection was Bluetooth
reported `flow: web_bluetooth`. The accompanying step / failure
events already use the download flow type via `logFlashTransition`,
so the success event was the lone outlier and would split funnels in
GA4. Switch to `downloadFlowTypeToFlow(getDownloadFlowType(...))` and
export the helper for the call site.
The taxonomy is closed and feeds primarily from typed enums
(DataConnectionStep, DownloadStep, DeviceError.code), so the runtime
checks were catching things that are mostly statically guaranteed
already. DEV-only meant coverage depended on someone exercising every
code path; the reserved-name list was also the kind of thing that
bit-rots silently. Code review on new events is enough.

Also fix stale logging-file paths in the analytics doc that still
referenced the pre-rename ga-sentry.ts / firebase-analytics.ts names.
Two parallel refactors, behaviour-preserving:

- Pass the full `DownloadEvent` to `logFlashTransition` instead of
  splitting it into `eventType: string` + a separately-extracted
  `failureCode?: string`. Mirrors how `logConnectionTransition`
  already takes the typed event, and removes the manual code
  extraction at the download-actions call site.

- On the connect side, narrow on the failure event types directly
  rather than going through `connectFailureStage(event.type)` and
  re-checking `"code" in event && typeof event.code === "string"`.
  Once narrowed, TS knows `code?: string`, so `event.code ?? "unknown"`
  is enough. `connectFailureStage` was only called once and is gone.
Locks in the load-bearing branches of `logConnectionTransition` and
`logFlashTransition`: same-step suppression, terminal success,
failure with/without code (covering all three connect stages and
both flash stages), failure-plus-step double emission, the
ConnectionLost → device_disconnect pairing, exit-on-close, the
post-success Idle return that suppresses exit, transient-state step
filtering, and the "from: idle" fallback when the previous step name
is undefined.
Two small analytics tweaks:

- Adds `action_create`, fired when the user adds a new action to the
  dataset. Pairs with the existing `action_delete`. Was an oversight
  in the original catalogue.

- Filters `WebUsbBluetoothUnsupported` out of `connectStepNames`, so
  no device_connect_step / _exit fires when the user's browser can't
  run the app at all. There's no funnel through that terminal screen
  and `state.type` falls back to Radio there, which made `flow:
  radio` misleading. The cohort is captured by the
  webusb_available / webbluetooth_available user properties already.
Saving the .hex is the user's "save my project" action — the file
contains both their program and the trained model. Naming the event
for the user's mental model rather than the artifact format pairs
it with the rest of the project_* family and generalises across
sibling micro:bit apps where the saved artifact may not be a hex.

dataset_save (recorded data only) stays as-is for the clean split
between "save the data" and "save the project."
Adds a required `product` slug to BrandConfig (set to `ml-trainer`
in the OSS default) and threads it into both WebLogging and
NativeLogging at construction. The loggers attach it to every event
they emit so dashboards can split traffic by product when sibling
apps share a GA4 property — without polluting individual call sites
or relying on hostname / app_id implementation details.
Focus on contracts and future readers rather than the evolution of this code.
The @capacitor-firebase/analytics plugin's iOS init() unconditionally
calls FirebaseApp.configure(), which crashes the app at launch when no
GoogleService-Info.plist is bundled — i.e. on every fresh OSS clone.
Patch the plugin (via patch-package) to skip configure() when no
plist is present, mirroring how the Android side already gates via a
conditional gradle plugin.

For private builds where Firebase IS configured, default
data-collection to off via FirebaseDataCollectionDefaultEnabled
(iOS Info.plist) and firebase_analytics_collection_enabled
(AndroidManifest meta-data) so nothing is auto-collected before the
user has granted consent. NativeLogging.setConsent now flips the
master analytics_collection_enabled flag via FirebaseAnalytics.setEnabled
in addition to the existing consent-mode signal.

vite.config.ts's runtime gate now checks the theme-package source
directory rather than the destination plist (which is gitignored and
absent on OSS clones — the patch makes that fine).
…e Firebase

The previous fix patched the Capacitor Firebase plugin to skip
FirebaseApp.configure() when no plist is bundled, which kept OSS
clones from crashing. But it also broke Firebase config on private
builds: Xcode's Copy Bundle Resources phase needs a file referenced
in the project to bundle anything, and the file must exist at build
time. Without that, sync-native's copy of the real plist on private
builds never made it into the .app, so configure() correctly skipped
on every build — including the ones that actually have credentials.

Switch back to the committed-stub approach: stub at the destination
(force-added past gitignore) so Xcode always finds something to
bundle; sync-native overlays the real plist on private. The
FirebaseDataCollectionDefaultEnabled setting in Info.plist /
AndroidManifest keeps Firebase Core dormant until consent regardless
of which credentials are bundled, so the stub is harmless on OSS and
the patch becomes redundant.
`npm install --no-save <pkg>` doesn't reliably re-run the consuming
project's postinstall, so sync-native wasn't always copying the
theme-package's plist / google-services.json into the project trees.
Previously the stub was committed at ios/App/App/GoogleService-Info.plist
(force-added past gitignore), which meant local modifications from
sync-native overlaying the real plist showed up in git status as
unwanted diffs. Now the stub lives at bin/stubs/GoogleService-Info.plist
as a build-time source, sync-native picks theme-package native/ first
and falls back to bin/stubs/, and the destination at ios/App/App/ goes
back to being a gitignored build artifact.
WebLogging and NativeLogging shared most of their code: Sentry init
and error/breadcrumb plumbing, param building (detail-flatten +
product injection), console fallthrough — all identical. The only
genuine differences were the SDK calls (gtag vs FirebaseAnalytics),
the consent-gate semantics, and the platform-specific navigate
behaviour.

Split that seam: Logger owns the cross-cutting work; AnalyticsSink
owns whatever the platform's SDK + consent flow demands. WebSink
and NativeSink replace the two old classes.
Collapses device_connect_* and device_flash_* into a unified
device_* family (device_step, device_success, device_failure,
device_exit) tagged with `task: data_connection | download`. PMs
filter or split by task to compare or isolate the two state
machines.

Drops the failure-event `stage` param (`connect | flash | data`),
which was hard to relate to as a user. Replace it with `at_step` — the actual
screen the user was on when the SDK reported the failure. Finer-grained,
but failure is only possible from a few steps.

Renames `flow` → `transport` everywhere (param key, type alias,
helper function names). The values stay (web_bluetooth /
native_bluetooth / radio); the new name describes them more
honestly. Doc note clarifies the value is the user's overall
setup, not the literal per-event transport — download events keep
their user's `transport` value even though the actual flash is
over USB.

`device_disconnect` keeps its current shape (no task — always
data_connection).
NativeSink takes a `stageEnabled` flag and no-ops every Firebase API
call (logEvent, setUserProperty, setEnabled, setConsent) when false.
createLogging passes `isStageWithAnalytics(env.VITE_STAGE)`, the same
PRODUCTION-or-STAGING predicate web's cookie modal already uses to
decide whether to offer GA opt-in. Extracted the predicate to
src/logging/stage.ts so both backends share one source of truth.

Net effect: REVIEW / BETA / local dev no longer hits the live GA
property from native; web has always behaved this way. The consent
UX (first-run dialog and Settings toggle) is intentionally left
visible on gated stages so it stays exercisable during development.
- Switch native compliance + Settings analytics toggle gates from
  isAppsBuild to isNativePlatform(), so flags.ios / flags.android
  surface the native consent UX in a web build for testing.
- Fire project_import from ImportPage when the user opens a
  microbit.org resource — previously only the hex-file branches
  emitted the event. Adds source value `microbit_org`.
- Track WebUsbChooseMicrobit in connection step events. The OS
  picker is user-driven and can take well over a second, or get
  cancelled — worth a step in the funnel.
- Fire project_rename from EditableName (toolbar inline rename of an
  opened project). Skipped on no-op submits to avoid noise from
  blur-without-change. Adds surface value `toolbar`.
After e2a95ac switched createCompliance from isAppsBuild to
isNativePlatform(), e2e tests using `flags=android` / `flags=ios`
trigger the native consent dialog on home-page load. The dialog
intercepts pointer events and breaks tests that click "New project"
without dismissing it, e.g. native-bluetooth.spec.ts.

HomePage.goto now writes analyticsConsent="denied" into IndexedDB
between the initial navigation and reload, mirroring how the MBCC
cookie suppresses the web compliance modal. Polls until the app's
default settings row exists so the put doesn't race the app's
db.add (which would otherwise throw on key collision and break
startup).
Revisit analytics events, add native app support
Extract the app brand following a similar approach to the web but with the relevant iOS/Android approaches to externalising configuration and a .json file for Capacitor.

* Decouple Android namespace from brand applicationId

Pinning the Java/R-class namespace to org.microbit.mltrainer so the
manifest's `.MainActivity` reference resolves regardless of brand;
applicationId stays brand-driven so the Play Store identity for
CreateAI (org.microbit.createai) is unchanged.

* Commit shared Xcode scheme; gitignore xcuserdata

User schemes under xcuserdata/ get regenerated unpredictably by Xcode
and Capacitor's pod sync, which is how the current project ended up
in the "No scheme" state locally. A shared scheme makes every
checkout — clones, fresh sandboxes, CI — pick up the same scheme
with no Xcode dance.

* Document local brand overrides for OSS contributors

Apple bundle ids are globally unique. The OSS default
(org.microbit.mltrainer) can be claimed by any developer's account
the first time they hit auto-signing in Xcode, locking out everyone
else. To run on a real device contributors need their own bundle id +
team — but the existing `.private` overlay slot is managed by
`apply-brand` and gets wiped on every `npm install`.

Add a separate `.local` tier of brand overrides that's gitignored,
untouched by `apply-brand`, and loaded after the theme overlay so it
wins. Wire it through Brand.xcconfig, brand.gradle, and
capacitor.config.ts. README documents the iOS signing case.

* Bump theme version
@microbit-matt-hillsdon microbit-matt-hillsdon marked this pull request as ready for review May 7, 2026 14:48
@microbit-matt-hillsdon microbit-matt-hillsdon changed the title [DO NOT MERGE] Merge apps into main Merge apps into main May 7, 2026
@microbit-matt-hillsdon microbit-matt-hillsdon merged commit d13214e into main May 7, 2026
3 checks passed
@microbit-matt-hillsdon microbit-matt-hillsdon deleted the apps branch May 7, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants