@cat-factory/app 0.45.2 → 0.45.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/components/layout/BoardToolbar.vue +9 -7
- package/app/components/layout/NotificationsInbox.vue +1 -1
- package/app/components/layout/SideBar.vue +255 -170
- package/app/components/panels/InspectorPanel.vue +6 -3
- package/app/composables/useViewport.ts +19 -0
- package/app/pages/index.vue +14 -0
- package/app/stores/ui.ts +18 -0
- package/i18n/locales/en.json +28 -1
- package/package.json +1 -1
|
@@ -81,7 +81,7 @@ const decisionItems = computed(() =>
|
|
|
81
81
|
|
|
82
82
|
<template>
|
|
83
83
|
<div
|
|
84
|
-
class="absolute left-1/2 top-
|
|
84
|
+
class="absolute left-1/2 top-3 z-20 flex max-w-[calc(100vw-1rem)] -translate-x-1/2 items-center gap-1 overflow-x-auto rounded-full border border-slate-700 bg-slate-900/90 px-2 py-1.5 shadow-xl backdrop-blur"
|
|
85
85
|
>
|
|
86
86
|
<!-- zoom controls -->
|
|
87
87
|
<UButton
|
|
@@ -91,7 +91,8 @@ const decisionItems = computed(() =>
|
|
|
91
91
|
size="sm"
|
|
92
92
|
@click="zoomOut()"
|
|
93
93
|
/>
|
|
94
|
-
|
|
94
|
+
<!-- The zoom %/LOD readout is the first thing to drop on narrow viewports. -->
|
|
95
|
+
<div class="hidden w-20 text-center text-xs tabular-nums text-slate-300 sm:block">
|
|
95
96
|
{{ zoomPct }}%
|
|
96
97
|
<div class="text-[9px] uppercase tracking-wide text-slate-500">{{ lodLabel }}</div>
|
|
97
98
|
</div>
|
|
@@ -115,16 +116,17 @@ const decisionItems = computed(() =>
|
|
|
115
116
|
icon="i-lucide-circle-help"
|
|
116
117
|
data-testid="decision-badge"
|
|
117
118
|
>
|
|
118
|
-
{{ execution.pendingDecisionCount
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
{{ execution.pendingDecisionCount
|
|
120
|
+
}}<span class="hidden sm:inline"
|
|
121
|
+
> {{ $t('board.toolbar.decisionWord', execution.pendingDecisionCount) }}</span
|
|
122
|
+
>
|
|
121
123
|
</UButton>
|
|
122
124
|
</UDropdownMenu>
|
|
123
125
|
|
|
124
126
|
<!-- in-org sharing: add an existing org service to this board -->
|
|
125
127
|
<UDropdownMenu v-if="mountableItems.length" :items="mountableItems">
|
|
126
128
|
<UButton color="neutral" variant="ghost" size="sm" icon="i-lucide-plus-circle">
|
|
127
|
-
|
|
129
|
+
<span class="hidden sm:inline">{{ $t('board.toolbar.addService') }}</span>
|
|
128
130
|
</UButton>
|
|
129
131
|
</UDropdownMenu>
|
|
130
132
|
|
|
@@ -140,7 +142,7 @@ const decisionItems = computed(() =>
|
|
|
140
142
|
icon="i-lucide-wallet"
|
|
141
143
|
:title="spend?.exceeded ? 'Spend limit reached — runs paused' : 'Token spend this month'"
|
|
142
144
|
>
|
|
143
|
-
{{ spendLabel }}
|
|
145
|
+
<span class="hidden sm:inline">{{ spendLabel }}</span>
|
|
144
146
|
</UButton>
|
|
145
147
|
</div>
|
|
146
148
|
</template>
|
|
@@ -174,7 +174,7 @@ function revealDecision(n: Notification) {
|
|
|
174
174
|
</UButton>
|
|
175
175
|
|
|
176
176
|
<template #content>
|
|
177
|
-
<div class="max-h-[28rem] w-
|
|
177
|
+
<div class="max-h-[28rem] w-[min(24rem,92vw)] overflow-y-auto p-2">
|
|
178
178
|
<div class="px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
179
179
|
Needs your attention
|
|
180
180
|
</div>
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// actions, repository management, integration management, the workspace-wide
|
|
6
6
|
// context-fragment library, and workspace configuration (merge thresholds +
|
|
7
7
|
// default models).
|
|
8
|
+
import { useEventListener, useScrollLock } from '@vueuse/core'
|
|
8
9
|
import BoardSwitcher from '~/components/layout/BoardSwitcher.vue'
|
|
9
10
|
import UserMenu from '~/components/auth/UserMenu.vue'
|
|
11
|
+
import { useViewport } from '~/composables/useViewport'
|
|
10
12
|
|
|
11
13
|
const documents = useDocumentsStore()
|
|
12
14
|
const tasks = useTasksStore()
|
|
@@ -17,6 +19,60 @@ const workspace = useWorkspaceStore()
|
|
|
17
19
|
const accounts = useAccountsStore()
|
|
18
20
|
const ui = useUiStore()
|
|
19
21
|
|
|
22
|
+
// `isCompact` (< lg) is the breakpoint at which the navbar is an off-canvas drawer;
|
|
23
|
+
// above it the aside is static and the drawer flag is inert.
|
|
24
|
+
const { isCompact } = useViewport()
|
|
25
|
+
|
|
26
|
+
// The off-canvas drawer is a modal surface on compact viewports, so give it the
|
|
27
|
+
// expected affordances:
|
|
28
|
+
// • Escape closes it (keyboard parity with the backdrop tap),
|
|
29
|
+
// • body scroll is locked while it's open (defensive — the shell root is already
|
|
30
|
+
// `overflow-hidden`, so this just guards any future scrollable ancestor),
|
|
31
|
+
// • crossing back to lg+ clears the flag so it can't linger as stale open state,
|
|
32
|
+
// • focus moves into the drawer on open, and back to the hamburger on close,
|
|
33
|
+
// • when closed (off-screen) on compact the whole aside is `inert`, so its nav
|
|
34
|
+
// controls aren't reachable by keyboard / assistive tech behind the board.
|
|
35
|
+
const aside = ref<HTMLElement>()
|
|
36
|
+
const drawerOpen = computed(() => isCompact.value && ui.mobileNavOpen)
|
|
37
|
+
|
|
38
|
+
const bodyLocked = useScrollLock(import.meta.client ? document.body : null)
|
|
39
|
+
watchEffect(() => {
|
|
40
|
+
bodyLocked.value = drawerOpen.value
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
useEventListener('keydown', (e: KeyboardEvent) => {
|
|
44
|
+
if (e.key === 'Escape' && ui.mobileNavOpen) ui.closeMobileNav()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
watch(isCompact, (compact) => {
|
|
48
|
+
if (!compact) ui.closeMobileNav()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Closing via a nav action immediately opens a board-covering panel/modal that claims
|
|
52
|
+
// focus itself, so don't yank focus back to the hamburger in that case.
|
|
53
|
+
let suppressFocusRestore = false
|
|
54
|
+
watch(drawerOpen, (open) => {
|
|
55
|
+
if (open) {
|
|
56
|
+
suppressFocusRestore = false
|
|
57
|
+
void nextTick(() => aside.value?.focus())
|
|
58
|
+
} else if (isCompact.value && !suppressFocusRestore) {
|
|
59
|
+
void nextTick(() =>
|
|
60
|
+
document.querySelector<HTMLElement>('[data-testid="mobile-nav-toggle"]')?.focus(),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// On compact (< lg) viewports the navbar is an off-canvas drawer. Activating any
|
|
66
|
+
// nav control reveals a board-covering panel/modal, so close the drawer on the way
|
|
67
|
+
// out — otherwise it lingers in front of (or behind) whatever just opened. Scoped to
|
|
68
|
+
// the action sections (not the BoardSwitcher / UserMenu dropdowns at the ends).
|
|
69
|
+
function onNavAction(e: MouseEvent) {
|
|
70
|
+
if ((e.target as HTMLElement).closest('button, a')) {
|
|
71
|
+
suppressFocusRestore = true
|
|
72
|
+
ui.closeMobileNav()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
20
76
|
// Resolve whether the document-source / task-source / GitHub integrations are
|
|
21
77
|
// enabled on the backend, so each section is hidden entirely when it is off
|
|
22
78
|
// (mirrors how auth gates its UI). A 503 from a probe flips its `available` to
|
|
@@ -36,190 +92,219 @@ watch(
|
|
|
36
92
|
</script>
|
|
37
93
|
|
|
38
94
|
<template>
|
|
95
|
+
<!-- On < lg the navbar slides in over the board; this backdrop dims the board and
|
|
96
|
+
closes the drawer on tap. Hidden on lg+ where the navbar is a static aside. -->
|
|
97
|
+
<Transition
|
|
98
|
+
enter-active-class="transition-opacity duration-200"
|
|
99
|
+
leave-active-class="transition-opacity duration-200"
|
|
100
|
+
enter-from-class="opacity-0"
|
|
101
|
+
leave-to-class="opacity-0"
|
|
102
|
+
>
|
|
103
|
+
<div
|
|
104
|
+
v-if="ui.mobileNavOpen"
|
|
105
|
+
class="fixed inset-0 z-30 bg-slate-950/60 backdrop-blur-sm lg:hidden"
|
|
106
|
+
data-testid="sidebar-backdrop"
|
|
107
|
+
role="button"
|
|
108
|
+
tabindex="-1"
|
|
109
|
+
:aria-label="$t('common.close')"
|
|
110
|
+
@click="ui.closeMobileNav()"
|
|
111
|
+
/>
|
|
112
|
+
</Transition>
|
|
113
|
+
|
|
39
114
|
<aside
|
|
40
|
-
|
|
115
|
+
ref="aside"
|
|
116
|
+
data-testid="sidebar"
|
|
117
|
+
tabindex="-1"
|
|
118
|
+
:role="drawerOpen ? 'dialog' : undefined"
|
|
119
|
+
:aria-modal="drawerOpen ? 'true' : undefined"
|
|
120
|
+
:aria-label="isCompact ? $t('nav.menu') : undefined"
|
|
121
|
+
:inert="isCompact && !ui.mobileNavOpen"
|
|
122
|
+
class="fixed inset-y-0 left-0 z-40 flex h-full w-64 shrink-0 flex-col gap-4 overflow-y-auto border-r border-slate-800 bg-slate-900/95 p-3 backdrop-blur transition-transform duration-200 focus:outline-none lg:static lg:z-auto lg:translate-x-0 lg:bg-slate-900/80"
|
|
123
|
+
:class="ui.mobileNavOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
|
|
41
124
|
>
|
|
42
125
|
<BoardSwitcher />
|
|
43
126
|
|
|
44
|
-
|
|
127
|
+
<div class="contents" @click="onNavAction">
|
|
128
|
+
<!-- Command bar launcher (⌘K) — the primary way to create blocks / pipelines
|
|
45
129
|
and reach every action below. -->
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
class="flex items-center gap-2 rounded-lg border border-slate-700 bg-slate-800/60 px-2.5 py-2 text-left text-sm text-slate-400 transition hover:border-slate-500 hover:bg-slate-800"
|
|
133
|
+
@click="ui.openCommandBar()"
|
|
134
|
+
>
|
|
135
|
+
<UIcon name="i-lucide-search" class="h-4 w-4 shrink-0" />
|
|
136
|
+
<span class="flex-1 truncate">{{ $t('nav.commandBar') }}</span>
|
|
137
|
+
<UKbd value="⌘K" />
|
|
138
|
+
</button>
|
|
139
|
+
|
|
140
|
+
<section>
|
|
141
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
142
|
+
{{ $t('nav.create') }}
|
|
143
|
+
</h2>
|
|
144
|
+
<div class="space-y-1.5">
|
|
145
|
+
<UButton
|
|
146
|
+
block
|
|
147
|
+
color="primary"
|
|
148
|
+
variant="soft"
|
|
149
|
+
size="sm"
|
|
150
|
+
icon="i-lucide-workflow"
|
|
151
|
+
class="justify-start"
|
|
152
|
+
@click="ui.openBuilder()"
|
|
153
|
+
>
|
|
154
|
+
{{ $t('nav.buildPipeline') }}
|
|
155
|
+
</UButton>
|
|
156
|
+
</div>
|
|
157
|
+
</section>
|
|
158
|
+
|
|
159
|
+
<USeparator />
|
|
160
|
+
|
|
161
|
+
<section>
|
|
162
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
163
|
+
{{ $t('nav.repositories') }}
|
|
164
|
+
</h2>
|
|
165
|
+
<div class="space-y-1.5">
|
|
166
|
+
<UButton
|
|
167
|
+
v-if="github.available"
|
|
168
|
+
block
|
|
169
|
+
color="primary"
|
|
170
|
+
variant="soft"
|
|
171
|
+
size="sm"
|
|
172
|
+
icon="i-lucide-folder-git-2"
|
|
173
|
+
class="justify-start"
|
|
174
|
+
@click="ui.openAddService()"
|
|
175
|
+
>
|
|
176
|
+
{{ $t('nav.addFromRepo') }}
|
|
177
|
+
</UButton>
|
|
178
|
+
<UButton
|
|
179
|
+
block
|
|
180
|
+
color="primary"
|
|
181
|
+
variant="soft"
|
|
182
|
+
size="sm"
|
|
183
|
+
icon="i-lucide-git-branch-plus"
|
|
184
|
+
class="justify-start"
|
|
185
|
+
@click="ui.openBootstrap()"
|
|
186
|
+
>
|
|
187
|
+
{{ $t('nav.bootstrapRepo') }}
|
|
188
|
+
</UButton>
|
|
189
|
+
</div>
|
|
190
|
+
</section>
|
|
191
|
+
|
|
192
|
+
<USeparator />
|
|
193
|
+
|
|
194
|
+
<section>
|
|
195
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
196
|
+
{{ $t('nav.integrations') }}
|
|
197
|
+
</h2>
|
|
198
|
+
<div class="space-y-1.5">
|
|
199
|
+
<!-- Every external system the workspace can enable/link now lives behind
|
|
116
200
|
this single button — the hub modal lists them grouped (source control,
|
|
117
201
|
communication, documents, trackers, observability, model providers). -->
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
202
|
+
<UButton
|
|
203
|
+
block
|
|
204
|
+
color="primary"
|
|
205
|
+
variant="soft"
|
|
206
|
+
size="sm"
|
|
207
|
+
icon="i-lucide-blocks"
|
|
208
|
+
class="justify-start"
|
|
209
|
+
@click="ui.openIntegrations()"
|
|
210
|
+
>
|
|
211
|
+
{{ $t('nav.integrations') }}
|
|
212
|
+
</UButton>
|
|
213
|
+
<!-- The Sandbox: try prompt versions/models against graded fixtures, off to the
|
|
130
214
|
side of the board. Opens the on-demand testing window. -->
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
215
|
+
<UButton
|
|
216
|
+
block
|
|
217
|
+
color="primary"
|
|
218
|
+
variant="soft"
|
|
219
|
+
size="sm"
|
|
220
|
+
icon="i-lucide-flask-conical"
|
|
221
|
+
class="justify-start"
|
|
222
|
+
@click="ui.openSandbox()"
|
|
223
|
+
>
|
|
224
|
+
{{ $t('nav.sandbox') }}
|
|
225
|
+
</UButton>
|
|
226
|
+
<!-- The Kaizen screen: grading history + verified prompt/agent/model combos. -->
|
|
227
|
+
<UButton
|
|
228
|
+
block
|
|
229
|
+
color="primary"
|
|
230
|
+
variant="soft"
|
|
231
|
+
size="sm"
|
|
232
|
+
icon="i-lucide-sparkles"
|
|
233
|
+
class="justify-start"
|
|
234
|
+
@click="ui.openKaizen()"
|
|
235
|
+
>
|
|
236
|
+
{{ $t('nav.kaizen') }}
|
|
237
|
+
</UButton>
|
|
238
|
+
</div>
|
|
239
|
+
</section>
|
|
240
|
+
|
|
241
|
+
<template v-if="library.available">
|
|
242
|
+
<USeparator />
|
|
243
|
+
<section>
|
|
244
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
245
|
+
{{ $t('nav.workspaceContext') }}
|
|
246
|
+
</h2>
|
|
247
|
+
<UButton
|
|
248
|
+
block
|
|
249
|
+
color="primary"
|
|
250
|
+
variant="soft"
|
|
251
|
+
size="sm"
|
|
252
|
+
icon="i-lucide-book-marked"
|
|
253
|
+
class="justify-start"
|
|
254
|
+
@click="ui.openFragmentLibrary()"
|
|
255
|
+
>
|
|
256
|
+
{{ $t('nav.contextFragments') }}
|
|
257
|
+
</UButton>
|
|
258
|
+
</section>
|
|
259
|
+
</template>
|
|
260
|
+
|
|
158
261
|
<USeparator />
|
|
159
262
|
<section>
|
|
160
263
|
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
161
|
-
|
|
264
|
+
{{ $t('nav.configuration') }}
|
|
162
265
|
</h2>
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
color="primary"
|
|
166
|
-
variant="soft"
|
|
167
|
-
size="sm"
|
|
168
|
-
icon="i-lucide-book-marked"
|
|
169
|
-
class="justify-start"
|
|
170
|
-
@click="ui.openFragmentLibrary()"
|
|
171
|
-
>
|
|
172
|
-
Context fragments
|
|
173
|
-
</UButton>
|
|
174
|
-
</section>
|
|
175
|
-
</template>
|
|
176
|
-
|
|
177
|
-
<USeparator />
|
|
178
|
-
<section>
|
|
179
|
-
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
180
|
-
Configuration
|
|
181
|
-
</h2>
|
|
182
|
-
<div class="space-y-1.5">
|
|
183
|
-
<!-- Merge thresholds, issue writeback and default service best practices are
|
|
266
|
+
<div class="space-y-1.5">
|
|
267
|
+
<!-- Merge thresholds, issue writeback and default service best practices are
|
|
184
268
|
now tabs inside Workspace settings. -->
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
269
|
+
<UButton
|
|
270
|
+
block
|
|
271
|
+
color="primary"
|
|
272
|
+
variant="soft"
|
|
273
|
+
size="sm"
|
|
274
|
+
icon="i-lucide-sliders-horizontal"
|
|
275
|
+
class="justify-start"
|
|
276
|
+
@click="ui.openWorkspaceSettings()"
|
|
277
|
+
>
|
|
278
|
+
{{ $t('nav.workspaceSettings') }}
|
|
279
|
+
</UButton>
|
|
280
|
+
<UButton
|
|
281
|
+
block
|
|
282
|
+
color="primary"
|
|
283
|
+
variant="soft"
|
|
284
|
+
size="sm"
|
|
285
|
+
icon="i-lucide-cpu"
|
|
286
|
+
class="justify-start"
|
|
287
|
+
@click="ui.openModelConfig()"
|
|
288
|
+
>
|
|
289
|
+
{{ $t('nav.modelConfiguration') }}
|
|
290
|
+
</UButton>
|
|
291
|
+
<!-- Account & team: members + roles, invitations, email sender, account API keys.
|
|
208
292
|
Shown once accounts (auth) are enabled. -->
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
293
|
+
<UButton
|
|
294
|
+
v-if="accounts.enabled"
|
|
295
|
+
block
|
|
296
|
+
color="primary"
|
|
297
|
+
variant="soft"
|
|
298
|
+
size="sm"
|
|
299
|
+
icon="i-lucide-users"
|
|
300
|
+
class="justify-start"
|
|
301
|
+
@click="ui.openAccountSettings()"
|
|
302
|
+
>
|
|
303
|
+
{{ $t('nav.accountSettings') }}
|
|
304
|
+
</UButton>
|
|
305
|
+
</div>
|
|
306
|
+
</section>
|
|
307
|
+
</div>
|
|
223
308
|
|
|
224
309
|
<UserMenu class="mt-auto" />
|
|
225
310
|
</aside>
|
|
@@ -204,13 +204,16 @@ const showOriginalDescription = ref(false)
|
|
|
204
204
|
<template>
|
|
205
205
|
<div
|
|
206
206
|
v-if="block && statusMeta && typeMeta"
|
|
207
|
-
|
|
207
|
+
data-testid="inspector-panel"
|
|
208
|
+
class="fixed inset-x-0 bottom-0 z-20 overflow-hidden rounded-t-2xl border border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:absolute lg:inset-x-auto lg:bottom-auto lg:right-4 lg:top-4 lg:w-80 lg:rounded-2xl"
|
|
208
209
|
>
|
|
209
210
|
<div class="h-1.5 w-full" :style="{ backgroundColor: statusMeta.color }" />
|
|
210
211
|
<!-- A tall task (execution steps + scenarios + docs) can overflow the
|
|
211
212
|
viewport; cap the body height and let it scroll so the lower controls
|
|
212
|
-
(Run / Focus / Delete) stay reachable. The status bar above stays put.
|
|
213
|
-
|
|
213
|
+
(Run / Focus / Delete) stay reachable. The status bar above stays put.
|
|
214
|
+
On compact viewports the panel is a bottom sheet capped to the visible
|
|
215
|
+
height (dvh excludes mobile browser chrome). -->
|
|
216
|
+
<div class="max-h-[80dvh] space-y-4 overflow-y-auto p-4 lg:max-h-[calc(100vh-5rem)]">
|
|
214
217
|
<!-- header -->
|
|
215
218
|
<div class="flex items-start justify-between gap-2">
|
|
216
219
|
<div class="flex items-center gap-2">
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useBreakpoints, breakpointsTailwind, useMediaQuery } from '@vueuse/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Single source of truth for responsive / input-modality decisions across the SPA.
|
|
5
|
+
*
|
|
6
|
+
* `isCompact` is the app-wide "mobile/compact" flag: true below Tailwind's `lg`
|
|
7
|
+
* (1024px), matching the breakpoint the already-responsive surfaces use
|
|
8
|
+
* (`AgentStepDetail`, the review windows) so the whole shell stays consistent.
|
|
9
|
+
*
|
|
10
|
+
* `isTouch` reports a coarse pointer (phones/tablets) so a component can enlarge
|
|
11
|
+
* hit targets without affecting precise-pointer (mouse) desktops — orthogonal to
|
|
12
|
+
* width, since a small window on a desktop is compact but not touch.
|
|
13
|
+
*/
|
|
14
|
+
export function useViewport() {
|
|
15
|
+
const breakpoints = useBreakpoints(breakpointsTailwind)
|
|
16
|
+
const isCompact = breakpoints.smaller('lg')
|
|
17
|
+
const isTouch = useMediaQuery('(pointer: coarse)')
|
|
18
|
+
return { isCompact, isTouch }
|
|
19
|
+
}
|
package/app/pages/index.vue
CHANGED
|
@@ -231,6 +231,20 @@ watch(
|
|
|
231
231
|
<SideBar />
|
|
232
232
|
<main class="relative min-w-0 flex-1">
|
|
233
233
|
<BoardCanvas />
|
|
234
|
+
<!-- Compact-viewport navbar trigger: the SideBar is an off-canvas drawer below
|
|
235
|
+
lg, so surface a hamburger to open it. Hidden on lg+ (static sidebar). -->
|
|
236
|
+
<!-- z-30 keeps the trigger above the centered BoardToolbar (z-20), whose
|
|
237
|
+
max-width can otherwise reach this corner on the narrowest viewports. -->
|
|
238
|
+
<UButton
|
|
239
|
+
class="absolute left-3 top-3 z-30 lg:hidden"
|
|
240
|
+
icon="i-lucide-menu"
|
|
241
|
+
color="neutral"
|
|
242
|
+
variant="soft"
|
|
243
|
+
size="sm"
|
|
244
|
+
:aria-label="ui.mobileNavOpen ? $t('nav.closeMenu') : $t('nav.openMenu')"
|
|
245
|
+
data-testid="mobile-nav-toggle"
|
|
246
|
+
@click="ui.toggleMobileNav()"
|
|
247
|
+
/>
|
|
234
248
|
<BoardToolbar />
|
|
235
249
|
<SpendWarningBanner />
|
|
236
250
|
<InspectorPanel />
|
package/app/stores/ui.ts
CHANGED
|
@@ -85,6 +85,11 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
85
85
|
// Command bar (⌘K) — searchable launcher for every navbar action.
|
|
86
86
|
const commandBarOpen = ref(false)
|
|
87
87
|
|
|
88
|
+
// Mobile navigation drawer: on compact (< lg) viewports the SideBar is an
|
|
89
|
+
// off-canvas drawer toggled by a hamburger; on lg+ it is a static aside and this
|
|
90
|
+
// flag is ignored. Closed on any nav action so the board is revealed immediately.
|
|
91
|
+
const mobileNavOpen = ref(false)
|
|
92
|
+
|
|
88
93
|
// Integrations hub: a single modal listing every external system the workspace
|
|
89
94
|
// can enable/link (GitHub, Slack, document + task sources, Datadog, LLM vendors,
|
|
90
95
|
// local runners, OpenRouter). Replaces the per-integration navbar buttons; each
|
|
@@ -393,6 +398,15 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
393
398
|
function toggleCommandBar() {
|
|
394
399
|
commandBarOpen.value = !commandBarOpen.value
|
|
395
400
|
}
|
|
401
|
+
function openMobileNav() {
|
|
402
|
+
mobileNavOpen.value = true
|
|
403
|
+
}
|
|
404
|
+
function closeMobileNav() {
|
|
405
|
+
mobileNavOpen.value = false
|
|
406
|
+
}
|
|
407
|
+
function toggleMobileNav() {
|
|
408
|
+
mobileNavOpen.value = !mobileNavOpen.value
|
|
409
|
+
}
|
|
396
410
|
// Clear BOTH hub came-from markers. Every direct `open*` below calls this so that a
|
|
397
411
|
// panel opened outside the hubs never grows a dead Back control, and so switching from
|
|
398
412
|
// one hub's panel to the other's clears the stale marker.
|
|
@@ -638,6 +652,7 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
638
652
|
slackOpen,
|
|
639
653
|
fragmentLibraryOpen,
|
|
640
654
|
commandBarOpen,
|
|
655
|
+
mobileNavOpen,
|
|
641
656
|
integrationsOpen,
|
|
642
657
|
cameFromIntegrations,
|
|
643
658
|
personalSetupOpen,
|
|
@@ -707,6 +722,9 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
707
722
|
openCommandBar,
|
|
708
723
|
closeCommandBar,
|
|
709
724
|
toggleCommandBar,
|
|
725
|
+
openMobileNav,
|
|
726
|
+
closeMobileNav,
|
|
727
|
+
toggleMobileNav,
|
|
710
728
|
openIntegrations,
|
|
711
729
|
closeIntegrations,
|
|
712
730
|
openFromIntegrations,
|
package/i18n/locales/en.json
CHANGED
|
@@ -3,7 +3,34 @@
|
|
|
3
3
|
"save": "Save",
|
|
4
4
|
"cancel": "Cancel",
|
|
5
5
|
"retry": "Retry",
|
|
6
|
-
"actionFailed": "Action failed"
|
|
6
|
+
"actionFailed": "Action failed",
|
|
7
|
+
"close": "Close"
|
|
8
|
+
},
|
|
9
|
+
"nav": {
|
|
10
|
+
"menu": "Navigation menu",
|
|
11
|
+
"openMenu": "Open menu",
|
|
12
|
+
"closeMenu": "Close menu",
|
|
13
|
+
"commandBar": "Search or run a command…",
|
|
14
|
+
"create": "Create",
|
|
15
|
+
"buildPipeline": "Build a pipeline",
|
|
16
|
+
"repositories": "Repositories",
|
|
17
|
+
"addFromRepo": "Add from existing repo",
|
|
18
|
+
"bootstrapRepo": "Bootstrap repo",
|
|
19
|
+
"integrations": "Integrations",
|
|
20
|
+
"sandbox": "Sandbox",
|
|
21
|
+
"kaizen": "Kaizen",
|
|
22
|
+
"workspaceContext": "Workspace context",
|
|
23
|
+
"contextFragments": "Context fragments",
|
|
24
|
+
"configuration": "Configuration",
|
|
25
|
+
"workspaceSettings": "Workspace settings",
|
|
26
|
+
"modelConfiguration": "Model Configuration",
|
|
27
|
+
"accountSettings": "Account settings"
|
|
28
|
+
},
|
|
29
|
+
"board": {
|
|
30
|
+
"toolbar": {
|
|
31
|
+
"addService": "Add service",
|
|
32
|
+
"decisionWord": "decision | decisions"
|
|
33
|
+
}
|
|
7
34
|
},
|
|
8
35
|
"errors": {
|
|
9
36
|
"action": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.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",
|