From 8ef71728f6cf8ebc18b30d2b82265d47c312eec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 16:27:42 +0100 Subject: [PATCH 01/15] fix: add missing breadcrumb to books page Add a BooksBreadcrumb function that renders PageBreadcrumb with Home and Books. It is a separate function because the breadcrumb is needed in every return of the component --- .../app/components/books/SearchBar/SearchBar.tsx | 2 +- frontend/app/routes/books.tsx | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/app/components/books/SearchBar/SearchBar.tsx b/frontend/app/components/books/SearchBar/SearchBar.tsx index 6225530e..75191558 100644 --- a/frontend/app/components/books/SearchBar/SearchBar.tsx +++ b/frontend/app/components/books/SearchBar/SearchBar.tsx @@ -66,7 +66,7 @@ function SearchBar({ resultCount }: SearchBarProps) { const filtersDescription = `${filtersCount} ${filtersCount === 1 ? "filter" : "filters"} active`; return ( -
+

Browse books

diff --git a/frontend/app/routes/books.tsx b/frontend/app/routes/books.tsx index c08e3932..312c58ab 100644 --- a/frontend/app/routes/books.tsx +++ b/frontend/app/routes/books.tsx @@ -3,6 +3,7 @@ import { useSearchBooks, type SearchBook } from "~/hooks/useSearchBooks"; import { SearchBar } from "~/components/books/SearchBar/SearchBar"; import { BookCard } from "~/components/BookCard/BookCard"; import { SortSelect } from "~/components/books/SortSelect/SortSelect"; +import { PageBreadcrumb } from "~/components/PageBreadcrumb/PageBreadcrumb"; import { useBookSearchParams } from "~/hooks/useBookSearchParams"; import { useSuggestedBooks } from "~/hooks/useSuggestedBooks"; import { useSearchParams } from "react-router"; @@ -33,6 +34,14 @@ function BookList({ ); } +function BooksBreadcrumb() { + return ( + + ); +} + export default function BooksPage() { const [searchParams] = useSearchParams(); const { query, page, filters, sort } = useBookSearchParams(); @@ -69,6 +78,7 @@ export default function BooksPage() { if (loading) { return (
+

+

0) { return (

+
+

+ From 60a92adf4dbf4d2d5005eac5034372da7b6cdd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 16:53:53 +0100 Subject: [PATCH 02/15] feat: add role and aria-label to book card rating text --- frontend/app/components/BookCard/BookCard.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/app/components/BookCard/BookCard.tsx b/frontend/app/components/BookCard/BookCard.tsx index a453540a..df4eca2f 100644 --- a/frontend/app/components/BookCard/BookCard.tsx +++ b/frontend/app/components/BookCard/BookCard.tsx @@ -41,7 +41,15 @@ function BookCard({ book, searchListParams }: BookCardProps) {

{book.authors.join(", ")}

{book.publishedYear ?? "Unknown"}

-

+

{book.rating == null ? ( "No ratings yet" ) : ( From 48cc51cf6553fb3a562b137480a1355b37f76974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 16:56:17 +0100 Subject: [PATCH 03/15] fix: remove "rating" text from book card rating on profile page The cards are smaller on the profile page, which made the rating text wrap awkwardly --- frontend/app/components/BookCard/BookCard.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/BookCard/BookCard.tsx b/frontend/app/components/BookCard/BookCard.tsx index df4eca2f..95d6699b 100644 --- a/frontend/app/components/BookCard/BookCard.tsx +++ b/frontend/app/components/BookCard/BookCard.tsx @@ -14,12 +14,13 @@ interface BookCardProps { function BookCard({ book, searchListParams }: BookCardProps) { const [searchParams] = useSearchParams(); const location = useLocation(); + const isProfilePage = location.pathname === "/profile"; const nextSearchParams = new URLSearchParams( searchListParams ?? searchParams, ); - if (location.pathname === "/profile") { + if (isProfilePage) { nextSearchParams.set("origin", "favorites"); } @@ -61,7 +62,8 @@ function BookCard({ book, searchListParams }: BookCardProps) { {book.rating.toFixed(2)} - ({(book.numRatings ?? 0).toLocaleString("en")} ratings) + ({(book.numRatings ?? 0).toLocaleString("en")} + {!isProfilePage && " ratings"}) )}

From 0b17f83d96781a03b0793ade5284ec1d04165c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 17:33:47 +0100 Subject: [PATCH 04/15] fix: set color-scheme so search input autofill fits dark mode Chrome painted a light blue autofill background on the search input when a previous search was picked in dark mode. With a color-scheme for light and dark mode the browser uses matching native styling. This does not remove the autofill tint completely, but it is much better in both themes. --- frontend/app/index.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/app/index.css b/frontend/app/index.css index 24dbb3e2..bb00b97d 100644 --- a/frontend/app/index.css +++ b/frontend/app/index.css @@ -88,6 +88,7 @@ } :root { + color-scheme: light; --background: oklch(0.9659 0.0042 56.37); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); @@ -126,6 +127,7 @@ } .dark { + color-scheme: dark; --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); --card: oklch(0.205 0 0); From c897d24d3457ece53f9e7dba2de954a39820067b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 18:05:03 +0100 Subject: [PATCH 05/15] fix: keep book card height fixed on large screens --- frontend/app/routes/book/selectedBook.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/routes/book/selectedBook.tsx b/frontend/app/routes/book/selectedBook.tsx index 61846bbb..16afd8df 100644 --- a/frontend/app/routes/book/selectedBook.tsx +++ b/frontend/app/routes/book/selectedBook.tsx @@ -147,7 +147,7 @@ export default function SelectedBookPage({ params }: Route.ComponentProps) {
- + From 8d92b71759d45edc99c0465b2755444dc4e71d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 18:07:13 +0100 Subject: [PATCH 06/15] fix: match right-hand card heights to the cover card on large screens --- frontend/app/routes/book/selectedBook.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/routes/book/selectedBook.tsx b/frontend/app/routes/book/selectedBook.tsx index 16afd8df..6a33e527 100644 --- a/frontend/app/routes/book/selectedBook.tsx +++ b/frontend/app/routes/book/selectedBook.tsx @@ -146,15 +146,15 @@ export default function SelectedBookPage({ params }: Route.ComponentProps) { Go back -
+
-
- +
+

{book.title}

From d4fa3f00e332760dbd2dd6f3cbc3bcce962517dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 18:17:49 +0100 Subject: [PATCH 07/15] fix: reduce gap in header --- frontend/app/components/Header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Header/Header.tsx b/frontend/app/components/Header/Header.tsx index d0d5f89b..f2d10633 100644 --- a/frontend/app/components/Header/Header.tsx +++ b/frontend/app/components/Header/Header.tsx @@ -35,7 +35,7 @@ function Header() { -
+
{!isPending && session ? ( <> From 5aa6d1a2d86170d7c337b7bc7c90a7bf92fc8206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 18:38:33 +0100 Subject: [PATCH 08/15] fix: make it clearer what "unknown" means on book cards --- frontend/app/components/BookCard/BookCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/BookCard/BookCard.tsx b/frontend/app/components/BookCard/BookCard.tsx index 95d6699b..55ea4fe0 100644 --- a/frontend/app/components/BookCard/BookCard.tsx +++ b/frontend/app/components/BookCard/BookCard.tsx @@ -41,7 +41,7 @@ function BookCard({ book, searchListParams }: BookCardProps) {

{book.authors.join(", ")}

-

{book.publishedYear ?? "Unknown"}

+

{book.publishedYear ?? "Year unknown"}

Date: Wed, 11 Nov 2026 19:11:52 +0100 Subject: [PATCH 09/15] feat: set dynamic page title on selected book page Render a title in the selected book page component instead of exporting meta, since the title depends on the book data that is defined dynamically in the route. React 19 "hoists" the tag into the head, so the tab title updates with the book. --- frontend/app/routes/book/selectedBook.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/app/routes/book/selectedBook.tsx b/frontend/app/routes/book/selectedBook.tsx index 6a33e527..189ce817 100644 --- a/frontend/app/routes/book/selectedBook.tsx +++ b/frontend/app/routes/book/selectedBook.tsx @@ -31,6 +31,8 @@ export default function SelectedBookPage({ params }: Route.ComponentProps) { const origin = searchParams.get("origin"); const { book, loading, error } = useBook({ id: params.bookId }); + + const pageTitle = book ? `${book.title} | Readr` : "Book details | Readr"; const { books: searchBooks } = useSearchBooks({ filters, @@ -64,6 +66,8 @@ export default function SelectedBookPage({ params }: Route.ComponentProps) { if (loading) { return (

+ {/* Uses instead of meta() to allow the document title to be defined dynamically in the route. */} + <title>{pageTitle}

Loading Book Details

The requested book details are currently being loaded.

@@ -73,6 +77,8 @@ export default function SelectedBookPage({ params }: Route.ComponentProps) { if (error || !book || !books) { return (
+ {/* Uses instead of meta() to allow the document title to be defined dynamically in the route. */} + <title>{pageTitle}

Book Not Found

The requested book could not be found.

@@ -90,6 +96,8 @@ export default function SelectedBookPage({ params }: Route.ComponentProps) { return (
+ {/* Uses instead of meta() to allow the document title to be defined dynamically in the route. */} + <title>{pageTitle} {origin === "favorites" ? ( Date: Wed, 11 Nov 2026 19:40:14 +0100 Subject: [PATCH 10/15] test: Update books test and BookCard snapshot to pass all tests --- .../BookCard/__snapshots__/BookCard.test.tsx.snap | 4 +++- frontend/app/routes/books.test.tsx | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap b/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap index 62eede47..b05b6a16 100644 --- a/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap +++ b/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap @@ -44,7 +44,9 @@ exports[`BookCard > matches the snapshot 1`] = `

Joe Abercrombie

-

+

2001

{ }); expect(await screen.findByText("15 results")).toBeInTheDocument(); - expect(screen.getByRole("link", { current: "page" })).toHaveTextContent( - "2", - ); + expect( + within(screen.getByRole("navigation", { name: "pagination" })).getByRole( + "link", + { current: "page" }, + ), + ).toHaveTextContent("2"); }); it("tells the user when no books match the search", async () => { From 774c127600bd740ebe67562c2416f3d0133a92f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Wed, 11 Nov 2026 19:58:47 +0100 Subject: [PATCH 11/15] style: format frontend with prettier --- frontend/app/components/BookCard/BookCard.tsx | 4 +++- frontend/app/routes/book/selectedBook.tsx | 2 +- frontend/app/routes/books.tsx | 4 +--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/app/components/BookCard/BookCard.tsx b/frontend/app/components/BookCard/BookCard.tsx index 55ea4fe0..0f3fd005 100644 --- a/frontend/app/components/BookCard/BookCard.tsx +++ b/frontend/app/components/BookCard/BookCard.tsx @@ -41,7 +41,9 @@ function BookCard({ book, searchListParams }: BookCardProps) {

{book.authors.join(", ")}

-

{book.publishedYear ?? "Year unknown"}

+

+ {book.publishedYear ?? "Year unknown"} +

+ ); } From fb5332177bd5c4a2f21c4a16c39c768f293de361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Thu, 12 Nov 2026 11:22:27 +0100 Subject: [PATCH 12/15] fix: remove duplicate title from root Co-authored-by: Martin Alexander Kaminski --- frontend/app/root.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx index 97dd2e53..5167a0e4 100644 --- a/frontend/app/root.tsx +++ b/frontend/app/root.tsx @@ -39,7 +39,6 @@ export function Layout({ children }: { children: React.ReactNode }) { - Readr Date: Thu, 12 Nov 2026 11:42:44 +0100 Subject: [PATCH 13/15] fix: use pointer cursor on dropdowns --- frontend/app/components/ui/Combobox.tsx | 2 +- frontend/app/components/ui/DropdownMenu.tsx | 8 ++++---- frontend/app/components/ui/Select.tsx | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/app/components/ui/Combobox.tsx b/frontend/app/components/ui/Combobox.tsx index 534a2525..ea40bae7 100644 --- a/frontend/app/components/ui/Combobox.tsx +++ b/frontend/app/components/ui/Combobox.tsx @@ -143,7 +143,7 @@ function ComboboxItem({ Date: Thu, 12 Nov 2026 11:50:22 +0100 Subject: [PATCH 14/15] feat: add log out button to profile page --- frontend/app/routes/profile.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/app/routes/profile.tsx b/frontend/app/routes/profile.tsx index 438e7157..be598c63 100644 --- a/frontend/app/routes/profile.tsx +++ b/frontend/app/routes/profile.tsx @@ -6,7 +6,7 @@ import { BookPagination } from "~/components/BookPagination/BookPagination"; import { PageBreadcrumb } from "~/components/PageBreadcrumb/PageBreadcrumb"; import { Avatar, AvatarFallback } from "~/components/ui/Avatar"; import { Badge } from "~/components/ui/Badge"; -import { buttonVariants } from "~/components/ui/Button"; +import { Button, buttonVariants } from "~/components/ui/Button"; import { Card, CardContent, @@ -112,7 +112,7 @@ export default function ProfilePage() {

)} - + Edit profile +
From abfb1283c320c68600e916b7e053e7270ed224db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Str=C3=B8m-Andersen?= Date: Thu, 12 Nov 2026 21:55:25 +0100 Subject: [PATCH 15/15] fix: add page title to the error boundary Co-authored-by: Johannes Horn --- frontend/app/root.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx index 5167a0e4..7f453363 100644 --- a/frontend/app/root.tsx +++ b/frontend/app/root.tsx @@ -128,6 +128,7 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { alignItems: "center", }} > + {`${message === "404" ? "Page not found" : "Error"} | Readr`}

{message}

{details}

{stack && (