@cat-factory/app 0.131.0 → 0.133.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.
- package/app/components/layout/BoardToolbar.vue +18 -0
- package/app/components/layout/CommandBar.vue +95 -182
- package/app/components/layout/SideBar.vue +18 -266
- package/app/composables/useNavContributions.ts +75 -0
- package/app/modular/nav-contributions.spec.ts +182 -0
- package/app/modular/nav-contributions.ts +483 -0
- package/app/modular/nav-gates.ts +63 -0
- package/app/modular/registry.spec.ts +76 -0
- package/app/modular/registry.ts +93 -0
- package/app/plugins/modular.client.ts +40 -0
- package/app/utils/modular.ts +11 -0
- package/package.json +7 -2
|
@@ -5,6 +5,11 @@ import IconButton from '~/components/common/IconButton.vue'
|
|
|
5
5
|
|
|
6
6
|
const ui = useUiStore()
|
|
7
7
|
const board = useBoardStore()
|
|
8
|
+
// Toolbar contributions from the shared nav manifest (docs/initiatives/modular-vue-adoption.md,
|
|
9
|
+
// slice 1). First-party contributes none — this is the reactive extension point a consumer
|
|
10
|
+
// deployment uses to add a board-toolbar action via `registerAppModule`, gated + rendered like
|
|
11
|
+
// the sidebar/command entries with zero edits here.
|
|
12
|
+
const { toolbarItems, invoke: invokeNav } = useNavContributions()
|
|
8
13
|
const execution = useExecutionStore()
|
|
9
14
|
const workspace = useWorkspaceStore()
|
|
10
15
|
const workspaceSettings = useWorkspaceSettingsStore()
|
|
@@ -233,6 +238,19 @@ const decisionItems = computed(() =>
|
|
|
233
238
|
</UButton>
|
|
234
239
|
</UDropdownMenu>
|
|
235
240
|
|
|
241
|
+
<!-- consumer-contributed toolbar actions from the nav manifest (none first-party) -->
|
|
242
|
+
<IconButton
|
|
243
|
+
v-for="item in toolbarItems"
|
|
244
|
+
:key="item.id"
|
|
245
|
+
:label="t(item.labelKey)"
|
|
246
|
+
:icon="item.icon"
|
|
247
|
+
color="neutral"
|
|
248
|
+
variant="ghost"
|
|
249
|
+
size="sm"
|
|
250
|
+
:data-testid="item.testId"
|
|
251
|
+
@click="invokeNav(item)"
|
|
252
|
+
/>
|
|
253
|
+
|
|
236
254
|
<!-- human-actionable notifications (merge review, pipeline complete, CI failed) -->
|
|
237
255
|
<NotificationsInbox />
|
|
238
256
|
|
|
@@ -25,6 +25,13 @@ const tasks = useTasksStore()
|
|
|
25
25
|
const library = useFragmentLibraryStore()
|
|
26
26
|
const access = useWorkspaceAccess()
|
|
27
27
|
|
|
28
|
+
// The static destination catalog + its RBAC/availability gating now comes from
|
|
29
|
+
// the shared nav manifest (docs/initiatives/modular-vue-adoption.md, slice 1),
|
|
30
|
+
// rendered here as command entries. The DYNAMIC per-connection commands below
|
|
31
|
+
// (github/slack/doc/task connect + import) stay local to the palette — they vary
|
|
32
|
+
// per live connection, so they are not part of the static manifest this slice.
|
|
33
|
+
const { commandGroups, invoke } = useNavContributions()
|
|
34
|
+
|
|
28
35
|
const open = computed({
|
|
29
36
|
get: () => ui.commandBarOpen,
|
|
30
37
|
set: (v: boolean) => (v ? ui.openCommandBar() : ui.closeCommandBar()),
|
|
@@ -33,211 +40,117 @@ const open = computed({
|
|
|
33
40
|
const query = ref('')
|
|
34
41
|
const activeIndex = ref(0)
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
// The per-connection integration commands the manifest deliberately doesn't
|
|
44
|
+
// carry: their label (connect vs manage) and set (one per document/task source)
|
|
45
|
+
// depend on live connection state. Gated by `integrations.manage`, they render
|
|
46
|
+
// under the palette's Integrations group.
|
|
47
|
+
const dynamicIntegrationCommands = computed<Command[]>(() => {
|
|
48
|
+
if (!access.canManageIntegrations.value) return []
|
|
41
49
|
const groupIntegrations = t('layout.commandBar.groups.integrations')
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// ---- Create -------------------------------------------------------------
|
|
46
|
-
// Command entries mirror the SideBar nav gating: each is listed only when the caller
|
|
47
|
-
// holds the permission its action's writes require (dev-open ⇒ every `can*` is true).
|
|
48
|
-
if (access.canWriteBoard.value) {
|
|
49
|
-
list.push({
|
|
50
|
-
id: 'new-pipeline',
|
|
51
|
-
label: t('layout.commandBar.cmd.newPipeline'),
|
|
52
|
-
group: groupCreate,
|
|
53
|
-
icon: 'i-lucide-workflow',
|
|
54
|
-
keywords: t('layout.commandBar.keywords.newPipeline'),
|
|
55
|
-
run: () => ui.openBuilder(),
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---- Repositories -------------------------------------------------------
|
|
60
|
-
if (github.available && access.canWriteBoard.value) {
|
|
50
|
+
const list: Command[] = []
|
|
51
|
+
if (github.available) {
|
|
61
52
|
list.push({
|
|
62
|
-
id: '
|
|
63
|
-
label:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
id: 'github',
|
|
54
|
+
label: github.connected
|
|
55
|
+
? t('layout.commandBar.cmd.githubManage')
|
|
56
|
+
: t('layout.commandBar.cmd.githubConnect'),
|
|
57
|
+
group: groupIntegrations,
|
|
58
|
+
icon: 'i-lucide-github',
|
|
59
|
+
keywords: t('layout.commandBar.keywords.github'),
|
|
60
|
+
run: () => ui.openGitHub(),
|
|
68
61
|
})
|
|
69
62
|
}
|
|
70
|
-
if (
|
|
63
|
+
if (slack.available) {
|
|
71
64
|
list.push({
|
|
72
|
-
id: '
|
|
73
|
-
label:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
id: 'slack',
|
|
66
|
+
label: slack.connected
|
|
67
|
+
? t('layout.commandBar.cmd.slackManage')
|
|
68
|
+
: t('layout.commandBar.cmd.slackConnect'),
|
|
69
|
+
group: groupIntegrations,
|
|
70
|
+
icon: 'i-lucide-slack',
|
|
71
|
+
keywords: t('layout.commandBar.keywords.slack'),
|
|
72
|
+
run: () => ui.openSlack(),
|
|
78
73
|
})
|
|
79
74
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (access.canManageIntegrations.value) {
|
|
83
|
-
if (github.available) {
|
|
75
|
+
if (documents.available) {
|
|
76
|
+
for (const src of documents.sources) {
|
|
84
77
|
list.push({
|
|
85
|
-
id:
|
|
86
|
-
label:
|
|
87
|
-
? t('layout.commandBar.cmd.
|
|
88
|
-
: t('layout.commandBar.cmd.
|
|
78
|
+
id: `doc-connect-${src.source}`,
|
|
79
|
+
label: documents.isConnected(src.source)
|
|
80
|
+
? t('layout.commandBar.cmd.sourceManage', { source: src.label })
|
|
81
|
+
: t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
|
|
89
82
|
group: groupIntegrations,
|
|
90
|
-
icon:
|
|
91
|
-
keywords: t('layout.commandBar.keywords.
|
|
92
|
-
run: () => ui.
|
|
83
|
+
icon: src.icon,
|
|
84
|
+
keywords: t('layout.commandBar.keywords.documentSource'),
|
|
85
|
+
run: () => ui.openDocumentConnect(src.source),
|
|
93
86
|
})
|
|
94
87
|
}
|
|
95
|
-
if (
|
|
88
|
+
if (documents.anyConnected) {
|
|
96
89
|
list.push({
|
|
97
|
-
id: '
|
|
98
|
-
label:
|
|
99
|
-
? t('layout.commandBar.cmd.slackManage')
|
|
100
|
-
: t('layout.commandBar.cmd.slackConnect'),
|
|
90
|
+
id: 'doc-import',
|
|
91
|
+
label: t('layout.commandBar.cmd.documentImport'),
|
|
101
92
|
group: groupIntegrations,
|
|
102
|
-
icon: 'i-lucide-
|
|
103
|
-
keywords: t('layout.commandBar.keywords.
|
|
104
|
-
run: () => ui.
|
|
93
|
+
icon: 'i-lucide-file-down',
|
|
94
|
+
keywords: t('layout.commandBar.keywords.documentImport'),
|
|
95
|
+
run: () => ui.openDocumentImport(null),
|
|
105
96
|
})
|
|
106
97
|
}
|
|
107
|
-
if (documents.available) {
|
|
108
|
-
for (const src of documents.sources) {
|
|
109
|
-
list.push({
|
|
110
|
-
id: `doc-connect-${src.source}`,
|
|
111
|
-
label: documents.isConnected(src.source)
|
|
112
|
-
? t('layout.commandBar.cmd.sourceManage', { source: src.label })
|
|
113
|
-
: t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
|
|
114
|
-
group: groupIntegrations,
|
|
115
|
-
icon: src.icon,
|
|
116
|
-
keywords: t('layout.commandBar.keywords.documentSource'),
|
|
117
|
-
run: () => ui.openDocumentConnect(src.source),
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
if (documents.anyConnected) {
|
|
121
|
-
list.push({
|
|
122
|
-
id: 'doc-import',
|
|
123
|
-
label: t('layout.commandBar.cmd.documentImport'),
|
|
124
|
-
group: groupIntegrations,
|
|
125
|
-
icon: 'i-lucide-file-down',
|
|
126
|
-
keywords: t('layout.commandBar.keywords.documentImport'),
|
|
127
|
-
run: () => ui.openDocumentImport(null),
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (tasks.available) {
|
|
132
|
-
for (const src of tasks.sources) {
|
|
133
|
-
list.push({
|
|
134
|
-
id: `task-connect-${src.source}`,
|
|
135
|
-
label: src.available
|
|
136
|
-
? t('layout.commandBar.cmd.sourceManage', { source: src.label })
|
|
137
|
-
: t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
|
|
138
|
-
group: groupIntegrations,
|
|
139
|
-
icon: src.icon,
|
|
140
|
-
keywords: t('layout.commandBar.keywords.taskSource'),
|
|
141
|
-
run: () => ui.openTaskConnect(src.source),
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
if (tasks.anyOffered) {
|
|
145
|
-
list.push({
|
|
146
|
-
id: 'task-import',
|
|
147
|
-
label: t('layout.commandBar.cmd.taskImport'),
|
|
148
|
-
group: groupIntegrations,
|
|
149
|
-
icon: 'i-lucide-file-down',
|
|
150
|
-
keywords: t('layout.commandBar.keywords.taskImport'),
|
|
151
|
-
run: () => ui.openTaskImport(null),
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
98
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// Workspace + model configuration and the fragment library are `settings.manage`.
|
|
159
|
-
if (access.canManageSettings.value) {
|
|
160
|
-
if (library.available) {
|
|
99
|
+
if (tasks.available) {
|
|
100
|
+
for (const src of tasks.sources) {
|
|
161
101
|
list.push({
|
|
162
|
-
id:
|
|
163
|
-
label:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
102
|
+
id: `task-connect-${src.source}`,
|
|
103
|
+
label: src.available
|
|
104
|
+
? t('layout.commandBar.cmd.sourceManage', { source: src.label })
|
|
105
|
+
: t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
|
|
106
|
+
group: groupIntegrations,
|
|
107
|
+
icon: src.icon,
|
|
108
|
+
keywords: t('layout.commandBar.keywords.taskSource'),
|
|
109
|
+
run: () => ui.openTaskConnect(src.source),
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
if (tasks.anyOffered) {
|
|
113
|
+
list.push({
|
|
114
|
+
id: 'task-import',
|
|
115
|
+
label: t('layout.commandBar.cmd.taskImport'),
|
|
116
|
+
group: groupIntegrations,
|
|
117
|
+
icon: 'i-lucide-file-down',
|
|
118
|
+
keywords: t('layout.commandBar.keywords.taskImport'),
|
|
119
|
+
run: () => ui.openTaskImport(null),
|
|
168
120
|
})
|
|
169
121
|
}
|
|
170
|
-
list.push({
|
|
171
|
-
id: 'merge-thresholds',
|
|
172
|
-
label: t('layout.commandBar.cmd.mergeThresholds'),
|
|
173
|
-
group: groupWorkspace,
|
|
174
|
-
icon: 'i-lucide-git-merge',
|
|
175
|
-
keywords: t('layout.commandBar.keywords.mergeThresholds'),
|
|
176
|
-
run: () => ui.openWorkspaceSettings('merge'),
|
|
177
|
-
})
|
|
178
|
-
list.push({
|
|
179
|
-
id: 'workspace-settings',
|
|
180
|
-
label: t('layout.commandBar.cmd.workspaceSettings'),
|
|
181
|
-
group: groupWorkspace,
|
|
182
|
-
icon: 'i-lucide-sliders-horizontal',
|
|
183
|
-
keywords: t('layout.commandBar.keywords.workspaceSettings'),
|
|
184
|
-
run: () => ui.openWorkspaceSettings(),
|
|
185
|
-
})
|
|
186
|
-
list.push({
|
|
187
|
-
id: 'model-configuration',
|
|
188
|
-
label: t('layout.commandBar.cmd.modelConfiguration'),
|
|
189
|
-
group: groupWorkspace,
|
|
190
|
-
icon: 'i-lucide-cpu',
|
|
191
|
-
keywords: t('layout.commandBar.keywords.modelConfiguration'),
|
|
192
|
-
run: () => ui.openModelConfig(),
|
|
193
|
-
})
|
|
194
|
-
list.push({
|
|
195
|
-
id: 'service-fragment-defaults',
|
|
196
|
-
label: t('layout.commandBar.cmd.serviceFragmentDefaults'),
|
|
197
|
-
group: groupWorkspace,
|
|
198
|
-
icon: 'i-lucide-book-open-check',
|
|
199
|
-
keywords: t('layout.commandBar.keywords.serviceFragmentDefaults'),
|
|
200
|
-
run: () => ui.openWorkspaceSettings('fragments'),
|
|
201
|
-
})
|
|
202
|
-
}
|
|
203
|
-
list.push({
|
|
204
|
-
id: 'account-settings',
|
|
205
|
-
label: t('layout.commandBar.cmd.accountSettings'),
|
|
206
|
-
group: groupAccount,
|
|
207
|
-
icon: 'i-lucide-settings',
|
|
208
|
-
keywords: t('layout.commandBar.keywords.accountSettings'),
|
|
209
|
-
run: () => ui.openAccountSettings(),
|
|
210
|
-
})
|
|
211
|
-
list.push({
|
|
212
|
-
id: 'local-models',
|
|
213
|
-
label: t('layout.commandBar.cmd.localModels'),
|
|
214
|
-
group: groupWorkspace,
|
|
215
|
-
icon: 'i-lucide-server',
|
|
216
|
-
keywords: t('layout.commandBar.keywords.localModels'),
|
|
217
|
-
run: () => ui.openLocalModels(),
|
|
218
|
-
})
|
|
219
|
-
if (access.canManageIntegrations.value) {
|
|
220
|
-
list.push({
|
|
221
|
-
id: 'sandbox',
|
|
222
|
-
label: t('layout.commandBar.cmd.sandbox'),
|
|
223
|
-
group: groupWorkspace,
|
|
224
|
-
icon: 'i-lucide-flask-conical',
|
|
225
|
-
keywords: t('layout.commandBar.keywords.sandbox'),
|
|
226
|
-
run: () => ui.openSandbox(),
|
|
227
|
-
})
|
|
228
122
|
}
|
|
229
|
-
list.push({
|
|
230
|
-
id: 'keyboard-shortcuts',
|
|
231
|
-
label: t('layout.commandBar.cmd.shortcuts'),
|
|
232
|
-
group: groupWorkspace,
|
|
233
|
-
icon: 'i-lucide-keyboard',
|
|
234
|
-
keywords: t('layout.commandBar.keywords.shortcuts'),
|
|
235
|
-
run: () => ui.openShortcutsHelp(),
|
|
236
|
-
})
|
|
237
|
-
|
|
238
123
|
return list
|
|
239
124
|
})
|
|
240
125
|
|
|
126
|
+
const commands = computed<Command[]>(() => {
|
|
127
|
+
// Flatten the reactively-gated manifest command groups (already in canonical
|
|
128
|
+
// order: create, repositories, integrations, workspace, account), splicing the
|
|
129
|
+
// dynamic per-connection commands into the Integrations group's position.
|
|
130
|
+
const staticByGroup = new Map(commandGroups.value.map((g) => [g.group, g]))
|
|
131
|
+
const asCommand = (g: (typeof commandGroups.value)[number]): Command[] =>
|
|
132
|
+
g.items.map((ci) => ({
|
|
133
|
+
id: ci.item.id,
|
|
134
|
+
label: t(ci.labelKey),
|
|
135
|
+
group: t(g.labelKey),
|
|
136
|
+
icon: ci.item.icon,
|
|
137
|
+
keywords: ci.keywordsKey ? t(ci.keywordsKey) : undefined,
|
|
138
|
+
run: () => invoke(ci.item),
|
|
139
|
+
}))
|
|
140
|
+
const groupOrEmpty = (name: (typeof commandGroups.value)[number]['group']) => {
|
|
141
|
+
const g = staticByGroup.get(name)
|
|
142
|
+
return g ? asCommand(g) : []
|
|
143
|
+
}
|
|
144
|
+
return [
|
|
145
|
+
...groupOrEmpty('create'),
|
|
146
|
+
...groupOrEmpty('repositories'),
|
|
147
|
+
...groupOrEmpty('integrations'),
|
|
148
|
+
...dynamicIntegrationCommands.value,
|
|
149
|
+
...groupOrEmpty('workspace'),
|
|
150
|
+
...groupOrEmpty('account'),
|
|
151
|
+
]
|
|
152
|
+
})
|
|
153
|
+
|
|
241
154
|
const filtered = computed<Command[]>(() => {
|
|
242
155
|
const q = query.value.trim().toLowerCase()
|
|
243
156
|
if (!q) return commands.value
|
|
@@ -19,53 +19,16 @@ const github = useGitHubStore()
|
|
|
19
19
|
const slack = useSlackStore()
|
|
20
20
|
const library = useFragmentLibraryStore()
|
|
21
21
|
const workspace = useWorkspaceStore()
|
|
22
|
-
const accounts = useAccountsStore()
|
|
23
|
-
const auth = useAuthStore()
|
|
24
22
|
const providerConnections = useProviderConnectionsStore()
|
|
25
23
|
const ui = useUiStore()
|
|
26
|
-
const access = useWorkspaceAccess()
|
|
27
24
|
|
|
28
|
-
// The
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
// (create/repos) needs `board.write`; connections/infra/sandbox/bootstrap need
|
|
36
|
-
// `integrations.manage`; workspace + model configuration and the fragment library need
|
|
37
|
-
// `settings.manage`. Kaizen and account settings are reads / account-scoped and stay.
|
|
38
|
-
// Absent access (dev-open) ⇒ every `can*` is true, so nothing is hidden.
|
|
39
|
-
const showCreate = computed(() => access.canWriteBoard.value)
|
|
40
|
-
const showAddFromRepo = computed(() => github.available && access.canWriteBoard.value)
|
|
41
|
-
const showBootstrap = computed(() => access.canManageIntegrations.value)
|
|
42
|
-
const showRepositories = computed(() => showAddFromRepo.value || showBootstrap.value)
|
|
43
|
-
const showIntegrationsHub = computed(() => access.canManageIntegrations.value)
|
|
44
|
-
const showSandbox = computed(() => access.canManageIntegrations.value)
|
|
45
|
-
// The Configuration section carries workspace/model settings (`settings.manage`) AND the
|
|
46
|
-
// account-scoped account-settings / operator-dashboard entries, so it shows when the caller
|
|
47
|
-
// can manage settings OR accounts are enabled (the account entries have their own gates).
|
|
48
|
-
const showConfiguration = computed(() => access.canManageSettings.value || accounts.enabled)
|
|
49
|
-
|
|
50
|
-
// The Infrastructure menu (agent-container execution + test environments) shows whenever the
|
|
51
|
-
// deployment reports its infrastructure capability — every facade populates `auth.infrastructure`
|
|
52
|
-
// (it drives the execution-backend selector), so there is always an execution + test-env backend
|
|
53
|
-
// to view, even on a Worker/Node deployment with no runner-pool/environment connection registered.
|
|
54
|
-
// The old provider-availability/local-mode signals stay as a defensive fallback for a backend that
|
|
55
|
-
// (somehow) omits the descriptor.
|
|
56
|
-
const showInfrastructure = computed(
|
|
57
|
-
() =>
|
|
58
|
-
// Provisioning/managing infrastructure is `integrations.manage`, so a member/viewer
|
|
59
|
-
// never sees the section (they'd only 403 on the writes inside it).
|
|
60
|
-
access.canManageIntegrations.value &&
|
|
61
|
-
(auth.infrastructure != null ||
|
|
62
|
-
auth.localMode?.enabled === true ||
|
|
63
|
-
providerConnections.isAvailable('runner-pool') ||
|
|
64
|
-
providerConnections.isAvailable('environment')),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// The prompt-fragment library is a `settings.manage` authoring surface.
|
|
68
|
-
const showWorkspaceContext = computed(() => library.available && access.canManageSettings.value)
|
|
25
|
+
// The nav catalog + its reactive RBAC/availability gating now lives in the shared
|
|
26
|
+
// modular-vue manifest (docs/initiatives/modular-vue-adoption.md, slice 1): every
|
|
27
|
+
// destination is declared once in `nav-contributions.ts`, gated by `navSlotFilter`
|
|
28
|
+
// over a reactive `gates` service, and rendered here (and in CommandBar / BoardToolbar)
|
|
29
|
+
// from `useReactiveSlots`. Sections + items appear/disappear reactively as a permission
|
|
30
|
+
// or connection flips, so this shell no longer hand-rolls per-item `show*` computeds.
|
|
31
|
+
const { sidebarGroups, invoke } = useNavContributions()
|
|
69
32
|
|
|
70
33
|
// `isCompact` (< lg) is the breakpoint at which the navbar is an off-canvas drawer;
|
|
71
34
|
// above it the aside is static and the drawer flag is inert.
|
|
@@ -191,240 +154,29 @@ watch(
|
|
|
191
154
|
<UKbd value="⌘K" />
|
|
192
155
|
</button>
|
|
193
156
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{{ t('nav.create') }}
|
|
199
|
-
</h2>
|
|
200
|
-
<div class="space-y-1.5">
|
|
201
|
-
<UButton
|
|
202
|
-
block
|
|
203
|
-
color="primary"
|
|
204
|
-
variant="soft"
|
|
205
|
-
size="sm"
|
|
206
|
-
icon="i-lucide-workflow"
|
|
207
|
-
class="justify-start"
|
|
208
|
-
data-testid="nav-build-pipeline"
|
|
209
|
-
@click="ui.openBuilder()"
|
|
210
|
-
>
|
|
211
|
-
{{ t('nav.buildPipeline') }}
|
|
212
|
-
</UButton>
|
|
213
|
-
</div>
|
|
214
|
-
</section>
|
|
215
|
-
</template>
|
|
216
|
-
|
|
217
|
-
<template v-if="showRepositories">
|
|
218
|
-
<USeparator />
|
|
219
|
-
<section>
|
|
220
|
-
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
221
|
-
{{ t('nav.repositories') }}
|
|
222
|
-
</h2>
|
|
223
|
-
<div class="space-y-1.5">
|
|
224
|
-
<UButton
|
|
225
|
-
v-if="showAddFromRepo"
|
|
226
|
-
block
|
|
227
|
-
color="primary"
|
|
228
|
-
variant="soft"
|
|
229
|
-
size="sm"
|
|
230
|
-
icon="i-lucide-folder-git-2"
|
|
231
|
-
class="justify-start"
|
|
232
|
-
data-testid="nav-add-from-repo"
|
|
233
|
-
@click="ui.openAddService()"
|
|
234
|
-
>
|
|
235
|
-
{{ t('nav.addFromRepo') }}
|
|
236
|
-
</UButton>
|
|
237
|
-
<UButton
|
|
238
|
-
v-if="showBootstrap"
|
|
239
|
-
block
|
|
240
|
-
color="primary"
|
|
241
|
-
variant="soft"
|
|
242
|
-
size="sm"
|
|
243
|
-
icon="i-lucide-git-branch-plus"
|
|
244
|
-
class="justify-start"
|
|
245
|
-
data-testid="nav-bootstrap-repo"
|
|
246
|
-
@click="ui.openBootstrap()"
|
|
247
|
-
>
|
|
248
|
-
{{ t('nav.bootstrapRepo') }}
|
|
249
|
-
</UButton>
|
|
250
|
-
</div>
|
|
251
|
-
</section>
|
|
252
|
-
</template>
|
|
253
|
-
|
|
254
|
-
<USeparator />
|
|
255
|
-
|
|
256
|
-
<section>
|
|
257
|
-
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
258
|
-
{{ t('nav.integrations') }}
|
|
259
|
-
</h2>
|
|
260
|
-
<div class="space-y-1.5">
|
|
261
|
-
<!-- Every external system the workspace can enable/link now lives behind
|
|
262
|
-
this single button — the hub modal lists them grouped (source control,
|
|
263
|
-
communication, documents, trackers, observability, model providers). -->
|
|
264
|
-
<UButton
|
|
265
|
-
v-if="showIntegrationsHub"
|
|
266
|
-
block
|
|
267
|
-
color="primary"
|
|
268
|
-
variant="soft"
|
|
269
|
-
size="sm"
|
|
270
|
-
icon="i-lucide-blocks"
|
|
271
|
-
class="justify-start"
|
|
272
|
-
data-testid="nav-integrations"
|
|
273
|
-
@click="ui.openIntegrations()"
|
|
274
|
-
>
|
|
275
|
-
{{ t('nav.integrations') }}
|
|
276
|
-
</UButton>
|
|
277
|
-
<!-- The Sandbox: try prompt versions/models against graded fixtures, off to the
|
|
278
|
-
side of the board. Opens the on-demand testing window. -->
|
|
279
|
-
<UButton
|
|
280
|
-
v-if="showSandbox"
|
|
281
|
-
block
|
|
282
|
-
color="primary"
|
|
283
|
-
variant="soft"
|
|
284
|
-
size="sm"
|
|
285
|
-
icon="i-lucide-flask-conical"
|
|
286
|
-
class="justify-start"
|
|
287
|
-
@click="ui.openSandbox()"
|
|
288
|
-
>
|
|
289
|
-
{{ t('nav.sandbox') }}
|
|
290
|
-
</UButton>
|
|
291
|
-
<!-- The Kaizen screen: grading history + verified prompt/agent/model combos. A
|
|
292
|
-
read surface (grading history), so it stays visible to every resolved role. -->
|
|
293
|
-
<UButton
|
|
294
|
-
block
|
|
295
|
-
color="primary"
|
|
296
|
-
variant="soft"
|
|
297
|
-
size="sm"
|
|
298
|
-
icon="i-lucide-sparkles"
|
|
299
|
-
class="justify-start"
|
|
300
|
-
@click="ui.openKaizen()"
|
|
301
|
-
>
|
|
302
|
-
{{ t('nav.kaizen') }}
|
|
303
|
-
</UButton>
|
|
304
|
-
</div>
|
|
305
|
-
</section>
|
|
306
|
-
|
|
307
|
-
<template v-if="showInfrastructure">
|
|
308
|
-
<USeparator />
|
|
309
|
-
<section>
|
|
310
|
-
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
311
|
-
{{ t('nav.infrastructure') }}
|
|
312
|
-
</h2>
|
|
313
|
-
<div class="space-y-1.5">
|
|
314
|
-
<!-- Where agent containers run + Tester environments + (local mode) the warm
|
|
315
|
-
pool/checkout reuse. Its own top-level destination, no longer inside the
|
|
316
|
-
Integrations hub. -->
|
|
317
|
-
<UButton
|
|
318
|
-
block
|
|
319
|
-
color="primary"
|
|
320
|
-
variant="soft"
|
|
321
|
-
size="sm"
|
|
322
|
-
icon="i-lucide-server-cog"
|
|
323
|
-
class="justify-start"
|
|
324
|
-
data-testid="nav-infrastructure"
|
|
325
|
-
@click="ui.openInfrastructure()"
|
|
326
|
-
>
|
|
327
|
-
{{ t('nav.infrastructure') }}
|
|
328
|
-
</UButton>
|
|
329
|
-
<!-- Guided detect → review → preflight → save flow for a service frame's
|
|
330
|
-
docker-compose provisioning (so the single Deployer stands its env up). -->
|
|
331
|
-
<UButton
|
|
332
|
-
block
|
|
333
|
-
color="primary"
|
|
334
|
-
variant="soft"
|
|
335
|
-
size="sm"
|
|
336
|
-
icon="i-lucide-flask-conical"
|
|
337
|
-
class="justify-start"
|
|
338
|
-
data-testid="nav-environment-setup"
|
|
339
|
-
@click="ui.openEnvironmentSetup()"
|
|
340
|
-
>
|
|
341
|
-
{{ t('nav.environmentSetup') }}
|
|
342
|
-
</UButton>
|
|
343
|
-
</div>
|
|
344
|
-
</section>
|
|
345
|
-
</template>
|
|
346
|
-
|
|
347
|
-
<template v-if="showWorkspaceContext">
|
|
348
|
-
<USeparator />
|
|
349
|
-
<section>
|
|
350
|
-
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
351
|
-
{{ t('nav.workspaceContext') }}
|
|
352
|
-
</h2>
|
|
353
|
-
<UButton
|
|
354
|
-
block
|
|
355
|
-
color="primary"
|
|
356
|
-
variant="soft"
|
|
357
|
-
size="sm"
|
|
358
|
-
icon="i-lucide-book-marked"
|
|
359
|
-
class="justify-start"
|
|
360
|
-
@click="ui.openFragmentLibrary()"
|
|
361
|
-
>
|
|
362
|
-
{{ t('nav.contextFragments') }}
|
|
363
|
-
</UButton>
|
|
364
|
-
</section>
|
|
365
|
-
</template>
|
|
366
|
-
|
|
367
|
-
<template v-if="showConfiguration">
|
|
157
|
+
<!-- Sections + items come from the shared nav manifest, already gated by the
|
|
158
|
+
reactive slotFilter (docs/initiatives/modular-vue-adoption.md, slice 1). An
|
|
159
|
+
empty section is dropped upstream, so there is no per-section `v-if` here. -->
|
|
160
|
+
<template v-for="section in sidebarGroups" :key="section.group">
|
|
368
161
|
<USeparator />
|
|
369
162
|
<section>
|
|
370
163
|
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
371
|
-
{{ t(
|
|
164
|
+
{{ t(section.labelKey) }}
|
|
372
165
|
</h2>
|
|
373
166
|
<div class="space-y-1.5">
|
|
374
|
-
<!-- Merge thresholds, issue writeback and default service best practices are
|
|
375
|
-
now tabs inside Workspace settings. `settings.manage` — hidden for members/viewers. -->
|
|
376
|
-
<UButton
|
|
377
|
-
v-if="access.canManageSettings.value"
|
|
378
|
-
block
|
|
379
|
-
color="primary"
|
|
380
|
-
variant="soft"
|
|
381
|
-
size="sm"
|
|
382
|
-
icon="i-lucide-sliders-horizontal"
|
|
383
|
-
class="justify-start"
|
|
384
|
-
data-testid="nav-workspace-settings"
|
|
385
|
-
@click="ui.openWorkspaceSettings()"
|
|
386
|
-
>
|
|
387
|
-
{{ t('nav.workspaceSettings') }}
|
|
388
|
-
</UButton>
|
|
389
|
-
<UButton
|
|
390
|
-
v-if="access.canManageSettings.value"
|
|
391
|
-
block
|
|
392
|
-
color="primary"
|
|
393
|
-
variant="soft"
|
|
394
|
-
size="sm"
|
|
395
|
-
icon="i-lucide-cpu"
|
|
396
|
-
class="justify-start"
|
|
397
|
-
@click="ui.openModelConfig()"
|
|
398
|
-
>
|
|
399
|
-
{{ t('nav.modelConfiguration') }}
|
|
400
|
-
</UButton>
|
|
401
|
-
<!-- Account & team: members + roles, invitations, email sender, account API keys.
|
|
402
|
-
Shown once accounts (auth) are enabled. -->
|
|
403
|
-
<UButton
|
|
404
|
-
v-if="accounts.enabled"
|
|
405
|
-
block
|
|
406
|
-
color="primary"
|
|
407
|
-
variant="soft"
|
|
408
|
-
size="sm"
|
|
409
|
-
icon="i-lucide-users"
|
|
410
|
-
class="justify-start"
|
|
411
|
-
@click="ui.openAccountSettings()"
|
|
412
|
-
>
|
|
413
|
-
{{ t('nav.accountSettings') }}
|
|
414
|
-
</UButton>
|
|
415
|
-
<!-- Platform observability: deployment-level run health. Admin-only, like the backend gate. -->
|
|
416
167
|
<UButton
|
|
417
|
-
v-
|
|
168
|
+
v-for="item in section.items"
|
|
169
|
+
:key="item.id"
|
|
418
170
|
block
|
|
419
171
|
color="primary"
|
|
420
172
|
variant="soft"
|
|
421
173
|
size="sm"
|
|
422
|
-
icon="
|
|
174
|
+
:icon="item.icon"
|
|
423
175
|
class="justify-start"
|
|
424
|
-
data-testid="
|
|
425
|
-
@click="
|
|
176
|
+
:data-testid="item.testId"
|
|
177
|
+
@click="invoke(item)"
|
|
426
178
|
>
|
|
427
|
-
{{ t(
|
|
179
|
+
{{ t(item.labelKey) }}
|
|
428
180
|
</UButton>
|
|
429
181
|
</div>
|
|
430
182
|
</section>
|