@cat-factory/app 0.96.1 → 0.96.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.
- package/app/assets/css/main.css +15 -0
- package/app/components/board/nodes/TaskPipelineMini.vue +4 -3
- package/app/components/brainstorm/BrainstormWindow.vue +9 -1
- package/app/components/clarity/ClarityReviewWindow.vue +9 -1
- package/app/components/common/IconButton.vue +19 -0
- package/app/components/focus/BlockFocusView.vue +8 -1
- package/app/components/followUp/FollowUpWindow.vue +1 -1
- package/app/components/gates/GateResultView.vue +1 -1
- package/app/components/humanTest/HumanTestWindow.vue +1 -1
- package/app/components/panels/InspectorPanel.vue +3 -1
- package/app/components/pipeline/PipelineProgress.vue +8 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +9 -1
- package/app/components/spec/ServiceSpecWindow.vue +9 -1
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +2 -2
- package/package.json +2 -2
package/app/assets/css/main.css
CHANGED
|
@@ -94,6 +94,21 @@ body {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/* Respect prefers-reduced-motion: the decorative, infinite attention pulses (the
|
|
98
|
+
* blocked / decision-needed halo, the PR-ready halo) and the marching-ants edge
|
|
99
|
+
* animation convey nothing the static state doesn't already, so we disable them for
|
|
100
|
+
* users who ask for less motion. Loading spinners are deliberately left alone — a
|
|
101
|
+
* spinner's motion IS its meaning ("work in progress"). */
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
.board-pulse,
|
|
104
|
+
.board-pulse-green {
|
|
105
|
+
animation: none;
|
|
106
|
+
}
|
|
107
|
+
.vue-flow__edge.is-live .vue-flow__edge-path {
|
|
108
|
+
animation: none;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
/* Custom block node should not show the default node chrome */
|
|
98
113
|
.vue-flow__node-block {
|
|
99
114
|
padding: 0;
|
|
@@ -79,8 +79,9 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
79
79
|
{{ t('board.task.buildSteps') }}
|
|
80
80
|
</div>
|
|
81
81
|
<div v-for="(s, i) in steps" :key="i" class="rounded bg-slate-900/60 px-1.5 py-1">
|
|
82
|
-
<
|
|
83
|
-
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
class="flex w-full cursor-pointer items-center gap-1 rounded text-start focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/60"
|
|
84
85
|
:title="`${agentKindMeta(s.agentKind).label} — ${agentKindMeta(s.agentKind).description}\n${t('board.task.clickToViewStep')}`"
|
|
85
86
|
@click.stop="openStep(i)"
|
|
86
87
|
>
|
|
@@ -116,7 +117,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
116
117
|
: STATE_META[s.state].color,
|
|
117
118
|
}"
|
|
118
119
|
/>
|
|
119
|
-
</
|
|
120
|
+
</button>
|
|
120
121
|
|
|
121
122
|
<!-- pending approval gate: jump straight to the conclusions reader. Suppressed
|
|
122
123
|
while a reviewer gate is folding/re-reviewing in the background (no human needed). -->
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// options or hits the iteration cap. The converged direction — not the original description — is
|
|
9
9
|
// what the downstream stage (the requirements review / the architect) consumes.
|
|
10
10
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
11
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
11
12
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
12
13
|
import type {
|
|
13
14
|
BrainstormItem,
|
|
@@ -281,7 +282,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
281
282
|
<UBadge v-if="session" color="neutral" variant="subtle" size="sm">
|
|
282
283
|
{{ t('brainstorm.iteration', { current: iteration, max: maxIterations }) }}
|
|
283
284
|
</UBadge>
|
|
284
|
-
<
|
|
285
|
+
<IconButton
|
|
286
|
+
icon="i-lucide-x"
|
|
287
|
+
color="neutral"
|
|
288
|
+
variant="ghost"
|
|
289
|
+
size="sm"
|
|
290
|
+
:label="t('common.close')"
|
|
291
|
+
@click="close"
|
|
292
|
+
/>
|
|
285
293
|
</div>
|
|
286
294
|
</header>
|
|
287
295
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// description — is what every downstream agent step (the bug investigator, the coder)
|
|
10
10
|
// consumes.
|
|
11
11
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
12
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
12
13
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
13
14
|
import type {
|
|
14
15
|
ClarityItemStatus,
|
|
@@ -327,7 +328,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
327
328
|
<UBadge v-if="review" color="neutral" variant="subtle" size="sm">
|
|
328
329
|
{{ t('clarity.iteration', { current: iteration, max: maxIterations }) }}
|
|
329
330
|
</UBadge>
|
|
330
|
-
<
|
|
331
|
+
<IconButton
|
|
332
|
+
icon="i-lucide-x"
|
|
333
|
+
color="neutral"
|
|
334
|
+
variant="ghost"
|
|
335
|
+
size="sm"
|
|
336
|
+
:label="t('common.close')"
|
|
337
|
+
@click="close"
|
|
338
|
+
/>
|
|
331
339
|
</div>
|
|
332
340
|
</header>
|
|
333
341
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shared icon-only button primitive that ENFORCES the accessible-name convention (UX-62/63):
|
|
3
|
+
// every icon button must carry a `label`, applied as BOTH `title` (pointer tooltip) and
|
|
4
|
+
// `aria-label` (screen readers) — `title` alone never fires on touch and is invisible to
|
|
5
|
+
// assistive tech, so an unlabeled icon button is a bug this component makes unrepresentable.
|
|
6
|
+
// All other UButton props/listeners (icon, color, variant, size, @click, :loading, :disabled)
|
|
7
|
+
// pass straight through via `$attrs`; `label` is declared as a prop so it strips off before
|
|
8
|
+
// reaching UButton (whose own `label` prop would otherwise render visible text). Mirrors the
|
|
9
|
+
// shape of `common/CopyButton.vue`.
|
|
10
|
+
defineProps<{
|
|
11
|
+
/** Accessible name + tooltip. Required — the whole point of the primitive. */
|
|
12
|
+
label: string
|
|
13
|
+
}>()
|
|
14
|
+
defineOptions({ inheritAttrs: false })
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<UButton v-bind="$attrs" :title="label" :aria-label="label" />
|
|
19
|
+
</template>
|
|
@@ -4,6 +4,7 @@ import type { Block } from '~/types/domain'
|
|
|
4
4
|
import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
|
|
5
5
|
import { pipelineAllowedForManualStart } from '~/utils/pipeline'
|
|
6
6
|
import PipelineProgress from '~/components/pipeline/PipelineProgress.vue'
|
|
7
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
7
8
|
|
|
8
9
|
const board = useBoardStore()
|
|
9
10
|
const pipelines = usePipelinesStore()
|
|
@@ -106,7 +107,13 @@ function openApprovalFor(approvalId: string) {
|
|
|
106
107
|
{{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
|
|
107
108
|
</UButton>
|
|
108
109
|
</UDropdownMenu>
|
|
109
|
-
<
|
|
110
|
+
<IconButton
|
|
111
|
+
icon="i-lucide-x"
|
|
112
|
+
color="neutral"
|
|
113
|
+
variant="ghost"
|
|
114
|
+
:label="t('common.close')"
|
|
115
|
+
@click="close"
|
|
116
|
+
/>
|
|
110
117
|
</div>
|
|
111
118
|
</header>
|
|
112
119
|
|
|
@@ -200,7 +200,7 @@ const STATUS_META: Record<
|
|
|
200
200
|
v-model="drafts[item.id]"
|
|
201
201
|
rows="2"
|
|
202
202
|
:placeholder="t('followUp.answerPlaceholder')"
|
|
203
|
-
class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none"
|
|
203
|
+
class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/60"
|
|
204
204
|
/>
|
|
205
205
|
<div class="flex items-center gap-2">
|
|
206
206
|
<UButton
|
|
@@ -297,7 +297,7 @@ const conflictVerdict = computed(() => {
|
|
|
297
297
|
rows="3"
|
|
298
298
|
:disabled="fixBusy"
|
|
299
299
|
:placeholder="t('gates.humanReview.requestFixPlaceholder')"
|
|
300
|
-
class="w-full resize-y rounded-md border border-slate-800 bg-slate-950/60 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-violet-500/60 focus:outline-none"
|
|
300
|
+
class="w-full resize-y rounded-md border border-slate-800 bg-slate-950/60 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-violet-500/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/60"
|
|
301
301
|
/>
|
|
302
302
|
<div class="mt-2 flex justify-end">
|
|
303
303
|
<UButton
|
|
@@ -289,7 +289,7 @@ const canDestroy = computed(
|
|
|
289
289
|
v-model="findings"
|
|
290
290
|
rows="4"
|
|
291
291
|
:placeholder="t('humanTest.fix.placeholder')"
|
|
292
|
-
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
292
|
+
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/60"
|
|
293
293
|
/>
|
|
294
294
|
<UButton
|
|
295
295
|
size="sm"
|
|
@@ -18,6 +18,7 @@ import TaskExecution from '~/components/panels/inspector/TaskExecution.vue'
|
|
|
18
18
|
import TaskEstimateBadge from '~/components/panels/inspector/TaskEstimateBadge.vue'
|
|
19
19
|
import EpicChildren from '~/components/panels/inspector/EpicChildren.vue'
|
|
20
20
|
import InitiativeInspector from '~/components/panels/inspector/InitiativeInspector.vue'
|
|
21
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
21
22
|
import RecurringScheduleSettings from '~/components/panels/inspector/RecurringScheduleSettings.vue'
|
|
22
23
|
import AgentFailureCard from '~/components/board/AgentFailureCard.vue'
|
|
23
24
|
import AgentStopButton from '~/components/board/AgentStopButton.vue'
|
|
@@ -265,11 +266,12 @@ const showOriginalDescription = ref(false)
|
|
|
265
266
|
</div>
|
|
266
267
|
</div>
|
|
267
268
|
</div>
|
|
268
|
-
<
|
|
269
|
+
<IconButton
|
|
269
270
|
icon="i-lucide-x"
|
|
270
271
|
color="neutral"
|
|
271
272
|
variant="ghost"
|
|
272
273
|
size="xs"
|
|
274
|
+
:label="t('common.close')"
|
|
273
275
|
@click="ui.select(null)"
|
|
274
276
|
/>
|
|
275
277
|
</div>
|
|
@@ -607,4 +607,12 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
607
607
|
.followup-blink {
|
|
608
608
|
animation: followup-blink 1.4s ease-in-out infinite;
|
|
609
609
|
}
|
|
610
|
+
/* These decorative attention halos carry no information the static state lacks, so
|
|
611
|
+
silence them under prefers-reduced-motion (matches the board-pulse rule in main.css). */
|
|
612
|
+
@media (prefers-reduced-motion: reduce) {
|
|
613
|
+
.step-active,
|
|
614
|
+
.followup-blink {
|
|
615
|
+
animation: none;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
610
618
|
</style>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
11
11
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
12
12
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
13
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
13
14
|
import type {
|
|
14
15
|
RequirementRecommendation,
|
|
15
16
|
RequirementReview,
|
|
@@ -489,7 +490,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
489
490
|
:step-index="stepIndex"
|
|
490
491
|
@restarted="close"
|
|
491
492
|
/>
|
|
492
|
-
<
|
|
493
|
+
<IconButton
|
|
494
|
+
icon="i-lucide-x"
|
|
495
|
+
color="neutral"
|
|
496
|
+
variant="ghost"
|
|
497
|
+
size="sm"
|
|
498
|
+
:label="t('common.close')"
|
|
499
|
+
@click="close"
|
|
500
|
+
/>
|
|
493
501
|
</div>
|
|
494
502
|
</header>
|
|
495
503
|
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
RequirementPriority,
|
|
13
13
|
SpecModule,
|
|
14
14
|
} from '~/types/spec'
|
|
15
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
15
16
|
|
|
16
17
|
const { t } = useI18n()
|
|
17
18
|
const board = useBoardStore()
|
|
@@ -176,7 +177,14 @@ function kindLabel(item: RequirementItem): string {
|
|
|
176
177
|
{{ t('spec.mode.gherkin') }}
|
|
177
178
|
</UButton>
|
|
178
179
|
</div>
|
|
179
|
-
<
|
|
180
|
+
<IconButton
|
|
181
|
+
icon="i-lucide-x"
|
|
182
|
+
color="neutral"
|
|
183
|
+
variant="ghost"
|
|
184
|
+
size="sm"
|
|
185
|
+
:label="t('common.close')"
|
|
186
|
+
@click="close"
|
|
187
|
+
/>
|
|
180
188
|
</div>
|
|
181
189
|
</header>
|
|
182
190
|
|
|
@@ -286,7 +286,7 @@ const canApprove = computed(
|
|
|
286
286
|
v-model="perViewNotes[p.view]"
|
|
287
287
|
rows="2"
|
|
288
288
|
:placeholder="t('visualConfirm.notePlaceholder', { view: p.view })"
|
|
289
|
-
class="mt-1 w-full rounded-md border border-slate-700 bg-slate-950 px-2 py-1.5 text-[12px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
289
|
+
class="mt-1 w-full rounded-md border border-slate-700 bg-slate-950 px-2 py-1.5 text-[12px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/60"
|
|
290
290
|
/>
|
|
291
291
|
</div>
|
|
292
292
|
</div>
|
|
@@ -340,7 +340,7 @@ const canApprove = computed(
|
|
|
340
340
|
v-model="globalFindings"
|
|
341
341
|
rows="3"
|
|
342
342
|
:placeholder="t('visualConfirm.requestFix.placeholder')"
|
|
343
|
-
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
343
|
+
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/60"
|
|
344
344
|
/>
|
|
345
345
|
<div class="mt-2 flex items-center justify-between">
|
|
346
346
|
<span class="text-[11px] text-slate-500">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.96.
|
|
3
|
+
"version": "0.96.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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.106.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|