diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index a971f12ec..4df152bdf 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -7,7 +7,7 @@ import { defaultViewForResult } from "@/lib/query/queryResultDefaultView"; import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError"; import type { CSSProperties } from "vue"; import { useI18n } from "vue-i18n"; -import { Check, CheckSquare2, Columns3Cog, EyeOff, Loader2, Search, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, Toolbox, Database, Download, Upload, X, Pin, Rows3, SquareDashed, Minus, Plus, ShieldAlert, AlignLeft, AlignRight, PanelsTopLeft } from "@lucide/vue"; +import { Check, CheckSquare2, Columns3Cog, Copy, EyeOff, Loader2, Search, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, Toolbox, Database, Download, Upload, X, Pin, Rows3, SquareDashed, Minus, Plus, ShieldAlert, AlignLeft, AlignRight, PanelsTopLeft } from "@lucide/vue"; import { Splitpanes, Pane } from "splitpanes"; import "splitpanes/dist/splitpanes.css"; import { Button } from "@/components/ui/button"; @@ -96,6 +96,7 @@ import type { DataGridSortMode } from "@/lib/dataGrid/dataGridSort"; import { isDataGridToolbarCompact, type DataGridReloadIntent } from "@/lib/dataGrid/dataGridToolbar"; import { useTabScroll } from "@/composables/useTabScroll"; import { formatElapsedSeconds } from "@/lib/common/elapsedTime"; +import { copyToClipboard } from "@/lib/common/clipboard"; import type { CustomSaveHandler } from "@/composables/useDataGridEditor"; import type { QueryTab, ConnectionConfig, TableInfoTab, TreeNode, VectorCollectionMeta, ObjectBrowserViewport } from "@/types/database"; import type { SqlObjectNavigationTarget } from "@/lib/sql/sqlNavigation"; @@ -900,6 +901,15 @@ function focusExecutionSummaryItem(item: ExecutionSummaryItem) { queryEditorRef.value?.focusStatementRange(executionSummaryItemRange(item) ?? null); } +async function copyExecutionSummaryError(error: string) { + try { + await copyToClipboard(error); + toast(t("grid.copied")); + } catch (copyError: any) { + toast(t("grid.copyFailed", { message: copyError?.message || String(copyError) }), 5000); + } +} + function handleModRTarget(target: Element): boolean { if (target.closest("[data-query-editor-root]")) return queryEditorRef.value?.openReplace() ?? false; if (target.closest("[data-cell-detail-editor-root]")) return dataGridRef.value?.openCellDetailSearch() ?? false; @@ -1406,22 +1416,29 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
{{ t("executionSummary.affected") }}
{{ t("executionSummary.time") }}
- + + -
+
{{ item.status === "pending" || item.status === "running" || item.status === "skipped" ? "—" : item.affectedRows.toLocaleString() }}
-
{{ item.executionTimeMs > 0 || item.status === "success" || item.status === "error" ? `${item.executionTimeMs}ms` : "—" }}
- +
{{ item.status === "pending" || item.status === "running" || item.status === "skipped" ? "—" : item.affectedRows.toLocaleString() }}
+
{{ item.executionTimeMs > 0 || item.status === "success" || item.status === "error" ? `${item.executionTimeMs}ms` : "—" }}
+
{{ t("executionSummary.navigationHint") }}
diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorExecution.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorExecution.spec.ts index a973f5d1f..4f6ba1e40 100644 --- a/apps/desktop/src/lib/__tests__/editor/queryEditorExecution.spec.ts +++ b/apps/desktop/src/lib/__tests__/editor/queryEditorExecution.spec.ts @@ -54,3 +54,13 @@ describe("QueryEditor execution routing", () => { expect(queryEditorSource).toContain("if (options.bypassPicker || !settingsStore.editorSettings.showExecutionTargetPicker"); }); }); + +describe("ContentArea execution summary errors", () => { + it("keeps batch errors selectable and copyable without triggering statement navigation", () => { + expect(contentAreaSource).toContain('class="absolute inset-0 z-0 cursor-pointer'); + expect(contentAreaSource).toContain('data-native-clipboard class="min-w-0 flex-1 cursor-text select-text truncate"'); + expect(contentAreaSource).toContain("@mousedown.stop @click.stop @dblclick.stop"); + expect(contentAreaSource).toContain('@click.stop="copyExecutionSummaryError(item.error)"'); + expect(contentAreaSource).toContain("await copyToClipboard(error)"); + }); +});