From 9b7c83532b5e52e4aefa5dff3b8f91fd0b846e88 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Sat, 22 Aug 2026 23:16:06 +0200 Subject: [PATCH] fix: ai audit improvements --- src/api/__tests__/client.spec.ts | 6 +- src/api/client.ts | 15 ++- src/components/DeleteListModal.vue | 3 +- src/components/ListCard.vue | 30 ++--- src/components/ListItemRow.vue | 42 +++---- src/components/__tests__/ListItemRow.spec.ts | 2 - src/composables/useDismissableMenu.ts | 28 +++++ src/database/db.ts | 50 ++++++-- src/stores/__tests__/lists.spec.ts | 115 +++++++++++++++++++ src/stores/lists.ts | 80 +++++++------ src/views/ListDetailView.vue | 39 ++++--- src/views/ListsView.vue | 6 +- 12 files changed, 296 insertions(+), 120 deletions(-) create mode 100644 src/composables/useDismissableMenu.ts diff --git a/src/api/__tests__/client.spec.ts b/src/api/__tests__/client.spec.ts index 755c718..fcd6620 100644 --- a/src/api/__tests__/client.spec.ts +++ b/src/api/__tests__/client.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' -import { fetchWithAuth, apiClient } from '../client' +import { fetchWithAuth, apiClient, SessionExpiredError } from '../client' import { useAuthStore } from '@/stores/auth' import * as authApi from '@/api/auth' import router from '@/router' @@ -122,7 +122,7 @@ describe('api client (fetchWithAuth)', () => { } as unknown as Response) global.fetch = fetchMock - await fetchWithAuth('/lists') + await expect(fetchWithAuth('/lists')).rejects.toThrow(SessionExpiredError) expect(fetchMock).toHaveBeenCalledTimes(1) expect(router.currentRoute.value.name).toBe('login') @@ -144,7 +144,7 @@ describe('api client (fetchWithAuth)', () => { } as unknown as Response) global.fetch = fetchMock - await fetchWithAuth('/lists') + await expect(fetchWithAuth('/lists')).rejects.toThrow(SessionExpiredError) expect(fetchMock).toHaveBeenCalledTimes(1) expect(authStore.accessToken).toBeNull() diff --git a/src/api/client.ts b/src/api/client.ts index 26c2204..846e964 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -6,6 +6,17 @@ export { API_BASE_URL, extractErrorMessage } let refreshPromise: Promise | null = null +// Thrown instead of returning the stale 401 Response when redirecting to +// login, so callers show an accurate message rather than reading `!response.ok` +// and flashing an unrelated "Failed to ..." banner in the instant before +// navigation away completes. +export class SessionExpiredError extends Error { + constructor() { + super('Your session has expired. Please log in again.') + this.name = 'SessionExpiredError' + } +} + async function redirectToLogin(): Promise { const currentRoute = router.currentRoute.value if (currentRoute.name === 'login') { @@ -57,7 +68,7 @@ export async function fetchWithAuth( if (response.status === 401 && !skipRefresh) { if (!authStore.refreshToken) { await redirectToLogin() - return response + throw new SessionExpiredError() } try { @@ -74,7 +85,7 @@ export async function fetchWithAuth( }) } catch { await redirectToLogin() - return response + throw new SessionExpiredError() } } diff --git a/src/components/DeleteListModal.vue b/src/components/DeleteListModal.vue index c32d652..d8f614c 100644 --- a/src/components/DeleteListModal.vue +++ b/src/components/DeleteListModal.vue @@ -38,7 +38,8 @@ function handleConfirm() {