@cat-factory/app 0.315.3 → 0.315.6

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 (79) hide show
  1. package/README.md +25 -4
  2. package/app/components/assistant/AssistantModal.vue +3 -2
  3. package/app/components/board/AddTaskModal.vue +3 -2
  4. package/app/components/board/CreateInitiativeModal.vue +3 -2
  5. package/app/components/board/RecurringPipelineModal.vue +3 -2
  6. package/app/components/board/ReviewFrictionDialog.vue +4 -3
  7. package/app/components/bootstrap/AdoptionReviewModal.vue +2 -0
  8. package/app/components/bootstrap/BootstrapModal.vue +4 -3
  9. package/app/components/common/AppModal.vue +50 -0
  10. package/app/components/common/ConfirmDialog.vue +5 -3
  11. package/app/components/common/KeyboardShortcutsHelp.vue +6 -4
  12. package/app/components/documents/DocumentImportModal.vue +3 -2
  13. package/app/components/documents/DocumentSourceConnectModal.vue +7 -2
  14. package/app/components/documents/DocumentTemplatesModal.vue +3 -2
  15. package/app/components/documents/SpawnPreviewModal.vue +3 -2
  16. package/app/components/documents/StartFromDesignModal.vue +3 -2
  17. package/app/components/environments/EnvironmentSetupWizard.vue +2 -0
  18. package/app/components/foundational/FoundationalServicePanel.vue +2 -0
  19. package/app/components/fragments/FragmentLibraryPanel.vue +2 -0
  20. package/app/components/github/AddServiceFromRepoModal.vue +5 -4
  21. package/app/components/github/GitHubPanel.vue +4 -3
  22. package/app/components/layout/BoardSwitcher.vue +7 -2
  23. package/app/components/layout/CommandBar.vue +3 -2
  24. package/app/components/layout/IntegrationsHub.vue +3 -6
  25. package/app/components/layout/ModelProvidersHub.vue +3 -6
  26. package/app/components/layout/PersonalSetupModal.vue +3 -6
  27. package/app/components/layout/RolePrompt.vue +3 -2
  28. package/app/components/notifications/NotificationSettingsPanel.vue +3 -6
  29. package/app/components/panels/DecisionModal.vue +3 -2
  30. package/app/components/panels/InspectorPanel.vue +6 -1
  31. package/app/components/panels/inspector/ServiceTestConfig.vue +6 -4
  32. package/app/components/pipeline/AgentPromptEditor.vue +2 -0
  33. package/app/components/pipeline/PipelineBuilder.vue +11 -2
  34. package/app/components/pipeline/PipelineHealthModal.vue +3 -2
  35. package/app/components/providers/AiPresetMismatchDialog.vue +3 -6
  36. package/app/components/providers/AiProviderOnboardingModal.vue +3 -6
  37. package/app/components/providers/PersonalCredentialModal.vue +3 -2
  38. package/app/components/providers/VendorCredentialsModal.vue +3 -6
  39. package/app/components/queue/DescribeWorkModal.vue +3 -2
  40. package/app/components/sandbox/SandboxPanel.vue +2 -0
  41. package/app/components/settings/AccountSettingsPanel.vue +1 -0
  42. package/app/components/settings/ApiTokensPanel.vue +3 -2
  43. package/app/components/settings/InfrastructureWindow.vue +3 -6
  44. package/app/components/settings/LocalModelEndpointsPanel.vue +3 -6
  45. package/app/components/settings/ModelConfigurationPanel.vue +9 -1
  46. package/app/components/settings/ModelPresetHealthModal.vue +3 -2
  47. package/app/components/settings/ObservabilityConnectionPanel.vue +3 -6
  48. package/app/components/settings/OpenRouterCatalogPanel.vue +3 -2
  49. package/app/components/settings/RiskPolicyHealthModal.vue +3 -2
  50. package/app/components/settings/UserSecretsSection.vue +3 -6
  51. package/app/components/settings/WorkspaceSettingsPanel.vue +1 -0
  52. package/app/components/setup/SetupChecklistPanel.vue +3 -2
  53. package/app/components/slack/SlackPanel.vue +3 -2
  54. package/app/components/tasks/BugHuntModal.vue +2 -0
  55. package/app/components/tasks/TaskImportModal.vue +3 -2
  56. package/app/components/tasks/TaskSourceConnectModal.vue +3 -2
  57. package/app/components/theme/ThemeImportModal.vue +4 -2
  58. package/app/components/tutorial/TutorialCatalogue.vue +4 -3
  59. package/app/components/tutorial/TutorialPrompt.vue +3 -2
  60. package/app/stores/github/connection.ts +2 -4
  61. package/app/stores/github/context.ts +5 -1
  62. package/app/stores/github/projectionLoad.ts +127 -0
  63. package/app/stores/github/repoActions.ts +7 -2
  64. package/app/stores/github.spec.ts +355 -1
  65. package/app/stores/github.ts +21 -22
  66. package/app/stores/ui/builderModelConfigHandoff.spec.ts +88 -0
  67. package/app/stores/ui/modals.ts +78 -12
  68. package/app/utils/overlaySize.ts +22 -0
  69. package/i18n/locales/de.json +1 -0
  70. package/i18n/locales/en.json +1 -0
  71. package/i18n/locales/es.json +1 -0
  72. package/i18n/locales/fr.json +1 -0
  73. package/i18n/locales/he.json +1 -0
  74. package/i18n/locales/it.json +1 -0
  75. package/i18n/locales/ja.json +1 -0
  76. package/i18n/locales/pl.json +1 -0
  77. package/i18n/locales/tr.json +1 -0
  78. package/i18n/locales/uk.json +1 -0
  79. package/package.json +1 -1
