@cat-factory/app 0.128.0 → 0.130.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.
Files changed (44) hide show
  1. package/app/components/board/AgentFailureCard.vue +3 -1
  2. package/app/components/board/AgentStopButton.vue +3 -0
  3. package/app/components/board/BoardCanvas.vue +16 -3
  4. package/app/components/board/RecurringPipelineModal.vue +7 -1
  5. package/app/components/board/nodes/BlockNode.vue +47 -41
  6. package/app/components/brainstorm/BrainstormWindow.vue +25 -5
  7. package/app/components/clarity/ClarityReviewWindow.vue +17 -4
  8. package/app/components/docs/DocInterviewWindow.vue +5 -1
  9. package/app/components/followUp/FollowUpWindow.vue +15 -1
  10. package/app/components/forkDecision/ForkDecisionWindow.vue +7 -2
  11. package/app/components/gates/GateResultView.vue +7 -1
  12. package/app/components/humanTest/HumanTestWindow.vue +11 -5
  13. package/app/components/layout/BoardSwitcher.vue +21 -12
  14. package/app/components/layout/BoardToolbar.vue +8 -4
  15. package/app/components/layout/CommandBar.vue +137 -123
  16. package/app/components/layout/NotificationsInbox.vue +5 -1
  17. package/app/components/layout/SideBar.vue +157 -115
  18. package/app/components/layout/SpendWarningBanner.vue +3 -0
  19. package/app/components/panels/InspectorPanel.vue +23 -10
  20. package/app/components/panels/inspector/TaskExecution.vue +13 -4
  21. package/app/components/prReview/PrReviewWindow.vue +7 -3
  22. package/app/components/requirements/RequirementsReviewWindow.vue +33 -5
  23. package/app/components/settings/ApiTokensPanel.vue +18 -0
  24. package/app/components/visualConfirm/VisualConfirmationWindow.vue +7 -3
  25. package/app/composables/useBlockDeletion.ts +7 -0
  26. package/app/composables/useBlockDrag.ts +5 -0
  27. package/app/composables/useFrameResize.ts +4 -0
  28. package/app/composables/useWorkspaceAccess.spec.ts +102 -0
  29. package/app/composables/useWorkspaceAccess.ts +84 -0
  30. package/app/stores/publicApiKeys.spec.ts +1 -0
  31. package/app/stores/workspace.spec.ts +19 -0
  32. package/app/stores/workspace.ts +27 -7
  33. package/app/types/domain.ts +6 -0
  34. package/i18n/locales/de.json +7 -0
  35. package/i18n/locales/en.json +10 -0
  36. package/i18n/locales/es.json +7 -0
  37. package/i18n/locales/fr.json +7 -0
  38. package/i18n/locales/he.json +7 -0
  39. package/i18n/locales/it.json +7 -0
  40. package/i18n/locales/ja.json +7 -0
  41. package/i18n/locales/pl.json +7 -0
  42. package/i18n/locales/tr.json +7 -0
  43. package/i18n/locales/uk.json +7 -0
  44. package/package.json +2 -2
@@ -23,6 +23,7 @@ const slack = useSlackStore()
23
23
  const documents = useDocumentsStore()
24
24
  const tasks = useTasksStore()
25
25
  const library = useFragmentLibraryStore()
26
+ const access = useWorkspaceAccess()
26
27
 
