Merge apps into main#740
Merged
Merged
Conversation
Android should have signing. We'll revisit iOS.
No signing for iOS
Give it time...
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
This one has the Firebase config.
`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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).