@cat-factory/app 0.46.1 → 0.46.2

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.
@@ -9,6 +9,7 @@ import AgentStopButton from '~/components/board/AgentStopButton.vue'
9
9
  import { useBlockDrag } from '~/composables/useBlockDrag'
10
10
  import { useFrameResize } from '~/composables/useFrameResize'
11
11
  import { useFrameStacking } from '~/composables/useFrameStacking'
12
+ import { useViewport } from '~/composables/useViewport'
12
13
 
13
14
  // Vue Flow passes the node's `id` and `data` as props to custom node components.
14
15
  // Only frames are rendered as board nodes; their tasks live inside the card.
@@ -22,6 +23,9 @@ const agentRuns = useAgentRunsStore()
22
23
  const services = useServicesStore()
23
24
  const reviews = useReviewStage()
24
25
  const { lod } = useSemanticZoom()
26
+ // Coarse-pointer (touch) bumps the frame-header actions from `xs` to `sm` so
27
+ // they clear a comfortable tap target on phones without affecting mouse desktops.
28
+ const { isTouch } = useViewport()
25
29
 
26
30
  const block = computed<Block | undefined>(() => board.getBlock(props.id))
27
31
  /** This service frame is mounted on more than one board in the org. */
@@ -412,7 +416,7 @@ const ITEM_ICON: Record<string, string> = {
412
416
  <UButton
413
417
  class="nodrag"
414
418
  data-testid="frame-add-task"
415
- size="xs"
419
+ :size="isTouch ? 'sm' : 'xs'"
416
420
  variant="ghost"
417
421
  color="neutral"
418
422
  icon="i-lucide-plus"
@@ -422,7 +426,7 @@ const ITEM_ICON: Record<string, string> = {
422
426
  <UButton
423
427
  v-if="tasks.anyOffered"
424
428
  class="nodrag"
425
- size="xs"
429
+ :size="isTouch ? 'sm' : 'xs'"
426
430
  variant="ghost"
427
431
  color="neutral"
428
432
  icon="i-lucide-ticket"
@@ -431,7 +435,7 @@ const ITEM_ICON: Record<string, string> = {
431
435
  />
432
436
  <UButton
433
437
  class="nodrag"
434
- size="xs"
438
+ :size="isTouch ? 'sm' : 'xs'"
435
439
  variant="ghost"
436
440
  color="neutral"
437
441
  icon="i-lucide-repeat"
@@ -440,7 +444,7 @@ const ITEM_ICON: Record<string, string> = {
440
444
  />
441
445
  <UButton
442
446
  class="nodrag"
443
- size="xs"
447
+ :size="isTouch ? 'sm' : 'xs'"
444
448
  variant="ghost"
445
449
  color="neutral"
446
450
  icon="i-lucide-chevron-up"
@@ -481,17 +485,17 @@ const ITEM_ICON: Record<string, string> = {
481
485
  `nopan` (alongside `nodrag`) so the pane doesn't pan while resizing —
482
486
  same reason as the header handle above. -->
483
487
  <div
484
- class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-sky-400/20"
488
+ class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-sky-400/20 pointer-coarse:w-4"
485
489
  title="Drag to resize"
486
490
  @pointerdown="onResize($event, 'e')"
487
491
  />
488
492
  <div
489
- class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-sky-400/20"
493
+ class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-sky-400/20 pointer-coarse:h-4"
490
494
  title="Drag to resize"
491
495
  @pointerdown="onResize($event, 's')"
492
496
  />
493
497
  <div
494
- class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize"
498
+ class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize pointer-coarse:h-11 pointer-coarse:w-11"
495
499
  title="Drag to resize"
496
500
  @pointerdown="onResize($event, 'se')"
497
501
  >
@@ -41,11 +41,14 @@ function onHandle(e: PointerEvent) {
41
41
  >
42
42
  <!-- drag handle (`nopan` so the pane doesn't pan on a left-drag from here) -->
43
43
  <div
44
- class="nodrag nopan flex cursor-grab items-center justify-center rounded-t-lg border border-b-0 border-slate-700 bg-slate-800/80 py-px active:cursor-grabbing"
44
+ class="nodrag nopan flex cursor-grab items-center justify-center rounded-t-lg border border-b-0 border-slate-700 bg-slate-800/80 py-px active:cursor-grabbing pointer-coarse:py-2"
45
45
  title="Drag task"
46
46
  @pointerdown="onHandle"
47
47
  >
48
- <UIcon name="i-lucide-grip-horizontal" class="h-3 w-3 text-slate-500" />
48
+ <UIcon
49
+ name="i-lucide-grip-horizontal"
50
+ class="h-3 w-3 text-slate-500 pointer-coarse:h-5 pointer-coarse:w-5"
51
+ />
49
52
  </div>
50
53
  <TaskCard :task-id="taskId" class="!rounded-t-none" />
51
54
  </div>
@@ -76,17 +76,17 @@ function onResize(e: PointerEvent, edge: 'e' | 's' | 'se') {
76
76
  <!-- resize handles (drag the borders to resize the module, Miro-style).
77
77
  `nopan` (with `nodrag`) so resizing doesn't pan the pane. -->
78
78
  <div
79
- class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-violet-400/20"
79
+ class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-violet-400/20 pointer-coarse:w-4"
80
80
  title="Drag to resize"
81
81
  @pointerdown="onResize($event, 'e')"
82
82
  />
83
83
  <div
84
- class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-violet-400/20"
84
+ class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-violet-400/20 pointer-coarse:h-4"
85
85
  title="Drag to resize"
86
86
  @pointerdown="onResize($event, 's')"
87
87
  />
88
88
  <div
89
- class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize"
89
+ class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize pointer-coarse:h-11 pointer-coarse:w-11"
90
90
  title="Drag to resize"
91
91
  @pointerdown="onResize($event, 'se')"
92
92
  >
@@ -210,12 +210,12 @@ function selectTask() {
210
210
  <!-- drag-to-connect handle: drag onto another task to make it depend on this one -->
211
211
  <button
212
212
  type="button"
213
- class="nodrag shrink-0 cursor-crosshair rounded-full p-0.5 text-slate-500 hover:bg-slate-800 hover:text-amber-400"
213
+ class="nodrag shrink-0 cursor-crosshair rounded-full p-0.5 text-slate-500 hover:bg-slate-800 hover:text-amber-400 pointer-coarse:p-2.5"
214
214
  title="Drag onto another task to make it depend on this one"
215
215
  @pointerdown.stop="startConnect(task.id, $event)"
216
216
  @click.stop
217
217
  >
218
- <UIcon name="i-lucide-spline" class="h-3 w-3" />
218
+ <UIcon name="i-lucide-spline" class="h-3 w-3 pointer-coarse:h-5 pointer-coarse:w-5" />
219
219
  </button>
220
220
  </div>
221
221
 
@@ -220,11 +220,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
220
220
  <Teleport to="body">
221
221
  <div
222
222
  v-if="open"
223
- class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
223
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
224
224
  @click.self="close"
225
225
  >
226
226
  <div
227
- class="flex h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
227
+ class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
228
228
  >
229
229
  <!-- header -->
230
230
  <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
@@ -217,11 +217,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
217
217
  <Teleport to="body">
218
218
  <div
219
219
  v-if="open"
220
- class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
220
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
221
221
  @click.self="close"
222
222
  >
223
223
  <div
224
- class="flex h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
224
+ class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
225
225
  >
226
226
  <!-- header -->
227
227
  <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
@@ -67,11 +67,11 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
67
67
  <Teleport to="body">
68
68
  <div
69
69
  v-if="open"
70
- class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
70
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
71
71
  @click.self="close"
72
72
  >
73
73
  <div
74
- class="flex h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
74
+ class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
75
75
  >
76
76
  <!-- header -->
77
77
  <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
@@ -73,7 +73,7 @@ const STATUS_META: Record<
73
73
  <Teleport to="body">
74
74
  <div
75
75
  v-if="open"
76
- class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
76
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
77
77
  @click.self="close"
78
78
  >
79
79
  <div
@@ -140,7 +140,7 @@ const conflictVerdict = computed(() => {
140
140
  <Teleport to="body">
141
141
  <div
142
142
  v-if="open"
143
- class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
143
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
144
144
  @click.self="close"
145
145
  >
146
146
  <div
@@ -100,7 +100,7 @@ const canDestroy = computed(
100
100
  <Teleport to="body">
101
101
  <div
102
102
  v-if="open"
103
- class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
103
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
104
104
  @click.self="close"
105
105
  >
106
106
  <div
@@ -176,7 +176,7 @@ async function copyOutput() {
176
176
  <div
177
177
  v-if="open && step && agent"
178
178
  data-testid="step-detail"
179
- class="fixed inset-0 z-50 flex bg-slate-950/96 backdrop-blur-sm"
179
+ class="fixed inset-0 z-50 flex max-h-[100dvh] bg-slate-950/96 backdrop-blur-sm"
180
180
  role="dialog"
181
181
  aria-modal="true"
182
182
  >
@@ -430,7 +430,7 @@ async function copyOutput() {
430
430
  bottom sheet (still reachable) below lg, so the gate is always actionable. -->
431
431
  <aside
432
432
  v-if="approvalPending && !companionExceeded"
433
- class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70vh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-l lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
433
+ class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70dvh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-l lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
434
434
  >
435
435
  <div class="border-b border-slate-800 px-4 py-3">
436
436
  <div class="text-[11px] font-semibold uppercase tracking-wide text-amber-400">
@@ -44,7 +44,7 @@ const customJson = computed<string | null>(() => {
44
44
  <Teleport to="body">
45
45
  <div
46
46
  v-if="open"
47
- class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
47
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
48
48
  @click.self="close"
49
49
  >
50
50
  <div
@@ -210,9 +210,12 @@ async function clone(p: Pipeline) {
210
210
  :ui="{ content: 'max-w-[90vw] sm:max-w-2xl lg:max-w-5xl xl:max-w-6xl' }"
211
211
  >
212
212
  <template #body>
213
- <div class="grid h-full grid-cols-1 gap-4 lg:grid-cols-3">
213
+ <!-- On compact viewports the three sections stack and the slideover body
214
+ scrolls as one column; on lg they become three independently-scrolling
215
+ columns filling the full height. -->
216
+ <div class="grid grid-cols-1 gap-4 lg:h-full lg:grid-cols-3">
214
217
  <!-- agent palette -->
215
- <div class="flex min-h-0 flex-col overflow-hidden">
218
+ <div class="flex flex-col lg:min-h-0 lg:overflow-hidden">
216
219
  <div class="mb-2 flex shrink-0 items-center justify-between gap-2">
217
220
  <h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
218
221
  Agent palette
@@ -227,13 +230,13 @@ async function clone(p: Pipeline) {
227
230
  Add agent
228
231
  </UButton>
229
232
  </div>
230
- <div class="min-h-0 flex-1 overflow-y-auto pr-1">
233
+ <div class="flex-1 pr-1 lg:min-h-0 lg:overflow-y-auto">
231
234
  <AgentPalette @add="add" />
232
235
  </div>
233
236
  </div>
234
237
 
235
238
  <!-- draft chain -->
236
- <div class="flex min-h-0 flex-col overflow-hidden">
239
+ <div class="flex flex-col lg:min-h-0 lg:overflow-hidden">
237
240
  <div class="mb-2 flex items-center justify-between gap-2">
238
241
  <h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">Pipeline</h3>
239
242
  <UButton
@@ -293,7 +296,7 @@ async function clone(p: Pipeline) {
293
296
  Click agents on the left to assemble a linear pipeline.
294
297
  </div>
295
298
 
296
- <ol v-else class="min-h-0 flex-1 space-y-2 overflow-y-auto pr-1">
299
+ <ol v-else class="flex-1 space-y-2 pr-1 lg:min-h-0 lg:overflow-y-auto">
297
300
  <li
298
301
  v-for="(unit, vi) in pipelines.units"
299
302
  :key="unit.index"
@@ -608,7 +611,7 @@ async function clone(p: Pipeline) {
608
611
 
609
612
  <!-- Saved pipelines: review the library + delete (the run affordance
610
613
  moved to the task card / inspector when the palettes were removed). -->
611
- <div v-if="pipelines.pipelines.length" class="flex min-h-0 flex-col overflow-hidden">
614
+ <div v-if="pipelines.pipelines.length" class="flex flex-col lg:min-h-0 lg:overflow-hidden">
612
615
  <div class="mb-2 flex shrink-0 items-center justify-between gap-2">
613
616
  <h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
614
617
  Saved pipelines
@@ -649,7 +652,7 @@ async function clone(p: Pipeline) {
649
652
  </UBadge>
650
653
  </div>
651
654
 
652
- <ul class="min-h-0 flex-1 space-y-1.5 overflow-y-auto pr-1">
655
+ <ul class="flex-1 space-y-1.5 pr-1 lg:min-h-0 lg:overflow-y-auto">
653
656
  <li
654
657
  v-for="p in visiblePipelines"
655
658
  :key="p.id"
@@ -386,11 +386,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
386
386
  <Teleport to="body">
387
387
  <div
388
388
  v-if="open"
389
- class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
389
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
390
390
  @click.self="close"
391
391
  >
392
392
  <div
393
- class="flex h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
393
+ class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
394
394
  >
395
395
  <!-- header -->
396
396
  <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
@@ -240,7 +240,7 @@ function fail(title: string, e: unknown) {
240
240
  <Transition name="reader-fade">
241
241
  <div
242
242
  v-if="open"
243
- class="fixed inset-0 z-50 flex flex-col bg-slate-950/96 backdrop-blur-sm"
243
+ class="fixed inset-0 z-50 flex max-h-[100dvh] flex-col bg-slate-950/96 backdrop-blur-sm"
244
244
  role="dialog"
245
245
  aria-modal="true"
246
246
  >
@@ -104,11 +104,11 @@ function priorityMeta(item: RequirementItem) {
104
104
  <Teleport to="body">
105
105
  <div
106
106
  v-if="open"
107
- class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
107
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
108
108
  @click.self="close"
109
109
  >
110
110
  <div
111
- class="flex h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
111
+ class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
112
112
  >
113
113
  <!-- header -->
114
114
  <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
@@ -230,7 +230,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
230
230
  <Teleport to="body">
231
231
  <div
232
232
  v-if="open"
233
- class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
233
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
234
234
  @click.self="close"
235
235
  >
236
236
  <div
@@ -181,7 +181,7 @@ const canApprove = computed(
181
181
  <Teleport to="body">
182
182
  <div
183
183
  v-if="open"
184
- class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
184
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
185
185
  @click.self="close"
186
186
  >
187
187
  <div
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.46.1",
3
+ "version": "0.46.2",
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",