@stacksjs/defaults 0.75.263 → 0.75.267

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.
Files changed (23) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +3 -0
  2. package/ai/skills/stacks-browse/scripts/browse.ts +73 -14
  3. package/ai/skills/stacks-chat/SKILL.md +32 -0
  4. package/ai/skills/stacks-native/CATALOG.md +8 -8
  5. package/ide/vscode/package.json +1 -1
  6. package/package.json +4 -4
  7. package/resources/components/Dashboard/App/RequestsOverview.stx +75 -58
  8. package/resources/components/Dashboard/App/SourceInventory.stx +68 -59
  9. package/resources/components/Dashboard/Auth/AccessTokens.stx +57 -37
  10. package/resources/components/Dashboard/Billing/BillingTransactionHistory.stx +27 -33
  11. package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +85 -49
  12. package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +63 -66
  13. package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +67 -23
  14. package/resources/components/Dashboard/Content/BlogDashboard.stx +73 -67
  15. package/resources/components/Dashboard/Content/FileManagerDashboard.stx +39 -12
  16. package/resources/components/Dashboard/Data/DataRecords.stx +85 -88
  17. package/resources/components/Dashboard/Deployments/DeploymentTable.stx +49 -64
  18. package/resources/components/Dashboard/Infrastructure/DnsDashboard.stx +46 -28
  19. package/resources/components/Dashboard/Operations/RecoveryPointTable.stx +72 -61
  20. package/resources/components/Dashboard/Operations/SchedulerTaskTable.stx +74 -70
  21. package/resources/components/Dashboard/Queue/QueueDashboard.stx +27 -61
  22. package/resources/components/Dashboard/Releases/ReleaseDashboard.stx +35 -61
  23. package/resources/components/Dashboard/Teams/TeamPeopleDashboard.stx +127 -143
@@ -1,3 +1,5 @@
1
+ @import('../UI/Table')
2
+
1
3
  <script client>
2
4
  import type { SchedulerTask } from '../../../../functions/operations'
3
5
 
@@ -6,76 +8,78 @@ const loading = useReactiveProp('loading', false)
6
8
  const busyTask = useReactiveProp('busyTask', '')
7
9
  const emit = defineEmits()
8
10
 
9
- function rows(): SchedulerTask[] {
10
- return tasks() as SchedulerTask[]
11
- }
11
+ const COLUMNS = [
12
+ { key: 'name', label: 'Task', sortable: true, subtitleKey: 'timezone', class: 'font-medium text-gray-900 dark:text-white' },
13
+ { key: 'pattern', label: 'Schedule', emptyText: 'Not configured', class: 'font-mono whitespace-nowrap' },
14
+ { key: 'nextRun', label: 'Next run', type: 'datetime' as const, sortable: true, emptyText: 'Not scheduled', class: 'whitespace-nowrap' },
15
+ {
16
+ key: 'state',
17
+ label: 'Status',
18
+ type: 'badge' as const,
19
+ sortable: true,
20
+ badgeClasses: {
21
+ active: 'text-emerald-700 dark:text-emerald-300 bg-emerald-50 dark:bg-emerald-950/40',
22
+ paused: 'text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-950/40',
23
+ },
24
+ badgeLabels: { active: 'Active', paused: 'Paused' },
25
+ },
26
+ ]
12
27
 
13
- function formatDate(value: string | null): string {
14
- if (!value)
15
- return 'Not scheduled'
16
- const date = new Date(value)
17
- return Number.isNaN(date.getTime())
18
- ? value
19
- : new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(date)
20
- }
28
+ const rows = derived(() => (tasks() as SchedulerTask[]).map(task => ({
29
+ ...task,
30
+ state: task.enabled ? 'active' : 'paused',
31
+ })))
21
32
  </script>
22
33
 
