From 16d48417d8132505ff4e0186f346e747eadb5a05 Mon Sep 17 00:00:00 2001 From: saubyk <39208279+saubyk@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:26:45 -0700 Subject: [PATCH] Fix CLN channel connection status shown inconsistently (#1606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLN's listpeerchannels reports connection state as peer_connected, but the open/pending channel list columns read the legacy `connected` field, which the backend never populated. It was therefore always empty, so the list always rendered "Disconnected" while the detail panel (which reads peer_connected) showed the true state — the contradiction reported in #1606. Normalize `connected = peer_connected` in the backend listPeerChannels response so legacy consumers stay in sync, and point the list columns at peer_connected directly. Add regression specs asserting the connected column follows peer_connected even when the legacy field disagrees. Co-Authored-By: Claude Opus 4.8 (1M context) --- server/controllers/cln/channels.ts | 3 +++ .../channel-open-table.component.html | 2 +- .../channel-open-table.component.spec.ts | 24 +++++++++++++++++++ .../channel-pending-table.component.html | 2 +- .../channel-pending-table.component.spec.ts | 24 +++++++++++++++++++ 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/server/controllers/cln/channels.ts b/server/controllers/cln/channels.ts index 4fd52cd1..5ee0f18d 100644 --- a/server/controllers/cln/channels.ts +++ b/server/controllers/cln/channels.ts @@ -21,6 +21,9 @@ export const listPeerChannels = (req, res, next) => { const getPeerAliasesTasks = body.channels.map((channel) => () => { channel.to_them_msat = channel.total_msat - channel.to_us_msat; channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - channel.to_them_msat) / channel.total_msat)).toFixed(3); + // listpeerchannels reports connection state as peer_connected. Mirror it onto the + // legacy 'connected' field so backward-compat consumers stay in sync (issue #1606). + channel.connected = channel.peer_connected; return getAlias(req.session.selectedNode, channel, 'peer_id'); }); common.runWithConcurrencyLimit(getPeerAliasesTasks, 20, () => { diff --git a/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.html b/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.html index d8ec138e..24f447e7 100644 --- a/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.html +++ b/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.html @@ -66,7 +66,7 @@ Connected - {{(channel?.connected) ? 'Connected' : 'Disconnected'}} + {{(channel?.peer_connected) ? 'Connected' : 'Disconnected'}} Local Reserve (Sats) diff --git a/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.spec.ts b/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.spec.ts index 7984afa9..89408e46 100644 --- a/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.spec.ts +++ b/src/app/cln/peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component.spec.ts @@ -19,6 +19,7 @@ import { CLNChannelOpenTableComponent } from './channel-open-table.component'; import { ExtraOptions, Route, Router } from '@angular/router'; import { HttpClientTestingModule, provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { MatTableDataSource } from '@angular/material/table'; describe('CLNChannelOpenTableComponent', () => { let component: CLNChannelOpenTableComponent; @@ -58,6 +59,29 @@ describe('CLNChannelOpenTableComponent', () => { expect(component).toBeTruthy(); }); + const connectedCellText = (): string => { + const cell = fixture.nativeElement.querySelector('td.mat-column-connected'); + return cell ? cell.textContent.trim() : ''; + }; + + const renderSingleChannel = (channel: any) => { + component.displayedColumns = ['connected']; + component.channels = new MatTableDataSource([channel]); + fixture.detectChanges(); + }; + + // Issue #1606: the connected column must reflect peer_connected (what listpeerchannels + // returns), not the legacy 'connected' field, so it stays consistent with the detail panel. + it('should render Connected from peer_connected even when legacy connected is false', () => { + renderSingleChannel({ peer_connected: true, connected: false }); + expect(connectedCellText()).toBe('Connected'); + }); + + it('should render Disconnected from peer_connected even when legacy connected is true', () => { + renderSingleChannel({ peer_connected: false, connected: true }); + expect(connectedCellText()).toBe('Disconnected'); + }); + afterEach(() => { TestBed.resetTestingModule(); }); diff --git a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.html b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.html index 4042080d..82192f0d 100644 --- a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.html +++ b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.html @@ -58,7 +58,7 @@ Connected - {{(channel?.connected) ? 'Connected' : 'Disconnected'}} + {{(channel?.peer_connected) ? 'Connected' : 'Disconnected'}} State diff --git a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts index 5d97bffe..9d5448ce 100644 --- a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts +++ b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts @@ -15,6 +15,7 @@ import { RTLEffects } from '../../../../../store/rtl.effects'; import { SharedModule } from '../../../../../shared/shared.module'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DataService } from '../../../../../shared/services/data.service'; +import { MatTableDataSource } from '@angular/material/table'; describe('CLNChannelPendingTableComponent', () => { let component: CLNChannelPendingTableComponent; @@ -49,6 +50,29 @@ describe('CLNChannelPendingTableComponent', () => { expect(component).toBeTruthy(); }); + const connectedCellText = (): string => { + const cell = fixture.nativeElement.querySelector('td.mat-column-connected'); + return cell ? cell.textContent.trim() : ''; + }; + + const renderSingleChannel = (channel: any) => { + component.displayedColumns = ['connected']; + component.channels = new MatTableDataSource([channel]); + fixture.detectChanges(); + }; + + // Issue #1606: the connected column must reflect peer_connected (what listpeerchannels + // returns), not the legacy 'connected' field, so it stays consistent with the detail panel. + it('should render Connected from peer_connected even when legacy connected is false', () => { + renderSingleChannel({ peer_connected: true, connected: false }); + expect(connectedCellText()).toBe('Connected'); + }); + + it('should render Disconnected from peer_connected even when legacy connected is true', () => { + renderSingleChannel({ peer_connected: false, connected: true }); + expect(connectedCellText()).toBe('Disconnected'); + }); + afterEach(() => { TestBed.resetTestingModule(); });