@cat-factory/app 0.30.1 → 0.30.3

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.
@@ -372,7 +372,7 @@ async function add() {
372
372
  <template>
373
373
  <UModal v-model:open="open" title="Add a task">
374
374
  <template #body>
375
- <div class="space-y-4">
375
+ <div class="space-y-4" data-testid="add-task-modal">
376
376
  <p v-if="container" class="text-xs text-slate-400">
377
377
  New task in <span class="font-medium text-slate-200">{{ container.title }}</span>
378
378
  </p>
@@ -411,6 +411,7 @@ async function add() {
411
411
  <UFormField label="Title" required>
412
412
  <UInput
413
413
  v-model="title"
414
+ data-testid="add-task-title"
414
415
  placeholder="What needs to be done?"
415
416
  autofocus
416
417
  class="w-full"
@@ -746,6 +747,7 @@ async function add() {
746
747
  <UButton color="neutral" variant="ghost" @click="ui.closeAddTask()">Cancel</UButton>
747
748
  <UButton
748
749
  color="primary"
750
+ data-testid="add-task-submit"
749
751
  :icon="isRecurring ? 'i-lucide-arrow-right' : 'i-lucide-plus'"
750
752
  :loading="saving"
751
753
  :disabled="!canAdd"
@@ -368,6 +368,7 @@ const ITEM_ICON: Record<string, string> = {
368
368
  }}</UBadge>
369
369
  <UButton
370
370
  class="nodrag"
371
+ data-testid="frame-add-task"
371
372
  size="xs"
372
373
  variant="ghost"
373
374
  color="neutral"
@@ -425,6 +426,7 @@ const ITEM_ICON: Record<string, string> = {
425
426
  <button
426
427
  v-if="!hasTasks"
427
428
  type="button"
429
+ data-testid="frame-add-task"
428
430
  class="absolute inset-4 flex items-center justify-center gap-1 rounded-lg border border-dashed border-slate-700 text-[11px] text-slate-500 hover:border-slate-500 hover:text-slate-300"
429
431
  @click.stop="addTask"
430
432
  >
@@ -115,7 +115,13 @@ function revealDecision(n: Notification) {
115
115
 
116
116
  <template>
117
117
  <UPopover v-if="notifications.count" :content="{ align: 'end' }">
118
- <UButton :color="hasUrgent ? 'error' : 'warning'" variant="soft" size="sm" icon="i-lucide-bell">
118
+ <UButton
119
+ data-testid="notifications-bell"
120
+ :color="hasUrgent ? 'error' : 'warning'"
121
+ variant="soft"
122
+ size="sm"
123
+ icon="i-lucide-bell"
124
+ >
119
125
  {{ notifications.count }}
120
126
  </UButton>
121
127
 
@@ -127,6 +133,8 @@ function revealDecision(n: Notification) {
127
133
  <div
128
134
  v-for="n in notifications.open"
129
135
  :key="n.id"
136
+ data-testid="notification-item"
137
+ :data-notification-type="n.type"
130
138
  class="rounded-lg border p-2.5 mt-1.5"
131
139
  :class="
132
140
  isUrgent(n)
@@ -167,6 +175,7 @@ function revealDecision(n: Notification) {
167
175
  </a>
168
176
  <div class="mt-2 flex items-center gap-1.5">
169
177
  <UButton
178
+ data-testid="notification-act"
170
179
  :color="accent(n)"
171
180
  variant="soft"
172
181
  size="xs"
@@ -176,6 +185,7 @@ function revealDecision(n: Notification) {
176
185
  {{ META[n.type].action }}
177
186
  </UButton>
178
187
  <UButton
188
+ data-testid="notification-dismiss"
179
189
  color="neutral"
180
190
  variant="ghost"
181
191
  size="xs"
@@ -175,6 +175,7 @@ async function copyOutput() {
175
175
  <Transition name="reader-fade">
176
176
  <div
177
177
  v-if="open && step && agent"
178
+ data-testid="step-detail"
178
179
  class="fixed inset-0 z-50 flex bg-slate-950/96 backdrop-blur-sm"
179
180
  role="dialog"
180
181
  aria-modal="true"
@@ -552,6 +553,7 @@ async function copyOutput() {
552
553
  <div v-else class="space-y-2 border-t border-slate-800 px-4 py-3">
553
554
  <UButton
554
555
  color="primary"
556
+ data-testid="step-approve"
555
557
  size="sm"
556
558
  icon="i-lucide-check"
557
559
  block
@@ -8,7 +8,7 @@
8
8
  // The latter three are body-only section components rendered in tabs here (no longer
9
9
  // standalone modals).
10
10
  import { reactive, ref, watch } from 'vue'
11
- import type { CreateTaskType, TaskLimitMode, WorkspaceSettings } from '~/types/domain'
11
+ import type { CreateTaskType, TaskLimitMode } from '~/types/domain'
12
12
  import MergeThresholdsPanel from '~/components/settings/MergeThresholdsPanel.vue'
13
13
  import IssueTrackerPanel from '~/components/settings/IssueTrackerPanel.vue'
14
14
  import ServiceFragmentDefaultsPanel from '~/components/settings/ServiceFragmentDefaultsPanel.vue'
@@ -71,7 +71,6 @@ const draft = reactive({
71
71
  // Budget: empty string ⇒ "use the built-in default" (null on the wire).
72
72
  spendCurrency: '',
73
73
  spendMonthlyLimit: '',
74
- spendModelPrices: '',
75
74
  })
76
75
 
77
76
  function hydrate() {
@@ -84,7 +83,6 @@ function hydrate() {
84
83
  draft.storeAgentContext = s.storeAgentContext
85
84
  draft.spendCurrency = s.spendCurrency ?? ''
86
85
  draft.spendMonthlyLimit = s.spendMonthlyLimit == null ? '' : String(s.spendMonthlyLimit)
87
- draft.spendModelPrices = s.spendModelPrices ? JSON.stringify(s.spendModelPrices, null, 2) : ''
88
86
  }
89
87
 
90
88
  watch(() => store.settings, hydrate, { immediate: true, deep: true })
@@ -126,28 +124,15 @@ async function save() {
126
124
  const savingBudget = ref(false)
127
125
 
128
126
  async function saveBudget() {
129
- // Parse the optional per-model price overrides JSON (blank ⇒ no overrides).
130
- let prices: WorkspaceSettings['spendModelPrices'] = null
131
- const raw = draft.spendModelPrices.trim()
132
- if (raw) {
133
- try {
134
- prices = JSON.parse(raw)
135
- } catch {
136
- toast.add({
137
- title: 'Per-model prices must be valid JSON',
138
- icon: 'i-lucide-triangle-alert',
139
- color: 'error',
140
- })
141
- return
142
- }
143
- }
144
127
  savingBudget.value = true
128
+ // The number input emits a raw number once edited but starts as a string from hydrate, so
129
+ // coerce through String() before trimming. Blank ⇒ "use the built-in default" (null on the wire).
130
+ const raw = String(draft.spendMonthlyLimit ?? '').trim()
131
+ const monthlyLimit = raw === '' ? null : Number(raw)
145
132
  try {
146
133
  await store.update({
147
134
  spendCurrency: draft.spendCurrency.trim() ? draft.spendCurrency.trim().toUpperCase() : null,
148
- spendMonthlyLimit:
149
- draft.spendMonthlyLimit.trim() === '' ? null : Number(draft.spendMonthlyLimit),
150
- spendModelPrices: prices,
135
+ spendMonthlyLimit: monthlyLimit,
151
136
  })
152
137
  toast.add({ title: 'Budget saved', icon: 'i-lucide-check', color: 'success' })
153
138
  } catch (e) {
@@ -304,22 +289,6 @@ async function saveBudget() {
304
289
  </div>
305
290
  </section>
306
291
 
307
- <section class="space-y-2">
308
- <h3 class="text-sm font-semibold text-slate-200">Per-model price overrides</h3>
309
- <p class="text-[11px] text-slate-400">
310
- Optional. JSON object of <code>"provider:model"</code> (or a bare
311
- <code>"provider"</code>) → <code>{ inputPerMillion, outputPerMillion }</code>,
312
- overlaid on the built-in price table. Leave blank to use the defaults.
313
- </p>
314
- <UTextarea
315
- v-model="draft.spendModelPrices"
316
- :rows="6"
317
- size="sm"
318
- class="w-full font-mono text-[11px]"
319
- placeholder='{"openai:gpt-4o":{"inputPerMillion":2.3,"outputPerMillion":9.2}}'
320
- />
321
- </section>
322
-
323
292
  <div class="flex justify-end">
324
293
  <UButton
325
294
  color="primary"
@@ -12,7 +12,6 @@ const DEFAULTS: WorkspaceSettings = {
12
12
  storeAgentContext: true,
13
13
  spendCurrency: null,
14
14
  spendMonthlyLimit: null,
15
- spendModelPrices: null,
16
15
  }
17
16
 
18
17
  /**
@@ -499,8 +499,6 @@ export interface WorkspaceSettings {
499
499
  spendCurrency: string | null
500
500
  /** Monthly spend budget in `spendCurrency`. Null ⇒ the built-in default. */
501
501
  spendMonthlyLimit: number | null
502
- /** Per-model price overrides (`provider:model` → per-1M rates). Null ⇒ none. */
503
- spendModelPrices: Record<string, { inputPerMillion: number; outputPerMillion: number }> | null
504
502
  }
505
503
 
506
504
  /** Patch a workspace's settings (only the supplied fields change). */
@@ -512,7 +510,6 @@ export interface UpdateWorkspaceSettingsInput {
512
510
  storeAgentContext?: boolean
513
511
  spendCurrency?: string | null
514
512
  spendMonthlyLimit?: number | null
515
- spendModelPrices?: Record<string, { inputPerMillion: number; outputPerMillion: number }> | null
516
513
  }
517
514
 
518
515
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.30.1",
3
+ "version": "0.30.3",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",