From 3b210e0fdb14e22d884a2b4bf977aa7e2235478a Mon Sep 17 00:00:00 2001 From: Strycher Date: Sat, 1 Aug 2026 23:22:01 -0400 Subject: [PATCH] fix(#483): restore strict capability gating, remove the diagnostics screen A radio without the caps bits has no button and no buzzer to configure, so it gets nothing: no tile, no screen, no command. I had changed the tile to appear whenever a radio was connected and added a diagnostics pane showing its raw caps byte 2. Nobody asked for that. I added it on my own initiative because a silently absent screen looked like a broken client, and it broke the negative test #474 specifies verbatim: "A device that does not advertise the bit shows no screen and the client emits no command." "Settings screen appears ONLY when the device advertises the capability bit" That negative test is the reason client code ships ahead of firmware: cross-compatibility with radios that lack the bits can only be exercised if the client is out in front. My change would have made it fail on something I invented rather than on anything real. The tile is now gated on supportsButtonMatrix || supportsNotifyScope. The pane keeps a matching guard as belt and braces, not a reachable path. The no-command-emitted half was already correct and is unchanged. Full suite 740 pass, analyze clean, format clean. Epic: #474, #475 Agent: CalmBay (session d14220d9) --- lib/screens/settings/device_ui_view.dart | 47 +++--------------------- lib/screens/settings_screen.dart | 9 ++++- 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/lib/screens/settings/device_ui_view.dart b/lib/screens/settings/device_ui_view.dart index b1b8577..95f5619 100644 --- a/lib/screens/settings/device_ui_view.dart +++ b/lib/screens/settings/device_ui_view.dart @@ -40,49 +40,12 @@ class _DeviceUiViewState extends State { builder: (context, connector, _) { final showButtons = connector.supportsButtonMatrix; final showScope = connector.supportsNotifyScope; - // A radio that advertises nothing gets a diagnosis, not a blank screen. - // Silence used to be indistinguishable from a broken client, which is - // exactly the failure this pane is meant to make visible. + // A radio advertising neither bit has no button and no buzzer to + // configure, so it gets nothing. The tile that leads here is gated the + // same way, so this is a belt-and-braces guard rather than a path a + // user can reach. (#474 negative test) if (!showButtons && !showScope) { - final caps2 = connector.offbandCaps2; - return ListView( - children: [ - const _SectionHeader('Not advertised by this radio'), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), - child: Text( - caps2 == null - ? 'This radio sends no capability byte 2 at all, which ' - 'means its firmware predates the feature. Nothing ' - 'is wrong with the app; the radio needs newer ' - 'firmware.' - : 'This radio sends capability byte 2 as 0x' - '${caps2.toRadixString(16).padLeft(2, '0')}, with ' - 'neither the notification-scope bit (0x01) nor the ' - 'button-matrix bit (0x02) set. Its firmware knows ' - 'about byte 2 but does not claim these features, ' - 'for example a board with no buzzer.', - ), - ), - ListTile( - leading: const Icon(Icons.memory_outlined), - title: const Text('Capability byte 2'), - subtitle: Text( - caps2 == null - ? 'absent (frame shorter than 85 bytes)' - : '0x${caps2.toRadixString(16).padLeft(2, '0')}', - ), - ), - const ListTile( - leading: Icon(Icons.block_outlined), - title: Text('No command will be sent'), - subtitle: Text( - 'The app never emits this command to a radio that has not ' - 'advertised support for it.', - ), - ), - ], - ); + return const SizedBox.shrink(); } // The radio advertises the capability but shipped firmware has no // get/set command yet, so it can be detected and not queried. Say that diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 4cea205..bf1fa79 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -272,7 +272,14 @@ class _SettingsScreenState extends State { // Button and buzzer: device UI config (#474/#475). Owner-placed // under Node Settings, appended last so nothing already here // moves. - if (connector.isConnected) ...[ + // + // Gated STRICTLY on the capability bits. A radio without the + // hardware has no button to configure, so it gets no tile, no + // screen and no command. This is the negative test in #474: + // "A device that does not advertise the bit shows no screen and + // the client emits no command." + if (connector.supportsButtonMatrix || + connector.supportsNotifyScope) ...[ const Divider(height: 1), ListTile( leading: const Icon(Icons.radio_button_checked_outlined),