arkaos 2.79.0 → 2.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.79.0
1
+ 2.80.0
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ // PR64 v2.80.0 — shared loading / error / empty wrapper.
3
+ //
4
+ // Every page in dashboard/app/pages/ used to duplicate the same triple:
5
+ //
6
+ // <div v-if="status === 'pending'"><spinner/></div>
7
+ // <div v-else-if="error"><alert + Retry/></div>
8
+ // <template v-else>... content ...</template>
9
+ //
10
+ // Five copies of that pattern with subtle drift (different icon sizes,
11
+ // different empty-state shapes, inconsistent ARIA roles, some with
12
+ // retry buttons missing). PR64 extracts it into one component so the
13
+ // rest of the dashboard work (PR63 Settings, PR65 Budget, PR66 Index
14
+ // rebuild, etc.) inherits a consistent shell.
15
+ //
16
+ // Slots:
17
+ // default — the success/content block (only rendered on 'success')
18
+ // empty — optional override for the empty state (defaults to
19
+ // generic "no data" with the empty-* props below)
20
+ // loading — optional override for the spinner (rarely needed)
21
+ // error — optional override for the error state (rarely needed)
22
+ //
23
+ // The component never owns the data — pages still call useFetch /
24
+ // fetchApi and pass `status` + `error` + an `empty` boolean in.
25
+
26
+ import type { AsyncDataRequestStatus } from 'nuxt/app'
27
+
28
+ interface Props {
29
+ /** useFetch/useAsyncData status. 'pending' shows spinner. */
30
+ status: AsyncDataRequestStatus
31
+ /** Error from useFetch — present means render the error block. */
32
+ error?: Error | null
33
+ /** True when the request succeeded but returned no rows.
34
+ * Pages compute this from their data shape (e.g. `!list.length`). */
35
+ empty?: boolean
36
+ /** Heading for the default empty state. */
37
+ emptyTitle?: string
38
+ /** Helper text for the default empty state. */
39
+ emptyDescription?: string
40
+ /** Icon for the default empty state. Defaults to inbox. */
41
+ emptyIcon?: string
42
+ /** Optional retry handler — when provided, the error block shows a button. */
43
+ onRetry?: () => void | Promise<void>
44
+ /** Optional ARIA label for the loading region. */
45
+ loadingLabel?: string
46
+ }
47
+
48
+ const props = withDefaults(defineProps<Props>(), {
49
+ error: null,
50
+ empty: false,
51
+ emptyTitle: 'No data',
52
+ emptyDescription: '',
53
+ emptyIcon: 'i-lucide-inbox',
54
+ loadingLabel: 'Loading',
55
+ })
56
+ </script>
57
+
58
+ <template>
59
+ <!-- Loading -->
60
+ <div
61
+ v-if="status === 'pending'"
62
+ class="flex items-center justify-center py-12"
63
+ :aria-label="loadingLabel"
64
+ role="status"
65
+ >
66
+ <slot name="loading">
67
+ <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
68
+ </slot>
69
+ </div>
70
+
71
+ <!-- Error -->
72
+ <div
73
+ v-else-if="error"
74
+ class="flex flex-col items-center justify-center gap-4 py-12"
75
+ role="alert"
76
+ >
77
+ <slot name="error" :error="error">
78
+ <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
79
+ <p class="text-sm text-muted">
80
+ {{ error.message || 'Failed to load data.' }}
81
+ </p>
82
+ <UButton
83
+ v-if="onRetry"
84
+ label="Retry"
85
+ variant="outline"
86
+ color="primary"
87
+ icon="i-lucide-refresh-cw"
88
+ @click="onRetry"
89
+ />
90
+ </slot>
91
+ </div>
92
+
93
+ <!-- Empty -->
94
+ <div
95
+ v-else-if="empty"
96
+ class="flex flex-col items-center justify-center gap-4 py-16"
97
+ >
98
+ <slot name="empty">
99
+ <UIcon :name="emptyIcon" class="size-16 text-muted" />
100
+ <h3 class="text-lg font-semibold text-highlighted">{{ emptyTitle }}</h3>
101
+ <p
102
+ v-if="emptyDescription"
103
+ class="text-sm text-muted text-center max-w-md"
104
+ >
105
+ {{ emptyDescription }}
106
+ </p>
107
+ </slot>
108
+ </div>
109
+
110
+ <!-- Content -->
111
+ <slot v-else />
112
+ </template>
@@ -127,26 +127,15 @@ function goToAgent(id: string) {
127
127
  </template>
128
128
 
129
129
  <template #body>
130
- <!-- Loading -->
131
- <div v-if="status === 'pending'" class="flex items-center justify-center py-12">
132
- <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
133
- </div>
134
-
135
- <!-- Error -->
136
- <div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
137
- <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
138
- <p class="text-sm text-muted">Failed to load agents.</p>
139
- <UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
140
- </div>
141
-
142
- <!-- Empty -->
143
- <div v-else-if="!agents.length" class="flex flex-col items-center justify-center gap-4 py-12">
144
- <UIcon name="i-lucide-users" class="size-12 text-muted" />
145
- <p class="text-sm text-muted">No agents found.</p>
146
- </div>
147
-
148
- <!-- Content -->
149
- <template v-else>
130
+ <DashboardState
131
+ :status="status"
132
+ :error="error"
133
+ :empty="!agents.length"
134
+ empty-title="No agents found"
135
+ empty-icon="i-lucide-users"
136
+ loading-label="Loading agents"
137
+ :on-retry="() => refresh()"
138
+ >
150
139
  <div class="flex flex-wrap items-center gap-3 mb-4">
151
140
  <UInput
152
141
  v-model="search"
@@ -219,7 +208,7 @@ function goToAgent(id: string) {
219
208
  @update:page="(val) => page = val"
220
209
  />
221
210
  </div>
222
- </template>
211
+ </DashboardState>
223
212
  </template>
224
213
  </UDashboardPanel>
225
214
  </template>
@@ -37,17 +37,13 @@ const tierLabels: Record<number, string> = {
37
37
  </template>
38
38
 
39
39
  <template #body>
40
- <div v-if="status === 'pending'" class="flex items-center justify-center py-12">
41
- <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
42
- </div>
43
-
44
- <div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
45
- <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
46
- <p class="text-sm text-muted">Failed to load budget data.</p>
47
- <UButton label="Retry" variant="outline" icon="i-lucide-refresh-cw" @click="refresh()" />
48
- </div>
49
-
50
- <div v-else class="space-y-6">
40
+ <DashboardState
41
+ :status="status"
42
+ :error="error"
43
+ loading-label="Loading budget"
44
+ :on-retry="() => refresh()"
45
+ >
46
+ <div class="space-y-6">
51
47
  <!-- Monthly Summary -->
52
48
  <UCard>
53
49
  <div class="space-y-3">
@@ -127,6 +123,7 @@ const tierLabels: Record<number, string> = {
127
123
  </div>
128
124
  </div>
129
125
  </div>
126
+ </DashboardState>
130
127
  </template>
131
128
  </UDashboardPanel>
132
129
  </template>
@@ -30,26 +30,15 @@ const allPassed = computed(() => passed.value === total.value && total.value > 0
30
30
  </template>
31
31
 
32
32
  <template #body>
33
- <!-- Loading -->
34
- <div v-if="status === 'pending'" class="flex items-center justify-center py-12">
35
- <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
36
- </div>
37
-
38
- <!-- Error -->
39
- <div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
40
- <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
41
- <p class="text-sm text-muted">Failed to load health checks.</p>
42
- <UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
43
- </div>
44
-
45
- <!-- Empty -->
46
- <div v-else-if="!checks.length" class="flex flex-col items-center justify-center gap-4 py-12">
47
- <UIcon name="i-lucide-heart-pulse" class="size-12 text-muted" />
48
- <p class="text-sm text-muted">No health checks available.</p>
49
- </div>
50
-
51
- <!-- Content -->
52
- <template v-else>
33
+ <DashboardState
34
+ :status="status"
35
+ :error="error"
36
+ :empty="!checks.length"
37
+ empty-title="No health checks available"
38
+ empty-icon="i-lucide-heart-pulse"
39
+ loading-label="Loading health checks"
40
+ :on-retry="() => refresh()"
41
+ >
53
42
  <!-- Overall Status -->
54
43
  <div
55
44
  class="mb-6 rounded-lg border p-6 text-center"
@@ -92,7 +81,7 @@ const allPassed = computed(() => passed.value === total.value && total.value > 0
92
81
  />
93
82
  </div>
94
83
  </div>
95
- </template>
84
+ </DashboardState>
96
85
  </template>
97
86
  </UDashboardPanel>
98
87
  </template>
@@ -39,20 +39,12 @@ function formatCurrency(value: number): string {
39
39
  </template>
40
40
 
41
41
  <template #body>
42
- <!-- Loading State -->
43
- <div v-if="status === 'pending'" class="flex items-center justify-center py-12">
44
- <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
45
- </div>
46
-
47
- <!-- Error State -->
48
- <div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
49
- <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
50
- <p class="text-sm text-muted">Failed to load overview data.</p>
51
- <UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
52
- </div>
53
-
54
- <!-- Content -->
55
- <template v-else>
42
+ <DashboardState
43
+ :status="status"
44
+ :error="error"
45
+ loading-label="Loading overview"
46
+ :on-retry="() => refresh()"
47
+ >
56
48
  <!-- Stats Grid -->
57
49
  <div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
58
50
  <div
@@ -120,7 +112,7 @@ function formatCurrency(value: number): string {
120
112
  </div>
121
113
  </div>
122
114
  </div>
123
- </template>
115
+ </DashboardState>
124
116
  </template>
125
117
  </UDashboardPanel>
126
118
  </template>
@@ -94,20 +94,12 @@ const columns: TableColumn<Task>[] = [
94
94
  </template>
95
95
 
96
96
  <template #body>
97
- <!-- Loading -->
98
- <div v-if="status === 'pending'" class="flex items-center justify-center py-12">
99
- <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
100
- </div>
101
-
102
- <!-- Error -->
103
- <div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
104
- <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
105
- <p class="text-sm text-muted">Failed to load tasks.</p>
106
- <UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
107
- </div>
108
-
109
- <!-- Content -->
110
- <template v-else>
97
+ <DashboardState
98
+ :status="status"
99
+ :error="error"
100
+ loading-label="Loading tasks"
101
+ :on-retry="() => refresh()"
102
+ >
111
103
  <!-- Summary Cards -->
112
104
  <div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
113
105
  <div class="rounded-lg border border-default p-4 text-center">
@@ -197,7 +189,7 @@ const columns: TableColumn<Task>[] = [
197
189
  </template>
198
190
  </UTable>
199
191
  </div>
200
- </template>
192
+ </DashboardState>
201
193
  </template>
202
194
  </UDashboardPanel>
203
195
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.79.0",
3
+ "version": "2.80.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "2.79.0"
3
+ version = "2.80.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}