@@ -2,6 +2,7 @@
2
2
  import { showOverrideField } from '~/utils/uiMode'
3
3
  import SectionLabel from '~/components/common/SectionLabel.vue'
4
4
  import EmptyState from '~/components/common/EmptyState.vue'
5
+ import AppModal from '~/components/common/AppModal.vue'
5
6
 
6
7
  // The Integrations hub: a single modal that lists the OPTIONAL external systems the WORKSPACE
7
8
  // can enable or link in — the ones that feed a run its context (source control, documents,
@@ -370,11 +371,7 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
370
371
  </script>
371
372
 
372
373
  <template>
373
- <UModal
374
- v-model:open="open"
375
- :title="t('layout.integrationsHub.title')"
376
- :ui="{ content: 'max-w-xl' }"
377
- >
374
+ <AppModal size="form" v-model:open="open" :title="t('layout.integrationsHub.title')">
378
375
  <template #body>
379
376
  <!-- Named for the same reason `model-providers-hub` is: the tutorial tour that explains
380
377
  what a connection adds to a run points at this list. -->
@@ -489,5 +486,5 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
489
486
  </section>
490
487
  </div>
491
488
  </template>
492
- </UModal>
489
+ </AppModal>
493
490
  </template>
@@ -14,6 +14,7 @@
14
14
  // hub row-for-row so the two read as siblings.
15
15
  import SectionLabel from '~/components/common/SectionLabel.vue'
16
16
  import EmptyState from '~/components/common/EmptyState.vue'
17
+ import AppModal from '~/components/common/AppModal.vue'
17
18
 
18
19
  const { t } = useI18n()
19
20
  const ui = useUiStore()
