diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 7b273c92..bdb6cb9f 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -97,10 +97,7 @@
"oidc_clients": "OIDC Clients",
"api_keys": "API Keys",
"application_configuration": "Application Configuration",
- "settings": "Settings",
- "update_pocket_id": "Update Pocket ID",
"sqlite_storage_warning": "We detected that your SQLite database is stored on a network-attached drive like NFS, SMB, or FUSE. This is unsupported and can lead to your database getting corrupted. {#link href=|https://pocket-id.org/docs/configuration/environment-variables#sqlite|}See docs.{/link}",
- "powered_by": "Powered by",
"see_your_recent_account_activities": "See your account activities within the configured retention period.",
"time": "Time",
"event": "Event",
@@ -714,5 +711,16 @@
"custom_color_note_lightened": "Lightened in dark mode so buttons stand out from the background.",
"custom_color_note_edge": "Buttons get a thin border because the color is close to the background.",
"custom_color_note_near_status": "This color is close to the error, warning or success color, so buttons may read as a status.",
- "custom": "Custom"
+ "custom": "Custom",
+ "account": "Account",
+ "activity": "Activity",
+ "claim_mappings": "Claim mappings",
+ "configuration": "Configuration",
+ "main": "Main",
+ "update_available": "Update available",
+ "collapse_sidebar": "Collapse sidebar",
+ "expand_sidebar": "Expand sidebar",
+ "appearance": "Appearance",
+ "account_menu": "Account menu",
+ "more": "More"
}
diff --git a/frontend/src/app.css b/frontend/src/app.css
index f015cb6f..e0fc0650 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -94,12 +94,11 @@
--info-fg: #0a0a0a;
--info-soft: #f5f5f5;
--info-border: #e8e8e8;
- --well-light: #f6f4f0;
- --well-dark: #242220;
- --well-light-fg: #57534d;
- --well-dark-fg: #bab4aa;
+ --well-light: #f5f5f5;
+ --well-dark: #000000;
+ --well-light-fg: #5e5e5e;
+ --well-dark-fg: #a1a1a1;
--qr-bg: #ffffff;
- --qr-fg: #121212;
--scrim: rgb(18 17 16 / 0.4);
--ring: #0a0a0a;
--ring-halo: rgb(10 10 10 / 0.24);
@@ -185,12 +184,11 @@
--info-fg: #ededed;
--info-soft: #0e0e0e;
--info-border: #1f1f1f;
- --well-light: #f6f4f0;
- --well-dark: #242220;
- --well-light-fg: #57534d;
- --well-dark-fg: #bab4aa;
+ --well-light: #f5f5f5;
+ --well-dark: #000000;
+ --well-light-fg: #5e5e5e;
+ --well-dark-fg: #a1a1a1;
--qr-bg: #ffffff;
- --qr-fg: #121212;
--scrim: rgb(0 0 0 / 0.6);
--ring: #ededed;
--ring-halo: rgb(237 237 237 / 0.24);
diff --git a/frontend/src/lib/components/header/header-avatar.svelte b/frontend/src/lib/components/header/header-avatar.svelte
deleted file mode 100644
index 95841ab1..00000000
--- a/frontend/src/lib/components/header/header-avatar.svelte
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {$userStore?.displayName}
-
-
{$userStore?.email}
-
-
-
-
- goto('/settings/account')}
- > {m.my_account()}
- {m.logout()}
-
-
-
diff --git a/frontend/src/lib/components/header/header.svelte b/frontend/src/lib/components/header/header.svelte
deleted file mode 100644
index 3d24ba74..00000000
--- a/frontend/src/lib/components/header/header.svelte
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
- {#if !isAuthPage}
-
- {/if}
- {#if $userStore?.id}
-
- {/if}
-
-
-
diff --git a/frontend/src/lib/components/header/mode-switcher.svelte b/frontend/src/lib/components/header/mode-switcher.svelte
deleted file mode 100644
index 1ef3a563..00000000
--- a/frontend/src/lib/components/header/mode-switcher.svelte
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
- Toggle theme
-
-
- setMode('light')}>{m.light()}
- setMode('dark')}>{m.dark()}
- resetMode()}>{m.system()}
-
-
diff --git a/frontend/src/lib/components/shell/account-menu.svelte b/frontend/src/lib/components/shell/account-menu.svelte
new file mode 100644
index 00000000..23e9d828
--- /dev/null
+++ b/frontend/src/lib/components/shell/account-menu.svelte
@@ -0,0 +1,153 @@
+
+
+{#snippet appearanceOptions()}
+ setModeInstantly(v as 'light' | 'dark' | 'system')}
+ >
+ {m.light()}
+ {m.dark()}
+ {m.system()}
+
+{/snippet}
+
+{#if $userStore}
+ {
+ if (o) jumping = false;
+ }}
+ >
+
+
+ {#snippet child({ props: tooltipProps })}
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+ {/snippet}
+
+
+ {$userStore.displayName}{#if version}
+ · {version}{/if}
+
+
+
+
+
+
+
+ {$userStore.firstName?.[0]}
+
+
+ {$userStore.displayName}
+ {$userStore.email}
+
+
+
+
+ {
+ jumping = true;
+ goto('/settings/account');
+ }}
+ >
+
+ {m.my_account()}
+
+ {#if variant !== 'avatar'}
+
+
+
+
+ {m.appearance()}
+ {appearanceLabels[userPrefersMode.current]}
+
+
+
+ {@render appearanceOptions()}
+
+
+ {/if}
+
+
+ {#if variant === 'avatar'}
+
+
+ {m.appearance()}
+ {@render appearanceOptions()}
+
+ {/if}
+
+
+
+ {m.sign_out()}
+
+
+
+{/if}
diff --git a/frontend/src/lib/components/shell/app-sidebar.svelte b/frontend/src/lib/components/shell/app-sidebar.svelte
new file mode 100644
index 00000000..34dfeb2a
--- /dev/null
+++ b/frontend/src/lib/components/shell/app-sidebar.svelte
@@ -0,0 +1,247 @@
+
+
+
diff --git a/frontend/src/lib/components/shell/nav-items.ts b/frontend/src/lib/components/shell/nav-items.ts
new file mode 100644
index 00000000..1a42a199
--- /dev/null
+++ b/frontend/src/lib/components/shell/nav-items.ts
@@ -0,0 +1,98 @@
+import { m } from '#lib/paraglide/messages.js';
+import {
+ Blocks,
+ Braces,
+ FileText,
+ History,
+ IdCard,
+ Key,
+ LayoutGrid,
+ Server,
+ SlidersHorizontal,
+ User,
+ UsersRound
+} from '@lucide/svelte';
+import type { Component } from 'svelte';
+
+export type NavItem = { href: string; label: string; icon: Component };
+export type NavGroup = { label: string; items: NavItem[] };
+
+// The sidebar, the rail and the phone bar share these destinations
+export function navGroups({
+ isAdmin,
+ verificationPending,
+ showApis,
+ showApiKeys
+}: {
+ isAdmin: boolean;
+ verificationPending: boolean;
+ showApis: boolean;
+ showApiKeys: boolean;
+}): NavGroup[] {
+ // A user who still has to verify their email can only reach their account until they do
+ if (verificationPending) {
+ return [
+ {
+ label: m.account(),
+ items: [{ href: '/settings/account', label: m.my_account(), icon: IdCard }]
+ }
+ ];
+ }
+
+ const groups: NavGroup[] = [
+ {
+ label: m.account(),
+ items: [
+ { href: '/settings/account', label: m.my_account(), icon: IdCard },
+ { href: '/settings/apps', label: m.my_apps(), icon: LayoutGrid },
+ { href: '/settings/audit-log', label: m.activity(), icon: History }
+ ]
+ }
+ ];
+
+ if (isAdmin) {
+ groups.push({
+ label: m.administration(),
+ items: [
+ { href: '/settings/admin/users', label: m.users(), icon: User },
+ { href: '/settings/admin/user-groups', label: m.groups(), icon: UsersRound },
+ { href: '/settings/admin/oidc-clients', label: m.oidc_clients(), icon: Blocks },
+ {
+ href: '/settings/admin/oidc-claim-mapping-policies',
+ label: m.claim_mappings(),
+ icon: Braces
+ },
+ { href: '/settings/admin/apis', label: m.apis(), icon: Server },
+ { href: '/settings/admin/api-keys', label: m.api_keys(), icon: Key },
+ { href: '/settings/audit-log/global', label: m.audit_log(), icon: FileText },
+ {
+ href: '/settings/admin/application-configuration',
+ label: m.configuration(),
+ icon: SlidersHorizontal
+ }
+ ]
+ });
+ }
+
+ return groups;
+}
+
+// APIs and API keys join the sidebar once one exists (docs/design-system.md, PO11), and a failed check keeps the row
+export async function checkApiRows() {
+ const first = { pagination: { page: 1, limit: 1 } };
+ const exists = (list: Promise>) =>
+ list.then((r) => r.data.length > 0).catch(() => true);
+ const [apis, apiKeys] = await Promise.all([
+ exists(new ApisService().list(first)),
+ exists(new ApiKeyService().list(first))
+ ]);
+ return { apis, apiKeys };
+}
+
+// The most specific destination wins, so the global audit log is not also marked as Activity
+export function activeHref(groups: NavGroup[], pathname: string) {
+ return groups
+ .flatMap((g) => g.items.map((i) => i.href))
+ .filter((href) => pathname === href || pathname.startsWith(href + '/'))
+ .sort((a, b) => b.length - a.length)[0];
+}
diff --git a/frontend/src/lib/components/shell/phone-nav.svelte b/frontend/src/lib/components/shell/phone-nav.svelte
new file mode 100644
index 00000000..8c6d8a8b
--- /dev/null
+++ b/frontend/src/lib/components/shell/phone-nav.svelte
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+{#snippet tab(
+ href: string,
+ label: string,
+ Icon: NavGroup['items'][number]['icon'],
+ active: boolean
+)}
+
+
+ {label}
+
+{/snippet}
diff --git a/frontend/src/lib/components/sidebar.svelte b/frontend/src/lib/components/sidebar.svelte
deleted file mode 100644
index 65c63917..00000000
--- a/frontend/src/lib/components/sidebar.svelte
+++ /dev/null
@@ -1,161 +0,0 @@
-
-
-
diff --git a/frontend/src/lib/utils/color-theme-util.ts b/frontend/src/lib/utils/color-theme-util.ts
index 21e99663..2e078ce5 100644
--- a/frontend/src/lib/utils/color-theme-util.ts
+++ b/frontend/src/lib/utils/color-theme-util.ts
@@ -25,6 +25,9 @@ export type ColorTheme = {
accent: string;
tokens: Record>;
accentInputs: Record;
+ // Mono separates its sidebar with a hairline; Midnight paints a navy sidebar that remaps the neutrals inside it
+ sidebarEdge?: boolean;
+ sidebarVars?: Record;
};
export const DEFAULT_COLOR_THEME = 'mono';
@@ -406,15 +409,22 @@ export function applyColorTheme(value: string) {
Object.entries(vars)
.map(([k, v]) => `--${k}: ${v};`)
.join(' ');
- const css = (['light', 'dark'] as const)
- .map((mode) => {
- const vars = {
- ...theme.tokens[mode],
- ...primaryVars(deriveAccent(theme.accent, theme.accentInputs[mode]))
- };
- return `${mode === 'light' ? 'html:root' : 'html.dark'} { ${block(vars)} }`;
- })
- .join('\n');
+ const rules = (['light', 'dark'] as const).map((mode) => {
+ const vars = {
+ ...theme.tokens[mode],
+ ...primaryVars(deriveAccent(theme.accent, theme.accentInputs[mode]))
+ };
+ return `${mode === 'light' ? 'html:root' : 'html.dark'} { ${block(vars)} }`;
+ });
+ if (theme.sidebarEdge) {
+ rules.push('[data-slot="app-sidebar"] { box-shadow: inset -1px 0 0 var(--border); }');
+ }
+ if (theme.sidebarVars) {
+ rules.push(
+ `[data-slot="app-sidebar"], [data-slot="phone-top-bar"] { ${block(theme.sidebarVars)} color: var(--fg); }`
+ );
+ }
+ const css = rules.join('\n');
let el = document.getElementById('color-theme');
if (!el) {
diff --git a/frontend/src/lib/utils/color-themes.ts b/frontend/src/lib/utils/color-themes.ts
index 7c7fd904..003824b0 100644
--- a/frontend/src/lib/utils/color-themes.ts
+++ b/frontend/src/lib/utils/color-themes.ts
@@ -27,13 +27,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#ffffff',
'fill-skeleton': '#eeeeee',
'border-control': '#8c8c8c',
- 'border-field': '#8c8c8c',
- 'on-solid': '#ffffff',
'info-solid': '#5e5e5e',
'info-fg': '#0a0a0a',
'info-soft': '#f5f5f5',
'info-border': '#e8e8e8',
- 'qr-fg': '#121212'
+ 'well-light': '#f5f5f5',
+ 'well-dark': '#000000',
+ 'well-light-fg': '#5e5e5e',
+ 'well-dark-fg': '#a1a1a1'
},
dark: {
sidebar: '#0a0a0a',
@@ -57,13 +58,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#121212',
'fill-skeleton': '#1a1a1a',
'border-control': '#6b6b6b',
- 'border-field': '#6b6b6b',
- 'on-solid': '#0a0a0a',
'info-solid': '#a1a1a1',
'info-fg': '#ededed',
'info-soft': '#0e0e0e',
'info-border': '#1f1f1f',
- 'qr-fg': '#121212'
+ 'well-light': '#f5f5f5',
+ 'well-dark': '#000000',
+ 'well-light-fg': '#5e5e5e',
+ 'well-dark-fg': '#a1a1a1'
}
},
accentInputs: {
@@ -117,7 +119,8 @@ export const colorThemes: ColorTheme[] = [
success: 150
}
}
- }
+ },
+ sidebarEdge: true
},
{
id: 'mist',
@@ -144,13 +147,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#ffffff',
'fill-skeleton': '#eceef2',
'border-control': '#868d99',
- 'border-field': '#868d99',
- 'on-solid': '#ffffff',
'info-solid': '#545b67',
'info-fg': '#0f1218',
'info-soft': '#f7f8fa',
'info-border': '#e3e6eb',
- 'qr-fg': '#121212'
+ 'well-light': '#f7f8fa',
+ 'well-dark': '#1b1e23',
+ 'well-light-fg': '#545b67',
+ 'well-dark-fg': '#a3aab5'
},
dark: {
sidebar: '#15171b',
@@ -174,13 +178,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#23272d',
'fill-skeleton': '#272b31',
'border-control': '#737b87',
- 'border-field': '#737b87',
- 'on-solid': '#15171b',
'info-solid': '#a3aab5',
'info-fg': '#eef0f3',
'info-soft': '#202328',
'info-border': '#2e333a',
- 'qr-fg': '#121212'
+ 'well-light': '#f7f8fa',
+ 'well-dark': '#1b1e23',
+ 'well-light-fg': '#545b67',
+ 'well-dark-fg': '#a3aab5'
}
},
accentInputs: {
@@ -261,13 +266,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#ffffff',
'fill-skeleton': '#e8ede4',
'border-control': '#838c7e',
- 'border-field': '#838c7e',
- 'on-solid': '#ffffff',
'info-solid': '#515a4e',
'info-fg': '#121611',
'info-soft': '#f6f8f4',
'info-border': '#e0e6db',
- 'qr-fg': '#121212'
+ 'well-light': '#f6f8f4',
+ 'well-dark': '#1b1f1a',
+ 'well-light-fg': '#515a4e',
+ 'well-dark-fg': '#a5ad9e'
},
dark: {
sidebar: '#151814',
@@ -291,13 +297,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#242922',
'fill-skeleton': '#272c25',
'border-control': '#747d6f',
- 'border-field': '#747d6f',
- 'on-solid': '#151814',
'info-solid': '#a5ad9e',
'info-fg': '#eef2eb',
'info-soft': '#20241e',
'info-border': '#2f362c',
- 'qr-fg': '#121212'
+ 'well-light': '#f6f8f4',
+ 'well-dark': '#1b1f1a',
+ 'well-light-fg': '#515a4e',
+ 'well-dark-fg': '#a5ad9e'
}
},
accentInputs: {
@@ -378,13 +385,23 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#ffffff',
'fill-skeleton': '#ebedf2',
'border-control': '#858c9b',
- 'border-field': '#858c9b',
- 'on-solid': '#ffffff',
'info-solid': '#535a6b',
'info-fg': '#0e1220',
'info-soft': '#f6f7fa',
'info-border': '#e2e5ec',
- 'qr-fg': '#121212'
+ 'well-light': '#f6f7fa',
+ 'well-dark': '#161a25',
+ 'well-light-fg': '#535a6b',
+ 'well-dark-fg': '#a2aabb',
+ 'sidebar-fg': '#d6dcea',
+ 'sidebar-muted': '#97a2ba',
+ 'sidebar-subtle': '#66718a',
+ 'sidebar-hover': '#1e2540',
+ 'sidebar-selected': '#28304e',
+ 'sidebar-border': '#262e48',
+ 'sidebar-field': '#1e2540',
+ 'sidebar-edge': '#36405f',
+ 'sidebar-ring': '#d6dcea'
},
dark: {
sidebar: '#0d111d',
@@ -408,13 +425,23 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#1f2430',
'fill-skeleton': '#232836',
'border-control': '#727a8c',
- 'border-field': '#727a8c',
- 'on-solid': '#0d111d',
'info-solid': '#a2aabb',
'info-fg': '#edf0f6',
'info-soft': '#1b1f2a',
'info-border': '#2a3040',
- 'qr-fg': '#121212'
+ 'well-light': '#f6f7fa',
+ 'well-dark': '#161a25',
+ 'well-light-fg': '#535a6b',
+ 'well-dark-fg': '#a2aabb',
+ 'sidebar-fg': '#d6dcea',
+ 'sidebar-muted': '#97a2ba',
+ 'sidebar-subtle': '#5f6a82',
+ 'sidebar-hover': '#161c2e',
+ 'sidebar-selected': '#212a43',
+ 'sidebar-border': '#1b2236',
+ 'sidebar-field': '#141a2b',
+ 'sidebar-edge': '#2d3652',
+ 'sidebar-ring': '#d6dcea'
}
},
accentInputs: {
@@ -468,6 +495,20 @@ export const colorThemes: ColorTheme[] = [
success: 150
}
}
+ },
+ sidebarVars: {
+ canvas: 'var(--sidebar)',
+ surface: 'var(--sidebar-field)',
+ 'surface-raised': 'var(--sidebar-selected)',
+ fg: 'var(--sidebar-fg)',
+ 'fg-muted': 'var(--sidebar-muted)',
+ 'fg-subtle': 'var(--sidebar-subtle)',
+ 'fill-hover': 'var(--sidebar-hover)',
+ 'fill-selected': 'var(--sidebar-selected)',
+ 'fill-muted': 'var(--sidebar-selected)',
+ border: 'var(--sidebar-border)',
+ 'border-strong': 'var(--sidebar-edge)',
+ ring: 'var(--sidebar-ring)'
}
},
{
@@ -495,13 +536,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#ffffff',
'fill-skeleton': '#ebe9f3',
'border-control': '#888498',
- 'border-field': '#888498',
- 'on-solid': '#ffffff',
'info-solid': '#56526a',
'info-fg': '#14121b',
'info-soft': '#f8f7fb',
'info-border': '#e5e2ee',
- 'qr-fg': '#121212'
+ 'well-light': '#f8f7fb',
+ 'well-dark': '#1c1a23',
+ 'well-light-fg': '#56526a',
+ 'well-dark-fg': '#aaa5ba'
},
dark: {
sidebar: '#16141c',
@@ -525,13 +567,14 @@ export const colorThemes: ColorTheme[] = [
'surface-raised': '#25222e',
'fill-skeleton': '#292632',
'border-control': '#787387',
- 'border-field': '#787387',
- 'on-solid': '#16141c',
'info-solid': '#aaa5ba',
'info-fg': '#f1eff6',
'info-soft': '#211e29',
'info-border': '#312d3c',
- 'qr-fg': '#121212'
+ 'well-light': '#f8f7fb',
+ 'well-dark': '#1c1a23',
+ 'well-light-fg': '#56526a',
+ 'well-dark-fg': '#aaa5ba'
}
},
accentInputs: {
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index 8f95d6a7..14376967 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -1,11 +1,12 @@
{#if !appConfig}
{:else}
-
{@render children()}
{/if}
{
+ if (from?.url?.pathname.startsWith('/settings/admin/api'))
+ recheckedApiRows = await checkApiRows();
+ });
+ // Only the content region crossfades between pages; the shell never moves (docs/design-system.md, Page transitions)
onNavigate((navigation) => {
if (
navigation.shallow ||
!document.startViewTransition ||
- $appConfigStore.disableAnimations ||
+ document.documentElement.classList.contains('no-motion') ||
window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
!navigation.to?.url.pathname.startsWith('/settings/') ||
navigation.from?.url.pathname === navigation.to.url.pathname
@@ -76,28 +78,24 @@
});
-
-
-
-
-
-
- {#if sqliteStorageWarning && ($userStore?.isAdmin || user?.isAdmin)}
-
+
+
+
+
+
+
+ {#if sqliteStorageWarning && isAdmin}
+
@@ -106,18 +104,7 @@
{/if}
{@render children()}
-
-
-
-
- {m.powered_by()}
- Pocket ID
- ({versionInformation.currentVersion})
-
+
-
+
diff --git a/frontend/src/routes/settings/+layout.ts b/frontend/src/routes/settings/+layout.ts
index 5e72d1e5..062ca237 100644
--- a/frontend/src/routes/settings/+layout.ts
+++ b/frontend/src/routes/settings/+layout.ts
@@ -1,3 +1,4 @@
+import { checkApiRows } from '#lib/components/shell/nav-items.ts';
import StorageService from '#lib/services/storage-service.ts';
import VersionService from '#lib/services/version-service.ts';
import WebAuthnService from '#lib/services/webauthn-service.ts';
@@ -5,17 +6,21 @@ import type { AppVersionInformation } from '#lib/types/application-configuration
import { redirect } from '@sveltejs/kit';
import type { LayoutLoad } from './$types';
-export const load: LayoutLoad = async ({ url }) => {
+export const load: LayoutLoad = async ({ url, parent, untrack }) => {
const versionService = new VersionService();
const storageService = new StorageService();
const webauthnService = new WebAuthnService();
const currentVersion = versionService.getCurrentVersion();
- const [newestVersion, sqliteStorageWarning, passkeys] = await Promise.all([
+ const [newestVersion, sqliteStorageWarning, passkeys, apiRows] = await Promise.all([
versionService.getNewestVersion().catch(() => null),
storageService.getSqliteStorageWarning().catch(() => false),
- webauthnService.listCredentials()
+ webauthnService.listCredentials(),
+ // A tracked parent() would rerun this load on every navigation, because the root load reads the pathname
+ untrack(() => parent()).then(({ user }) =>
+ user?.isAdmin ? checkApiRows() : { apis: false, apiKeys: false }
+ )
]);
// If newestVersion is empty, it means the check is disabled or failed.
@@ -39,6 +44,7 @@ export const load: LayoutLoad = async ({ url }) => {
return {
versionInformation,
sqliteStorageWarning,
- passkeys
+ passkeys,
+ apiRows
};
};
diff --git a/tests/specs/navigation.spec.ts b/tests/specs/navigation.spec.ts
index 5e1fdf6e..0314fd37 100644
--- a/tests/specs/navigation.spec.ts
+++ b/tests/specs/navigation.spec.ts
@@ -3,22 +3,22 @@ import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(async () => await cleanupBackend());
-test('settings sidebar has an accessible name', async ({ page }) => {
+test('main navigation has an accessible name', async ({ page }) => {
await page.goto('/settings/account');
- const nav = page.getByRole('navigation', { name: 'Settings' });
+ const nav = page.getByRole('navigation', { name: 'Main' }).first();
await expect(nav).toBeVisible();
});
test('keyboard focus stays on sidebar link after navigating', async ({ page }) => {
await page.goto('/settings/account');
- const auditLog = page.getByRole('link', { name: 'Audit Log' });
- await auditLog.focus();
+ const activity = page.getByRole('link', { name: 'Activity' }).first();
+ await activity.focus();
await page.keyboard.press('Enter');
await page.waitForURL('**/settings/audit-log');
- await expect(auditLog).toBeFocused();
+ await expect(activity).toBeFocused();
});
test('unsaved changes block navigation until they are discarded', async ({ page }) => {
@@ -29,7 +29,7 @@ test('unsaved changes block navigation until they are discarded', async ({ page
await displayName.fill('Pending navigation');
await expect(page.getByText('You have unsaved changes', { exact: true })).toBeVisible();
- const myApps = page.getByRole('link', { name: 'My Apps' });
+ const myApps = page.getByRole('link', { name: 'My Apps' }).first();
await myApps.click();
await expect(page).toHaveURL(/\/settings\/account$/);