23
- <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
24
- <div :if="loading()" class="px-5 py-14 text-center text-gray-500 text-sm dark:text-neutral-400">
25
- Loading scheduled tasks
26
- </div>
27
- <div :if="!loading() && rows().length === 0" class="px-5 py-14 text-center">
28
- <span aria-hidden="true" class="block mb-3 mx-auto h-9 w-9 text-gray-400 i-hugeicons-calendar-03"></span>
29
- <p class="font-semibold text-gray-900 dark:text-white">No scheduled tasks registered</p>
30
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Add a rate to an app job or register a task in app/Scheduler.ts.</p>
31
- </div>
32
- <div :if="rows().length > 0" class="overflow-x-auto">
33
- <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
34
- <thead class="bg-gray-50 dark:bg-neutral-700/50">
35
- <tr>
36
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Task</th>
37
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Schedule</th>
38
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Next run</th>
39
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Status</th>
40
- <th class="px-4 py-3"><span class="sr-only">Actions</span></th>
41
- </tr>
42
- </thead>
43
- <tbody class="divide-gray-200 divide-y dark:divide-neutral-700">
44
- <template :for="task in rows()">
45
- <tr>
46
- <td class="px-4 py-3">
47
- <p class="font-medium text-gray-900 text-sm dark:text-white">{{ task.name }}</p>
48
- <p class="mt-0.5 text-gray-500 text-xs dark:text-neutral-400">{{ task.timezone }}</p>
49
- </td>
50
- <td class="px-4 py-3 font-mono text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ task.pattern || 'Not configured' }}</td>
51
- <td class="px-4 py-3 text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ formatDate(task.nextRun) }}</td>
52
- <td class="px-4 py-3">
53
- <Badge :variant="task.enabled ? 'success' : 'warning'" dot>{{ task.enabled ? 'Active' : 'Paused' }}</Badge>
54
- </td>
55
- <td class="px-4 py-3 text-right whitespace-nowrap">
56
- <div class="inline-flex gap-2 items-center">
57
- <Button
58
- size="sm"
59
- :disabled="busyTask() !== '' || !task.enabled"
60
- :loading="busyTask() === task.name + ':run'"
61
- @click="emit('run', task.name)"
62
- >
63
- Run now
64
- </Button>
65
- <Button
66
- size="sm"
67
- variant="outline"
68
- :disabled="busyTask() !== ''"
69
- :loading="busyTask() === task.name + ':toggle'"
70
- @click="emit('toggle', { name: task.name, enabled: !task.enabled })"
71
- >
72
- {{ task.enabled ? 'Pause' : 'Resume' }}
73
- </Button>
74
- </div>
75
- </td>
76
- </tr>
77
- </template>
78
- </tbody>
79
- </table>
80
- </div>
81
- </div>
34
+ {{--
35
+ The actions are a slot rather than declared row actions: they are text
36
+ buttons whose disabled and loading state depends on which request is in
37
+ flight, not icons.
38
+ --}}
39
+ <Table
40
+ :columns="COLUMNS"
41
+ :data="rows()"
42
+ :loading="loading()"
43
+ row-key="name"
44
+ paginated
45
+ :page-size="25"
46
+ empty-title="No scheduled tasks registered"
47
+ empty-description="Add a rate to an app job or register a task in app/Scheduler.ts."
48
+ >
49
+ <template #actions="{ row }">
50
+ <div class="inline-flex gap-2 items-center justify-end w-full whitespace-nowrap">
51
+ <Button
52
+ size="sm"
53
+ :disabled="busyTask() !== '' || !row.enabled"
54
+ :loading="busyTask() === row.name + ':run'"
55
+ @click="emit('run', row.name)"
56
+ >
57
+ Run now
58
+ </Button>
59
+ {{-- Two buttons rather than one with a computed label: the label is
60
+ the Button's slot content, which a binding on Button cannot set. --}}
61
+ <template :if="row.enabled">
62
+ <Button
63
+ size="sm"
64
+ variant="outline"
65
+ :disabled="busyTask() !== ''"
66
+ :loading="busyTask() === row.name + ':toggle'"
67
+ @click="emit('toggle', { name: row.name, enabled: false })"
68
+ >
69
+ Pause
70
+ </Button>
71
+ </template>
72
+ <template :else>
73
+ <Button
74
+ size="sm"
75
+ variant="outline"
76
+ :disabled="busyTask() !== ''"
77
+ :loading="busyTask() === row.name + ':toggle'"
78
+ @click="emit('toggle', { name: row.name, enabled: true })"
79
+ >
80
+ Resume
81
+ </Button>
82
+ </template>
83
+ </div>
84
+ </template>
85
+ </Table>
@@ -1,3 +1,5 @@
1
+ @import('../UI/Table')
2
+
1
3
  <script client>
2
4
  import type { QueueAggregateStats, QueueBucket, QueueWorker } from '../../../../functions/queue'
3
5
  import { fetchQueueStats, fetchQueueWorkers, retryQueueFailures } from '../../../../functions/queue'
@@ -29,14 +31,27 @@ function workerRows(): QueueWorker[] {
29
31
  return workers()
30
32
  }
31
33
 
32
- function formatDate(value: string): string {
33
- if (!value)
34
- return 'Not recorded'
35
- const date = new Date(value)
36
- return Number.isNaN(date.getTime())
37
- ? value
38
- : new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(date)
39
- }
34
+ const count = (key: string, label: string) => ({ key, label, type: 'number' as const, align: 'right' as const, sortable: true, searchable: false, class: 'text-gray-500 dark:text-neutral-300' })
35
+
36
+ const QUEUE_COLUMNS = [
37
+ { key: 'name', label: 'Queue', sortable: true, subtitleKey: 'status', class: 'font-medium text-gray-900 dark:text-white' },
38
+ count('pending', 'Pending'),
39
+ count('active', 'Active'),
40
+ count('completed', 'Completed'),
41
+ count('failed', 'Failed'),
42
+ { ...count('total', 'Total'), class: 'font-medium text-gray-900 dark:text-white' },
43
+ ]
44
+
45
+ const WORKER_COLUMNS = [
46
+ { key: 'name', label: 'Worker', sortable: true, class: 'font-medium text-gray-900 dark:text-white' },
47
+ { key: 'queuesLabel', label: 'Queues', sortable: true, class: 'text-gray-500 dark:text-neutral-300' },
48
+ { key: 'status', label: 'Status', sortable: true, class: 'text-gray-500 dark:text-neutral-300' },
49
+ count('jobs_processed', 'Processed'),
50
+ count('failed_jobs', 'Failed'),
51
+ { key: 'last_heartbeat', label: 'Last heartbeat', type: 'datetime' as const, align: 'right' as const, sortable: true, emptyText: 'Not recorded', class: 'text-gray-500 dark:text-neutral-300' },
52
+ ]
53
+
54
+ const workerTableRows = derived(() => workerRows().map(worker => ({ ...worker, queuesLabel: worker.queues.join(', ') })))
40
55
 
41
56
  async function loadQueue(): Promise<void> {
42
57
  isLoading.set(true)
@@ -129,34 +144,8 @@ onMount(() => {
129
144
  <p class="font-semibold text-gray-900 dark:text-white">No queues have recorded jobs</p>
130
145
  <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Dispatch a job to populate queue metrics.</p>
131
146
  </div>
132
- <div :if="queueRows().length > 0" class="overflow-x-auto">
133
- <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
134
- <thead class="bg-gray-50 dark:bg-neutral-700/50">
135
- <tr>
136
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Queue</th>
137
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Pending</th>
138
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Active</th>
139
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Completed</th>
140
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Failed</th>
141
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Total</th>
142
- </tr>
143
- </thead>
144
- <tbody class="divide-gray-200 divide-y dark:divide-neutral-700">
145
- <template :for="queue in queueRows()">
146
- <tr>
147
- <td class="px-4 py-3">
148
- <p class="font-medium text-gray-900 text-sm dark:text-white">{{ queue.name }}</p>
149
- <p class="text-gray-500 text-xs dark:text-neutral-400">{{ queue.status }}</p>
150
- </td>
151
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ queue.pending }}</td>
152
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ queue.active }}</td>
153
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ queue.completed }}</td>
154
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ queue.failed }}</td>
155
- <td class="px-4 py-3 font-medium text-gray-900 text-right text-sm dark:text-white">{{ queue.total }}</td>
156
- </tr>
157
- </template>
158
- </tbody>
159
- </table>
147
+ <div :if="queueRows().length > 0">
148
+ <Table :columns="QUEUE_COLUMNS" :data="queueRows()" row-key="name" paginated :page-size="25" />
160
149
  </div>
