Skip to content

fix(onboarding): Watch instructions layout, specific pairing errors, find-us fits on screen - #21426

Merged
Git-on-my-level merged 5 commits into
BasedHardware:mainfrom
Git-on-my-level:lane/onb-polish
Oct 13, 2026
Merged

Git-on-my-level merged 5 commits into
BasedHardware:mainfrom
Git-on-my-level:lane/onb-polish

Conversation

@Git-on-my-level

@Git-on-my-level Git-on-my-level commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Android pairs after opening Connect at 26%, versus 54% on iOS (new mobile signups observed September 13–October 12). The current failure copy offers no actionable cause; the Apple Watch paragraph uses title weight with its action mid-screen, and the acquisition survey hides its final choices below Continue.

This change pins Watch's primary action using the shared first-capture layout, presents body-weight recording steps while preserving paired/installed guidance, and fits all 12 acquisition choices in an ordered two-column layout at 393×852. Large text uses scrollable rows. Option labels, values, and order stay unchanged.

Connect displays an inline failure card with a visible dismiss, Try Again, and How to pair. Exposed Bluetooth power, Bluetooth/Nearby devices/location permissions, stale-bond, combined reachability, and unknown cases have actionable localized messages. Device Connect Failure Shown is a new registered event with failure_reason and platform, emitted after the card's first frame. Device Connect Failed and the registry contract stay unchanged. A 10-second empty scan retains No Omi Found, its instructions and Contact Support, and emits no Device Connect Failure Shown. A visible saved or discovered device clears the reachability card. Bluetooth-off and unauthorized exception types take precedence over stale-bond advice.

The connection service catches transport errors and returns null: not found, timed out, and connected-then-dropped cannot be separated here. They use not_found_or_dropped and shared reachability guidance. Follow-up: preserve typed transport causes at the service boundary in a separately reviewed connection change; this PR keeps transport and connection/launch-timeout behavior unchanged.

Validation:

  • bash app/test.sh test/widgets/onboarding_polish_test.dart: bootstrap and all 138 protected contract tests passed. The focused fixture was then corrected to use an inert provider rather than a live service singleton.
  • ONB_POLISH_RENDER=1 flutter test test/widgets/onboarding_polish_test.dart: all 6 tests passed; Watch bottom inset/paired/installed guidance, every reason→copy, event properties and one event per presentation, connect dismiss, all 12 survey options visible at 393×852, 2.0-scale scrolling to Other and its editor, plus 21 original renders; the two provider-driven empty-scan renders are covered below.
  • flutter test test/widgets/first_capture_test.dart test/widgets/onboarding_survey_sync_test.dart test/widgets/onboarding_bottom_inset_test.dart test/providers/onboarding_provider_connect_failure_test.dart test/services/devices/pairing_failure_test.dart test/utils/analytics/onboarding_funnel_analytics_test.dart: all 31 neighboring regressions passed. Updated only the first-capture pairing assertion/fixture for the new inline card; the sibling lane's five red test files are untouched.
  • Review regression coverage: ONB_POLISH_RENDER=1 flutter test test/providers/onboarding_provider_pairing_presentation_test.dart test/widgets/onboarding_polish_test.dart test/widgets/first_capture_test.dart test/services/devices/pairing_failure_test.dart test/providers/onboarding_provider_connect_failure_test.dart passed all 37 tests. This drives the real OnboardingProvider timer and onDevices paths, verifies telemetry via a recording AnalyticsAdapter, retains stale-bond guidance, and tests exception precedence. Connect-failure and empty-scan renders were regenerated on iOS/Android and opened; the empty-state controls inherit the themed real font.
  • Focused flutter analyze: no issues. python3 app/scripts/l10n.py check: 49 locales, 3,877 consistent keys. python3 scripts/check_event_registry.py --write: 183 events and executable consumers pass. make preflight: all 24 selected checks passed on the final commit; metadata-only preflight also passed all 5 selected checks.
  • Screenshots: all 23 inspected PNGs and reproduction details. Watch, iOS reachability failure, Android reachability failure, iOS find-us, Android find-us, large text scrolled to final choices.

CI at a269e7046334eb49daeac059b3640325b8de592e: Dart Analyze & Tests ran under ci:full and completed failure. Analyzer passed. The only five failing cases are the known main failures tracked by #21425: launch-timeout connection preservation (1), category large-text layout (2), AI app generator large-text layout (1), and settings search header stability (1). No onboarding tests failed. No physical device pairing or full-suite acceptance is claimed.

Known follow-up: unify the global Bluetooth guidance sheet and inline failure card so they cannot stack. This PR preserves the existing guidance flow.

Risks: new messages use English fallback for other locales; no physical-device BLE acceptance is claimed; the existing global Bluetooth guidance can appear alongside the inline card; acquisition labels remain localized legacy analytics values; large text deliberately needs scrolling.

Watch instructions

Android reachability recovery

All source options visible

Empty scan retains instructions and Contact Support

Failure-Class: none

🤖 Generated with Claude Code

View guided diff

Git-on-my-level and others added 3 commits October 14, 2026 01:04
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Git-on-my-level Git-on-my-level added the ci:full Maintainer-granted: run full CI on this fork PR label Oct 13, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T19:28:59.091416Z 0390834 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23e4945f09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/lib/l10n/app_ar.arb
"firstCaptureProcessing": "Creating your first conversation…",
"firstCaptureResult": "Your first conversation",
"firstCaptureEmpty": "I didn't catch enough speech, try again with a few sentences",
"watchCaptureTitle": "Record from your Apple Watch",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Supply real translations for every new locale key