27
28
  const open = computed({
28
29
  get: () => ui.commandBarOpen,
@@ -42,17 +43,21 @@ const commands = computed<Command[]>(() => {
42
43
  const groupAccount = t('layout.commandBar.groups.account')
43
44
 
44
45
  // ---- Create -------------------------------------------------------------
45
- list.push({
46
- id: 'new-pipeline',
47
- label: t('layout.commandBar.cmd.newPipeline'),
48
- group: groupCreate,
49
- icon: 'i-lucide-workflow',
50
- keywords: t('layout.commandBar.keywords.newPipeline'),
51
- run: () => ui.openBuilder(),
52
- })
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
+ }
53
58
 
54
59
  // ---- Repositories -------------------------------------------------------
55
- if (github.available) {
60
+ if (github.available && access.canWriteBoard.value) {
56
61
  list.push({
57
62
  id: 'add-from-repo',
58
63
  label: t('layout.commandBar.cmd.addFromRepo'),
@@ -62,132 +67,139 @@ const commands = computed<Command[]>(() => {
62
67
  run: () => ui.openAddService(),
63
68
  })
64
69
  }
65
- list.push({
66
- id: 'bootstrap-repo',
67
- label: t('layout.commandBar.cmd.bootstrapRepo'),
68
- group: groupRepositories,
69
- icon: 'i-lucide-git-branch-plus',
70
- keywords: t('layout.commandBar.keywords.bootstrapRepo'),
71
- run: () => ui.openBootstrap(),
72
- })
73
-
74
- // ---- Integrations -------------------------------------------------------
75
- if (github.available) {
76
- list.push({
77
- id: 'github',
78
- label: github.connected
79
- ? t('layout.commandBar.cmd.githubManage')
80
- : t('layout.commandBar.cmd.githubConnect'),
81
- group: groupIntegrations,
82
- icon: 'i-lucide-github',
83
- keywords: t('layout.commandBar.keywords.github'),
84
- run: () => ui.openGitHub(),
85
- })
86
- }
87
- if (slack.available) {
70
+ if (access.canManageIntegrations.value) {
88
71
  list.push({
89
- id: 'slack',
90
- label: slack.connected
91
- ? t('layout.commandBar.cmd.slackManage')
92
- : t('layout.commandBar.cmd.slackConnect'),
93
- group: groupIntegrations,
94
- icon: 'i-lucide-slack',
95
- keywords: t('layout.commandBar.keywords.slack'),
96
- run: () => ui.openSlack(),
72
+ id: 'bootstrap-repo',
73
+ label: t('layout.commandBar.cmd.bootstrapRepo'),
74
+ group: groupRepositories,
75
+ icon: 'i-lucide-git-branch-plus',
76
+ keywords: t('layout.commandBar.keywords.bootstrapRepo'),
77
+ run: () => ui.openBootstrap(),
97
78
  })
98
79
  }
99
- if (documents.available) {
100
- for (const src of documents.sources) {
80
+
81
+ // ---- Integrations (connection management `integrations.manage`) --------
82
+ if (access.canManageIntegrations.value) {
83
+ if (github.available) {
101
84
  list.push({
102
- id: `doc-connect-${src.source}`,
103
- label: documents.isConnected(src.source)
104
- ? t('layout.commandBar.cmd.sourceManage', { source: src.label })
105
- : t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
85
+ id: 'github',
86
+ label: github.connected
87
+ ? t('layout.commandBar.cmd.githubManage')
88
+ : t('layout.commandBar.cmd.githubConnect'),
106
89
  group: groupIntegrations,
107
- icon: src.icon,
108
- keywords: t('layout.commandBar.keywords.documentSource'),
109
- run: () => ui.openDocumentConnect(src.source),
90
+ icon: 'i-lucide-github',
91
+ keywords: t('layout.commandBar.keywords.github'),
92
+ run: () => ui.openGitHub(),
110
93
  })
111
94
  }
112
- if (documents.anyConnected) {
95
+ if (slack.available) {
113
96
  list.push({
114
- id: 'doc-import',
115
- label: t('layout.commandBar.cmd.documentImport'),
97
+ id: 'slack',
98
+ label: slack.connected
99
+ ? t('layout.commandBar.cmd.slackManage')
100
+ : t('layout.commandBar.cmd.slackConnect'),
116
101
  group: groupIntegrations,
117
- icon: 'i-lucide-file-down',
118
- keywords: t('layout.commandBar.keywords.documentImport'),
119
- run: () => ui.openDocumentImport(null),
102
+ icon: 'i-lucide-slack',
103
+ keywords: t('layout.commandBar.keywords.slack'),
104
+ run: () => ui.openSlack(),
120
105
  })
121
106
  }
122
- }
123
- if (tasks.available) {
124
- for (const src of tasks.sources) {
125
- list.push({
126
- id: `task-connect-${src.source}`,
127
- label: src.available
128
- ? t('layout.commandBar.cmd.sourceManage', { source: src.label })
129
- : t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
130
- group: groupIntegrations,
131
- icon: src.icon,
132
- keywords: t('layout.commandBar.keywords.taskSource'),
133
- run: () => ui.openTaskConnect(src.source),
134
- })
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
+ }
135
130
  }
136
- if (tasks.anyOffered) {
137
- list.push({
138
- id: 'task-import',
139
- label: t('layout.commandBar.cmd.taskImport'),
140
- group: groupIntegrations,
141
- icon: 'i-lucide-file-down',
142
- keywords: t('layout.commandBar.keywords.taskImport'),
143
- run: () => ui.openTaskImport(null),
144
- })
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
+ }
145
154
  }
146
155
  }
147
156
 
148
157
  // ---- Workspace ----------------------------------------------------------
149
- if (library.available) {
158
+ // Workspace + model configuration and the fragment library are `settings.manage`.
159
+ if (access.canManageSettings.value) {
160
+ if (library.available) {
161
+ list.push({
162
+ id: 'fragments',
163
+ label: t('layout.commandBar.cmd.fragments'),
164
+ group: groupWorkspace,
165
+ icon: 'i-lucide-book-marked',
166
+ keywords: t('layout.commandBar.keywords.fragments'),
167
+ run: () => ui.openFragmentLibrary(),
168
+ })
169
+ }
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
+ })
150
178
  list.push({
151
- id: 'fragments',
152
- label: t('layout.commandBar.cmd.fragments'),
179
+ id: 'workspace-settings',
180
+ label: t('layout.commandBar.cmd.workspaceSettings'),
153
181
  group: groupWorkspace,
154
- icon: 'i-lucide-book-marked',
155
- keywords: t('layout.commandBar.keywords.fragments'),
156
- run: () => ui.openFragmentLibrary(),
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'),
157
201
  })
158
202
  }
159
- list.push({
160
- id: 'merge-thresholds',
161
- label: t('layout.commandBar.cmd.mergeThresholds'),
162
- group: groupWorkspace,
163
- icon: 'i-lucide-git-merge',
164
- keywords: t('layout.commandBar.keywords.mergeThresholds'),
165
- run: () => ui.openWorkspaceSettings('merge'),
166
- })
167
- list.push({
168
- id: 'workspace-settings',
169
- label: t('layout.commandBar.cmd.workspaceSettings'),
170
- group: groupWorkspace,
171
- icon: 'i-lucide-sliders-horizontal',
172
- keywords: t('layout.commandBar.keywords.workspaceSettings'),
173
- run: () => ui.openWorkspaceSettings(),
174
- })
175
- list.push({
176
- id: 'model-configuration',
177
- label: t('layout.commandBar.cmd.modelConfiguration'),
178
- group: groupWorkspace,
179
- icon: 'i-lucide-cpu',
180
- keywords: t('layout.commandBar.keywords.modelConfiguration'),
181
- run: () => ui.openModelConfig(),
182
- })
183
- list.push({
184
- id: 'service-fragment-defaults',
185
- label: t('layout.commandBar.cmd.serviceFragmentDefaults'),
186
- group: groupWorkspace,
187
- icon: 'i-lucide-book-open-check',
188
- keywords: t('layout.commandBar.keywords.serviceFragmentDefaults'),
189
- run: () => ui.openWorkspaceSettings('fragments'),
190
- })
191
203
  list.push({
192
204
  id: 'account-settings',
193
205
  label: t('layout.commandBar.cmd.accountSettings'),
@@ -204,14 +216,16 @@ const commands = computed<Command[]>(() => {
204
216
  keywords: t('layout.commandBar.keywords.localModels'),
205
217
  run: () => ui.openLocalModels(),
206
218
  })
207
- list.push({
208
- id: 'sandbox',
209
- label: t('layout.commandBar.cmd.sandbox'),
210
- group: groupWorkspace,
211
- icon: 'i-lucide-flask-conical',
212
- keywords: t('layout.commandBar.keywords.sandbox'),
213
- run: () => ui.openSandbox(),
214
- })
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
+ }
215
229
  list.push({
216
230
  id: 'keyboard-shortcuts',
217
231
  label: t('layout.commandBar.cmd.shortcuts'),
@@ -11,6 +11,7 @@ const { t, te } = useI18n()
11
11
 
12
12
  const notifications = useNotificationsStore()
13
13
  const ui = useUiStore()
14
+ const access = useWorkspaceAccess()
14
15
  const execution = useExecutionStore()
15
16
  const toast = useToast()
16
17
 
@@ -328,6 +329,8 @@ function revealDecision(n: Notification) {
328
329
  variant="soft"
329
330
  size="xs"
330
331
  :loading="busy === n.id"
332
+ :disabled="!access.canExecuteRuns.value"
333
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
331
334
  @click="act(n)"
332
335
  >
333
336
  {{ actionLabel(n) }}
@@ -337,7 +340,8 @@ function revealDecision(n: Notification) {
337
340
  color="neutral"
338
341
  variant="ghost"
339
342
  size="xs"
340
- :disabled="busy === n.id"
343
+ :disabled="busy === n.id || !access.canExecuteRuns.value"
344
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
341
345
  @click="dismiss(n)"
342
346
  >
343
347
  {{ t('layout.notifications.dismiss') }}