161
150
  </section>
162
151
 
@@ -175,31 +164,8 @@ onMount(() => {
175
164
  <p class="font-semibold text-gray-900 dark:text-white">No workers tracked in this process</p>
176
165
  <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Start a queue worker to populate heartbeat and processing metrics.</p>
177
166
  </div>
178
- <div :if="workerRows().length > 0" class="overflow-x-auto">
179
- <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
180
- <thead class="bg-gray-50 dark:bg-neutral-700/50">
181
- <tr>
182
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Worker</th>
183
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Queues</th>
184
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Status</th>
185
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Processed</th>
186
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Failed</th>
187
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Last heartbeat</th>
188
- </tr>
189
- </thead>
190
- <tbody class="divide-gray-200 divide-y dark:divide-neutral-700">
191
- <template :for="worker in workerRows()">
192
- <tr>
193
- <td class="px-4 py-3 font-medium text-gray-900 text-sm dark:text-white">{{ worker.name }}</td>
194
- <td class="px-4 py-3 text-gray-500 text-sm dark:text-neutral-300">{{ worker.queues.join(', ') }}</td>
195
- <td class="px-4 py-3 text-gray-500 text-sm dark:text-neutral-300">{{ worker.status }}</td>
196
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatNumber(worker.jobs_processed) }}</td>
197
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatNumber(worker.failed_jobs) }}</td>
198
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatDate(worker.last_heartbeat) }}</td>
199
- </tr>
200
- </template>
201
- </tbody>
202
- </table>
167
+ <div :if="workerRows().length > 0">
168
+ <Table :columns="WORKER_COLUMNS" :data="workerTableRows()" row-key="name" paginated :page-size="25" />
203
169
  </div>
