From 570b2b57f7219f4000ad438c33122275ccf12860 Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Mon, 20 Apr 2026 11:14:03 -0300 Subject: [PATCH 1/5] ui: Blink external features tabs with pending items Pulses the opacity of inactive tabs in the external features dialog when they contain pending actions or joystick suggestions, so users notice there is still something to review after finishing the first tab. --- .../ExternalFeaturesDiscoveryModal.vue | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/components/ExternalFeaturesDiscoveryModal.vue b/src/components/ExternalFeaturesDiscoveryModal.vue index d2ed78ab03..5795a82beb 100644 --- a/src/components/ExternalFeaturesDiscoveryModal.vue +++ b/src/components/ExternalFeaturesDiscoveryModal.vue @@ -9,8 +9,13 @@ - Actions - Joystick Mappings + Actions + + Joystick Mappings + @@ -1095,11 +1100,21 @@ const ignoredJoystickSuggestionsByExtension = computed(() => { .filter((ext) => ext.suggestionGroups.length > 0) }) +/** + * Whether there are new actions pending user action + */ +const hasPendingActions = computed(() => filteredActions.value.length > 0) + +/** + * Whether there are new joystick suggestions pending user action + */ +const hasPendingJoystickSuggestions = computed(() => filteredJoystickSuggestionsByExtension.value.length > 0) + /** * Whether there are new extension features that still need user action */ const hasPendingBlueOSFeatures = computed(() => { - return filteredActions.value.length > 0 || filteredJoystickSuggestionsByExtension.value.length > 0 + return hasPendingActions.value || hasPendingJoystickSuggestions.value }) /** @@ -1523,6 +1538,32 @@ watch(activeTab, () => { transition: width 0.2s ease; } +.tab-blink { + position: relative; +} + +.tab-blink::before { + content: ''; + position: absolute; + inset: 0; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + background-color: #ffffff22; + animation: tab-blink 1.2s ease-in-out infinite; + pointer-events: none; + z-index: 0; +} + +@keyframes tab-blink { + 0%, + 100% { + opacity: 0; + } + 50% { + opacity: 1; + } +} + .features-modal:has(.v-expansion-panels) { width: 760px; } From c0ccf95dfe710d3ba3ceb609a91021aaeb34cb9e Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Mon, 20 Apr 2026 11:37:13 -0300 Subject: [PATCH 2/5] ui: Confirm closing external features dialog when items are pending Intercepts X-button and outside-click dismissals to warn users that unhandled actions and joystick suggestions will cause the dialog to reopen on the next Cockpit launch. The confirmation lists each pending item with its source extension so users can see exactly what is left to accept or ignore. --- .../ExternalFeaturesDiscoveryModal.vue | 106 +++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/src/components/ExternalFeaturesDiscoveryModal.vue b/src/components/ExternalFeaturesDiscoveryModal.vue index 5795a82beb..c943ec3d9f 100644 --- a/src/components/ExternalFeaturesDiscoveryModal.vue +++ b/src/components/ExternalFeaturesDiscoveryModal.vue @@ -1,11 +1,11 @@ @@ -1117,6 +1181,20 @@ const hasPendingBlueOSFeatures = computed(() => { return hasPendingActions.value || hasPendingJoystickSuggestions.value }) +/** + * Flat list of pending joystick suggestions with their extension names + */ +const pendingJoystickSuggestions = computed((): JoystickSuggestionWithExtensionName[] => { + return filteredJoystickSuggestionsByExtension.value.flatMap((ext) => + ext.suggestionGroups.flatMap((group) => + group.buttonMappingSuggestions.map((suggestion) => ({ + ...suggestion, + extensionName: ext.extensionName, + })) + ) + ) +}) + /** * Get the human-readable name for an action type * @param {customActionTypes} type - The action type @@ -1452,6 +1530,11 @@ const restoreIgnoredSuggestion = (suggestion: JoystickMapSuggestion): void => { }) } +/** + * Controls visibility of the close confirmation dialog + */ +const closeConfirmationDialog = ref(false) + /** * Close the modal */ @@ -1460,6 +1543,25 @@ const closeModal = (): void => { emit('close') } +/** + * Request to close the modal. If there are still pending items to decide upon, ask the user to confirm first. + */ +const requestCloseModal = (): void => { + if (hasPendingBlueOSFeatures.value) { + closeConfirmationDialog.value = true + return + } + closeModal() +} + +/** + * Confirm closing the modal from the confirmation dialog + */ +const confirmCloseModal = (): void => { + closeConfirmationDialog.value = false + closeModal() +} + /** * Check for available actions from BlueOS. */ From e2cad75d194d47743c7f9862f3f4fbf356c64d25 Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Tue, 21 Apr 2026 14:11:28 -0300 Subject: [PATCH 3/5] glass-modal: Add option to keep modal open on outside click Add a noCloseOnOutsideClick prop to GlassModal that suppresses the outside-click close behavior without changing the semantics of isPersistent. This is useful when the modal hosts child dialogs that are teleported outside the modal's DOM subtree (e.g. Vuetify v-dialogs attached to the document body), whose clicks would otherwise be interpreted as outside clicks of the modal. --- src/components/GlassModal.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/GlassModal.vue b/src/components/GlassModal.vue index 3c403b3beb..dcb332b617 100644 --- a/src/components/GlassModal.vue +++ b/src/components/GlassModal.vue @@ -41,6 +41,12 @@ const props = defineProps<{ * If true, modal will not close by pressing 'esc' or by an outside click. */ isPersistent?: boolean + /** + * If true, the modal will not emit `outside-click` when the user clicks outside of it. + * Useful when the modal hosts Vuetify dialogs that are teleported to the document body, + * whose clicks would otherwise be interpreted as outside clicks of the modal. + */ + noCloseOnOutsideClick?: boolean /** * The overflow property of the modal. */ @@ -177,7 +183,7 @@ const closeModal = (): void => { } onClickOutside(modal, () => { - if (!isPersistent.value) { + if (!isPersistent.value && !props.noCloseOnOutsideClick) { closeModal() } if (!isAlwaysOnTop.value) { From 6225a4800f2b10dc1cb883092bd5111ebee644ee Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Tue, 21 Apr 2026 14:11:28 -0300 Subject: [PATCH 4/5] ui: Prevent external features dialog from auto-closing on inner dialog clicks The dialog hosts Vuetify v-dialogs that Vuetify teleports to the document body, so clicks inside them were detected as outside clicks of the outer GlassModal and triggered an automatic close once no pending items remained. Opt out of the outside-click close behavior so the modal stays open after the user applies the last suggestion and only closes through the explicit close button. --- src/components/ExternalFeaturesDiscoveryModal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ExternalFeaturesDiscoveryModal.vue b/src/components/ExternalFeaturesDiscoveryModal.vue index c943ec3d9f..a5bbd343d6 100644 --- a/src/components/ExternalFeaturesDiscoveryModal.vue +++ b/src/components/ExternalFeaturesDiscoveryModal.vue @@ -1,5 +1,5 @@