All eleven additions here are copied verbatim from app_en.arb into every one of the 48 non-English ARBs, so Arabic, Spanish, Japanese, and other users see the new Watch and pairing-recovery flow in English; this is especially harmful for the permission and stale-bond remediation text. Supply per-locale values and regenerate l10n instead of shipping English fallbacks.

AGENTS.md reference: app/AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

key: ValueKey(provider.pairingFailureVersion),
reason: provider.pairingFailureReason!,
onDismiss: provider.dismissPairingFailure,
onRetry: widget.onRescan,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retry the failed pairing instead of only rescanning

When an unsaved device's handleTap fails, this binds Try Again to _scanAgain, so the action only restarts discovery and never retries handleTap; the user must tap the device row a second time. For staleBond this is a dead end: DeviceProvider.initiateConnection returns while staleBondRecoveryRequired is set, and forgetting the OS bond does not clear that Dart latch, so the advertised retry cannot discover or pair until a restart or a separate app-side forget path. Give the card the failed-device recovery callback, including clearing the stale latch on an explicit retry, rather than onRescan.

Useful? React with 👍 / 👎.

if (_isDisposed || epoch != _scanEpoch) return;
if (shouldReportEmptyDeviceScan(deviceCount: deviceList.length)) {
pairingFailureVersion++;
pairingFailureReason = DeviceConnectFailureShownFailureReason.notFoundOrDropped;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the scan timeout once a device is discovered

When the first scan is empty at 10 seconds but a periodic scan later discovers a device, for example after the user turns it on, this assignment remains set because onDevices updates the rows but never clears pairingFailureReason. The page consequently keeps showing “Couldn't reach your Omi,” suppresses the devices-found status, and keeps the artwork reduced even while a tappable device is present; clear the scan-originated timeout state when nonempty discovery results arrive.

Useful? React with 👍 / 👎.

if (_isDisposed || epoch != _scanEpoch) return;
if (shouldReportEmptyDeviceScan(deviceCount: deviceList.length)) {
pairingFailureVersion++;
pairingFailureReason = DeviceConnectFailureShownFailureReason.notFoundOrDropped;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not overwrite a native scan-block reason on timeout

When native onScanBlocked fires after this timer is created, such as Android returning scanner_unavailable or the radio/permission changing between the readiness check and native scan start, _emitPairingBlocked first publishes the applicable reason but this still-live callback unconditionally replaces it with notFoundOrDropped. That swaps permission/power guidance for incorrect reachability advice and emits a second misclassified Shown event; cancel the timer on a native block or guard this fallback when a failure reason already exists.

Useful? React with 👍 / 👎.

reason: provider.pairingFailureReason!,
onDismiss: provider.dismissPairingFailure,
onRetry: widget.onRescan,
onGuide: () => ConnectionGuideSheet.show(context),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit the guide-open event from the failure card

When the failure card's How to pair action is used, most notably for the 10-second empty-scan state that now replaces FindDevicesPage's prior empty state, this opens the sheet directly and bypasses _showConnectionGuide(), so the active Connection Guide Opened event is never emitted. The footer and old empty-state entry points both emit it, so guide usage after failures will be systematically undercounted; route this action through a tracked callback or emit before showing the sheet.

Useful? React with 👍 / 👎.

…lity errors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a269e70463

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +599 to +600
if (pairingFailureReason == DeviceConnectFailureShownFailureReason.notFoundOrDropped) {
pairingFailureReason = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the Bluetooth-off card after the enable retry

When Connect starts with Bluetooth off on Android, the global listener's Enable button turns the adapter on and calls deviceService.discover() directly (bluetooth_guidance_listener.dart:144-178), so a successful nonempty result reaches onDevices without going through _scanAgain. Fresh evidence in the final tree is this direct post-enable path: because this branch clears only notFoundOrDropped, bluetoothOff remains set, leaving device rows visible while the card still tells the user to turn on Bluetooth and the illustration remains shrunk; clear resolved scan-block reasons when discovery succeeds while preserving connection failures such as stale bond.

Useful? React with 👍 / 👎.

Comment on lines +14 to +15
DeviceConnectFailureShownFailureReason.notFoundOrDropped => l10n.pairingNotFoundOrDropped,
DeviceConnectFailureShownFailureReason.staleBond => l10n.pairingBondRecovery,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep failure copy device-agnostic

FoundDevices routes Bee, Plaud, Fieldy, Friend Pendant, and Limitless rows through the same handleTap path (found_devices.dart:393-403), but these two failure branches resolve to text that literally says “Couldn't reach your Omi” or “Forget Omi.” A connection failure for any of those supported products therefore tells the user to troubleshoot or forget a different device; carry the attempted device name/type into the card or use generic “device” copy.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0390834f37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/// timeout, missing device or dropped link. Keep one reachability bucket and
/// use the existing stale-bond latch for the distinct Bluetooth-settings action.
DeviceConnectFailureShownFailureReason connectFailureReason(Object error, {bool staleBond = false}) {
if (error is BluetoothAdapterUnavailableException) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve adapter causes before classifying connection failures

When Bluetooth is switched off or its permission is revoked after discovery but before a device row is tapped, NativeBleTransport.connect throws BluetoothAdapterUnavailableException, but DeviceConnection.connect wraps it in DeviceConnectionException, DeviceService.ensureConnection catches that and returns null, and OnboardingProvider.handleTap substitutes StateError('Device initialization did not complete'). Consequently this new branch never receives the adapter exception on the production pairing path, so the card and Device Connect Failure Shown report not_found_or_dropped instead of the actionable Bluetooth reason; preserve or unwrap the cause before calling connectFailureReason.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:full Maintainer-granted: run full CI on this fork PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant