diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 1131306a..37b35e64 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -48,6 +48,17 @@ PSD Gitea push and production deployment are still pending. ## Current product shape +### Login copy and language-selector focus — 2026-09-13 + +Removed the redundant login eyebrow/icon and installation-account explanation; +the main sign-in heading remains. The full-header language select no longer +shows an outer focus ring after pointer interaction; keyboard focus remains +visible, including after returning with Tab. EN/IT login and both themes are +covered by 36 targeted tests and 14 browser scenarios; typecheck/build pass. +Only the Mac frontend was rebuilt/recreated: `204efd40d3eb`, image `7dd0752ff823`, +healthy. Other containers are unchanged. Rollback image: +`thothii-frontend:before-login-focus-20260913`. No production deployment. + ### Full-header and layout refinements — 2026-09-13 The owner's four visual adjustments are implemented: full header uses Omics diff --git a/docs/reports/2026-09-13-header-layout-refinements.md b/docs/reports/2026-09-13-header-layout-refinements.md index 3ffb4a13..79924f85 100644 --- a/docs/reports/2026-09-13-header-layout-refinements.md +++ b/docs/reports/2026-09-13-header-layout-refinements.md @@ -49,3 +49,27 @@ bash /private/tmp/thothii-memory-preview.sh up -d --no-deps --no-build --wait fr Per tornare alla revisione nuova, ricostruire il frontend dal branch aggiornato e ripetere il comando di avvio. Non toccare volumi, Core o configurazione Omics. + +## Follow-up: login e focus del selettore lingua + +Rimossi dalla form login il soprattitolo «Accesso a ThothII», il relativo lucchetto +decorativo e la spiegazione «Usa l’account della tua installazione per continuare.». +Il titolo «Accedi a ThothII» rimane. La modifica vale in inglese e italiano. + +I select nativi possono mantenere `:focus-visible` anche dopo un clic. Il selettore +lingua distingue ora il focus da puntatore: niente outline esterno dopo il clic; +il normale bordo del controllo rimane. Il ritorno con Tab e l'interazione da +tastiera conservano l'indicatore visibile. Escape chiude il menu senza cambiare +la modalità di focus. Nessuna sfocatura forzata o modifica all'header Omics. + +Verificati 36 test LoginPage/AuthGate/AppShell.host e 14 scenari browser, inclusi +login EN/IT e clic, Escape, Tab/Shift+Tab in entrambi i temi. Screenshot controllati; +nessuna autenticazione reale o sessione utente è stata modificata dalle fixture. +Typecheck e build Docker superati. Impeccable ha guidato la rimozione dei testi +ridondanti mantenendo titolo principale e accessibilità da tastiera. + +Solo il frontend Mac è stato ricreato, healthy: container `204efd40d3eb`, immagine +`7dd0752ff823`. Il CSS effettivamente servito include la correzione del focus. +Core, catalogo, Qdrant ed embedding sono invariati. Per annullare soltanto questo +follow-up, usare nei comandi di rollback sopra il tag +`thothii-frontend:before-login-focus-20260913`. diff --git a/frontend/e2e/ui-visual-review.spec.ts b/frontend/e2e/ui-visual-review.spec.ts index d0818cfa..5c4236af 100644 --- a/frontend/e2e/ui-visual-review.spec.ts +++ b/frontend/e2e/ui-visual-review.spec.ts @@ -162,6 +162,40 @@ for (const width of [390, 1280]) { }); } +for (const locale of ["en", "it"]) { + test(`login keeps only the main title in ${locale}`, async ({ page }, testInfo) => { + await fixtures(page); + await page.route("**/config.js", route => route.fulfill({ contentType: "application/javascript", + body: `window.__THOTHII_CONFIG__ = ${JSON.stringify({ backendBaseUrl: "/api", shell: { mode: "full", defaultLocale: locale } })};` })); + await page.route("**/api/auth/config", route => route.fulfill({ json: { mode: "local", localLogin: true, oidcLogin: false } })); + await page.route("**/api/me", route => route.fulfill({ status: 401, json: { code: "unauthenticated" } })); + await page.goto("/"); + await expect(page.getByRole("heading", { name: locale === "it" ? "Accedi a ThothII" : "Sign in to ThothII" })).toBeVisible(); + await expect(page.getByText(locale === "it" ? "Accesso a ThothII" : "ThothII access", { exact: true })).toHaveCount(0); + await expect(page.getByText(locale === "it" ? "Usa l’account della tua installazione per continuare." : "Use your installation account to continue.", { exact: true })).toHaveCount(0); + await page.screenshot({ path: testInfo.outputPath(`login-${locale}.png`), animations: "disabled" }); + }); +} + +test("language selector has no pointer ring and retains keyboard focus in both themes", async ({ page }, testInfo) => { + await fixtures(page); + await page.goto("/"); + const language = page.getByRole("combobox", { name: "Interface language" }); + for (const theme of ["light", "dark"]) { + if (theme === "dark") await page.getByRole("button", { name: "Use dark theme", exact: true }).click(); + await language.click(); + await page.keyboard.press("Escape"); + await expect(language).toBeFocused(); + await expect(language).toHaveCSS("outline-style", "none"); + await expect(language).toHaveCSS("box-shadow", "none"); + await page.screenshot({ path: testInfo.outputPath(`language-pointer-${theme}.png`), animations: "disabled" }); + await page.keyboard.press("Tab"); + await page.keyboard.press("Shift+Tab"); + await expect(language).toBeFocused(); + await expect(language).toHaveCSS("outline-style", "solid"); + } +}); + for (const route of ["/", "/?thoth_route=administration%2Fdatabase"]) { test(`Core prompt remains readable after loading ${route}`, async ({ page }, testInfo) => { const writes = await fixtures(page); diff --git a/frontend/src/auth/LoginPage.test.tsx b/frontend/src/auth/LoginPage.test.tsx index b99a6aed..76bbe429 100644 --- a/frontend/src/auth/LoginPage.test.tsx +++ b/frontend/src/auth/LoginPage.test.tsx @@ -7,6 +7,7 @@ import { LoginPage } from "./LoginPage"; import { clearAuthState, setAuthState } from "./authState"; import * as authApi from "../api/auth"; import { server } from "../test/msw"; +import { setLocale } from "../i18n"; const localConfig = { mode: "local", localLogin: true, oidcLogin: false } as const; const oidcConfig = { mode: "oidc", localLogin: false, oidcLogin: true } as const; @@ -26,9 +27,16 @@ const authenticated = { }; beforeEach(() => clearAuthState()); -afterEach(() => clearAuthState()); +afterEach(() => { clearAuthState(); setLocale("en"); }); describe("LoginPage", () => { + test.each(["en", "it"])("keeps only the main sign-in title in %s", locale => { + setLocale(locale); + render(); + expect(screen.getByRole("heading", { name: locale === "it" ? "Accedi a ThothII" : "Sign in to ThothII" })).toBeInTheDocument(); + expect(screen.queryByText(locale === "it" ? "Accesso a ThothII" : "ThothII access")).not.toBeInTheDocument(); + expect(screen.queryByText(locale === "it" ? "Usa l’account della tua installazione per continuare." : "Use your installation account to continue.")).not.toBeInTheDocument(); + }); test("shows an unchecked local Remember me control and clears the password after failure", async () => { let submittedPassword = ""; server.use(http.post("/api/auth/local/login", async ({ request }) => { diff --git a/frontend/src/auth/LoginPage.tsx b/frontend/src/auth/LoginPage.tsx index 3f55b22f..367a7a7c 100644 --- a/frontend/src/auth/LoginPage.tsx +++ b/frontend/src/auth/LoginPage.tsx @@ -1,7 +1,7 @@ import { useI18n } from "../i18n"; import { useEffect, useRef, useState } from "react"; import type { FormEvent } from "react"; -import { AlertTriangle, ArrowRight, Eye, EyeOff, LockKeyhole } from "lucide-react"; +import { AlertTriangle, ArrowRight, Eye, EyeOff } from "lucide-react"; import { ApiError } from "../api/client"; import { beginOidcLogin, loginLocal } from "../api/auth"; import type { AuthenticatedUser, AuthPublicConfig } from "../api/types"; @@ -85,12 +85,7 @@ export function LoginPage({ config, onAuthenticated, onRetry }: LoginPageProps)
- -

