Skip to content

WHOOP MG Heart Screener: ECG capture, store, history and coach reading - #367

Draft
DropTabl wants to merge 11 commits into
OpenStrap:mainfrom
DropTabl:feat/mg-ecg
Draft

WHOOP MG Heart Screener: ECG capture, store, history and coach reading#367
DropTabl wants to merge 11 commits into
OpenStrap:mainfrom
DropTabl:feat/mg-ecg

Conversation

@DropTabl

@DropTabl DropTabl commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

WHOOP MG "Heart Screener": a user-initiated ECG reading end to end — capture,
storage, history, detail, and an optional coach reading of the trace.

Draft: blocked on OpenStrap/protocol#54. pubspec.yaml still pins a
protocol SHA with no Labrador parser, so CI cannot compile until that merges
and this branch repins (pubspec.yaml ref + kProtocolPin +
pubspec.lock, which db_serve_version_and_reads_test enforces move
together). Everything else is finished and verified.

What lands

db (schema v52)ecg_reading, ecg_reading_packet (exact accepted
packets, samples as signed-i16-LE BLOB) and ecg_raw_packet (byte-exact
historical R16). Its own rung, CREATE TABLE IF NOT EXISTS and nothing else,
so a throw has nothing to roll back onto. Raw R16 is committed inside the same
pre-ACK transaction as everything else — it has no other durable home. A coach
view v_ecg_readings exposes the summary only: never device_id, notes, hex
or samples.

ble — an EcgLease (session + link generation) serializes the whole
capture against history: refresh/backfill are refused and maintenance writes
pause while it is held, and teardown voids it. PREPARE / START / RESTART /
CLEANUP go through the protocol builders with seq+opcode correlation, a 5 s
timeout, attempt-all and no retry. A READY-time recovery hook cleans up a band
left generating by a dead process BEFORE listening resumes.

owner — a pure reducer (reduceEcg) implements the official state machine:
WAITING/ACTIVE/CONTACT_LOST/done, one interruption per ACTIVE→CONTACT_LOST
transition, failure on progress 255 or a third interruption, one placeholder
per sequence jump, and the distinct explicit-RESTART branch. The controller
owns single-flight, the 120 s timeout, save-before-completed, and exactly one
cleanup on every exit.

ui + coach — the Health entry appears once a band has positively identified
itself as an MG (revision-1 MAVERICK hello), capture screen with live preview,
detail with the full waveform, and "Analyze now".

Hardware

Run on a physical MG. PREPARE (123/139/125), abortHistorical (20) and START
(124) all returned SUCCESS in order; live R17 parsed at 100 samples/s; the
CLEANUP triplet fired exactly once on exit and was followed by the ordinary
sync. Contact handling was exercised for real: the band resets progress and
recovers on its own, and the reducer's interruption budget ended the capture at
the third reset exactly as the contract specifies.

Notes for review

  • Schema 52, not 51. main took 51 for multi-device attribution while this
    branch was out; the ECG store moved up a rung rather than share one with a
    different migration. The rung comment records that.
  • BandHost.commitNativeBatch now carries ecgRawPackets through to
    commitSyncBatch — without it the raw records have no route to the DB
    through main's commit path.
  • The coach tool sends the accepted waveform at the band's own rate, with a
    how_to_read block stating the 500 Hz → 100 Hz decimation and the 10 ms
    resolution, so the model does not read intervals off avg_hr or judge QRS
    width as if this were a diagnostic ECG. A window too long for one result is
    decimated by a whole-number stride rather than clipped mid-number.
  • The remote-endpoint dialog names the host and model and now states plainly
    that the summary and the full waveform are what leave the device. A local
    endpoint sends nothing off the phone.
  • Test fixtures use a synthetic band serial.

Verification

flutter analyze lib test clean. Full suite 3,799 passing / 462 skipped, with
one expected red: the sibling pins match the SHAs, which fails only under the
local pubspec_overrides.yaml and clears at the repin.

Summary by Sourcery

Deliver the WHOOP MG Heart Screener from ECG capture through durable storage, history, waveform review, and optional coach analysis.

New Features:

  • Add end-to-end user-initiated ECG capture for positively identified WHOOP MG bands, including live preview, saved history, waveform detail, and optional coach analysis.
  • Expose band-reported ECG summaries and accepted waveforms through a dedicated coach tool with privacy safeguards and interpretation guidance.

