fix(studio): clarify fast database reboot (#49741)

## What kind of change does this PR introduce?

Bug fix. Resolves DEPR-657.

## What is the current behavior?

The Fast database reboot description suggests the action may fail to
recover from some failure modes, which can be read as a risk of the
reboot itself.

## What is the new behavior?

The description clearly explains that the faster option restarts only
the database service, has less downtime than a full project restart, and
leaves other project services running.

| Before | After |
| --- | --- |
| <img width="1460" height="512" alt="CleanShot 2026-08-31 at 09 24
49@2x"
src="https://github.com/user-attachments/assets/2d4a940c-4d66-4753-99d8-9d0d2b4951af"
/> | <img width="1458" height="500" alt="CleanShot 2026-08-31 at 09 31
18@2x"
src="https://github.com/user-attachments/assets/f58aa351-5c8c-4a9a-b31d-b771659defd3"
/> |

## To test

1. Open a project's **Settings > General** page.
2. Under **Project availability**, tab to **Restart project**, then tab
again to the adjacent chevron button.
3. Press Enter and confirm focus moves to **Fast database reboot**.
4. Confirm its description reads: “Restarts only the database service,
with less downtime than a full project restart. Other project services
remain running.”
5. Confirm the project availability descriptions appear as secondary
text beneath their action labels.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Accessibility Improvements**
* Improved keyboard navigation with separate tab stops for restart
actions and restart-type selection.
* Added clearer labeling and focus behavior when choosing a restart
type.

* **UI Improvements**
* Clarified that fast database restarts affect only PostgreSQL while
other services continue running.
  * Improved text contrast on the project settings page.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Danny White authored and GitHub committed 2026-08-31 13:01:17 +10:00
1 parent b0e31be89a
commit 0b27205ae4
3 files changed
+79 -6

No files matched your search

@@ -0,0 +1,71 @@
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { RestartServerButton } from './RestartServerButton'
import { customRender } from '@/tests/lib/custom-render'
const {
mockUseAsyncCheckPermissions,
mockUseFlag,
mockUseIsFeatureEnabled,
mockUseSelectedProjectQuery,
} = vi.hoisted(() => ({
mockUseAsyncCheckPermissions: vi.fn(),
mockUseFlag: vi.fn(),
mockUseIsFeatureEnabled: vi.fn(),
mockUseSelectedProjectQuery: vi.fn(),
}))
vi.mock('common', async (importOriginal) => ({
...(await importOriginal<typeof import('common')>()),
useFlag: mockUseFlag,
}))
vi.mock('next/router', () => ({
useRouter: () => ({ push: vi.fn() }),
}))
vi.mock('@/hooks/misc/useCheckPermissions', () => ({
useAsyncCheckPermissions: mockUseAsyncCheckPermissions,
}))
vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({
useIsFeatureEnabled: mockUseIsFeatureEnabled,
}))
vi.mock('@/hooks/misc/useSelectedProject', () => ({
useIsAwsK8sCloudProvider: () => false,
useIsProjectActive: () => true,
useSelectedProjectQuery: mockUseSelectedProjectQuery,
}))
describe('RestartServerButton', () => {
beforeEach(() => {
vi.clearAllMocks()
mockUseFlag.mockReturnValue(false)
mockUseAsyncCheckPermissions.mockReturnValue({ can: true })
mockUseIsFeatureEnabled.mockReturnValue({ projectSettingsRestartProject: true })
mockUseSelectedProjectQuery.mockReturnValue({
data: { ref: 'default', region: 'us-east-1', status: 'ACTIVE_HEALTHY' },
})
})
it('uses separate tab stops for the primary action and restart type menu', async () => {
const user = userEvent.setup()
customRender(<RestartServerButton />)
const restartProject = screen.getByRole('button', { name: 'Restart project' })
const chooseRestartType = screen.getByRole('button', { name: 'Choose restart type' })
await user.tab()
expect(restartProject).toHaveFocus()
await user.tab()
expect(chooseRestartType).toHaveFocus()
await user.keyboard('{Enter}')
expect(await screen.findByRole('menuitem', { name: /Fast database reboot/ })).toHaveFocus()
expect(screen.getByText(/Other project services remain running/)).toBeVisible()
})
})
@@ -107,6 +107,7 @@ export const RestartServerButton = () => {
{projectSettingsRestartProject ? (
<div className="flex w-full @lg:w-auto">
<ButtonTooltip
type="button"
variant="default"
className={cn(
'flex-1 px-3 hover:z-10 focus-visible:z-10 @lg:flex-none',
@@ -141,8 +142,9 @@ export const RestartServerButton = () => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="default"
aria-label={`Restart ${entityLabel}`}
aria-label="Choose restart type"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
icon={<ChevronDown />}
disabled={!canRestartProject}
@@ -158,9 +160,9 @@ export const RestartServerButton = () => {
>
<div className="space-y-0.5">
<p className="block text-foreground">Fast database reboot</p>
<p className="block text-foreground-light">
Restarts only the database. Faster, but may not be able to recover from all
failure modes.
<p className="block text-foreground-lighter">
Restarts only the database service, with less downtime than a full project
restart. Other project services remain running.
</p>
</div>
</DropdownMenuItem>
@@ -72,7 +72,7 @@ export const Project = () => {
<div>
<p className="text-sm">{primaryActionLabel}</p>
<div className="max-w-[420px]">
<p className="text-sm text-foreground-light">{primaryActionDescription}</p>
<p className="text-sm text-foreground-lighter">{primaryActionDescription}</p>
</div>
</div>
{isPaused ? (
@@ -98,7 +98,7 @@ export const Project = () => {
<div>
<p className="text-sm">Pause {entityLabel}</p>
<div className="max-w-[420px]">
<p className="text-sm text-foreground-light">
<p className="text-sm text-foreground-lighter">
Your {entityLabel} will not be accessible while it is paused.
</p>
</div>