authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-15 23:02:57-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-15 23:02:57-07:00
log6ee03240bccd9287a8c2246e9547697aa5857998
treed08e521f1f594d4783d986f426c348dac22f485c
parentc1b2ed6504bbd57440d03c6a5af2736f33be7c65
signature Signed by PGP key B5690EEEBB952194

fix(clients): hold the discovered machine icon across relay refreshes (#12030)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

2 files changed, 18 insertions(+), 10 deletions(-)

apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+7-2
...@@ -235,6 +235,12 @@ function ConnectedCloudEnvironmentRow(props: {...@@ -235,6 +235,12 @@ function ConnectedCloudEnvironmentRow(props: {
235 );235 );
236 const unsupported = props.environment.connectionState === "unsupported";236 const unsupported = props.environment.connectionState === "unsupported";
237 const enabled = props.environment.isEnabled && !unsupported;237 const enabled = props.environment.isEnabled && !unsupported;
238 // Discovery empties its map on every refresh; hold the last descriptor seen
239 // so the glyph does not blink back to the generic one each time.
240 const [lastDescriptor, setLastDescriptor] = useState(props.descriptor);
241 if (props.descriptor !== undefined && props.descriptor !== lastDescriptor) {
242 setLastDescriptor(props.descriptor);
243 }
238 return (244 return (
239 <Pressable245 <Pressable
240 accessibilityHint="Long press to remove from this device"246 accessibilityHint="Long press to remove from this device"
...@@ -248,8 +254,7 @@ function ConnectedCloudEnvironmentRow(props: {...@@ -248,8 +254,7 @@ function ConnectedCloudEnvironmentRow(props: {
248 errorExpanded={props.errorExpanded}254 errorExpanded={props.errorExpanded}
249 label={props.environment.environmentLabel}255 label={props.environment.environmentLabel}
250 machine={resolveEnvironmentMachineKind(256 machine={resolveEnvironmentMachineKind(
251 serverConfig ??257 serverConfig ?? (lastDescriptor === undefined ? null : { environment: lastDescriptor }),
252 (props.descriptor === undefined ? null : { environment: props.descriptor }),
253 )}258 )}
254 onValueChange={props.onSetEnabled}259 onValueChange={props.onSetEnabled}
255 onToggleError={props.onToggleError}260 onToggleError={props.onToggleError}
apps/web/src/components/settings/ConnectionsSettings.tsx+11-8
...@@ -1523,14 +1523,20 @@ function SavedBackendListRow({...@@ -1523,14 +1523,20 @@ function SavedBackendListRow({
1523 const serverVersion = environment.serverConfig?.environment.serverVersion ?? null;1523 const serverVersion = environment.serverConfig?.environment.serverVersion ?? null;
1524 // A saved T3 Connect machine this device has never reached (unsupported,1524 // A saved T3 Connect machine this device has never reached (unsupported,
1525 // or not yet connected) still has a descriptor from relay discovery, so1525 // or not yet connected) still has a descriptor from relay discovery, so
1526 // it can wear its detected glyph instead of the generic server.1526 // it can wear its detected glyph instead of the generic server. Discovery
1527 // empties its map on every refresh, so hold the last descriptor seen or
1528 // the glyph would blink back to the generic one each time.
1527 const relayDiscovery = useRelayEnvironmentDiscovery();1529 const relayDiscovery = useRelayEnvironmentDiscovery();
1528 const discoveredDescriptor = Option.getOrNull(1530 const discoveredDescriptor = Option.getOrNull(
1529 relayDiscovery.environments.get(environmentId)?.status ?? Option.none(),1531 relayDiscovery.environments.get(environmentId)?.status ?? Option.none(),
1530 )?.descriptor;1532 )?.descriptor;
1533 const [lastDescriptor, setLastDescriptor] = useState(discoveredDescriptor);
1534 if (discoveredDescriptor !== undefined && discoveredDescriptor !== lastDescriptor) {
1535 setLastDescriptor(discoveredDescriptor);
1536 }
1531 const machineKind = resolveEnvironmentMachineKind(1537 const machineKind = resolveEnvironmentMachineKind(
1532 environment.serverConfig ??1538 environment.serverConfig ??
1533 (discoveredDescriptor === undefined ? null : { environment: discoveredDescriptor }),1539 (lastDescriptor === undefined ? null : { environment: lastDescriptor }),
1534 );1540 );
1535 const subtitleText = [1541 const subtitleText = [
1536 environmentTransportLabel(environment),1542 environmentTransportLabel(environment),
...@@ -1560,10 +1566,7 @@ function SavedBackendListRow({...@@ -1560,10 +1566,7 @@ function SavedBackendListRow({
1560 <span1566 <span
1561 className={cn(1567 className={cn(
1562 "block truncate",1568 "block truncate",
1563 (enabled || unsupported) &&1569 enabled && status.tone === "error" && !resumingServerUpdate && "text-destructive",
1564 status.tone === "error" &&
1565 !resumingServerUpdate &&
1566 "text-destructive",
1567 )}1570 )}
1568 />1571 />
1569 }1572 }
...@@ -1571,8 +1574,8 @@ function SavedBackendListRow({...@@ -1571,8 +1574,8 @@ function SavedBackendListRow({
1571 {subtitleText}1574 {subtitleText}
1572 </TooltipTrigger>1575 </TooltipTrigger>
1573 <TooltipPopup side="top" className="max-w-80 whitespace-pre-wrap leading-tight">1576 <TooltipPopup side="top" className="max-w-80 whitespace-pre-wrap leading-tight">
1574 {unsupported && environment.connection.error1577 {unsupported
1575 ? environment.connection.error1578 ? (environment.connection.error ?? connectionStatusText(environment.connection))
1576 : enabled1579 : enabled
1577 ? connectionStatusText(environment.connection)1580 ? connectionStatusText(environment.connection)
1578 : "Switched off"}1581 : "Switched off"}