Bug Fixes:

  • Ensure ECG raw history packets are durably committed before history acknowledgements and are recovered safely after interrupted captures or process death.
  • Prevent ECG transport ownership from racing with history sync, maintenance traffic, link replacement, or concurrent captures.

Enhancements:

  • Implement the ECG protocol transport, reducer-driven capture lifecycle, durable recovery guard, waveform buffering, cleanup handling, and multi-owner screen wake support.
  • Add schema version 52 ECG storage, migrations, backup/salvage ownership, summary views, packet codecs, and raw packet persistence.
  • Add localized ECG capture, history, detail, consent, and coach-analysis UI.

Tests:

  • Add comprehensive coverage for ECG protocol transport, reducer state transitions, controller lifecycle, database persistence, coach data security, waveform handling, UI states, recovery, and screen-wake ownership.

Three additive tables behind a CREATE-IF-NOT-EXISTS rung: ecg_reading (the band-reported result of one user-initiated Labrador reading), ecg_reading_packet (the accepted R17 window, exact signed-i16-LE samples as a BLOB plus the inner hex, one placeholder row per sequence jump) and ecg_raw_packet (the raw R16 records the band saved and ordinary history recovered, byte-exact, keyed by bytes). No foreign keys — this DB never enables them — so the reading→packet cascade is a manual two-statement transaction. Units pinned: domain/strap timestamps in seconds, created_at/captured_at in ms like raw_archive.

Raw R16 rides commitSyncBatch inside the same transaction as decoded rows, archives and the trim cursor, so nothing the band trims on ACK lacks a durable home.

Ownership: the three tables join the salvage order and the backup-restore list (lifted to _restoreTables so a test can pin it); wipeAll enumerates sqlite_master and needs nothing.

Coach: v_ecg_readings is a summary-only view over ecg_reading alone — the structural btree gate admits every base table a view reads, so the packet tables are never joined; ecg_reading/ecg_reading_packet/ecg_raw_packet join reservedTableNames so device_id/notes stay out of run_sql.

Tests: fresh schema + view, v50→v51 ladder + idempotent reopen, atomic insert and rollback, signed BLOB / placeholder round trip, manual cascade, R16 in the safe-trim commit (idempotent on bytes), ownership lists + wipe, and the coach guard at both layers.
…EADY recovery

The engine owns the transport half of an ECG reading:

- EcgLease: exclusive ownership of the command transport for one link,
  bound to the session and link generation, acquired synchronously,
  validated on every ECG write and on release, voided at teardown (with an
  EcgLinkDownEvent naming the old generation). History refresh/backfill are
  refused while a valid lease is held; the heartbeat's LINK_VALID and the
  keep-alive's command writes pause under it (the liveness fuse keeps
  running).
- ecgPrepare/ecgStart/ecgRestart/ecgCleanup: the exact docs/mg/05 lists
  through the protocol builders (cmdSelectWrist, cmdLabradorFiltered /
  RawSave / DataGeneration, cmdAbortHistorical with the gen5 profile).
  Attempt-all, one correlated await each — observer before write,
  seq+opcode match, the common 5 s timeout, no retry. _sendAwaited gains an
  owner so a stale continuation cannot write onto a replacement link.
- ecgCancelHistory: ends an active history task through the one abort
  boundary (new terminal kind 'preempted') and waits for lifecycle
  quiescence. START's own opcode 20 stays unconditional.
- Live type-43 revision-17 frames are parsed and delivered synchronously as
  EcgFrameEvent; a frame that claims R17 but does not parse is
  EcgMalformedR17Event. RAM only.
- Historical type-47 revision-16 raw ECG is buffered on the DrainController
  as a burst count member and rides the safe-trim commit into
  ecg_raw_packet. Every durable-progress predicate knows about it:
  HISTORY_END, the HISTORY_START mid-burst discard, aborted-task leftovers,
  the commit early-return, snapshot/restore/discard; the headless drainer's
  commit closure forwards it too.
- READY recovery: onReadyEcgRecovery runs after bootstrap and BEFORE
  listening is published or INIT claims history, under a recovery lease, so
  a retained may-be-active guard can send the cleanup triplet first.