204
170
  </section>
205
171
  </div>
@@ -1,3 +1,5 @@
1
+ @import('../UI/Table')
2
+
1
3
  <script client>
2
4
  import type { ReleaseRecord, ReleaseSummary } from '../../../../app/Actions/Dashboard/Releases/release-records'
3
5
  import { dashboardApi } from '../../../../functions/dashboard-api'
@@ -41,27 +43,30 @@ function formatNumber(value: number): string {
41
43
  return new Intl.NumberFormat().format(value)
42
44
  }
43
45
 
44
- function formatDate(value: string): string {
45
- if (!value)
46
- return 'Not recorded'
47
- const parsed = new Date(value)
48
- if (Number.isNaN(parsed.getTime()))
49
- return value
50
- return new Intl.DateTimeFormat(undefined, {
51
- dateStyle: 'medium',
52
- timeStyle: 'short',
53
- }).format(parsed)
54
- }
46
+ const COLUMNS = [
47
+ { key: 'version', label: 'Version', sortable: true, subtitleKey: 'notes', class: 'font-semibold text-gray-900 dark:text-white' },
48
+ { key: 'type', label: 'Type', sortable: true, class: 'capitalize' },
49
+ {
50
+ key: 'statusKey',
51
+ label: 'Status',
52
+ type: 'badge' as const,
53
+ sortable: true,
54
+ badgeLabelKey: 'status',
55
+ badgeClasses: {
56
+ published: 'capitalize text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/30',
57
+ scheduled: 'capitalize text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/30',
58
+ draft: 'capitalize text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-950/30',
59
+ },
60
+ },
61
+ { key: 'downloads', label: 'Downloads', type: 'number' as const, sortable: true, align: 'right' as const, class: 'font-mono' },
62
+ { key: 'author', label: 'Author', sortable: true, emptyText: 'Not recorded' },
63
+ { key: 'createdAt', label: 'Created', type: 'datetime' as const, sortable: true, emptyText: 'Not recorded', class: 'whitespace-nowrap' },
64
+ ]
55
65
 
