From 27eefd1c67fba7fdf8d29bdeb8a2a38ea380a780 Mon Sep 17 00:00:00 2001 From: slvnlrt Date: Sat, 14 Mar 2026 22:36:39 +0100 Subject: [PATCH] fix(addressing): accept device UUID as device_slug in SdPath The frontend VolumesGroup was sending volume.device_id (UUID) instead of device.slug when constructing Physical SdPaths. This caused as_local_path() and is_local() to return false, breaking ephemeral volume browsing ("Location root path is not local"). Backend: SdPath::is_current_device() now accepts slug, UUID, or "local". Frontend: VolumesGroup.tsx looks up the Device by ID and uses device.slug. Co-Authored-By: Claude Opus 4.6 --- core/src/domain/addressing.rs | 19 +++++++++++++------ .../components/SpacesSidebar/VolumesGroup.tsx | 18 +++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/core/src/domain/addressing.rs b/core/src/domain/addressing.rs index 4c81178aa..26d073457 100644 --- a/core/src/domain/addressing.rs +++ b/core/src/domain/addressing.rs @@ -199,13 +199,21 @@ impl SdPath { } } + /// Check if the given device_slug refers to this device. + /// Accepts the slug, the UUID string, or the "local" placeholder. + fn is_current_device(device_slug: &str) -> bool { + device_slug == "local" + || device_slug == get_current_device_slug() + || device_slug == get_current_device_id().to_string() + } + /// Check if this path is on the current device pub fn is_local(&self) -> bool { match self { - Self::Physical { device_slug, .. } => *device_slug == get_current_device_slug(), - Self::Cloud { .. } => false, // Cloud paths are never local - Self::Content { .. } => false, // Content paths are abstract, not inherently local - Self::Sidecar { .. } => false, // Sidecar paths are abstract, must be resolved + Self::Physical { device_slug, .. } => Self::is_current_device(device_slug), + Self::Cloud { .. } => false, + Self::Content { .. } => false, + Self::Sidecar { .. } => false, } } @@ -213,8 +221,7 @@ impl SdPath { pub fn as_local_path(&self) -> Option<&Path> { match self { Self::Physical { device_slug, path } => { - // "local" is a special placeholder from the frontend meaning "current device" - if *device_slug == "local" || *device_slug == get_current_device_slug() { + if Self::is_current_device(device_slug) { Some(path) } else { None diff --git a/packages/interface/src/components/SpacesSidebar/VolumesGroup.tsx b/packages/interface/src/components/SpacesSidebar/VolumesGroup.tsx index 219cf0634..49329f546 100644 --- a/packages/interface/src/components/SpacesSidebar/VolumesGroup.tsx +++ b/packages/interface/src/components/SpacesSidebar/VolumesGroup.tsx @@ -1,6 +1,6 @@ import {EyeSlash, Plugs, WifiSlash} from '@phosphor-icons/react'; import {getVolumeIcon, useNormalizedQuery} from '@sd/ts-client'; -import type {Volume} from '@sd/ts-client'; +import type {Device, Volume} from '@sd/ts-client'; import {useNavigate} from 'react-router-dom'; import {GroupHeader} from './GroupHeader'; import {SpaceItem} from './SpaceItem'; @@ -29,9 +29,13 @@ const getVolumeIndicator = (volume: Volume) => ( ); // Component for individual volume items with context menu -function VolumeItem({volume, index, volumesLength}: {volume: Volume; index: number; volumesLength: number}) { +function VolumeItem({volume, index, volumesLength, devices}: {volume: Volume; index: number; volumesLength: number; devices: Device[]}) { const contextMenu = useVolumeContextMenu({volume}); + // Look up the device by ID to get the slug (not the UUID) + const device = devices.find((d) => d.id === volume.device_id); + const deviceSlug = device?.slug || 'local'; + return ( ({ + query: 'devices.list', + input: {include_offline: true, include_details: false}, + resourceType: 'device' + }); + const volumes = volumesData?.volumes || []; + const devices = (devicesData as Device[]) || []; return (
@@ -98,6 +109,7 @@ export function VolumesGroup({ volume={volume} index={index} volumesLength={volumes.length} + devices={devices} /> )) )}