A firmware build can ACK a broker enable/disable as success without applying it
(HV4; meshcore-firmware#179). The quick toggle and the editor save previously
trusted the ACK and showed a success snackbar, so an ack-but-no-op looked like
it worked.
Both paths now SET, wait a short settle, re-read the slot, and report what the
device ACTUALLY did:
- applied -> confirm
- notApplied -> warn "possible firmware issue"; the editor stays and re-seeds to
the device's true state instead of popping a false success
- unverified -> re-read failed (e.g. an enable that rebooted the device)
Every device is treated identically: the UI reflects the re-read, never assuming
a change took that the device didn't confirm.
- BrokerConfig.classifyApply pure classifier + BrokerApplyOutcome (unit-tested)
- ObserverConfigService.applySettleDelay
- wired into mqtt_brokers_screen quick toggle + broker_editor_screen save
- widget test for the ACK-but-not-applied path
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On-hardware feedback: JWT owner defaults to the device pubkey and IATA override
is optional by definition, so gating a save on them is wrong -- they have
under-the-hood firmware defaults.
BrokerConfig.enableError now checks only the structural fields with no default:
URL present, port in range. The JWT field checks are gone; the firmware enforces
JWT completeness with its defaults, and a genuinely-bad enable surfaces the
firmware's reason via the failure messaging added earlier. The editor's pre-save
check and the list's quick Enable both follow.
Tests updated to the new contract (a JWT broker with blank owner/audience now
enables; a structurally-complete quick Enable confirms). Full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On-hardware test feedback:
- Long-press Enable/Disable was silent on success AND failure. It now confirms
on success and, on failure, surfaces the device's reason (lastError) instead
of nothing.
- The client now refuses to quick-Enable a broker that can't work (incomplete
required fields, e.g. JWT without audience/owner) and says why -- so it never
reports "enabled" on a broker that won't run. (Firmware should reject too.)
- The editor blocked a DISABLE on blank/invalid fields. Validation now gates
ENABLING only -- disabling a slot with partial values is fine; it won't be
active.
Validation rules shared via BrokerConfig.enableError (editor pre-save + list
quick-Enable). Full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The broker pool gets its own screen, reached from a "MQTT brokers" row in the
Observer pane (replacing the read-only inline list and its stale "pending
firmware support" note).
- MqttBrokersScreen: a tile per broker (tap -> editor, long-press -> Enable /
Disable / Edit / Clear), and a + FAB that opens the next empty slot (disabled
when all 10 are full) -- matching channels_screen's FAB-add pattern. Each
action re-reads only the affected slot, never the full pool dump.
- Observer pane: the broker section is now a navigational row showing
"N configured - M enabled"; the editor is wired in end-to-end.
3/3 screen tests green; full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The edit UI for one broker slot. Staged-save: controls edit local state; Save
sends only the CHANGED fields through saveBroker (enabled written last), so a
partial save can't leave a live-corrupt broker.
- Fields: url, port, transport, auth_type, username, write-only password,
topic_prefix, iata_override, jwt_* (when auth=jwt), ca_cert (when tls/wss),
plus the enabled toggle.
- Validation before any SET: port 1-65535, jwt audience+owner when auth=jwt.
- Write-only password: sent only when typed; blank keeps the stored secret.
- Refresh: single-slot re-GET (getBroker) + reseed, with a dirty discard guard.
- Back button guards unsaved edits (PopScope -> Discard? dialog) -- Gemini's
state-loss finding.
4/4 widget tests green. Brokers list screen + Observer-pane wiring next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three observer-delta fixes (per the #80 sync diagnosis / G2):
- A: settings screen gates the Observer category via context.select on the
supported flag, not a connector-wide context.watch -- the watch rebuilt the
whole settings screen on every sync frame, thrashing the UI thread.
- B: a flat Save re-reads only the flat settings (refresh includeBrokers:false),
never the 84-frame broker pool -- no BLE re-flood for a display toggle.
- C: the initial observer read is deferred until the device's channel/contact
sync settles, so it never competes with that sync for BLE.
TDD: B (no OCFG_BROKERS on a flat refresh) + C (refresh deferred while syncing,
fires once idle). Full observer suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The G2 BLE log showed the config protocol works (GETs return values; the broker
dump starts) but the firmware's broker dump stalls without BROKERS_END, so
getBrokers() times out. The client was treating that as a TOTAL failure --
discarding the 7 flat settings it read cleanly and blanking the pane.
Now the broker pool loads independently: refresh() builds + shows the flat
config even when getBrokers fails, flags brokersUnavailable, and the pane shows
the broker section as "unavailable" instead of going stale. Flat settings stay
usable. TDD red->green; full observer suite (39) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the 2 MINOR findings from the G1 re-review (#76):
- MINOR-A: ObserverConfigService.refresh() no longer wipes lastError / clears
stale on a PARTIAL read. A null from any getFlat is a failed read; the
snapshot is marked stale and the error kept (error-visibility).
- MINOR-B: the settings pane validates status_interval against the firmware
range (10-3600) before sending; an out-of-range value is not put on the wire
and the user is told.
TDD: both tests RED first (current sent '5'; refresh wiped the error), GREEN
after. Full observer suite (37 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardened the staged-save pane (settings_screen capability gate already correct):
- Normalize an unexpected device rotation to a valid SegmentedButton segment
{0,180} so the control always has a valid selection and never echoes an
out-of-range value back on save.
- Disable Save during a refresh (no save/refresh race on the controllers).
- digitsOnly formatter on status-interval (firmware still range-checks).
- Keys on the SSID/password fields for robust widget-test targeting.
4 widget tests pin the security-sensitive behavior: saves send ONLY changed
keys, a blank password keeps the stored secret, an entered password is sent as
wifi.pwd then the field clears, rotation is normalized, stale/error surfaced
(SAFELANE §6). Full observer suite (33 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TcpScreen.initState reads AppSettingsService from context
to pre-fill host/port fields, but the test helper only
provided MeshCoreConnector. Switch to MultiProvider so
AppSettingsService is also in the widget tree.
- Replace thin MeshCoreTcpManager facade with a proper MeshCoreTcpConnector
that owns TcpTransportService and the frame subscription, mirroring
MeshCoreUsbManager. The connector no longer holds a raw TcpTransportService
or a _tcpFrameSubscription field.
- Remove hardcoded default host IP from TcpScreen (keep port 5000 hint).
- Disable connect button during scanning state, not just connecting state.
- Fix tcpPortLabel mistranslated as nautical "port/harbor" in de, it, pt,
nl, sv, sk, sl, zh; fix corrupted Slovak tcpPortHint ("5 000" → "5000").
- Remove unused tcpStatus_connecting string from all 15 locale arb files
and all generated app_localizations_*.dart files.
- Add extendedPadding to TCP screen FABs to match USB screen.
- Add Key to connect button; update tests to use byKey and assert
onPressed == null when button is disabled during scanning.
- Implemented TCP transport service for native platforms.
- Added TCP connection screen with input fields for host and port.
- Integrated TCP connection options into the scanner and USB screens.
- Updated localization files for new TCP-related strings.
- Added tests for TCP connection flow and error handling.
- Enhanced USB screen to include TCP connection option.
- Improved layout to ensure no overflow in narrow widths for scanner and USB screens.
- Rewrite UsbScreen to mirror ScannerScreen patterns (status bar,
tap-to-connect port list, bottom FABs, SnackBar errors)
- Extract MeshCoreUsbManager from MeshCoreConnector for cleaner
USB transport ownership
- Add debug logging throughout USB connection flow (connector,
manager, web/native services)
- Print debug logs to console in debug mode even when app debug
log setting is disabled
- Localize remaining hardcoded strings (Web Serial Device fallback
label, USB status bar keys, companion firmware timeout hint)
- Fix Swedish misspelling in translations (stöderliga → stödda)
- Guard Linux notification init against missing D-Bus session bus
- Fix SNRIndicator hit-test error by adding minimum size constraints
- Update USB flow tests for new UI patterns
- Updated the _friendlyErrorMessage method in UsbScreen to provide more user-friendly error messages based on specific PlatformException codes.
- Added localized error messages for various USB-related errors, improving clarity for users.
- Modified the UsbSerialService to rethrow exceptions instead of throwing StateError, allowing for better error propagation.
- Updated the usb_flow_test to reflect changes in the USB display label behavior, ensuring the test accurately describes the functionality.
- replace Android USB dependency with app-owned USB host implementation\n- restore BLE-first scanner flow with USB secondary action\n- tighten Web Serial key handling and disconnect logging\n\nTODO (follow-up):\n- review non-English localization copy for tone and consistency\n- trim remaining unused/awkward localization strings introduced during USB UI changes