Tests: identity, lease, exact bytes/order per list, correlation and
timeout, attempt-all after FAILURE and after a failed write, stale lease,
history preemption and refusal, R17 parsed/malformed/other-revision/gen4,
R16 buffering + commit + restore + discard, link-down event, opcode-safety
pin, READY order via the real official connect (cleanup before
GET_DATA_RANGE, READY invisible during the hook).
…ry, wiring

lib/ecg/ is the phone-side half of a Labrador reading, behind an abstract
EcgTransport (BleEngineEcgTransport forwards to the engine):

- ecg_policy.dart: the official foreground R17 state machine as a PURE
  reducer (docs/mg/05 §4 / 06 §6) — WAITING ignores pre-contact packets;
  ACTIVE appends while the current-S2-state-1 flag is set, clears and counts
  one interruption per contact-loss transition, fails on progress 255,
  routes the exact explicit-RESTART predicate (presence, positive
  nondecreasing nonterminal progress, S2 flag clear) to a restart effect;
  CONTACT_LOST recovers without a command and fails on 255 or a bad packet
  with three interruptions banked; one placeholder per sequence jump; the
  terminal maps result+live HR for the branch and result+average HR for the
  persisted category; repeated terminals are ignored; first-attempt
  inconclusive offers exactly one retry.
- ecg_controller.dart: lease → retained-guard recovery → history cancel →
  acknowledged guard write → PREPARE → subscribe → START (frames at the
  write boundary count) → reducer → durable save BEFORE completed → ONE
  cleanup on every exit (success, cancel, pause, timeout, malformed R17,
  link loss, save failure, partial command failure) → guard cleared only
  when all three cleanup members succeeded → post-reading history sync.
  Single-flight epoch, frames gated on link generation and dropped while a
  restart is in flight, screen hold released only when held.
- ecg_guard_store.dart: per-serial guard / wrist / remembered-MG flag in
  SharedPreferences through the awaited API; ecg_recovery.dart: the
  controller-free retained-guard cleanup used at READY by both the
  foreground engine and the headless drainer.
- ecg_models.dart: the band-result category table, typed rows and the
  signed-i16 BLOB codec; ecg_waveform_buffer.dart: the bounded live-preview
  ring plus a per-tick repaint coalescer.
- ScreenWake gains owners (hold/releaseOwner); the workout call sites move
  to their own owner so neither feature can drop the other's hold.
- AppState hosts the controller, folds capturing into _hasLiveConsumer,
  stops a reading on pauseForBackground before the live downgrade,
  remembers a MAVERICK hello per serial (pairedIsMaverick), and wires the
  READY recovery hook; runHeadlessSync wires the same recovery.

Tests: every reducer rule plus the 86→30→3,000 official shape; the category
table at every boundary; row codecs and stats; buffer/scheduler; prefs
store; recovery outcomes; the controller over a scripted fake transport
(order, guard-before-write, START-boundary frame, failures, retained
guard, exits, restart single-flight, retry, save failure, single flight);
screen-wake owners.
… now

Health → Heart Screener appears only when the paired band has positively
identified itself as a WHOOP MG (remembered per serial, so saved readings
stay reachable while the band is away); Take ECG additionally needs that
MG connected and READY. The wrist choice is remembered per band.

Capture screen: the pipeline is armed while the instructions and an
original CustomPainter illustration (band on the chosen wrist, both
electrode indents, the opposite thumb and index finger, pulsing contact
rings, phase owned by the screen — still under reduced motion) are shown;
the first valid contact packet moves it to measurement automatically. Band
progress, live HR, "Adjust your fingers and keep still" on contact loss,
and a "Live signal preview" of the real samples over a bounded ring,
repainted at most every Motion.ecgPreviewTick and never per packet. Back
and close cancel through the controller; the app's pause path stops a
reading before the live downgrade. Every terminal state has its own copy;
the result is always labelled band-reported and never a diagnosis.

Detail: the complete accepted waveform with visible breaks at placeholder
seconds, horizontal scroll and stepped zoom, the band's category and
stats, delete, and Analyze now — which routes through Coach setup when
unconfigured, shows an explicit disclosure (host + model) before a remote
endpoint receives anything, then opens a NEW coach conversation and sends
the fixed prompt visibly as the user's turn.

