From c778bcdedc4487c2dcd0e6f9c59649ee1ee55da6 Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:03:47 +0300 Subject: [PATCH 1/6] fix(login): replace navigate-during-render with Calling navigate() in the component body triggers React updates during render and can loop while auth state resolves. Return a declarative after all hooks instead. Found by /impeccable audit (P1). --- frontend/src/Login.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/Login.tsx b/frontend/src/Login.tsx index d6c5f89e8..bc8d8116d 100644 --- a/frontend/src/Login.tsx +++ b/frontend/src/Login.tsx @@ -3,7 +3,7 @@ import cx from "classnames"; import { type FC, useState } from "react"; import { Button, Col, Form, Row } from "react-bootstrap"; import { useForm } from "react-hook-form"; -import { Link, useLocation, useNavigate } from "react-router-dom"; +import { Link, Navigate, useLocation } from "react-router-dom"; import { ROUTE_FORGOT_PASSWORD, ROUTE_REGISTER } from "src/constants/route"; import { getCredentialsSetting, getPlatformURL } from "src/utils/createClient"; import * as yup from "yup"; @@ -25,7 +25,6 @@ const Messages: Record = { const Login: FC = () => { const [loading, setLoading] = useState(false); const location = useLocation(); - const navigate = useNavigate(); const [loginError, setLoginError] = useState(""); const msg = new URLSearchParams(location.search).get("msg"); const redirect = new URLSearchParams(location.search).get("redirect"); @@ -38,7 +37,7 @@ const Login: FC = () => { resolver: yupResolver(schema), }); - if (isAuthenticated) navigate("/"); + if (isAuthenticated) return ; const onSubmit = async (formData: LoginFormData) => { setLoading(true); From 6e7e4ee4777de7bfafa21c9135521a513c20daa0 Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:03:47 +0300 Subject: [PATCH 2/6] fix(ui): repair react-select contrast failures - Selected value and typed input were forced to black on the dark $secondary control (~2.3:1); they now inherit the control's $text-color (~9.9:1). - Multi-value chips paired near-white text with a near-white chip; use $dark-text for ~9.6:1. - StudioSelect parent label used rgba(black, 0.5) inside the value container, rendering near-invisible; drop the override so it uses the legible $text-muted applied elsewhere. Brings all select surfaces to WCAG AA text contrast. Found by /impeccable audit (P1). --- frontend/src/App.scss | 7 +------ frontend/src/components/studioSelect/styles.scss | 6 ------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 2f92b7972..840fb387f 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -37,14 +37,9 @@ color: $text-color; cursor: pointer; - .react-select__single-value, - .react-select__input { - color: black; - } - .react-select__multi-value { background-color: $muted-gray; - color: $text-color; + color: $dark-text; } } diff --git a/frontend/src/components/studioSelect/styles.scss b/frontend/src/components/studioSelect/styles.scss index 92b17be2f..5d37f7384 100644 --- a/frontend/src/components/studioSelect/styles.scss +++ b/frontend/src/components/studioSelect/styles.scss @@ -2,10 +2,4 @@ .parent-studio { color: $text-muted; } - - .react-select__value-container { - .parent-studio { - color: rgba($black, 0.5); - } - } } From bc07f0a39f41b667c52f398092bd0c48a01e357a Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:03:47 +0300 Subject: [PATCH 3/6] fix(ui): render placeholder for deleted images in edit diffs Deleted images were rendered as srcless tags, which browsers draw as zero-width boxes, leaving blank gaps where removed images should appear in edit diffs. Add a shared DeletedImage fragment (muted tile with an X icon and Deleted label, matching the existing empty-thumbnail language) and use it in ImageChangeRow and AmendableImageChangeRow. Found by /impeccable audit (P1). --- .../AmendableImageChangeRow.tsx | 6 +++--- .../src/components/fragments/DeletedImage.tsx | 14 +++++++++++++ frontend/src/components/fragments/index.ts | 1 + frontend/src/components/fragments/styles.scss | 21 +++++++++++++++++++ .../imageChangeRow/ImageChangeRow.tsx | 3 ++- 5 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/fragments/DeletedImage.tsx diff --git a/frontend/src/components/amendableEditCard/AmendableImageChangeRow.tsx b/frontend/src/components/amendableEditCard/AmendableImageChangeRow.tsx index c6a1aa971..94b975b7f 100644 --- a/frontend/src/components/amendableEditCard/AmendableImageChangeRow.tsx +++ b/frontend/src/components/amendableEditCard/AmendableImageChangeRow.tsx @@ -2,7 +2,7 @@ import { faUndo, faXmark } from "@fortawesome/free-solid-svg-icons"; import cx from "classnames"; import type { FC } from "react"; import { Button, Col, Row } from "react-bootstrap"; -import { Icon } from "src/components/fragments"; +import { DeletedImage, Icon } from "src/components/fragments"; import ImageComponent from "src/components/image"; import { useAmendment } from "./AmendmentContext"; @@ -65,7 +65,7 @@ const AmendableImageChangeRow: FC = ({ })} > {image === null ? ( - Deleted + ) : (
= ({ })} > {image === null ? ( - Deleted + ) : (
( +
+ + Deleted +
+); + +export default DeletedImage; diff --git a/frontend/src/components/fragments/index.ts b/frontend/src/components/fragments/index.ts index 44e3ae376..b0f4f3973 100644 --- a/frontend/src/components/fragments/index.ts +++ b/frontend/src/components/fragments/index.ts @@ -1,3 +1,4 @@ +export { default as DeletedImage } from "./DeletedImage"; export { default as ErrorMessage } from "./ErrorMessage"; export { FavoriteStar } from "./Favorite"; export { default as GenderIcon } from "./GenderIcon"; diff --git a/frontend/src/components/fragments/styles.scss b/frontend/src/components/fragments/styles.scss index 6b85486b5..01accbea2 100644 --- a/frontend/src/components/fragments/styles.scss +++ b/frontend/src/components/fragments/styles.scss @@ -145,3 +145,24 @@ } } } + +.DeletedImage { + align-items: center; + background-color: $secondary; + border-radius: 4px; + color: var(--bs-gray-400); + display: flex; + flex-direction: column; + height: 150px; + justify-content: center; + margin: 5px; + padding: 0 1.25rem; + + .fa-icon { + font-size: 1.75rem; + } + + span { + font-size: 0.85rem; + } +} diff --git a/frontend/src/components/imageChangeRow/ImageChangeRow.tsx b/frontend/src/components/imageChangeRow/ImageChangeRow.tsx index 9d5990024..e8f206b69 100644 --- a/frontend/src/components/imageChangeRow/ImageChangeRow.tsx +++ b/frontend/src/components/imageChangeRow/ImageChangeRow.tsx @@ -1,5 +1,6 @@ import type { FC } from "react"; import { Col, Row } from "react-bootstrap"; +import { DeletedImage } from "src/components/fragments"; import ImageComponent from "src/components/image"; type Image = { @@ -28,7 +29,7 @@ const Images: FC<{ {(images ?? []).map((image, i) => image === null ? ( // biome-ignore lint/suspicious/noArrayIndexKey: Image is deleted, no other key - Deleted + ) : (
Date: Wed, 26 Aug 2026 15:03:47 +0300 Subject: [PATCH 4/6] fix(ui): remove global 1210px body min-width The hard min-width on body, carried over from the Bootstrap 5 migration, locked every viewport narrower than 1210px into permanent horizontal scrolling of the entire app. The navbar already wraps (react-bootstrap defaults to .navbar-expand), grids are auto-fill, and filter rows flex-wrap, so content degrades rather than breaking. The login prompt's fixed 960px width becomes a max-width so the auth pages - the first screen on small viewports - no longer overflow. Wider tables can still overflow their page; full responsive pass is tracked separately (/impeccable adapt). Found by /impeccable audit (P1). --- frontend/src/App.scss | 2 +- frontend/src/styles/theme.scss | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 840fb387f..299886263 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -60,7 +60,7 @@ div.react-select__menu { .LoginPrompt { height: 70vh; - width: 960px; + max-width: 960px; margin-left: auto; margin-right: auto; display: flex; diff --git a/frontend/src/styles/theme.scss b/frontend/src/styles/theme.scss index 6e94052cd..181f96310 100644 --- a/frontend/src/styles/theme.scss +++ b/frontend/src/styles/theme.scss @@ -88,7 +88,6 @@ body { color: $text-color; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - min-width: 1210px; } .table { From 21fffbe874fc5f6aa42656678b6792a510848d31 Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:03:47 +0300 Subject: [PATCH 5/6] fix(ui): pin react-select text color to $text-color token The contrast fix for selected values and input text relied on react-select emotion defaults surviving upgrades; make the light token explicit. Addresses code-review deviation on audit P1-2. --- frontend/src/App.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 299886263..29be4065d 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -37,6 +37,11 @@ color: $text-color; cursor: pointer; + .react-select__single-value, + .react-select__input { + color: $text-color; + } + .react-select__multi-value { background-color: $muted-gray; color: $dark-text; From 924f582029a82424df55ad8a8a1404100d5ca2cf Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:16:58 +0300 Subject: [PATCH 6/6] test(editCard): update deleted-image assertion to placeholder component The deleted-image placeholder is now a .DeletedImage element instead of a srcless Deleted, so query for it directly and also assert its label. Test intent (placeholder renders for null entries) is unchanged. Fixes CI failure in PR #1218. --- .../components/editCard/__tests__/renderStudioDetails.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/editCard/__tests__/renderStudioDetails.test.tsx b/frontend/src/components/editCard/__tests__/renderStudioDetails.test.tsx index 9c0c4f4a9..487f21e69 100644 --- a/frontend/src/components/editCard/__tests__/renderStudioDetails.test.tsx +++ b/frontend/src/components/editCard/__tests__/renderStudioDetails.test.tsx @@ -125,8 +125,9 @@ describe("renderStudioDetails", () => { {}, true, ); - const deleted = container.querySelector("img[alt='Deleted']"); + const deleted = container.querySelector(".DeletedImage"); expect(deleted).toBeInTheDocument(); + expect(within(deleted as HTMLElement).getByText("Deleted")); }); });