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

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 566e213  ui dashboards: cascade-clear is keyed off fetchKey freshness, 
not data===null
566e213 is described below

commit 566e2131a84d06217539a37373d0c843117e416c
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 19:30:08 2026 +0800

    ui dashboards: cascade-clear is keyed off fetchKey freshness, not 
data===null
    
    The old loading gate (`data === null && reachable`) only fired when
    vue-query actually surfaced an undefined `data`. With a cache hit
    or a background-fetch on the new queryKey vue-query can keep the
    prior key's data visible, so the gate missed the transition and
    the page looked frozen on the old widgets until the new fetch
    landed.
    
    Now we track `fetchKey` (layerKey + scope + service + instance +
    endpoint + range bucket) and remember the `lastFreshKey` we got a
    non-null `data` for. `dataIsFresh = lastFreshKey === fetchKey`.
    The moment the operator clicks a different instance / endpoint /
    service / scope, fetchKey changes, dataIsFresh flips false, the
    template's "Reading data…" gate fires, and `resultsById` returns
    empty so individual widgets don't briefly paint prior values
    under the new selection. When the new query response lands, the
    watch on `data` stamps the matching fetchKey and the grid takes
    over.
    
    This makes the visible click-then-load story honour CLAUDE.md's
    cascade-clear-then-load principle even under vue-query cache hits
    + background fetches.
---
 .../render/layer-dashboard/LayerDashboardsView.vue | 28 +++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue 
b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
index 246ba7f..60c8918 100644
--- a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
+++ b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
@@ -272,8 +272,34 @@ useLayerPageOrchestrator({
 });
 
 const widgets = computed(() => config.value?.widgets ?? []);
+
+// Cascade-clear-then-load: track whether the dashboard data on hand
+// actually matches the current selection. The moment an upstream
+// pick (service / instance / endpoint / scope / range) changes, we
+// flip to "stale" and the template's "Reading data…" gate fires
+// even if vue-query is still surfacing the previous key's data
+// during the fetch (cache hit OR background fetch). When the new
+// query resolves, the data ref updates and we mark fresh again.
+// This is what makes the page visibly reset on click rather than
+// looking frozen on the prior widgets.
+const fetchKey = computed(
+  () =>
+    `${layerKey.value}|${scope.value}|${serviceName.value ?? 
''}|${selectedInstance.value ?? ''}|${selectedEndpoint.value ?? 
''}|${timeRange.range.startMs}|${timeRange.range.endMs}|${timeRange.step}`,
+);
+const lastFreshKey = ref<string | null>(null);
+watch(data, (d) => {
+  if (d !== null && d !== undefined) {
+    lastFreshKey.value = fetchKey.value;
+  }
+});
+const dataIsFresh = computed(() => lastFreshKey.value === fetchKey.value);
+
 const resultsById = computed(() => {
   const out = new Map<string, NonNullable<typeof 
data.value>['widgets'][number]>();
+  // Only surface widget results from a response that matches the
+  // current selection — otherwise we'd briefly render prior-key
+  // values under the new picker state.
+  if (!dataIsFresh.value) return out;
   for (const r of data.value?.widgets ?? []) out.set(r.id, r);
   return out;
 });
@@ -554,7 +580,7 @@ function isVisible(
          cards. Once `data` arrives, the grid takes over and shows
          each widget's value / no-data / error normally. Background
          refetches keep showing the prior data, no flash. -->
-    <div v-else-if="data === null && reachable" class="empty reading">
+    <div v-else-if="!dataIsFresh && reachable" class="empty reading">
       <span class="reading-dot" /> Reading data…
     </div>
     <div v-else class="grid">

Reply via email to