From 7686d7dc132a6142465a6996c88c06887d9cdb9a Mon Sep 17 00:00:00 2001
From: t8y2 <1156263951@qq.com>
Date: Tue, 25 Aug 2026 10:20:47 +0800
Subject: [PATCH] fix(tabs): replace tab icon during query execution
---
.../src/components/layout/AppTabBar.vue | 96 ++++++++++---------
.../components/layout/TabExecutionStatus.vue | 5 +-
.../layout/__tests__/AppTabBar.spec.ts | 5 +-
.../__tests__/TabExecutionStatus.spec.ts | 20 +++-
4 files changed, 74 insertions(+), 52 deletions(-)
diff --git a/apps/desktop/src/components/layout/AppTabBar.vue b/apps/desktop/src/components/layout/AppTabBar.vue
index 52304057f7..74266cff79 100644
--- a/apps/desktop/src/components/layout/AppTabBar.vue
+++ b/apps/desktop/src/components/layout/AppTabBar.vue
@@ -703,26 +703,27 @@ function onOverflowItemKeydown(event: KeyboardEvent, tabId: string, kind: "regul
@mousemove="handleTabDragTarget($event, tab)"
@mouseleave="tabDrag.clearTarget(tab.id)"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
*
{{ tabTitleText(tab) }}
@@ -911,26 +913,27 @@ function onOverflowItemKeydown(event: KeyboardEvent, tabId: string, kind: "regul
@mousemove="handleTabDragTarget($event, tab)"
@mouseleave="tabDrag.clearTarget(tab.id)"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
*
{{ tabTitleText(tab) }}
diff --git a/apps/desktop/src/components/layout/TabExecutionStatus.vue b/apps/desktop/src/components/layout/TabExecutionStatus.vue
index 9fe5d3a081..93c4be3702 100644
--- a/apps/desktop/src/components/layout/TabExecutionStatus.vue
+++ b/apps/desktop/src/components/layout/TabExecutionStatus.vue
@@ -21,11 +21,12 @@ const status = computed(() => {
v-if="status"
data-tab-execution-status
role="status"
- class="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-background/80"
+ class="inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center"
:class="status === 'cancelling' ? 'text-amber-600 dark:text-amber-400' : 'text-blue-600 dark:text-blue-400'"
:aria-label="t(queryExecutionLabelKey(tab))"
:title="t(queryExecutionLabelKey(tab))"
>
-
+
+
diff --git a/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts b/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts
index 54aa122b78..6d1468ff22 100644
--- a/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts
+++ b/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts
@@ -121,8 +121,9 @@ describe("AppTabBar overflow search", () => {
});
describe("AppTabBar query execution status", () => {
- it("renders one shared status indicator in regular, pinned, and both overflow surfaces", () => {
+ it("replaces the icon through one shared status component in every tab surface", () => {
expect(tabBarSource).toContain('import TabExecutionStatus from "@/components/layout/TabExecutionStatus.vue";');
- expect(tabBarSource.match(//g)).toHaveLength(4);
+ expect(tabBarSource.match(//g)).toHaveLength(4);
+ expect(tabBarSource.match(/<\/TabExecutionStatus>/g)).toHaveLength(4);
});
});
diff --git a/apps/desktop/src/components/layout/__tests__/TabExecutionStatus.spec.ts b/apps/desktop/src/components/layout/__tests__/TabExecutionStatus.spec.ts
index ff995fb3e6..8873cd5c31 100644
--- a/apps/desktop/src/components/layout/__tests__/TabExecutionStatus.spec.ts
+++ b/apps/desktop/src/components/layout/__tests__/TabExecutionStatus.spec.ts
@@ -10,12 +10,12 @@ vi.mock("vue-i18n", () => ({
const mountedApps: ReturnType[] = [];
-function mountStatus(initial: { mode: "query" | "data"; isExecuting: boolean; isCancelling?: boolean }) {
+function mountStatus(initial: { mode: "query" | "data"; isExecuting: boolean; isCancelling?: boolean }, withFallback = false) {
const state = reactive(initial);
const root = document.createElement("div");
document.body.appendChild(root);
const app = createApp({
- setup: () => () => h(TabExecutionStatus, { tab: state }),
+ setup: () => () => h(TabExecutionStatus, { tab: state }, withFallback ? { default: () => h("span", { "data-tab-icon": "" }) } : undefined),
});
mountedApps.push(app);
app.mount(root);
@@ -48,6 +48,22 @@ describe("TabExecutionStatus", () => {
expect(status?.className).toContain("text-amber-600");
});
+ it("replaces the tab icon while the query is executing", async () => {
+ const { root, state } = mountStatus({ mode: "query", isExecuting: false }, true);
+ await nextTick();
+ expect(root.querySelector("[data-tab-icon]")).not.toBeNull();
+
+ state.isExecuting = true;
+ await nextTick();
+ expect(root.querySelector("[data-tab-icon]")).toBeNull();
+ expect(root.querySelector("[data-tab-execution-status]")).not.toBeNull();
+
+ state.isExecuting = false;
+ await nextTick();
+ expect(root.querySelector("[data-tab-execution-status]")).toBeNull();
+ expect(root.querySelector("[data-tab-icon]")).not.toBeNull();
+ });
+
it.each(["success", "error", "cancelled"])("disappears as soon as %s completes", async () => {
const { root, state } = mountStatus({ mode: "query", isExecuting: true, isCancelling: true });
await nextTick();