{t("Sign in to ThothII")}

-

{t("Use your installation account to continue.")}

+

{t("Sign in to ThothII")}

{error && ( diff --git a/frontend/src/index.css b/frontend/src/index.css index e9cf6183..0f1f8e26 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -267,6 +267,12 @@ body.thot-full-document { margin: 0; } color: oklch(var(--foreground)); background: oklch(var(--card)); } + /* Native selects can match :focus-visible after a pointer click as well. + Suppress only that outer ring; keyboard focus retains its visible outline. */ + .thot-full-header .thot-shell-select[data-pointer-focus="true"]:focus { + outline: none; + box-shadow: none; + } .thot-shell-select { min-height: 2rem; max-width: 9rem; diff --git a/frontend/src/shell/host/FullHeader.tsx b/frontend/src/shell/host/FullHeader.tsx index 652029da..e65bfacf 100644 --- a/frontend/src/shell/host/FullHeader.tsx +++ b/frontend/src/shell/host/FullHeader.tsx @@ -12,6 +12,7 @@ export function FullHeader({ onLogout }: { onLogout?: () => Promise }) { const user = useAuthUser(); const [fullscreenPending, setFullscreenPending] = useState(false); const [logoutPending, setLogoutPending] = useState(false); + const [languagePointerFocus, setLanguagePointerFocus] = useState(false); const [error, setError] = useState(); if (shell?.mode !== "full") return null; const fullscreenLabel = shell.fullscreen ? t("Exit fullscreen") : t("Enter fullscreen"); @@ -41,7 +42,12 @@ export function FullHeader({ onLogout }: { onLogout?: () => Promise }) { ThothII
- setLanguagePointerFocus(true)} + onKeyDown={event => { if (event.key !== "Escape") setLanguagePointerFocus(false); }} + onBlur={() => setLanguagePointerFocus(false)} + onChange={event => shell.changeLocale(event.target.value)}> {availableLocales.map(({ code, name }) => )}