This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch fix/3d-fps-and-layer-rehydrate
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 3627d154651d30c2f4824037a6eafd28b0d21eb6
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 31 09:00:00 2026 +0800

    fix(layer): validate URL ?service= against the full roster
    
    The auto-pick watch validated selectedId only against the landing top-N
    sample, so a valid low-traffic ?service= outside the sample was treated as
    stale and overwritten by the first sampled row (clearing instance/endpoint
    too). Validate against useLayerServices (the layer's real catalog); only a
    genuinely-absent id auto-corrects, and only once the roster has loaded.
---
 apps/ui/src/layer/LayerShell.vue | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/apps/ui/src/layer/LayerShell.vue b/apps/ui/src/layer/LayerShell.vue
index 4247c59..e0b2438 100644
--- a/apps/ui/src/layer/LayerShell.vue
+++ b/apps/ui/src/layer/LayerShell.vue
@@ -42,6 +42,7 @@ import { useTimeRangeStore } from '@/controls/timeRange';
 import { useLayers, firstLayerTab } from '@/shell/useLayers';
 import { layerContentToDef, type LayerTemplateContent } from 
'@/shell/layerFromTemplate';
 import { useSelectedService } from '@/layer/useSelectedService';
+import { useLayerServices } from '@/layer/useLayerServices';
 import { useLayerSelectionStore } from '@/state/layerSelection';
 import { useSetupStore } from '@/state/setup';
 import { fmtMetric } from '@/utils/formatters';
@@ -303,19 +304,31 @@ const isZipkinTrace = computed<boolean>(() => {
   return scopeSegment.value === 'trace' && layer.value?.traces?.source === 
'zipkin';
 });
 
+// Full service roster (the layer's REAL catalog, independent of landing's
+// top-N sample which misses low-traffic services). A URL `?service=` is
+// validated against THIS, not the sample — otherwise a valid but
+// low-traffic deep link is wrongly treated as stale.
+const { services: fullRoster, isLoading: rosterLoading } = 
useLayerServices(layerKey);
+
 // Keep the URL-backed service selection honest for every page that
-// uses the shell picker. A stale `?service=` can survive navigation or
-// manual URL entry; the switch label used to fall back visually to the
-// first row while the metric query still waited for a valid service.
+// uses the shell picker. A `?service=` outside the landing sample is
+// trusted when it exists in the full roster; only a genuinely stale id
+// (absent from the roster) auto-corrects to the first sampled row, and
+// only once the roster has loaded so a valid pin isn't clobbered in flight.
 watch(
-  [sampledServices, selectedId, viewOwnsServiceSelector],
-  ([rows, id, ownsSelector]) => {
+  [sampledServices, selectedId, viewOwnsServiceSelector, fullRoster, 
rosterLoading],
+  ([rows, id, ownsSelector, roster, rosterIsLoading]) => {
     if (ownsSelector) return;
     const first = rows[0];
     if (!first) return;
-    if (!id || !rows.some((s) => s.serviceId === id)) {
+    if (!id) {
       setSelected(first.serviceId);
+      return;
     }
+    if (rows.some((s) => s.serviceId === id)) return; // in the sample → keep
+    if (rosterIsLoading) return; // don't clobber a pin while the roster loads
+    if (roster.some((s) => s.id === id)) return; // valid in the full roster → 
keep
+    setSelected(first.serviceId); // genuinely stale → fall back
   },
   { immediate: true },
 );

Reply via email to