diff --git a/webclient-vue/src/components/feedback/AppErrorState.vue b/webclient-vue/src/components/feedback/AppErrorState.vue
index 06feb9c..c268ce6 100644
--- a/webclient-vue/src/components/feedback/AppErrorState.vue
+++ b/webclient-vue/src/components/feedback/AppErrorState.vue
@@ -1,15 +1,31 @@
{{ title }}
- {{ message }}
- Retry
+ {{ displayMessage }}
+
+
+ {{ error.type }}
+ HTTP {{ error.statusCode }}
+ {{ error.errorAlias }}
+
+
+
+
+ Retry
+
+
+
diff --git a/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js b/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js
index ee3963f..92144b2 100644
--- a/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js
+++ b/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js
@@ -53,4 +53,76 @@
expect(retry).toHaveBeenCalled();
});
+
+ it("renders error type, statusCode and errorAlias badges when error prop provided", () => {
+ const wrapper = mount(AppErrorState, {
+ props: {
+ title: "Failed",
+ error: {
+ type: "api_error",
+ message: "Invalid input",
+ statusCode: 422,
+ errorAlias: "VALIDATION_FAILED",
+ raw: { status: false },
+ },
+ },
+ });
+
+ expect(wrapper.text()).toContain("api_error");
+ expect(wrapper.text()).toContain("HTTP 422");
+ expect(wrapper.text()).toContain("VALIDATION_FAILED");
+ });
+
+ it("shows copy button when error prop is provided", () => {
+ const wrapper = mount(AppErrorState, {
+ props: {
+ title: "Failed",
+ error: {
+ type: "network_error",
+ message: "Connection refused",
+ },
+ },
+ });
+
+ const copyBtn = wrapper.find('button[aria-label="Copy error details"]');
+ expect(copyBtn.exists()).toBe(true);
+ });
+
+ it("hides copy button when no error prop is provided", () => {
+ const wrapper = mount(AppErrorState, {
+ props: {
+ title: "Failed",
+ retry: null,
+ },
+ });
+
+ const copyBtn = wrapper.find('button[aria-label="Copy error details"]');
+ expect(copyBtn.exists()).toBe(false);
+ });
+
+ it("formats error details as JSON for copying", async () => {
+ Object.assign(navigator, {
+ clipboard: { writeText: vi.fn().mockResolvedValue(undefined) },
+ });
+
+ const error = {
+ type: "api_error",
+ message: "Invalid input",
+ statusCode: 422,
+ raw: { status: false },
+ };
+
+ const wrapper = mount(AppErrorState, {
+ props: { title: "Failed", error },
+ });
+
+ const copyBtn = wrapper.find('button[aria-label="Copy error details"]');
+ expect(copyBtn.exists()).toBe(true);
+
+ await copyBtn.trigger("click");
+
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
+ JSON.stringify(error, null, 2)
+ );
+ });
});
diff --git a/webclient-vue/src/features/areas/pages/AreaDetailPage.vue b/webclient-vue/src/features/areas/pages/AreaDetailPage.vue
index ba92f82..3d1cac2 100644
--- a/webclient-vue/src/features/areas/pages/AreaDetailPage.vue
+++ b/webclient-vue/src/features/areas/pages/AreaDetailPage.vue
@@ -5,14 +5,14 @@
diff --git a/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue b/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue
index 5bbfaba..4fdf185 100644
--- a/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue
+++ b/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue
@@ -11,7 +11,7 @@
diff --git a/webclient-vue/src/features/areas/pages/AreaTreePage.vue b/webclient-vue/src/features/areas/pages/AreaTreePage.vue
index d112571..d3a5db4 100644
--- a/webclient-vue/src/features/areas/pages/AreaTreePage.vue
+++ b/webclient-vue/src/features/areas/pages/AreaTreePage.vue
@@ -11,7 +11,7 @@
diff --git a/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue b/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue
index e6e935e..59b89d8 100644
--- a/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue
+++ b/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue
@@ -5,7 +5,7 @@
diff --git a/webclient-vue/src/features/devices/pages/DevicesListPage.vue b/webclient-vue/src/features/devices/pages/DevicesListPage.vue
index 99576d4..278f69b 100644
--- a/webclient-vue/src/features/devices/pages/DevicesListPage.vue
+++ b/webclient-vue/src/features/devices/pages/DevicesListPage.vue
@@ -17,7 +17,7 @@
@@ -38,7 +38,7 @@
diff --git a/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue b/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue
index 1610070..03f16eb 100644
--- a/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue
+++ b/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue
@@ -37,7 +37,7 @@
diff --git a/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue b/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue
index 8105059..e8d178d 100644
--- a/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue
+++ b/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue
@@ -5,7 +5,7 @@
@@ -104,7 +104,7 @@
diff --git a/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue
index b8e3043..6df139e 100644
--- a/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue
+++ b/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue
@@ -11,7 +11,7 @@
diff --git a/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue
index c2a0004..a667231 100644
--- a/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue
+++ b/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue
@@ -11,7 +11,7 @@
diff --git a/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue
index f950996..59276bf 100644
--- a/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue
+++ b/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue
@@ -11,7 +11,7 @@