@cat-factory/app 0.182.1 → 0.183.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/README.md +9 -0
- package/app/components/layout/IntegrationsHub.vue +6 -19
- package/app/components/settings/InfrastructureWindow.logic.spec.ts +100 -0
- package/app/components/settings/InfrastructureWindow.logic.ts +78 -0
- package/app/components/settings/InfrastructureWindow.vue +65 -46
- package/app/components/settings/PackageRegistriesPanel.vue +131 -123
- package/app/pages/index.vue +0 -1
- package/app/stores/packageRegistries.spec.ts +111 -0
- package/app/stores/packageRegistries.ts +21 -8
- package/app/stores/ui/modals.ts +20 -21
- package/app/types/providerConnections.ts +12 -0
- package/i18n/locales/de.json +5 -7
- package/i18n/locales/en.json +5 -7
- package/i18n/locales/es.json +5 -7
- package/i18n/locales/fr.json +5 -7
- package/i18n/locales/he.json +5 -7
- package/i18n/locales/it.json +5 -7
- package/i18n/locales/ja.json +5 -7
- package/i18n/locales/pl.json +5 -7
- package/i18n/locales/tr.json +5 -7
- package/i18n/locales/uk.json +5 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -192,6 +192,15 @@ wrong place is invisible until a user cannot find it:
|
|
|
192
192
|
It also carries a full "how it works / when you need this / when you can skip it"
|
|
193
193
|
explanation there rather than a one-line hint, since the decision to run it has to
|
|
194
194
|
be made before opening a five-minute wizard.
|
|
195
|
+
- **"It talks to an external service" is not what puts a surface in `integrations`.**
|
|
196
|
+
Private package registries connect to npmjs.com and GitHub Packages and still belong
|
|
197
|
+
in Infrastructure, because the question they answer is _what may a container install
|
|
198
|
+
from_ — a property of where agents RUN, which is what the Infrastructure window is
|
|
199
|
+
for. `integrations` is for a system the WORKSPACE links in and would still be a
|
|
200
|
+
coherent product without. Ask which question the destination answers, not whether a
|
|
201
|
+
credential leaves the building. A surface moved between sections must also move its
|
|
202
|
+
entry point: leaving a hub row behind as a shortcut splits the answer across two
|
|
203
|
+
places, so the row goes and the window's tab becomes the single route in.
|
|
195
204
|
|
|
196
205
|
## Develop & test
|
|
197
206
|
|
|
@@ -30,7 +30,6 @@ const documents = useDocumentsStore()
|
|
|
30
30
|
const tasks = useTasksStore()
|
|
31
31
|
const tracker = useTrackerStore()
|
|
32
32
|
const releaseHealth = useReleaseHealthStore()
|
|
33
|
-
const packageRegistries = usePackageRegistriesStore()
|
|
34
33
|
const publicApiKeys = usePublicApiKeysStore()
|
|
35
34
|
const userSecrets = useUserSecretsStore()
|
|
36
35
|
const uiMode = useUiModeStore()
|
|
@@ -60,7 +59,6 @@ watch(
|
|
|
60
59
|
if (isOpen) {
|
|
61
60
|
query.value = ''
|
|
62
61
|
void releaseHealth.ensureLoaded().catch(() => {})
|
|
63
|
-
void packageRegistries.ensureLoaded().catch(() => {})
|
|
64
62
|
void publicApiKeys.ensureLoaded().catch(() => {})
|
|
65
63
|
void userSecrets.load().catch(() => {})
|
|
66
64
|
}
|
|
@@ -271,22 +269,10 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
271
269
|
})
|
|
272
270
|
}
|
|
273
271
|
|
|
274
|
-
// --- Development (
|
|
275
|
-
//
|
|
276
|
-
//
|
|
272
|
+
// --- Development (API access tokens) ---------------------------------------
|
|
273
|
+
// Gated like observability: hidden until a probe confirms its module is wired
|
|
274
|
+
// (`available === true`), so an unconfigured backend doesn't show a dead row.
|
|
277
275
|
const development: IntegrationItem[] = []
|
|
278
|
-
if (packageRegistries.available) {
|
|
279
|
-
const hasEntries = packageRegistries.entries.length > 0
|
|
280
|
-
development.push({
|
|
281
|
-
key: 'package-registries',
|
|
282
|
-
icon: 'i-lucide-package',
|
|
283
|
-
label: t('layout.integrationsHub.items.packageRegistries.label'),
|
|
284
|
-
description: t('layout.integrationsHub.items.packageRegistries.description'),
|
|
285
|
-
status: hasEntries ? t('layout.integrationsHub.status.connected') : undefined,
|
|
286
|
-
connected: hasEntries,
|
|
287
|
-
onClick: () => go(ui.openPackageRegistries),
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
276
|
if (publicApiKeys.available) {
|
|
291
277
|
const hasKeys = publicApiKeys.keys.length > 0
|
|
292
278
|
development.push({
|
|
@@ -303,8 +289,9 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
303
289
|
out.push({ title: t('layout.integrationsHub.groups.development'), items: development })
|
|
304
290
|
|
|
305
291
|
// NOTE: Infrastructure (agent-container execution + Tester environments + the local-mode
|
|
306
|
-
// warm pool/checkout
|
|
307
|
-
// (SideBar → "Infrastructure" → the
|
|
292
|
+
// warm pool/checkout + the private package registries a checkout installs from) is no longer
|
|
293
|
+
// listed here — it moved to its OWN top-level navbar menu (SideBar → "Infrastructure" → the
|
|
294
|
+
// tabbed Infrastructure window). See `ui.openInfrastructure`.
|
|
308
295
|
|
|
309
296
|
// --- Personal (only you) — fallback when there is no UserMenu to host "My setup" -------
|
|
310
297
|
// Per-user connections normally live in the My-setup hub; with auth disabled they fold in
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
infrastructureTabs,
|
|
4
|
+
openInfrastructureTab,
|
|
5
|
+
repinInfrastructureTab,
|
|
6
|
+
} from './InfrastructureWindow.logic'
|
|
7
|
+
|
|
8
|
+
const NONE = { agents: false, environments: false, packageRegistries: false }
|
|
9
|
+
|
|
10
|
+
describe('infrastructureTabs', () => {
|
|
11
|
+
it('shows nothing when no probe reports a backend', () => {
|
|
12
|
+
expect(infrastructureTabs(NONE)).toEqual([])
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('rides the environment probe for shared stacks', () => {
|
|
16
|
+
// A shared stack is infra a Tester environment ATTACHES to, so it cannot stand on its own:
|
|
17
|
+
// without an environment backend there is nothing to attach it to.
|
|
18
|
+
expect(infrastructureTabs({ ...NONE, environments: true })).toEqual([
|
|
19
|
+
'environment',
|
|
20
|
+
'shared-stacks',
|
|
21
|
+
])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('gates the registries tab on its own module probe', () => {
|
|
25
|
+
// The backend 503s the module with no encryption key. That is independent of any execution
|
|
26
|
+
// or test-env backend, so the tab can be the ONLY one a deployment shows.
|
|
27
|
+
expect(infrastructureTabs({ ...NONE, packageRegistries: true })).toEqual(['package-registries'])
|
|
28
|
+
expect(infrastructureTabs({ ...NONE, agents: true })).toEqual(['runner-pool'])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('orders tabs by the question they answer, not by which probe resolved', () => {
|
|
32
|
+
expect(
|
|
33
|
+
infrastructureTabs({ agents: true, environments: true, packageRegistries: true }),
|
|
34
|
+
).toEqual(['runner-pool', 'environment', 'shared-stacks', 'package-registries'])
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('openInfrastructureTab', () => {
|
|
39
|
+
it('honours an available deep-linked tab over the first one', () => {
|
|
40
|
+
expect(openInfrastructureTab(['runner-pool', 'package-registries'], 'package-registries')).toBe(
|
|
41
|
+
'package-registries',
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('falls back to the first available tab when the request is off', () => {
|
|
46
|
+
expect(openInfrastructureTab(['runner-pool'], 'package-registries')).toBe('runner-pool')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('keeps naming the requested tab when nothing is available yet', () => {
|
|
50
|
+
// Every probe is still in flight on the first open. Holding the request (rather than
|
|
51
|
+
// inventing a selection) is what lets the re-pin land on it the moment its probe resolves.
|
|
52
|
+
expect(openInfrastructureTab([], 'package-registries')).toBe('package-registries')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('repinInfrastructureTab', () => {
|
|
57
|
+
it('leaves the user where they are when their tab is still available', () => {
|
|
58
|
+
// The list GROWS as probes resolve; someone reading a tab must not be yanked off it.
|
|
59
|
+
expect(
|
|
60
|
+
repinInfrastructureTab(
|
|
61
|
+
['runner-pool', 'environment', 'package-registries'],
|
|
62
|
+
'environment',
|
|
63
|
+
'runner-pool',
|
|
64
|
+
true,
|
|
65
|
+
),
|
|
66
|
+
).toBe('environment')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('lands on the deep-linked tab once its probe resolves', () => {
|
|
70
|
+
// The registries probe is the slow one: the window opened on `runner-pool` because
|
|
71
|
+
// `package-registries` was not there yet, and this is the moment it arrives.
|
|
72
|
+
expect(
|
|
73
|
+
repinInfrastructureTab(
|
|
74
|
+
['runner-pool', 'package-registries'],
|
|
75
|
+
'shared-stacks',
|
|
76
|
+
'package-registries',
|
|
77
|
+
true,
|
|
78
|
+
),
|
|
79
|
+
).toBe('package-registries')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('does not steal the selection while the probes are still settling', () => {
|
|
83
|
+
// A transient list holding only the fastest probe's tab. Falling back to it here would pin
|
|
84
|
+
// the user to whichever probe won the race and then REJECT the deep link for being a peer
|
|
85
|
+
// of the current tab.
|
|
86
|
+
expect(
|
|
87
|
+
repinInfrastructureTab(['runner-pool'], 'package-registries', 'package-registries', false),
|
|
88
|
+
).toBe('package-registries')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('falls back to the first tab once loading settled and the request is unavailable', () => {
|
|
92
|
+
expect(
|
|
93
|
+
repinInfrastructureTab(['runner-pool'], 'package-registries', 'package-registries', true),
|
|
94
|
+
).toBe('runner-pool')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('holds the current tab when settling left nothing available at all', () => {
|
|
98
|
+
expect(repinInfrastructureTab([], 'environment', 'environment', true)).toBe('environment')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { InfrastructureTab } from '~/types/providerConnections'
|
|
2
|
+
|
|
3
|
+
// Pure tab arithmetic behind `InfrastructureWindow.vue`: which tabs exist for this deployment,
|
|
4
|
+
// and which one to show. Extracted so it is unit-tested directly rather than through the DOM —
|
|
5
|
+
// the window's tabs come from THREE probes that resolve independently (the infrastructure
|
|
6
|
+
// capability, the provider-connection store, the package-registries module), so the interesting
|
|
7
|
+
// behaviour is what happens while the list is still growing, which a rendered-component test
|
|
8
|
+
// reproduces only by accident.
|
|
9
|
+
//
|
|
10
|
+
// Labels stay in the component: a label resolved here would have to travel as a message KEY and
|
|
11
|
+
// be fed to `t()` as a variable, which is exactly what defeats the typed-message-key check. The
|
|
12
|
+
// component maps a tab onto a literal `t()` call through an exhaustive Record instead.
|
|
13
|
+
|
|
14
|
+
/** Which of the window's tabs this deployment can show, as their probes currently read. */
|
|
15
|
+
export interface InfrastructureTabAvailability {
|
|
16
|
+
/** An execution backend is reported (runner pool / container runtime / local). */
|
|
17
|
+
agents: boolean
|
|
18
|
+
/** A test-environment backend is reported. Also gates the shared-stacks tab. */
|
|
19
|
+
environments: boolean
|
|
20
|
+
/** The package-registries module answered its probe affirmatively (it 503s unconfigured). */
|
|
21
|
+
packageRegistries: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The window's tabs, in display order. Order is the reading order of the questions they answer:
|
|
26
|
+
* where agent containers run, where test environments run, what those environments attach to,
|
|
27
|
+
* and what a checkout may install from.
|
|
28
|
+
*
|
|
29
|
+
* Shared stacks ride the test-environment probe because a stack is infra an environment attaches
|
|
30
|
+
* to — there is nothing to attach without an environment backend.
|
|
31
|
+
*/
|
|
32
|
+
export function infrastructureTabs(available: InfrastructureTabAvailability): InfrastructureTab[] {
|
|
33
|
+
const tabs: InfrastructureTab[] = []
|
|
34
|
+
if (available.agents) tabs.push('runner-pool')
|
|
35
|
+
if (available.environments) tabs.push('environment', 'shared-stacks')
|
|
36
|
+
if (available.packageRegistries) tabs.push('package-registries')
|
|
37
|
+
return tabs
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The tab to show when the window OPENS. The deep-linked request wins whenever it is available;
|
|
42
|
+
* otherwise fall back to the first tab that is.
|
|
43
|
+
*
|
|
44
|
+
* Whatever tab was showing last time is deliberately NOT consulted: the caller has just asked
|
|
45
|
+
* for `requested`, and honouring a leftover selection over it would silently ignore the deep
|
|
46
|
+
* link. Falling back to `requested` when nothing is available at all keeps the ref naming the
|
|
47
|
+
* thing that was asked for, so the re-pin below can land on it once its probe resolves.
|
|
48
|
+
*/
|
|
49
|
+
export function openInfrastructureTab(
|
|
50
|
+
tabs: readonly InfrastructureTab[],
|
|
51
|
+
requested: InfrastructureTab,
|
|
52
|
+
): InfrastructureTab {
|
|
53
|
+
if (tabs.includes(requested)) return requested
|
|
54
|
+
return tabs[0] ?? requested
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The tab to show after the tab list CHANGES (a probe resolved). Unlike the open-time choice
|
|
59
|
+
* this one protects the user's current position: a tab list that grows under someone reading a
|
|
60
|
+
* tab must not yank them elsewhere.
|
|
61
|
+
*
|
|
62
|
+
* `settled` is what stops the transient single-tab list from stealing the selection. The probes
|
|
63
|
+
* resolve independently, so the list legitimately passes through states missing tabs that are
|
|
64
|
+
* about to appear; falling back to the first tab before the probes have settled would pin the
|
|
65
|
+
* user to whichever one happened to resolve first, and the deep-linked tab would then be
|
|
66
|
+
* REJECTED for being the current one's peer rather than shown.
|
|
67
|
+
*/
|
|
68
|
+
export function repinInfrastructureTab(
|
|
69
|
+
tabs: readonly InfrastructureTab[],
|
|
70
|
+
current: InfrastructureTab,
|
|
71
|
+
requested: InfrastructureTab,
|
|
72
|
+
settled: boolean,
|
|
73
|
+
): InfrastructureTab {
|
|
74
|
+
if (tabs.includes(current)) return current
|
|
75
|
+
if (tabs.includes(requested)) return requested
|
|
76
|
+
if (settled && tabs.length) return tabs[0]!
|
|
77
|
+
return current
|
|
78
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// The single tabbed "Infrastructure" window — now a TOP-LEVEL navbar destination (no longer
|
|
3
|
-
// reached through the Integrations hub).
|
|
3
|
+
// reached through the Integrations hub). Its topical tabs:
|
|
4
4
|
// - "Agent containers" — where repo-operating agent containers run. Shows the execution
|
|
5
5
|
// backend selector, the runner-pool connection (ProviderConnectionTab), and — in local
|
|
6
6
|
// mode — the warm-container-pool + checkout-reuse settings (the local agent-container
|
|
@@ -10,24 +10,33 @@
|
|
|
10
10
|
// Compose setup (formerly a standalone "Environment setup" sidebar entry — it writes a
|
|
11
11
|
// service's Compose recipe plus the workspace's Compose handler, so it belongs beside the
|
|
12
12
|
// settings it edits rather than at the same level as them).
|
|
13
|
+
// - "Shared stacks" — long-lived Compose infra a Tester environment attaches to, so it rides
|
|
14
|
+
// the same probe as the environments tab (nothing to attach to without one).
|
|
15
|
+
// - "Package registries" — the private npm registries a checkout installs from (formerly an
|
|
16
|
+
// Integrations-hub row). What a container can resolve its dependencies from is part of the
|
|
17
|
+
// execution environment, not an optional external system a workspace links in.
|
|
13
18
|
// Local-specific affordances render inline, gated on `auth.localMode?.enabled`. A tab whose
|
|
14
19
|
// backend integration is disabled (503) simply doesn't render.
|
|
15
20
|
import { computed, ref, watch } from 'vue'
|
|
16
|
-
import type {
|
|
21
|
+
import type { InfrastructureTab } from '~/types/providerConnections'
|
|
22
|
+
import {
|
|
23
|
+
infrastructureTabs,
|
|
24
|
+
openInfrastructureTab,
|
|
25
|
+
repinInfrastructureTab,
|
|
26
|
+
} from '~/components/settings/InfrastructureWindow.logic'
|
|
17
27
|
import InfrastructureBackendPicker from '~/components/settings/InfrastructureBackendPicker.vue'
|
|
18
28
|
import InfraHandlersConfigurator from '~/components/settings/InfraHandlersConfigurator.vue'
|
|
19
29
|
import DefaultProvisionTypeSection from '~/components/settings/DefaultProvisionTypeSection.vue'
|
|
20
30
|
import LocalContainerPoolSettings from '~/components/settings/LocalContainerPoolSettings.vue'
|
|
21
31
|
import SharedStacksPanel from '~/components/settings/SharedStacksPanel.vue'
|
|
22
32
|
import ComposeEnvironmentSetupSection from '~/components/settings/ComposeEnvironmentSetupSection.vue'
|
|
23
|
-
|
|
24
|
-
// The shared-stacks tab uses its own slot key beyond the provider-connection kinds.
|
|
25
|
-
type InfraTabValue = ProviderConnectionKind | 'shared-stacks'
|
|
33
|
+
import PackageRegistriesPanel from '~/components/settings/PackageRegistriesPanel.vue'
|
|
26
34
|
|
|
27
35
|
const { t } = useI18n()
|
|
28
36
|
const ui = useUiStore()
|
|
29
37
|
const store = useProviderConnectionsStore()
|
|
30
38
|
const auth = useAuthStore()
|
|
39
|
+
const packageRegistries = usePackageRegistriesStore()
|
|
31
40
|
|
|
32
41
|
const open = computed({
|
|
33
42
|
get: () => ui.infrastructureOpen,
|
|
@@ -45,35 +54,41 @@ const isLocal = computed(() => auth.localMode?.enabled === true)
|
|
|
45
54
|
const agentsAvailable = computed(() => (auth.infrastructure?.execution.available.length ?? 0) > 0)
|
|
46
55
|
const envsAvailable = computed(() => (auth.infrastructure?.testEnv.available.length ?? 0) > 0)
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Shared stacks are long-lived compose infra a Tester environment attaches to, so they live
|
|
65
|
-
// alongside the test-environment config (shown wherever an environment backend is available).
|
|
66
|
-
if (envsAvailable.value)
|
|
67
|
-
out.push({
|
|
68
|
-
value: 'shared-stacks',
|
|
69
|
-
label: t('settings.sharedStacks.tab'),
|
|
70
|
-
icon: 'i-lucide-layers',
|
|
71
|
-
slot: 'shared-stacks',
|
|
72
|
-
})
|
|
73
|
-
return out
|
|
74
|
-
})
|
|
57
|
+
// Tab presentation, keyed by the closed `InfrastructureTab` union so a new tab fails to compile
|
|
58
|
+
// until it is given a label and an icon. The labels are literal `t()` calls for the same reason
|
|
59
|
+
// they are not resolved in the logic module: a key assembled at runtime is invisible to the
|
|
60
|
+
// typed-message-key check.
|
|
61
|
+
const TAB_LABELS = computed<Record<InfrastructureTab, string>>(() => ({
|
|
62
|
+
'runner-pool': t('settings.providerConnection.tabs.agentContainers'),
|
|
63
|
+
environment: t('settings.providerConnection.tabs.testEnvironments'),
|
|
64
|
+
'shared-stacks': t('settings.sharedStacks.tab'),
|
|
65
|
+
'package-registries': t('settings.packageRegistries.tab'),
|
|
66
|
+
}))
|
|
67
|
+
const TAB_ICONS: Record<InfrastructureTab, string> = {
|
|
68
|
+
'runner-pool': 'i-lucide-server-cog',
|
|
69
|
+
environment: 'i-lucide-cloud',
|
|
70
|
+
'shared-stacks': 'i-lucide-layers',
|
|
71
|
+
'package-registries': 'i-lucide-package',
|
|
72
|
+
}
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
// `slot` mirrors `value` — the template names one `<template #…>` per tab value.
|
|
75
|
+
const tabs = computed(() =>
|
|
76
|
+
infrastructureTabs({
|
|
77
|
+
agents: agentsAvailable.value,
|
|
78
|
+
environments: envsAvailable.value,
|
|
79
|
+
// The module's own probe (the backend 503s with no encryption key), same gate as the
|
|
80
|
+
// Integrations-hub row this replaced — an unconfigured backend shows no dead tab.
|
|
81
|
+
packageRegistries: packageRegistries.available === true,
|
|
82
|
+
}).map((value) => ({
|
|
83
|
+
value,
|
|
84
|
+
label: TAB_LABELS.value[value],
|
|
85
|
+
icon: TAB_ICONS[value],
|
|
86
|
+
slot: value,
|
|
87
|
+
})),
|
|
88
|
+
)
|
|
89
|
+
const tabValues = computed(() => tabs.value.map((x) => x.value))
|
|
90
|
+
|
|
91
|
+
const activeTab = ref<InfrastructureTab>(ui.infrastructureTab)
|
|
77
92
|
|
|
78
93
|
// Honour the deep-linked tab each time the window opens, falling back to the first available
|
|
79
94
|
// tab if the requested one is off.
|
|
@@ -82,24 +97,25 @@ watch(
|
|
|
82
97
|
(isOpen) => {
|
|
83
98
|
if (!isOpen) return
|
|
84
99
|
void store.ensureLoaded().catch(() => {})
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
// The registries tab gates on this probe, so it has to resolve for the tab to appear at
|
|
101
|
+
// all — the panel's own load is a no-op once this settled (`ensureLoaded` coalesces).
|
|
102
|
+
// Swallowed here on purpose: the PANEL reports a load failure, and it can only do that
|
|
103
|
+
// once the tab it lives in exists, so a probe failure has to leave the window itself alone.
|
|
104
|
+
void packageRegistries.ensureLoaded().catch(() => {})
|
|
105
|
+
activeTab.value = openInfrastructureTab(tabValues.value, ui.infrastructureTab)
|
|
88
106
|
},
|
|
89
107
|
{ immediate: true },
|
|
90
108
|
)
|
|
91
109
|
// When availability resolves after open, re-pin onto a valid tab — but keep honouring the
|
|
92
|
-
// deep-linked request. The
|
|
93
|
-
// through a transient
|
|
110
|
+
// deep-linked request. The three availability probes resolve independently, so `tabs` can pass
|
|
111
|
+
// through a transient short list; only fall back to the first tab once loading settled.
|
|
94
112
|
watch([tabs, () => store.loaded], () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
activeTab.value = list[0]!.value
|
|
102
|
-
}
|
|
113
|
+
activeTab.value = repinInfrastructureTab(
|
|
114
|
+
tabValues.value,
|
|
115
|
+
activeTab.value,
|
|
116
|
+
ui.infrastructureTab,
|
|
117
|
+
store.loaded,
|
|
118
|
+
)
|
|
103
119
|
})
|
|
104
120
|
</script>
|
|
105
121
|
|
|
@@ -157,6 +173,9 @@ watch([tabs, () => store.loaded], () => {
|
|
|
157
173
|
<template #shared-stacks>
|
|
158
174
|
<SharedStacksPanel />
|
|
159
175
|
</template>
|
|
176
|
+
<template #package-registries>
|
|
177
|
+
<PackageRegistriesPanel />
|
|
178
|
+
</template>
|
|
160
179
|
</UTabs>
|
|
161
180
|
|
|
162
181
|
<p v-else class="px-1 py-6 text-center text-sm text-slate-500">
|