Coach: v_ecg_readings on the allow-list (summary only, see the db commit);
get_ecg_reading(reading_id) — a bound read returning the band summary and
a ≤300-bucket min/max envelope, never raw frames, serial, device id, notes
or all samples; the system prompt gains the ECG law (band's HeartKey
category, unproven polarity, no diagnosis from the waveform, no
confirm/rule-out, clinical/emergency guidance, one Not-medical-advice
line) and the view/tool docs. CoachScreen takes initialMessage +
startNewSession for the Analyze-now hand-off.

Strings in all six locales. ecg_widgets.dart joins the ui2 barrel; the four
components are in the gallery, the routes on the screens list.

Tests: every capture phase, reduced-motion illustration, preview repaint
only on the scheduler tick, waveform width/range and placeholder handling,
detail screen with and without a waveform, zoom, the wrist sheet, the
Analyze prompt; the coach tool's bound read, envelope determinism and
leak checks, and the prompt pins.
The v49→v50 migration test asserted schemaVersion == 50 literally, so every later additive rung (v51 is the WHOOP MG ECG store) would fail it for no reason of its own. The v50 rung is still isolated; the ladder's top is a floor here.
`_analyze` gated on `coachReady`, which reads the config with `watch`.
`watch` is only legal during build; from a tap handler provider asserts,
and coachReady's own catch turned that assert into a plain "not
configured". A fully configured coach therefore sent the user to the
setup form every time, and the check after the form returned false again
and bailed out — the reading was never analysed.

It is an assert, so it bit debug builds only, which is why it survived to
hardware.

Add `coachReadyNow`, the same predicate over `read`, and use it in the
handler. The other watch-based helpers are all called from build.

The regression test drives the tap through a NavigatorObserver so it
asserts on the pushed route without building the coach screen itself.
get_ecg_reading sent a 300-bucket min/max envelope. At 100 Hz that is
100 ms per bucket and a QRS complex is 80-100 ms wide, so every beat
collapsed into one amplitude bar: the model could describe the envelope
but never the trace.

Send the accepted window itself instead, at the band's own rate. A
completed reading is 30 s = 3,000 samples and goes whole; nulls still
hold a missing segment's second open. Only a longer window is decimated,
by a whole-number stride reported alongside the effective rate, so the
result is never clipped mid-number into JSON the model cannot parse.

Add `how_to_read` beside it. Without the provenance the model reads the
data wrongly in specific ways: it inverts the band's avg_hr into an "RR
interval" and presents it as measured, and it judges QRS width as if the
trace were a 500 Hz diagnostic ECG. The block states the 500 Hz raw to
100 Hz filtered path, the 10 ms sample resolution and what it does and
does not support, that avg_hr is the band's and not measured here, and
what quality, interruptions and missing_segments mean.

get_ecg_reading also gets its own result ceiling. The shared bound exists
because the model widens its own run_sql queries until one result
dominates the window; a bound lookup of one reading by id is not that
shape, and clipping it would only decimate a waveform to make room for
the prose describing it.
Law 7 forbade the coach interpreting the waveform at all: it could
explain the band's category, the signal quality, the heart rate and the
unreadable reasons, and nothing else. With the trace now in the tool
result that left the model holding 3,000 samples it was instructed to
say nothing about, and the analysis read as generic no matter how good
the data was.

Owner's decision, made explicitly: allow the interpretation. The model
may now read rate, rhythm and its regularity, beat-to-beat variation,
intervals and morphology, give its own impression, and say plainly when
its reading and the band's category disagree instead of deferring.

What stays is everything that is a fact about the data rather than a
restriction on reading it: the anatomical lead polarity is still not
proven, so axis and R/S direction remain off limits; the category is
still the band's own HeartKey result; the emergency-symptom guidance is
untouched; and the standing caveat is now explicit that this is not a
cleared diagnostic device and no substitute for a clinician.

The Analyze-now message and the tool description asked for the old
behaviour in their own words and would otherwise have contradicted the
law they sit under.
# Conflicts:
#	lib/coach/coach_db.dart
#	lib/data/db.dart
#	lib/l10n/app_en.arb
#	lib/state/app_state.dart
#	lib/sync/background_sync.dart
#	test/db_alarm_schedule_migration_test.dart
The remote-endpoint dialog promised "a bounded waveform envelope". Since
the tool result became the accepted window itself that sentence is wrong
in the direction that matters: the user was consenting to a summary and
sending a complete 30-second recording.

Say what is sent — the summary and every sample the band recorded, 100
per second — in all six locales and in the code fallback. Nothing about
the payload changes; only the sentence that asks permission for it.

