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">