56
- function statusClass(value: string): string {
57
- if (value === 'published')
58
- return 'text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/30'
59
- if (value === 'scheduled')
60
- return 'text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/30'
61
- if (value === 'draft')
62
- return 'text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-950/30'
63
- return 'text-gray-700 dark:text-neutral-300 bg-gray-100 dark:bg-neutral-700'
64
- }
66
+ const releaseRows = derived(() => rows().map(release => ({
67
+ ...release,
68
+ statusKey: release.status.toLowerCase(),
69
+ })))
65
70
 
66
71
  async function loadReleases(): Promise<void> {
67
72
  isLoading.set(true)
@@ -141,47 +146,16 @@ onMount(() => {
141
146
  </div>
142
147
  </div>
143
148
 
144
- <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
145
- <div :if="isLoading()" class="flex items-center justify-center p-8 text-gray-500 text-sm dark:text-neutral-400">
146
- <span class="mr-2 h-4 w-4 animate-spin i-hugeicons-loading-03"></span>
147
- Loading releases
148
- </div>
149
- <div :else-if="rows().length === 0" class="p-8 text-center">
150
- <div class="mx-auto h-8 w-8 text-gray-400 i-hugeicons-right-to-left-list-number"></div>
151
- <p class="mt-3 font-medium text-gray-900 text-sm dark:text-white">No release records found</p>
152
- <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Create releases through your application workflow and they will appear here.</p>
153
- </div>
154
- <div :else class="overflow-x-auto">
155
- <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
156
- <thead class="bg-gray-50 dark:bg-neutral-900/50">
157
- <tr>
158
- <th class="px-4 py-3 font-semibold text-gray-600 text-left text-xs uppercase dark:text-neutral-400">Version</th>
159
- <th class="px-4 py-3 font-semibold text-gray-600 text-left text-xs uppercase dark:text-neutral-400">Type</th>
160
- <th class="px-4 py-3 font-semibold text-gray-600 text-left text-xs uppercase dark:text-neutral-400">Status</th>
161
- <th class="px-4 py-3 font-semibold text-gray-600 text-right text-xs uppercase dark:text-neutral-400">Downloads</th>
162
- <th class="px-4 py-3 font-semibold text-gray-600 text-left text-xs uppercase dark:text-neutral-400">Author</th>
163
- <th class="px-4 py-3 font-semibold text-gray-600 text-left text-xs uppercase dark:text-neutral-400">Created</th>
164
- </tr>
165
- </thead>
166
- <tbody class="divide-gray-100 divide-y dark:divide-neutral-700">
167
- <template :for="release in rows()" :key="release.id">
168
- <tr>
169
- <td class="px-4 py-3">
170
- <p class="font-semibold text-gray-900 text-sm dark:text-white">{{ release.version }}</p>
171
- <p :if="release.notes" class="mt-1 max-w-md text-gray-500 text-xs truncate dark:text-neutral-400">{{ release.notes }}</p>
172
- </td>
173
- <td class="px-4 py-3 capitalize text-gray-600 text-sm dark:text-neutral-300">{{ release.type }}</td>
174
- <td class="px-4 py-3">
175
- <span :class="'inline-flex px-2 py-1 font-medium text-xs capitalize rounded-full ' + statusClass(release.status)">{{ release.status }}</span>
176
- </td>
177
- <td class="px-4 py-3 font-mono text-gray-700 text-right text-sm dark:text-neutral-200">{{ formatNumber(release.downloads) }}</td>
178
- <td class="px-4 py-3 text-gray-600 text-sm dark:text-neutral-300">{{ release.author || 'Not recorded' }}</td>
179
- <td class="px-4 py-3 text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ formatDate(release.createdAt) }}</td>
180
- </tr>
181
- </template>
182
- </tbody>
183
- </table>
184
- </div>
149
+ <div class="bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
150
+ <Table
151
+ :columns="COLUMNS"
152
+ :data="releaseRows()"
153
+ :loading="isLoading()"
154
+ paginated
155
+ :page-size="25"
156
+ empty-title="No release records found"
157
+ empty-description="Create releases through your application workflow and they will appear here."
158
+ />
185
159
  </div>
186
160
  </section>
187
161
  </div>