@cat-factory/app 0.23.1 → 0.24.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.
@@ -42,6 +42,9 @@ const container = computed(() =>
42
42
  const title = ref('')
43
43
  const description = ref('')
44
44
  const saving = ref(false)
45
+ // Whether the user marks this as a purely technical task up front (a refactor /
46
+ // non-functional change). Left off ⇒ the engine infers it from the spec phase.
47
+ const technical = ref(false)
45
48
 
46
49
  // The kind of task being created. `recurring` is special: it is created through the
47
50
  // recurring-pipeline schedule flow (a schedule on the service frame), so picking it
@@ -276,6 +279,7 @@ watch(open, (isOpen) => {
276
279
  description.value = ''
277
280
  saving.value = false
278
281
  taskType.value = 'feature'
282
+ technical.value = false
279
283
  severity.value = ''
280
284
  stepsToReproduce.value = ''
281
285
  timeboxHours.value = undefined
@@ -339,6 +343,7 @@ async function add() {
339
343
  ...(Object.keys(agentConfigValues.value).length
340
344
  ? { agentConfig: agentConfigValues.value }
341
345
  : {}),
346
+ ...(technical.value ? { technical: true } : {}),
342
347
  })
343
348
  if (block) {
344
349
  const failed = await linkPending(block.id, pendingContext.value)
@@ -448,6 +453,19 @@ async function add() {
448
453
  />
449
454
  </UFormField>
450
455
 
456
+ <UCheckbox v-model="technical" name="technical">
457
+ <template #label>
458
+ <span class="text-sm text-slate-200">Technical task</span>
459
+ </template>
460
+ <template #description>
461
+ <span class="text-[11px] text-slate-500">
462
+ A refactor / non-functional / internal change. The implementer treats the task
463
+ definition as primary and the spec as a regression reference; leave off to let the
464
+ spec phase decide.
465
+ </span>
466
+ </template>
467
+ </UCheckbox>
468
+
451
469
  <!-- Per-type fields. -->
452
470
  <div v-if="taskType === 'bug'" class="grid grid-cols-2 gap-3">
453
471
  <UFormField label="Severity">
@@ -173,6 +173,31 @@ const commentOnPrOpenLabel = computed(() =>
173
173
  const resolveOnMergeLabel = computed(() =>
174
174
  writebackLabel(props.block.trackerResolveOnMerge, tracker.settings.writebackResolveOnMerge),
175
175
  )
176
+
177
+ // ---- technical label (tri-state) -------------------------------------------
178
+ // Whether this is a purely technical task (the implementer then treats the task
179
+ // definition as primary and the spec as a regression reference). Tri-state: Unset lets
180
+ // the engine infer it from the spec phase; Technical / Business are authoritative human
181
+ // choices the engine never overrides. `null` clears back to Unset.
182
+ function setTechnical(value: boolean | null) {
183
+ board.updateBlock(props.block.id, { technical: value })
184
+ }
185
+ const technicalMenu = [
186
+ [
187
+ {
188
+ label: 'Unset (auto-detect)',
189
+ icon: 'i-lucide-rotate-ccw',
190
+ onSelect: () => setTechnical(null),
191
+ },
192
+ { label: 'Technical', icon: 'i-lucide-wrench', onSelect: () => setTechnical(true) },
193
+ { label: 'Business', icon: 'i-lucide-briefcase', onSelect: () => setTechnical(false) },
194
+ ],
195
+ ]
196
+ const technicalLabel = computed(() => {
197
+ if (props.block.technical === true) return 'Technical'
198
+ if (props.block.technical === false) return 'Business'
199
+ return 'Unset (auto-detect)'
200
+ })
176
201
  </script>
177
202
 
178
203
  <template>
@@ -302,6 +327,38 @@ const resolveOnMergeLabel = computed(() =>
302
327
  </p>
303
328
  </div>
304
329
 
330
+ <!-- technical label (tri-state) -->
331
+ <div>
332
+ <div class="mb-1 flex items-center justify-between">
333
+ <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
334
+ Task kind
335
+ </span>
336
+ <UDropdownMenu :items="technicalMenu">
337
+ <UButton
338
+ size="xs"
339
+ variant="ghost"
340
+ color="neutral"
341
+ icon="i-lucide-wrench"
342
+ trailing-icon="i-lucide-chevron-down"
343
+ >
344
+ {{ technicalLabel }}
345
+ </UButton>
346
+ </UDropdownMenu>
347
+ </div>
348
+ <div class="text-[11px] text-slate-500">
349
+ <template v-if="block.technical === true">
350
+ Technical — the implementer treats the task definition as primary and the spec as a
351
+ regression reference; the spec-writer may produce no business specs.
352
+ </template>
353
+ <template v-else-if="block.technical === false">
354
+ Business — the specification leads, as usual.
355
+ </template>
356
+ <template v-else>
357
+ Auto-detect — inferred from the spec phase. Set it explicitly to override.
358
+ </template>
359
+ </div>
360
+ </div>
361
+
305
362
  <!-- issue-tracker writeback overrides -->
306
363
  <div>
307
364
  <div class="mb-1 flex items-center justify-between">
@@ -42,6 +42,7 @@ export function boardApi({ http, ws }: ApiContext) {
42
42
  modelPresetId?: string
43
43
  pipelineId?: string
44
44
  agentConfig?: Record<string, string>
45
+ technical?: boolean
45
46
  },
46
47
  ) => http<Block>(`${ws(workspaceId)}/blocks/${blockId}/tasks`, { method: 'POST', body }),
47
48
 
@@ -80,6 +80,7 @@ export const useBoardStore = defineStore('board', () => {
80
80
  modelPresetId?: string
81
81
  pipelineId?: string
82
82
  agentConfig?: Record<string, string>
83
+ technical?: boolean
83
84
  },
84
85
  ): Promise<Block | undefined> {
85
86
  if (!getBlock(containerId)) return
@@ -92,6 +93,7 @@ export const useBoardStore = defineStore('board', () => {
92
93
  ...(options?.modelPresetId ? { modelPresetId: options.modelPresetId } : {}),
93
94
  ...(options?.pipelineId ? { pipelineId: options.pipelineId } : {}),
94
95
  ...(options?.agentConfig ? { agentConfig: options.agentConfig } : {}),
96
+ ...(options?.technical ? { technical: true } : {}),
95
97
  })
96
98
  upsert(block)
97
99
  return block
@@ -132,6 +132,15 @@ export interface Block {
132
132
  taskType?: TaskType
133
133
  /** task-only: small per-type form fields (bug severity, spike timebox, …). */
134
134
  taskTypeFields?: TaskTypeFields | null
135
+ /**
136
+ * task-only: TECHNICAL label. `true` ⇒ a refactor / non-functional / internal change
137
+ * (the implementer treats the task definition as primary, specs as a regression
138
+ * reference; the spec-writer may produce no business specs). `false` ⇒ a business task.
139
+ * `null`/absent ⇒ not yet determined — the engine may infer it from the spec phase. A
140
+ * human-set value is authoritative and never overridden; the inspector toggle is
141
+ * tri-state (unset / technical / business) and sends `null` for "unset".
142
+ */
143
+ technical?: boolean | null
135
144
  /** ids of best-practice prompt fragments folded into this block's agent prompts. */
136
145
  fragmentIds?: string[]
137
146
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.23.1",
3
+ "version": "0.24.0",
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",