refac: revamp the update flow in settings so the user has better visibility that it has to restart when update downloaded

This commit is contained in:
fsylvain committed 2026-10-04 17:42:46 +02:00
1 parent daf497576f
commit 68401d66e0
9 files changed
+70 -16

No files matched your search

+13 -1
View File
@@ -17,6 +17,7 @@ const props = defineProps<{
title: string
text: string
meta?: string
tag?: string
live?: 'polite' | 'off'
}>()
@@ -37,10 +38,21 @@ const spinning = computed(() => props.icon === 'i-lucide-loader-circle')
/>
</span>
<div class="flex min-w-0 flex-1 flex-col gap-1">
<span class="text-base font-semibold text-highlighted">{{ title }}</span>
<div class="flex flex-wrap items-center gap-2">
<span class="text-base font-semibold text-highlighted">{{ title }}</span>
<span
v-if="tag"
data-testid="update-tag"
class="inline-flex h-5 items-center border border-warning bg-default px-1.5 font-mono text-[0.6875rem] font-medium text-warning"
>{{ tag }}</span
>
</div>
<span class="text-sm text-pretty text-toned">{{ text }}</span>
<slot />
<span v-if="meta" class="text-xs/5 text-muted">{{ meta }}</span>
<div v-if="$slots.action" class="flex pt-2">
<slot name="action" />
</div>
</div>
</div>
</template>
+34 -4
View File
@@ -39,6 +39,7 @@ interface Block {
title: string
text: string
meta?: string
tag?: string
}
interface ActionButton {
@@ -187,8 +188,11 @@ const applyBlocks = computed<Record<ApplyView, Block>>(() => ({
},
},
ready: {
tone: 'success',
icon: 'i-lucide-circle-check',
tone: 'warning',
icon: 'i-lucide-clock',
get tag() {
return t('Pending')
},
get title() {
return t('Version {0} is ready to install', [newVersion.value])
},
@@ -498,6 +502,12 @@ const actionButtons = computed<ActionButton[]>(() => {
})
})
const inBlockButton = computed(() =>
applyView.value === 'ready' ? (actionButtons.value[0] ?? null) : null,
)
const rowButtons = computed(() => (inBlockButton.value ? [] : actionButtons.value))
const showRestartHint = computed(() => actionButtons.value.includes(plainRestartButton))
function withPlainRestart(button: ActionButton) {
@@ -577,6 +587,7 @@ const notes = computed(() => {
:title="block.title"
:text="block.text"
:meta="block.meta"
:tag="block.tag"
:live="progressView ? 'off' : 'polite'"
>
<UpdateProgress
@@ -585,6 +596,25 @@ const notes = computed(() => {
:text="progressView.text"
:value="progressView.value"
/>
<template v-if="inBlockButton" #action>
<UButton
:data-testid="inBlockButton.action === 'restart' ? 'restart-button' : undefined"
:color="inBlockButton.primary ? 'primary' : 'neutral'"
:variant="inBlockButton.primary ? 'solid' : 'outline'"
:icon="inBlockButton.icon"
:disabled="inBlockButton.disabled"
class="h-11 justify-center gap-1.5 px-3.5 md:h-8"
:class="
inBlockButton.primary
? ''
: 'border border-accented bg-default text-highlighted ring-0'
"
:ui="{ leadingIcon: 'size-4' }"
@click="onAction(inBlockButton)"
>
{{ inBlockButton.label }}
</UButton>
</template>
</UpdateStatusBlock>
<p v-if="canApplyView" role="status" data-testid="update-announcer" class="sr-only">
@@ -595,9 +625,9 @@ const notes = computed(() => {
<ServerCommand v-if="showServerGuidance" :line="ENABLE_BROWSER_UPDATES_COMMAND" />
<div v-if="actionButtons.length" class="flex flex-col gap-2 md:flex-row md:items-center">
<div v-if="rowButtons.length" class="flex flex-col gap-2 md:flex-row md:items-center">
<UButton
v-for="button in actionButtons"
v-for="button in rowButtons"
:key="button.label"
:data-testid="button.action === 'restart' ? 'restart-button' : undefined"
:color="button.primary ? 'primary' : 'neutral'"
@@ -299,7 +299,7 @@ describe('Settings, owner, Restart buttons', () => {
it('ready: Restart is the enabled primary button', async () => {
await mountSettings({ role: 'owner', status: ready() })
expect(cardButtons()).toEqual(["What's new in 2.3.0-beta", 'Restart'])
expect(cardButtons()).toEqual(['Restart', "What's new in 2.3.0-beta"])
const restart = findButton('Restart') as HTMLButtonElement
expect(restart.disabled).toBe(false)
expect(restart.className).not.toContain('border-accented')
@@ -311,6 +311,16 @@ describe('Settings, owner, Ready to install', () => {
expect(findButton("What's new in 2.3.0-beta")!.getAttribute('aria-expanded')).toBe('false')
const restart = findButton('Restart') as HTMLButtonElement
expect(restart.disabled).toBe(false)
const tag = updatesCard().querySelector('[data-testid="update-tag"]')
expect(tag?.textContent?.trim()).toBe('Pending')
expect(updatesCard().querySelector('svg.text-warning')).not.toBeNull()
expect(updatesCard().querySelector('svg.text-success')).toBeNull()
const block = updatesCard().querySelector('[aria-live]') as HTMLElement
expect(block.className).toContain('--rc-warning-soft')
expect(block.contains(restart)).toBe(true)
expect(restart.className).toContain('h-11')
expect(restart.className).toContain('md:h-8')
expect(updatesCard().querySelector('[class*="md:flex-row"]')).toBeNull()
const readsBefore = callsTo(STATUS_PATH).length
await tick(3000)