@@ -167,11 +168,7 @@ const filteredGroups = computed<ProviderGroup[]>(() => {
167
168
  </script>
168
169
 
169
170
  <template>
170
- <UModal
171
- v-model:open="open"
172
- :title="t('layout.modelProvidersHub.title')"
173
- :ui="{ content: 'max-w-xl' }"
174
- >
171
+ <AppModal size="form" v-model:open="open" :title="t('layout.modelProvidersHub.title')">
175
172
  <template #body>
176
173
  <div class="space-y-5" data-testid="model-providers-hub">
177
174
  <p class="text-xs text-muted">
@@ -245,5 +242,5 @@ const filteredGroups = computed<ProviderGroup[]>(() => {
245
242
  </section>
246
243
  </div>
247
244
  </template>
248
- </UModal>
245
+ </AppModal>
249
246
  </template>
@@ -6,6 +6,7 @@
6
6
  // existing per-panel handlers via `ui.openFromPersonal(...)`, so opening one closes this
7
7
  // hub, reveals that panel, and gives it a "Back to My setup" control (IntegrationBackTitle).
8
8
  import SectionLabel from '~/components/common/SectionLabel.vue'
9
+ import AppModal from '~/components/common/AppModal.vue'
9
10
 
10
11
  const { t } = useI18n()
11
12
  const ui = useUiStore()
@@ -107,11 +108,7 @@ const groups = computed<PersonalGroup[]>(() => {
107
108
  </script>
108
109
 
109
110
  <template>
110
- <UModal
111
- v-model:open="open"
112
- :title="t('layout.personalSetup.title')"
113
- :ui="{ content: 'max-w-xl' }"
114
- >
111
+ <AppModal size="form" v-model:open="open" :title="t('layout.personalSetup.title')">
115
112
  <template #body>
116
113
  <div class="space-y-5">
117
114
  <p class="text-xs text-muted">
@@ -153,5 +150,5 @@ const groups = computed<PersonalGroup[]>(() => {
153
150
  </section>
154
151
  </div>
155
152
  </template>
156
- </UModal>
153
+ </AppModal>
157
154
  </template>
@@ -11,6 +11,7 @@
11
11
  // next launch asks again. There is deliberately no "don't ask me again": an unanswered question
12
12
  // costs nothing here, where a wrongly-recorded answer costs a person destinations they need.
13
13
  import { ROLE_PRESENTATION, UI_ROLES, type UiRole } from '~/utils/uiRole'
14
+ import AppModal from '~/components/common/AppModal.vue'
14
15
 
15
16
  const { t } = useI18n()
16
17
  const uiRole = useUiRoleStore()
@@ -26,7 +27,7 @@ function pick(role: UiRole) {
26
27
  </script>
27
28
 
28
29
  <template>
29
- <UModal v-model:open="open" :title="t('uiRole.prompt.title')" :ui="{ content: 'max-w-lg' }">
30
+ <AppModal size="prompt" v-model:open="open" :title="t('uiRole.prompt.title')">
30
31
  <template #body>
31
32
  <div class="space-y-4" data-testid="role-prompt">
32
33
  <p class="text-sm text-toned">{{ t('uiRole.prompt.intro') }}</p>
@@ -72,5 +73,5 @@ function pick(role: UiRole) {
72
73
  </UButton>
73
74
  </div>
74
75
  </template>
75
- </UModal>
76
+ </AppModal>
76
77
  </template>
@@ -26,6 +26,7 @@ import type {
26
26
  } from '~/types/notifications'
27
27
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
28
28
  import SectionLabel from '~/components/common/SectionLabel.vue'
29
+ import AppModal from '~/components/common/AppModal.vue'
29
30
 
30
31
  const ui = useUiStore()
31
32
  const notifications = useNotificationsStore()
@@ -134,11 +135,7 @@ function resetToDefaults() {
134
135
  </script>
135
136
 
136
137
  <template>
137
- <UModal
138
- v-model:open="open"
139
- :title="t('notificationSettings.panel.title')"
140
- :ui="{ content: 'max-w-2xl' }"
141
- >
138
+ <AppModal size="form" v-model:open="open" :title="t('notificationSettings.panel.title')">
142
139
  <template #title>
143
140
  <IntegrationBackTitle :title="t('notificationSettings.panel.title')" @back="back" />
144
141
  </template>
@@ -235,5 +232,5 @@ function resetToDefaults() {
235
232
  </template>
236
233
  </div>
237
234
  </template>
238
- </UModal>
235
+ </AppModal>
239
236
  </template>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { agentKindMeta } from '~/utils/catalog'
3
+ import AppModal from '~/components/common/AppModal.vue'
3
4
 
4
5
  const execution = useExecutionStore()
5
6
  const board = useBoardStore()
@@ -52,7 +53,7 @@ async function choose(option: string) {
52
53
  </script>
53
54
 
54
55
  <template>
55
- <UModal v-model:open="open" :title="t('panels.decision.title')">
56
+ <AppModal size="prompt" v-model:open="open" :title="t('panels.decision.title')">
56
57
  <template #body>
57
58
  <div v-if="decision && agent" class="space-y-4" data-testid="decision-modal">
58
59
  <div class="flex items-center gap-2 text-sm text-muted">
@@ -97,5 +98,5 @@ async function choose(option: string) {
97
98
  </p>
98
99
  </div>
99
100
  </template>
100
- </UModal>
101
+ </AppModal>
101
102
  </template>
@@ -24,10 +24,15 @@ const recurring = useRecurringPipelinesStore()
24
24
  const requirements = useRequirementsStore()
25
25
  const access = useWorkspaceAccess()
26
26
  const { t } = useI18n()
27
+ const { present } = usePipelineErrorToast()
27
28
 
28
29
  onMounted(() => {
29
30
  fragments.ensureLoaded()
30
- github.ensureLoaded()
31
+ // The repo list behind the inspector's repo link and the service config's detect-from-repo
32
+ // lookup. Both read the store, so this one call serves them.
33
+ github.ensureLoaded().catch((error: unknown) => {
34
+ present(error, 'github.connect.errors.loadProjection')
35
+ })
31
36
  })
32
37
 
33
38
  const block = computed<Block | undefined>(() =>
@@ -21,6 +21,7 @@ import InspectorSection from '~/components/panels/inspector/InspectorSection.vue
21
21
  import ServiceSelfTests from '~/components/panels/inspector/ServiceSelfTests.vue'
22
22
  import IconButton from '~/components/common/IconButton.vue'
23
23
  import { apiErrorEnvelope } from '~/composables/api/errors'
24
+ import AppModal from '~/components/common/AppModal.vue'
24
25
 
25
26
  // Service-level (frame) configuration: the service-owned PROVISIONING — the provision
26
27
  // TYPE this service produces (`infraless` / `docker-compose` / `kubernetes` / `custom`)
@@ -49,10 +50,10 @@ const ui = useUiStore()
49
50
  const { t, te } = useI18n()
50
51
 
51
52
  // The custom-manifest-type catalog feeds the `custom` picker. Cheap + shared (coalesced).
52
- // The repo list backs the detect-from-repo affordance (owner/name lookup).
53
+ // The repo list behind the detect-from-repo affordance (owner/name lookup) is loaded by the
54
+ // surface that hosts this section: the inspector panel, or the add-service modal on open.
53
55
  onMounted(() => {
54
56
  void infra.ensureLoaded()
55
- void github.ensureLoaded()
56
57
  })
57
58
 
58
59
  // The service's declared provision type (absent ⇒ treated as `infraless`: no environment
@@ -840,7 +841,8 @@ function setSize(value: InstanceSize) {
840
841
  </div>
841
842
  </div>
842
843
 
843
- <UModal
844
+ <AppModal
845
+ size="form"
844
846
  v-model:open="browseOpen"
845
847
  :title="
846
848
  browseTarget === 'compose'
@@ -880,7 +882,7 @@ function setSize(value: InstanceSize) {
880
882
  </div>
881
883
  </div>
882
884
  </template>
883
- </UModal>
885
+ </AppModal>
884
886
 
885
887
  <!-- Provisioning hints: advisory inputs to the ephemeral-environment provisioner.
886
888
  Collapsed by default — most services never tune them. -->
@@ -170,6 +170,8 @@ function revisionLabel(revision: AgentPromptRevision): string {
170
170
  </script>
171
171
 
172
172
  <template>
173
+ <!-- workbench-width-ok: a prompt editor, up to 1024px on a large screen. Its shell waits on
174
+ #2359. -->
173
175
  <UModal
174
176
  v-model:open="open"
175
177
  :title="t('agentPrompt.title', { agent: label })"
@@ -31,6 +31,7 @@ import type { ConsensusStrategy } from '~/types/consensus'
31
31
  import SectionLabel from '~/components/common/SectionLabel.vue'
32
32
  import IconButton from '~/components/common/IconButton.vue'
33
33
  import EmptyState from '~/components/common/EmptyState.vue'
34
+ import AppModal from '~/components/common/AppModal.vue'
34
35
 
35
36
  type DraftUnit = { index: number; kind: AgentKind; companionIndex: number | null }
36
37
 
@@ -251,6 +252,8 @@ const open = computed({
251
252
  // knows whether to offer the post-release-health gate (it's loaded on demand, not from
252
253
  // the snapshot). Best-effort: a failure just leaves the gate hidden.
253
254
  onModalOpen(open, () => {
255
+ // Coming back from the model configuration, where none of this changed.
256
+ if (ui.consumeBuilderReturn()) return
254
257
  releaseHealth.load().catch(() => {})
255
258
  // The prompt-override index badges the steps whose agent no longer runs the shipped prompt.
256
259
  // Best-effort: the builder is fully usable without it, and a deployment that wires no
@@ -403,6 +406,7 @@ const { toggleArchive, toggleDefault, edit, removePipeline, clone } = usePipelin
403
406
  :title="t('pipeline.builder.title')"
404
407
  side="left"
405
408
  :ui="{ content: 'max-w-[90vw] sm:max-w-2xl lg:max-w-5xl xl:max-w-6xl' }"
409
+ @after:leave="ui.builderLeft()"
406
410
  >
407
411
  <template #body>
408
412
  <!-- On compact viewports the three sections stack and the slideover body
@@ -442,6 +446,7 @@ const { toggleArchive, toggleDefault, edit, removePipeline, clone } = usePipelin
442
446
  size="xs"
443
447
  icon="i-lucide-cpu"
444
448
  :title="t('pipeline.builder.configureModelsTooltip')"
449
+ data-testid="pipeline-builder-configure-models"
445
450
  @click="ui.openModelConfig()"
446
451
  >
447
452
  {{ t('pipeline.builder.configureModels') }}
@@ -1392,7 +1397,11 @@ const { toggleArchive, toggleDefault, edit, removePipeline, clone } = usePipelin
1392
1397
  </USlideover>
1393
1398
 
1394
1399
  <!-- Add-agent form -->
1395
- <UModal v-model:open="addAgentOpen" :title="t('pipeline.builder.addAgentModal.title')">
1400
+ <AppModal
1401
+ size="prompt"
1402
+ v-model:open="addAgentOpen"
1403
+ :title="t('pipeline.builder.addAgentModal.title')"
1404
+ >
1396
1405
  <template #body>
1397
1406
  <div class="space-y-3">
1398
1407
  <div>
@@ -1456,7 +1465,7 @@ const { toggleArchive, toggleDefault, edit, removePipeline, clone } = usePipelin
1456
1465
  </UButton>
1457
1466
  </div>
1458
1467
  </template>
1459
- </UModal>
1468
+ </AppModal>
1460
1469
 
1461
1470
  <!-- The per-workspace system-prompt editor for one agent kind. Mounted alongside the builder
1462
1471
  (not inside its slideover body) so its own modal isn't nested inside the scrolling column. -->
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import AppModal from '~/components/common/AppModal.vue'
2
3
  // Startup advisory for unhealthy pipelines. Opened once per session from the board page when
3
4
  // `usePipelineHealth` reports any issue. Lists:
4
5
  // • new built-in pipelines the workspace doesn't have yet (ADD them);
@@ -121,7 +122,7 @@ const reseedableCount = computed(
121
122
  </script>
122
123
 
123
124
  <template>
124
- <UModal v-model:open="open" :title="t('pipeline.health.title')" :ui="{ content: 'max-w-2xl' }">
125
+ <AppModal size="form" v-model:open="open" :title="t('pipeline.health.title')">
125
126
  <template #body>
126
127
  <div v-if="!hasIssues" class="py-6 text-center text-sm text-muted">
127
128
  <UIcon name="i-lucide-check-circle-2" class="mx-auto mb-2 h-8 w-8 text-app-success-400" />
@@ -331,5 +332,5 @@ const reseedableCount = computed(
331
332
  </UButton>
332
333
  </div>
333
334
  </template>
334
- </UModal>
335
+ </AppModal>
335
336
  </template>
@@ -8,6 +8,7 @@
8
8
  // fixed (or all its models become available).
9
9
  import { computed } from 'vue'
10
10
  import SectionLabel from '~/components/common/SectionLabel.vue'
11
+ import AppModal from '~/components/common/AppModal.vue'
11
12
 
12
13
  const { t } = useI18n()
13
14
  const ui = useUiStore()
@@ -36,11 +37,7 @@ function go(action: () => void) {
36
37
  </script>
37
38
 
38
39
  <template>
39
- <UModal
40
- v-model:open="open"
41
- :title="t('providers.presetMismatch.title')"
42
- :ui="{ content: 'max-w-xl' }"
43
- >
40
+ <AppModal size="prompt" v-model:open="open" :title="t('providers.presetMismatch.title')">
44
41
  <template #body>
45
42
  <div class="space-y-5">
46
43
  <i18n-t
@@ -103,5 +100,5 @@ function go(action: () => void) {
103
100
  </div>
104
101
  </div>
105
102
  </template>
106
- </UModal>
103
+ </AppModal>
107
104
  </template>
@@ -6,6 +6,7 @@
6
6
  // duplicating them). It auto-opens once per session (driven from pages/index.vue) and, like
7
7
  // the banner, disappears automatically the moment a usable source exists.
8
8
  import { computed } from 'vue'
9
+ import AppModal from '~/components/common/AppModal.vue'
9
10
 
10
11
  const { t } = useI18n()
11
12
  const ui = useUiStore()
@@ -75,11 +76,7 @@ const routes = computed<Route[]>(() => [
75
76
  </script>
76
77
 
77
78
  <template>
78
- <UModal
79
- v-model:open="open"
80
- :title="t('providers.onboarding.title')"
81
- :ui="{ content: 'max-w-2xl' }"
82
- >
79
+ <AppModal size="wide" v-model:open="open" :title="t('providers.onboarding.title')">
83
80
  <template #body>
84
81
  <div class="space-y-5">
85
82
  <div
@@ -123,5 +120,5 @@ const routes = computed<Route[]>(() => [
123
120
  </p>
124
121
  </div>
125
122
  </template>
126
- </UModal>
123
+ </AppModal>
127
124
  </template>
@@ -6,6 +6,7 @@
6
6
  // password. The copy follows the pending vendor (Claude / GLM / ChatGPT-Codex).
7
7
  import { computed, ref } from 'vue'
8
8
  import SecretInput from '~/components/common/SecretInput.vue'
9
+ import AppModal from '~/components/common/AppModal.vue'
9
10
 
10
11
  const { t } = useI18n()
11
12
  const personal = usePersonalSubscriptionsStore()
@@ -85,7 +86,7 @@ function goConnect() {
85
86
  </script>
86
87
 
87
88
  <template>
88
- <UModal v-model:open="open" :title="title" :ui="{ content: 'max-w-md' }">
89
+ <AppModal size="prompt" v-model:open="open" :title="title">
89
90
  <template #body>
90
91
  <div class="space-y-4">
91
92
  <template v-if="needsConnect">
@@ -141,5 +142,5 @@ function goConnect() {
141
142
  </template>
142
143
  </div>
143
144
  </template>
144
- </UModal>
145
+ </AppModal>
145
146
  </template>
@@ -10,6 +10,7 @@ import type { SubscriptionVendor } from '~/types/domain'
10
10
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
11
11
  import SecretInput from '~/components/common/SecretInput.vue'
12
12
  import SectionLabel from '~/components/common/SectionLabel.vue'
13
+ import AppModal from '~/components/common/AppModal.vue'
13
14
 
14
15
  const { t, n } = useI18n()
15
16
  const ui = useUiStore()
@@ -173,11 +174,7 @@ function vendorLabel(v: SubscriptionVendor): string {
173
174
  </script>
174
175
 
175
176
  <template>
176
- <UModal
177
- v-model:open="open"
178
- :title="t('providers.vendorCredentials.title')"
179
- :ui="{ content: 'max-w-2xl' }"
180
- >
177
+ <AppModal size="wide" v-model:open="open" :title="t('providers.vendorCredentials.title')">
181
178
  <template #title>
182
179
  <IntegrationBackTitle :title="t('providers.vendorCredentials.title')" @back="back" />
183
180
  </template>
@@ -337,5 +334,5 @@ function vendorLabel(v: SubscriptionVendor): string {
337
334
  </template>
338
335
  </UTabs>
339
336
  </template>
340
- </UModal>
337
+ </AppModal>
341
338
  </template>
@@ -6,6 +6,7 @@ import { splitDescribedWork } from '~/utils/describeWork'
6
6
  import { isReviewDebtConflict, reviewFrictionContext } from '~/utils/reviewFriction'
7
7
  import { pipelineAllowedForManualStart } from '~/utils/pipeline'
8
8
  import { showOverrideField } from '~/utils/uiMode'
9
+ import AppModal from '~/components/common/AppModal.vue'
9
10
 
10
11
  /**
11
12
  * The one-sentence intake (issue #2258): what needs doing, which service, feature or bug. Nothing else on the
@@ -220,7 +221,7 @@ async function submit(startNow: boolean, acknowledgeReviewDebt = false) {
220
221
  </script>
221
222
 
222
223
  <template>
223
- <UModal v-model:open="open" :title="t('describeWork.title')" :ui="{ content: 'sm:max-w-2xl' }">
224
+ <AppModal size="wide" v-model:open="open" :title="t('describeWork.title')">
224
225
  <template #body>
225
226
  <div class="space-y-4" data-testid="describe-work-modal">
226
227
  <UFormField :label="t('describeWork.textLabel')" :description="t('describeWork.textHint')">
@@ -363,5 +364,5 @@ async function submit(startNow: boolean, acknowledgeReviewDebt = false) {
363
364
  </UButton>
364
365
  </div>
365
366
  </template>
366
- </UModal>
367
+ </AppModal>
367
368
  </template>
@@ -272,6 +272,8 @@ async function archive(prompt: SandboxPromptVersion) {
272
272
  </script>
273
273
 
274
274
  <template>
275
+ <!-- workbench-width-ok: experiments, prompts and fixtures with a graded grid (1024px). Its
276
+ shell waits on #2359. -->
275
277
  <UModal
276
278
  v-model:open="open"
277
279
  :title="t('sandbox.title')"
@@ -59,6 +59,7 @@ const tabs = computed(() => [
59
59
  </script>
60
60
 
61
61
  <template>
62
+ <!-- workbench-width-ok: tabbed account settings (768px). Its shell waits on #2359. -->
62
63
  <UModal v-model:open="open" :title="t('settings.account.title')" :ui="{ content: 'max-w-3xl' }">
63
64
  <template #body>
64
65
  <EmptyState
@@ -9,6 +9,7 @@ import { showOverrideField } from '~/utils/uiMode'
9
9
  import type { PublicApiKey, PublicApiScope } from '~/types/publicApiKeys'
10
10
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
11
11
  import CopyButton from '~/components/common/CopyButton.vue'
12
+ import AppModal from '~/components/common/AppModal.vue'
12
13
 
13
14
  const { t, d } = useI18n()
14
15
 
@@ -227,7 +228,7 @@ async function revokeToken(key: PublicApiKey) {
227
228
  </script>
228
229
 
229
230
  <template>
230
- <UModal v-model:open="open" :title="t('settings.apiTokens.title')" :ui="{ content: 'max-w-lg' }">
231
+ <AppModal size="form" v-model:open="open" :title="t('settings.apiTokens.title')">
231
232
  <template #title>
232
233
  <IntegrationBackTitle :title="t('settings.apiTokens.title')" @back="back" />
233
234
  </template>
@@ -416,5 +417,5 @@ async function revokeToken(key: PublicApiKey) {
416
417
  </section>
417
418
  </div>
418
419
  </template>
419
- </UModal>
420
+ </AppModal>
420
421
  </template>
@@ -37,6 +37,7 @@ import SharedStacksPanel from '~/components/settings/SharedStacksPanel.vue'
37
37
  import ComposeEnvironmentSetupSection from '~/components/settings/ComposeEnvironmentSetupSection.vue'
38
38
  import PackageRegistriesPanel from '~/components/settings/PackageRegistriesPanel.vue'
39
39
  import CapabilityCredentialsPanel from '~/components/settings/CapabilityCredentialsPanel.vue'
40
+ import AppModal from '~/components/common/AppModal.vue'
40
41
 
41
42
  const { t } = useI18n()
42
43
  const ui = useUiStore()
@@ -152,11 +153,7 @@ watch([tabs, () => store.loaded], () => {
152
153
  </script>
153
154
 
154
155
  <template>
155
- <UModal
156
- v-model:open="open"
157
- :title="t('settings.providerConnection.windowTitle')"
158
- :ui="{ content: 'max-w-xl' }"
159
- >
156
+ <AppModal size="form" v-model:open="open" :title="t('settings.providerConnection.windowTitle')">
160
157
  <template #body>
161
158
  <div class="space-y-4">
162
159
  <UTabs
@@ -218,5 +215,5 @@ watch([tabs, () => store.loaded], () => {
218
215
  </p>
219
216
  </div>
220
217
  </template>
221
- </UModal>
218
+ </AppModal>
222
219
  </template>
@@ -19,6 +19,7 @@ import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
19
19
  import SecretInput from '~/components/common/SecretInput.vue'
20
20
  import SectionLabel from '~/components/common/SectionLabel.vue'
21
21
  import IconButton from '~/components/common/IconButton.vue'
22
+ import AppModal from '~/components/common/AppModal.vue'
22
23
 
23
24
  const { t } = useI18n()
24
25
  const ui = useUiStore()
@@ -269,11 +270,7 @@ async function remove(p: LocalRunner) {
269
270
  </script>
270
271
 
271
272
  <template>
272
- <UModal
273
- v-model:open="open"
274
- :title="t('settings.localModelEndpoints.title')"
275
- :ui="{ content: 'max-w-2xl' }"
276
- >
273
+ <AppModal size="form" v-model:open="open" :title="t('settings.localModelEndpoints.title')">
277
274
  <template #title>
278
275
  <IntegrationBackTitle :title="t('settings.localModelEndpoints.title')" @back="back" />
279
276
  </template>
@@ -483,5 +480,5 @@ async function remove(p: LocalRunner) {
483
480
  </div>
484
481
  </div>
485
482
  </template>
486
- </UModal>
483
+ </AppModal>
487
484
  </template>
@@ -311,6 +311,7 @@ async function save() {
311
311
  <Transition name="reader-fade">
312
312
  <div
313
313
  v-if="open"
314
+ data-testid="model-configuration-panel"
314
315
  class="z-50 flex-col bg-app-950/96 backdrop-blur-sm"
315
316
  :class="FULL_BLEED_TAKEOVER_CLASS"
316
317
  role="dialog"
@@ -377,7 +378,13 @@ async function save() {
377
378
  </template>
378
379
  </i18n-t>
379
380
  </p>
380
- <UButton icon="i-lucide-plus" size="sm" class="shrink-0" @click="startCreate">
381
+ <UButton
382
+ icon="i-lucide-plus"
383
+ size="sm"
384
+ class="shrink-0"
385
+ data-testid="model-preset-new"
386
+ @click="startCreate"
387
+ >
381
388
  {{ t('settings.modelConfiguration.list.newPreset') }}
382
389
  </UButton>
383
390
  </div>
@@ -491,6 +498,7 @@ async function save() {
491
498
  </SectionLabel>
492
499
  <UInput
493
500
  v-model="editor.name"
501
+ data-testid="model-preset-name"
494
502
  :placeholder="t('settings.modelConfiguration.editor.namePlaceholder')"
495
503
  size="sm"
496
504
  class="w-full"
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import AppModal from '~/components/common/AppModal.vue'
2
3
  // Startup advisory for built-in model presets that drifted from the catalog. Opened once per
3
4
  // session from the board page when `useModelPresetHealth` reports any issue. Lists:
4
5
  // • new built-in presets the workspace doesn't have yet (ADD them);
@@ -57,7 +58,7 @@ const reseedableCount = computed(
57
58
  </script>
58
59
 
59
60
  <template>
60
- <UModal v-model:open="open" :title="t('modelPreset.health.title')" :ui="{ content: 'max-w-2xl' }">
61
+ <AppModal size="form" v-model:open="open" :title="t('modelPreset.health.title')">
61
62
  <template #body>
62
63
  <div v-if="!hasIssues" class="py-6 text-center text-sm text-muted">
63
64
  <UIcon name="i-lucide-check-circle-2" class="mx-auto mb-2 h-8 w-8 text-app-success-400" />
@@ -165,5 +166,5 @@ const reseedableCount = computed(
165
166
  </UButton>
166
167
  </div>
167
168
  </template>
168
- </UModal>
169
+ </AppModal>
169
170
  </template>
@@ -8,6 +8,7 @@ import { computed, reactive, ref, watch } from 'vue'
8
8
  import type { ObservabilityProviderKind } from '~/types/releaseHealth'
9
9
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
10
10
  import SecretInput from '~/components/common/SecretInput.vue'
11
+ import AppModal from '~/components/common/AppModal.vue'
11
12
 
12
13
  const { t } = useI18n()
13
14
  const ui = useUiStore()
@@ -136,11 +137,7 @@ const connectedLabel = computed(() => {
136
137
  </script>
137
138
 
138
139
  <template>
139
- <UModal
140
- v-model:open="open"
141
- :title="t('settings.observabilityConnection.title')"
142
- :ui="{ content: 'max-w-lg' }"
143
- >
140
+ <AppModal size="form" v-model:open="open" :title="t('settings.observabilityConnection.title')">
144
141
  <template #title>
145
142
  <IntegrationBackTitle :title="t('settings.observabilityConnection.title')" @back="back" />
146
143
  </template>
@@ -261,5 +258,5 @@ const connectedLabel = computed(() => {
261
258
  </section>
262
259
  </div>
263
260
  </template>
264
- </UModal>
261
+ </AppModal>
265
262
  </template>
@@ -12,6 +12,7 @@ import type { OpenRouterModelMeta } from '~/types/openrouter'
12
12
  import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
13
13
  import SecretInput from '~/components/common/SecretInput.vue'
14
14
  import SectionLabel from '~/components/common/SectionLabel.vue'
15
+ import AppModal from '~/components/common/AppModal.vue'
15
16
 
16
17
  const { t } = useI18n()
17
18
  const ui = useUiStore()
@@ -205,7 +206,7 @@ function manageKeys() {
205
206
  </script>
206
207
 
207
208
  <template>
208
- <UModal v-model:open="open" title="OpenRouter" :ui="{ content: 'max-w-2xl' }">
209
+ <AppModal size="form" v-model:open="open" title="OpenRouter">
209
210
  <template #title>
210
211
  <IntegrationBackTitle title="OpenRouter" @back="back" />
211
212
  </template>
@@ -387,5 +388,5 @@ function manageKeys() {
387
388
  </template>
388
389
  </div>
389
390
  </template>
390
- </UModal>
391
+ </AppModal>
391
392
  </template>