A local endpoint still sends nothing off the device, and the dialog is
still shown only for a remote one.
The old drawing did not teach the instruction under it. The forearm was
a plain slab, the band a narrow rectangle across it, and the two fingers
were detached angled bars floating to one side with nothing joining them
to a hand — a user could not tell what to do from the picture.

Redraw it as a tapered forearm wearing the band, with the opposite hand
reaching across and pinching both electrode indents: thumb on the upper
one, index on the lower, enough of the hand visible to read as a hand.
The two contact points carry the emphasis and their halos settle once
contact is reported.

Same painter, same seams: unchanged public API, the left-wrist mirror,
theme colours only, no assets, and legible at any frozen `t` for reduced
motion. shouldRepaint now compares every input it draws from.
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR implements a complete WHOOP MG Heart Screener: it identifies compatible hardware, serializes ECG capture against BLE history traffic, drives the Labrador protocol with crash-safe recovery and cleanup, reduces and persists accepted ECG data plus raw historical packets atomically, exposes history/detail UI and a bounded privacy-aware coach analysis path, and covers the behavior with extensive tests; CI remains blocked until the protocol parser SHA is repinned after upstream protocol#54 merges.

Sequence diagram for WHOOP MG ECG capture and persistence

sequenceDiagram
    actor User
    participant UI as EcgCaptureScreen
    participant Controller as EcgController
    participant BLE as BleEngineEcgTransport
    participant Band as WHOOPMG
    participant DB as LocalDb

    User->>UI: begin(wrist)
    UI->>Controller: begin(wrist)
    Controller->>BLE: acquire()
    Controller->>BLE: cancelHistory(lease)
    Controller->>BLE: prepare(lease, wrist)
    BLE->>Band: PREPARE command list
    Band-->>BLE: SUCCESS responses
    Controller->>BLE: start(lease)
    BLE->>Band: abortHistorical + generationStart
    Band-->>BLE: SUCCESS responses
    Band-->>BLE: EcgTransportFrame(R17)
    BLE-->>Controller: EcgTransportFrame
    Controller->>Controller: reduceEcg(frame)
    Controller->>BLE: restart(lease)
    BLE->>Band: abortHistorical + generationRestart
    Band-->>BLE: SUCCESS responses
    Band-->>Controller: terminal R17
    Controller->>DB: insertEcgReading(reading, packets)
    Controller->>BLE: cleanup(lease)
    BLE->>Band: generationStop + filteredOff + rawSaveOff
    Band-->>BLE: SUCCESS responses
    Controller->>BLE: requestSync()
    BLE->>Band: historical sync
    Band-->>BLE: raw R16 packets
    BLE->>DB: commitSyncBatch(ecgRawPackets)
    UI-->>User: Show completed reading
Loading

Entity relationship diagram for ECG storage

