@cat-factory/app 0.54.1 → 0.54.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.
@@ -52,115 +52,109 @@ function openApprovalFor(approvalId: string) {
52
52
  </script>
53
53
 
54
54
  <template>
55
- <Transition name="focus-fade">
56
- <div
57
- v-if="block && statusMeta && typeMeta"
58
- class="absolute inset-0 z-30 flex flex-col bg-slate-950/95 backdrop-blur"
59
- >
60
- <!-- header / breadcrumb -->
61
- <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
62
- <UButton
63
- icon="i-lucide-arrow-left"
64
- color="neutral"
65
- variant="ghost"
66
- size="sm"
67
- @click="close"
68
- >
69
- {{ t('focus.board') }}
70
- </UButton>
71
- <UIcon name="i-lucide-chevron-right" class="h-4 w-4 text-slate-600" />
55
+ <!-- The focus-fade Transition is owned by the parent mount (pages/index.vue), so the
56
+ leave animation survives this component being unmounted by its v-if gate. -->
57
+ <div
58
+ v-if="block && statusMeta && typeMeta"
59
+ class="absolute inset-0 z-30 flex flex-col bg-slate-950/95 backdrop-blur"
60
+ >
61
+ <!-- header / breadcrumb -->
62
+ <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
63
+ <UButton icon="i-lucide-arrow-left" color="neutral" variant="ghost" size="sm" @click="close">
64
+ {{ t('focus.board') }}
65
+ </UButton>
66
+ <UIcon name="i-lucide-chevron-right" class="h-4 w-4 text-slate-600" />
67
+ <div
68
+ class="flex h-9 w-9 items-center justify-center rounded-lg"
69
+ :style="{ backgroundColor: typeMeta.accent + '22' }"
70
+ >
71
+ <UIcon :name="typeMeta.icon" class="h-5 w-5" :style="{ color: typeMeta.accent }" />
72
+ </div>
73
+ <div>
74
+ <h1 class="text-lg font-semibold text-white">{{ block.title }}</h1>
75
+ <div class="text-xs text-slate-500">
76
+ {{ t('focus.typeSubtitle', { type: typeMeta.label }) }}
77
+ </div>
78
+ </div>
79
+ <UBadge :color="statusMeta.chip as any" variant="subtle" class="ml-2">
80
+ {{ statusMeta.label }}
81
+ </UBadge>
82
+ <div class="ml-auto flex items-center gap-2">
83
+ <UDropdownMenu :items="runMenu">
84
+ <UButton
85
+ color="primary"
86
+ variant="soft"
87
+ size="sm"
88
+ icon="i-lucide-play"
89
+ trailing-icon="i-lucide-chevron-down"
90
+ >
91
+ {{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
92
+ </UButton>
93
+ </UDropdownMenu>
94
+ <UButton icon="i-lucide-x" color="neutral" variant="ghost" @click="close" />
95
+ </div>
96
+ </header>
97
+
98
+ <div class="grid flex-1 grid-cols-[1fr_300px] gap-6 overflow-hidden p-6">
99
+ <!-- main: pipeline flow -->
100
+ <section
101
+ class="flex flex-col overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-6"
102
+ >
103
+ <div class="mb-4 flex items-center gap-2">
104
+ <UIcon name="i-lucide-workflow" class="h-4 w-4 text-slate-500" />
105
+ <h2 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
106
+ {{ instance ? instance.pipelineName : t('focus.noPipelineRunning') }}
107
+ </h2>
108
+ </div>
109
+
110
+ <PipelineProgress
111
+ v-if="instance"
112
+ :instance="instance"
113
+ @open-decision="openDecisionFor"
114
+ @open-approval="openApprovalFor"
115
+ />
116
+
72
117
  <div
73
- class="flex h-9 w-9 items-center justify-center rounded-lg"
74
- :style="{ backgroundColor: typeMeta.accent + '22' }"
118
+ v-else
119
+ class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-slate-700 text-sm text-slate-500"
75
120
  >
76
- <UIcon :name="typeMeta.icon" class="h-5 w-5" :style="{ color: typeMeta.accent }" />
121
+ {{ t('focus.emptyPipelineHint') }}
77
122
  </div>
123
+ </section>
124
+
125
+ <!-- side: details -->
126
+ <aside
127
+ class="space-y-4 overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-5"
128
+ >
78
129
  <div>
79
- <h1 class="text-lg font-semibold text-white">{{ block.title }}</h1>
80
- <div class="text-xs text-slate-500">
81
- {{ t('focus.typeSubtitle', { type: typeMeta.label }) }}
130
+ <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
131
+ {{ t('focus.description') }}
82
132
  </div>
133
+ <p class="text-sm text-slate-300">{{ block.description }}</p>
83
134
  </div>
84
- <UBadge :color="statusMeta.chip as any" variant="subtle" class="ml-2">
85
- {{ statusMeta.label }}
86
- </UBadge>
87
- <div class="ml-auto flex items-center gap-2">
88
- <UDropdownMenu :items="runMenu">
89
- <UButton
90
- color="primary"
91
- variant="soft"
92
- size="sm"
93
- icon="i-lucide-play"
94
- trailing-icon="i-lucide-chevron-down"
95
- >
96
- {{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
97
- </UButton>
98
- </UDropdownMenu>
99
- <UButton icon="i-lucide-x" color="neutral" variant="ghost" @click="close" />
100
- </div>
101
- </header>
102
-
103
- <div class="grid flex-1 grid-cols-[1fr_300px] gap-6 overflow-hidden p-6">
104
- <!-- main: pipeline flow -->
105
- <section
106
- class="flex flex-col overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-6"
107
- >
108
- <div class="mb-4 flex items-center gap-2">
109
- <UIcon name="i-lucide-workflow" class="h-4 w-4 text-slate-500" />
110
- <h2 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
111
- {{ instance ? instance.pipelineName : t('focus.noPipelineRunning') }}
112
- </h2>
135
+ <div v-if="instance">
136
+ <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
137
+ {{ t('focus.overallProgress') }}
113
138
  </div>
114
-
115
- <PipelineProgress
116
- v-if="instance"
117
- :instance="instance"
118
- @open-decision="openDecisionFor"
119
- @open-approval="openApprovalFor"
120
- />
121
-
122
- <div
123
- v-else
124
- class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-slate-700 text-sm text-slate-500"
125
- >
126
- {{ t('focus.emptyPipelineHint') }}
139
+ <UProgress :model-value="Math.round(block.progress * 100)" />
140
+ <div class="mt-1 text-[11px] text-slate-400">
141
+ {{ n(block.progress, 'percent') }}
127
142
  </div>
128
- </section>
129
-
130
- <!-- side: details -->
131
- <aside
132
- class="space-y-4 overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-5"
133
- >
134
- <div>
135
- <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
136
- {{ t('focus.description') }}
137
- </div>
138
- <p class="text-sm text-slate-300">{{ block.description }}</p>
139
- </div>
140
- <div v-if="instance">
141
- <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
142
- {{ t('focus.overallProgress') }}
143
- </div>
144
- <UProgress :model-value="Math.round(block.progress * 100)" />
145
- <div class="mt-1 text-[11px] text-slate-400">
146
- {{ n(block.progress, 'percent') }}
147
- </div>
143
+ </div>
144
+ <div>
145
+ <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
146
+ {{ t('focus.dependencies') }}
148
147
  </div>
149
- <div>
150
- <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
151
- {{ t('focus.dependencies') }}
152
- </div>
153
- <div v-if="deps.length" class="flex flex-wrap gap-1">
154
- <UBadge v-for="d in deps" :key="d.id" color="neutral" variant="subtle" size="sm">
155
- {{ d.title }}
156
- </UBadge>
157
- </div>
158
- <div v-else class="text-[11px] text-slate-500">{{ t('focus.noDependencies') }}</div>
148
+ <div v-if="deps.length" class="flex flex-wrap gap-1">
149
+ <UBadge v-for="d in deps" :key="d.id" color="neutral" variant="subtle" size="sm">
150
+ {{ d.title }}
151
+ </UBadge>
159
152
  </div>
160
- </aside>
161
- </div>
153
+ <div v-else class="text-[11px] text-slate-500">{{ t('focus.noDependencies') }}</div>
154
+ </div>
155
+ </aside>
162
156
  </div>
163
- </Transition>
157
+ </div>
164
158
  </template>
165
159
 
166
160
  <style scoped>
@@ -14,11 +14,7 @@ import InspectorPanel from '~/components/panels/InspectorPanel.vue'
14
14
  import DecisionModal from '~/components/panels/DecisionModal.vue'
15
15
  import AgentStepDetail from '~/components/panels/AgentStepDetail.vue'
16
16
  import StepResultViewHost from '~/components/panels/StepResultViewHost.vue'
17
- import BlockFocusView from '~/components/focus/BlockFocusView.vue'
18
- import TaskSourceConnectModal from '~/components/tasks/TaskSourceConnectModal.vue'
19
- import TaskImportModal from '~/components/tasks/TaskImportModal.vue'
20
17
  import AddTaskModal from '~/components/board/AddTaskModal.vue'
21
- import RecurringPipelineModal from '~/components/board/RecurringPipelineModal.vue'
22
18
  import GitHubOnboarding from '~/components/github/GitHubOnboarding.vue'
23
19
  import CommandBar from '~/components/layout/CommandBar.vue'
24
20
  import PersonalCredentialModal from '~/components/providers/PersonalCredentialModal.vue'
@@ -30,6 +26,17 @@ const ObservabilityPanel = defineAsyncComponent(
30
26
  () => import('~/components/panels/ObservabilityPanel.vue'),
31
27
  )
32
28
  const KaizenPanel = defineAsyncComponent(() => import('~/components/kaizen/KaizenPanel.vue'))
29
+ // Occasional, externally store-gated surfaces — deferred to their own chunks like the
30
+ // sibling document modals above. Each mounts only while its ui open-flag is set, so it
31
+ // loads on first open instead of bloating the initial bundle.
32
+ const BlockFocusView = defineAsyncComponent(() => import('~/components/focus/BlockFocusView.vue'))
33
+ const TaskSourceConnectModal = defineAsyncComponent(
34
+ () => import('~/components/tasks/TaskSourceConnectModal.vue'),
35
+ )
36
+ const TaskImportModal = defineAsyncComponent(() => import('~/components/tasks/TaskImportModal.vue'))
37
+ const RecurringPipelineModal = defineAsyncComponent(
38
+ () => import('~/components/board/RecurringPipelineModal.vue'),
39
+ )
33
40
  const DocumentSourceConnectModal = defineAsyncComponent(
34
41
  () => import('~/components/documents/DocumentSourceConnectModal.vue'),
35
42
  )
@@ -248,7 +255,12 @@ watch(
248
255
  <BoardToolbar />
249
256
  <SpendWarningBanner />
250
257
  <InspectorPanel />
251
- <BlockFocusView />
258
+ <!-- Code-split focus view. The fade lives here (not inside the component) so the
259
+ leave animation still plays when `focusBlockId` clears and the v-if unmounts
260
+ the chunk — an inner Transition would be torn down before it could run. -->
261
+ <Transition name="focus-fade">
262
+ <BlockFocusView v-if="ui.focusBlockId" />
263
+ </Transition>
252
264
  </main>
253
265
 
254
266
  <!-- Always-mounted, fast-path surfaces. -->
@@ -256,15 +268,15 @@ watch(
256
268
  <DecisionModal />
257
269
  <AgentStepDetail />
258
270
  <StepResultViewHost />
259
- <TaskSourceConnectModal />
260
- <TaskImportModal />
261
271
  <AddTaskModal />
262
- <RecurringPipelineModal />
263
272
  <CommandBar />
264
273
  <PersonalCredentialModal />
265
274
 
266
275
  <!-- Lazy panels: mounted only while their ui open-flag is set, so each loads on
267
276
  first open (its own chunk) rather than bloating the initial bundle. -->
277
+ <TaskSourceConnectModal v-if="ui.taskConnect" />
278
+ <TaskImportModal v-if="ui.taskImport" />
279
+ <RecurringPipelineModal v-if="ui.addRecurringFrameId" />
268
280
  <ObservabilityPanel v-if="ui.observabilityInstanceId" />
269
281
  <KaizenPanel v-if="ui.kaizenScreenOpen" />
270
282
  <DocumentSourceConnectModal v-if="ui.documentConnect" />
@@ -113,11 +113,16 @@ export const useWorkspaceStore = defineStore(
113
113
  ready.value = false
114
114
  error.value = null
115
115
  try {
116
- // Accounts are an auth concept — empty in dev, which leaves boards unscoped.
117
- await useAccountsStore()
118
- .load()
119
- .catch(() => {})
120
- workspaces.value = await api.listWorkspaces()
116
+ // Accounts (an auth concept — empty in dev, which leaves boards unscoped) and the
117
+ // workspace list are independent, so fetch them concurrently. resolveActiveBoard
118
+ // needs both, so it still runs after.
119
+ const [, workspaceList] = await Promise.all([
120
+ useAccountsStore()
121
+ .load()
122
+ .catch(() => {}),
123
+ api.listWorkspaces(),
124
+ ])
125
+ workspaces.value = workspaceList
121
126
  await resolveActiveBoard()
122
127
  ready.value = true
123
128
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.54.1",
3
+ "version": "0.54.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",