Let the radio settings pane open but not be used, rather than disabled

This commit is contained in:
Jack Kingsman
2026-03-16 20:30:34 -07:00
parent 9cd567895b
commit c215aedc0d
4 changed files with 13 additions and 30 deletions

View File

@@ -255,12 +255,6 @@ export function App() {
refreshUnreads,
} = useUnreadCounts(channels, contacts, activeConversation);
useEffect(() => {
if (showSettings && !config && settingsSection === 'radio') {
setSettingsSection('local');
}
}, [config, settingsSection, setSettingsSection, showSettings]);
useEffect(() => {
if (activeConversation?.type !== 'channel') {
setChannelUnreadMarker(null);
@@ -573,7 +567,6 @@ export function App() {
settingsSection={settingsSection}
sidebarOpen={sidebarOpen}
showCracker={showCracker}
disabledSettingsSections={config ? [] : ['radio']}
onSettingsSectionChange={setSettingsSection}
onSidebarOpenChange={setSidebarOpen}
onCrackerRunningChange={setCrackerRunning}

View File

@@ -155,13 +155,11 @@ export function SettingsModal(props: SettingsModalProps) {
const renderSectionHeader = (section: SettingsSection): ReactNode => {
if (!showSectionButton) return null;
const Icon = SETTINGS_SECTION_ICONS[section];
const disabled = section === 'radio' && !config;
return (
<button
type="button"
className={`${sectionButtonClasses} disabled:cursor-not-allowed disabled:opacity-50`}
className={sectionButtonClasses}
aria-expanded={expandedSections[section]}
disabled={disabled}
onClick={() => toggleSection(section)}
>
<span className="inline-flex items-center gap-2 font-medium" role="heading" aria-level={3}>
@@ -206,8 +204,8 @@ export function SettingsModal(props: SettingsModalProps) {
/>
) : (
<div className={sectionContentClass}>
<div className="rounded-md border border-input bg-muted/20 px-4 py-3 text-sm text-muted-foreground">
Radio settings are unavailable until a radio connects.
<div className="flex items-center justify-center py-12 text-sm text-muted-foreground">
Radio is not available.
</div>
</div>
))}

View File

@@ -327,19 +327,17 @@ describe('App startup hash resolution', () => {
expect(window.location.hash).toBe('');
});
it('opens settings from a settings hash and falls back away from radio when disconnected', async () => {
it('stays on radio settings section even when radio is disconnected', async () => {
window.location.hash = '#settings/radio';
mocks.api.getRadioConfig.mockRejectedValue(new Error('radio offline'));
render(<App />);
await waitFor(() => {
expect(screen.getByTestId('settings-modal-section')).toHaveTextContent('local');
expect(screen.getByTestId('settings-modal-section')).toHaveTextContent('radio');
});
for (const button of screen.getAllByRole('button', { name: 'Radio' })) {
expect(button).toBeDisabled();
}
expect(window.location.hash).toBe('#settings/local');
// Section stays on radio (no redirect to local) and hash is preserved
expect(window.location.hash).toBe('#settings/radio');
});
});

View File

@@ -206,30 +206,24 @@ describe('SettingsModal', () => {
expect(screen.getByText(/Configured radio contact capacity/i)).toBeInTheDocument();
});
it('keeps non-radio settings available when radio config is unavailable', () => {
it('shows radio-unavailable message when config is null', () => {
renderModal({ config: null });
const radioToggle = screen.getByRole('button', { name: /Radio/i });
expect(radioToggle).toBeDisabled();
expect(radioToggle).not.toBeDisabled();
openLocalSection();
expect(screen.getByLabelText('Local label text')).toBeInTheDocument();
openDatabaseSection();
expect(screen.getByText('Delete Undecrypted Packets')).toBeInTheDocument();
fireEvent.click(radioToggle);
expect(screen.getByText('Radio is not available.')).toBeInTheDocument();
});
it('shows a radio-unavailable message instead of blocking the whole settings page', () => {
it('shows radio-unavailable message in sidebar-nav mode when config is null', () => {
renderModal({
config: null,
externalSidebarNav: true,
desktopSection: 'radio',
});
expect(
screen.getByText('Radio settings are unavailable until a radio connects.')
).toBeInTheDocument();
expect(screen.queryByText('Loading configuration...')).not.toBeInTheDocument();
expect(screen.getByText('Radio is not available.')).toBeInTheDocument();
});
it('shows cached radio firmware and capacity info under the connection status', () => {