erDiagram
    ECG_READING ||--o{ ECG_READING_PACKET : contains
    ECG_READING {
        string id PK
        int start_ts
        int end_ts
        string category
        string status
        int sample_count
    }
    ECG_READING_PACKET {
        string reading_id PK
        int ordinal PK
        int sequence
        blob samples
        boolean is_placeholder
    }
    ECG_RAW_PACKET {
        string hex PK
        int sequence
        int strap_seconds
        string reading_id
    }
Loading

State diagram for the ECG reducer and capture lifecycle

stateDiagram-v2
    [*] --> WAITING
    WAITING --> ACTIVE: acceptable R17 packet
    ACTIVE --> ACTIVE: accepted packet
    ACTIVE --> CONTACT_LOST: contact loss or progress regression
    CONTACT_LOST --> ACTIVE: contact recovered
    ACTIVE --> ACTIVE: explicit restart condition / EcgSendRestart
    ACTIVE --> done: terminal packet
    CONTACT_LOST --> done: third interruption or progress_255
    ACTIVE --> done: progress_255
    done --> [*]

    state done <<choice>>
    done --> SAVE: completed or final inconclusive
    done --> CLEANUP: unreadable or failure
    SAVE --> CLEANUP: insertEcgReading succeeds
    CLEANUP --> [*]
Loading

File-Level Changes

Change Details Files
Adds an end-to-end WHOOP MG ECG capture pipeline with exclusive BLE ownership, protocol command sequencing, recovery, live R17 handling, and guarded lifecycle cleanup.
  • Introduces generation-bound ECG leases that pause or preempt history and maintenance traffic.
  • Implements PREPARE, START, RESTART, and attempt-all CLEANUP command lists with correlated responses and timeouts.
  • Parses live ECG frames, reports malformed frames and link loss, and performs pre-listening retained-generation recovery.
  • Adds a pure reducer and controller for contact transitions, interruptions, sequence gaps, retry policy, timeout, persistence ordering, and single-flight cleanup.
lib/ble/ble_engine.dart
lib/ecg/ble_ecg_transport.dart
lib/ecg/ecg_controller.dart
lib/ecg/ecg_policy.dart
lib/ecg/ecg_recovery.dart
lib/ecg/ecg_transport.dart
lib/ecg/ecg_models.dart
lib/state/app_state.dart
lib/sync/background_sync.dart
Adds schema v52 persistence for accepted ECG readings, waveform packets, and historical raw R16 records while preserving atomic safe-trim durability.
  • Creates ECG tables, indexes, summary view, typed models, signed-i16 little-endian packet codecs, and transactional reading insertion/deletion.
  • Routes raw R16 packets through the history drain and commit path before ACK, with rollback, idempotency, restore, salvage, and wipe support.
  • Updates schema migration and database integration to keep the v52 rung independent and idempotent.
lib/data/db.dart
lib/data/models.dart
lib/ble/adapters/host.dart
test/ecg_db_test.dart
test/db_migration_ladder_test.dart
test/ble_safe_trim_test.dart
test/gen5_wiring_test.dart
test/history_task_safety_test.dart
Adds ECG history, capture, waveform detail, and MG-gated Health UI flows.
  • Adds Health entry gating based on positively identified and remembered MAVERICK hardware.
  • Provides wrist selection, live signal preview, capture states, contact guidance, saved waveform rendering with visible gaps, history rows, detail statistics, deletion, and reduced-motion behavior.
  • Makes screen wake ownership composable so ECG and workouts can hold the display independently.
lib/ui2/screens/ecg.dart
lib/ui2/ecg_widgets.dart
lib/ui2/screens/health_screen.dart
lib/ui2/screens/screens.dart
lib/ui2/ui2.dart
lib/ui2/theme.dart
lib/gps/screen_wake.dart
test/ecg_ui_test.dart
test/screen_wake_owners_test.dart
Adds a constrained coach integration for ECG summaries and waveform analysis with explicit privacy and interpretation boundaries.
  • Exposes only summary fields through v_ecg_readings and a bound get_ecg_reading tool for the accepted waveform.
  • Adds whole-number stride decimation for oversized payloads and a how_to_read block describing 500 Hz acquisition, 100 Hz output, 10 ms resolution, units, polarity, and limitations.
  • Adds cloud-endpoint disclosure naming the host/model and clarifies that local endpoints keep data on-device; expands coach prompt and tool registration.
lib/coach/coach_actions.dart
lib/coach/coach_db.dart
lib/coach/coach_engine.dart
lib/coach/coach_prompt.dart
lib/ui2/screens/coach.dart
test/coach_ecg_security_test.dart
test/coach_ecg_tool_test.dart
Adds comprehensive unit, integration, persistence, security, transport, controller, reducer, support, UI, migration, and hardware-oriented test coverage.
  • Pins reducer state-machine rules, command bytes/correlation, lease invalidation, raw packet safe-trim behavior, retained recovery, atomic database operations, security gates, coach payload sizing, and controller exit ordering.
  • Adds synthetic MG fixtures and gallery cases for ECG capture, preview, history, and detail presentation.
test/ecg_ble_engine_test.dart
test/ecg_controller_test.dart
test/ecg_models_test.dart
test/ecg_policy_test.dart
test/ecg_support_test.dart
test/ecg_db_test.dart
test/ecg_ui_test.dart
lib/ui2/profile/gallery.dart
Updates localization and dependency metadata for the ECG feature and protocol integration.
  • Adds ECG strings across supported locales.
  • Updates lockfile metadata while retaining the documented protocol SHA repin dependency.
lib/l10n/app_en.arb
lib/l10n/app_de.arb
lib/l10n/app_es.arb
lib/l10n/app_fr.arb
lib/l10n/app_hi.arb
lib/l10n/app_zh.arb
pubspec.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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.

1 participant