From a375c56db3cc0aafd1d26cf61f7e6140896a2256 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:54:55 +0800 Subject: [PATCH 1/2] fix: correct account sync and sign-in claims Align account copy with the actual favourites/preferences persistence model and browser-session recents. Clearly disable unavailable SSO options while preserving configured Google and Microsoft OAuth. Verified with focused DOM tests, verify:cheap, verify:ui, and verify:pr-local. --- .../account-setup-dialog.tsx | 58 +++++++++---------- .../clinical-dashboard/auth-panel.tsx | 55 ++++++++++-------- .../clinical-dashboard/settings-dialog.tsx | 34 +++++++---- tests/auth-panel-product-truth.dom.test.tsx | 50 ++++++++++++++++ tests/favourites-auth-gate.dom.test.tsx | 16 +++++ tests/settings-dialog-actions.dom.test.tsx | 11 ++++ tests/ui-smoke.spec.ts | 9 +-- 7 files changed, 165 insertions(+), 68 deletions(-) create mode 100644 tests/auth-panel-product-truth.dom.test.tsx diff --git a/src/components/clinical-dashboard/account-setup-dialog.tsx b/src/components/clinical-dashboard/account-setup-dialog.tsx index 6b9382dfe..fe1be5c77 100644 --- a/src/components/clinical-dashboard/account-setup-dialog.tsx +++ b/src/components/clinical-dashboard/account-setup-dialog.tsx @@ -1,7 +1,7 @@ "use client"; import { type FormEvent, useRef, useState } from "react"; -import { Clock3, FileText, Heart, LockKeyhole, Mail, ShieldCheck, SlidersHorizontal, X } from "lucide-react"; +import { Clock3, Heart, LockKeyhole, Mail, ShieldCheck, SlidersHorizontal, X } from "lucide-react"; import { BrandMark } from "@/components/clinical-dashboard/brand"; import { ProviderBrandIcon, type SsoProvider } from "@/components/clinical-dashboard/provider-brand-icons"; @@ -19,8 +19,8 @@ import { useAuthSession } from "@/lib/supabase/client"; const accountBenefits = [ { - label: "Search history", - detail: "Pick up recent questions on any device.", + label: "Local recents", + detail: "Recent questions stay in this browser session.", icon: Clock3, }, { @@ -29,9 +29,9 @@ const accountBenefits = [ icon: SlidersHorizontal, }, { - label: "Saved sources", - detail: "Keep the guidelines you rely on close.", - icon: FileText, + label: "Saved favourites", + detail: "Reopen favourite clinical tools across signed-in devices.", + icon: Heart, }, ] as const; @@ -42,8 +42,8 @@ const favouritesAccountBenefits = [ icon: Heart, }, { - label: "Search history", - detail: "Pick up recent questions on any device.", + label: "Local recents", + detail: "Recent questions stay in this browser session.", icon: Clock3, }, { @@ -55,8 +55,8 @@ const favouritesAccountBenefits = [ const securitySummary = [ { - label: "Private workspace", - detail: "Your data stays private and is never shared.", + label: "Account-scoped saves", + detail: "Favourites and preferences are stored with your account.", icon: ShieldCheck, }, { @@ -84,37 +84,26 @@ export function AccountSetupDialog({ const auth = useAuthSession(); const emailInputRef = useRef(null); const [email, setEmail] = useState(""); - const [providerNotice, setProviderNotice] = useState(null); const [emailAttempted, setEmailAttempted] = useState(false); const busy = auth.status === "loading"; - const statusMessage = providerNotice ?? (emailAttempted ? auth.error : null); - // Only a submit error (not the SSO provider notice) marks the email field - // invalid and is associated to it, so assistive tech ties the alert to the - // control the user must correct. - const emailHasError = !providerNotice && Boolean(statusMessage); + const statusMessage = emailAttempted ? auth.error : null; + const emailHasError = Boolean(statusMessage); const isFavouritesIntent = intent === "favourites"; const benefits = isFavouritesIntent ? favouritesAccountBenefits : accountBenefits; const title = isFavouritesIntent ? "Sign up to save favourites" : "Set up your workspace"; const subtitle = isFavouritesIntent ? "Create an account to save clinical favourites and access them across devices." - : "Sync source preferences, search history, and clinical defaults across devices."; - const benefitsHeading = isFavouritesIntent - ? "Favourites stay with your account" - : "Everything syncs across your devices"; + : "Sync favourites and clinical defaults across signed-in devices. Recent searches stay in this browser session."; + const benefitsHeading = isFavouritesIntent ? "Favourites stay with your account" : "What your account saves"; async function submit(event: FormEvent) { event.preventDefault(); const trimmedEmail = email.trim(); if (!trimmedEmail) return; - setProviderNotice(null); setEmailAttempted(true); await auth.signInWithEmail(trimmedEmail); } - function chooseProvider(provider: SsoProvider) { - setProviderNotice(`${provider} sign-in is not connected yet. Continue with email to set up this workspace.`); - } - return (
- or continue with + Social sign-in unavailable
{(["Apple", "Google", "Microsoft"] as const).map((provider) => ( - chooseProvider(provider)} /> + ))}
+

+ Continue with email. Social sign-in is not available in this setup. +

{statusMessage} @@ -296,15 +288,19 @@ export function AccountSetupDialog({ ); } -function ProviderButton({ provider, onClick }: { provider: SsoProvider; onClick: () => void }) { +function ProviderButton({ provider }: { provider: SsoProvider }) { return ( ); } diff --git a/src/components/clinical-dashboard/auth-panel.tsx b/src/components/clinical-dashboard/auth-panel.tsx index 774efee88..437108286 100644 --- a/src/components/clinical-dashboard/auth-panel.tsx +++ b/src/components/clinical-dashboard/auth-panel.tsx @@ -4,7 +4,7 @@ import { type FormEvent, useState, useSyncExternalStore } from "react"; import { ChevronRight, Clock3, - FileText, + Heart, LogOut, Mail, ShieldAlert, @@ -14,7 +14,7 @@ import { } from "lucide-react"; import { ProviderBrandIcon } from "@/components/clinical-dashboard/provider-brand-icons"; -import { AUTH_EMAIL_STORAGE_KEY, type OAuthProvider, useAuthSession } from "@/lib/supabase/client"; +import { AUTH_EMAIL_STORAGE_KEY, useAuthSession } from "@/lib/supabase/client"; import { AsyncButton, cn, @@ -65,7 +65,6 @@ export function AuthPanel() { const { status, error, notice, isConfigured, signInWithEmail, signInWithOAuth, signOut, session } = useAuthSession(); const savedEmail = useSyncExternalStore(subscribeAuthEmail, getAuthEmailSnapshot, getServerAuthEmailSnapshot); const [draftEmail, setDraftEmail] = useState(null); - const [providerNotice, setProviderNotice] = useState(null); const [emailError, setEmailError] = useState(null); const email = draftEmail ?? savedEmail; const busy = status === "loading"; @@ -85,19 +84,11 @@ export function AuthPanel() { return; } setEmailError(null); - setProviderNotice(null); await signInWithEmail(trimmed); } - async function chooseProvider(provider: "Apple" | "Google" | "Microsoft") { - setProviderNotice(null); - const providerId: OAuthProvider | null = - provider === "Google" ? "google" : provider === "Microsoft" ? "azure" : null; - if (providerId) { - await signInWithOAuth(providerId); - return; - } - setProviderNotice(`${provider} sign-in is a placeholder for now. Continue with email to use this workspace.`); + async function chooseProvider(provider: "Google" | "Microsoft") { + await signInWithOAuth(provider === "Google" ? "google" : "azure"); } if (!isConfigured) { @@ -157,7 +148,7 @@ export function AuthPanel() {

{isExpired ? "Send a fresh link if this one failed or timed out." - : "Save searches, source history, and clinical defaults. Do not enter PHI."} + : "Save favourites and clinical defaults across signed-in devices. Do not enter PHI."}

@@ -226,40 +217,58 @@ export function AuthPanel() {
- chooseProvider("Apple")} /> + chooseProvider("Google")} /> chooseProvider("Microsoft")} />
+

+ Apple sign-in is unavailable. Continue with email, Google, or Microsoft. +

+
- - + +

{notice && {notice}} - {providerNotice && {providerNotice}} {error && {error}} ); } -function ProviderButton({ provider, onClick }: { provider: "Apple" | "Google" | "Microsoft"; onClick: () => void }) { +function ProviderButton({ + provider, + onClick, + disabled = false, +}: { + provider: "Apple" | "Google" | "Microsoft"; + onClick?: () => void; + disabled?: boolean; +}) { return ( ); } diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index 25025d124..17ea640cd 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -229,12 +229,8 @@ export function SettingsDialog({ setAccountNotice(null); } - async function chooseSettingsProvider(provider: "Apple" | "Google" | "Microsoft") { + async function chooseSettingsProvider(provider: "Google" | "Microsoft") { setAccountNotice(null); - if (provider === "Apple") { - setAccountNotice("Apple sign-in is not configured. Continue with email, Google, or Microsoft."); - return; - } await auth.signInWithOAuth(provider === "Google" ? "google" : "azure"); } @@ -478,7 +474,10 @@ export function SettingsDialog({
- void chooseSettingsProvider("Apple")} /> + void chooseSettingsProvider("Google")} /> void; + onClick?: () => void; + disabledReason?: string; }) { - const label = provider === "email" ? "Use email instead" : provider; + const label = + provider === "email" ? "Use email instead" : disabledReason ? `${provider} sign-in unavailable` : provider; + const descriptionId = disabledReason ? `settings-provider-${provider.toLowerCase()}-unavailable` : undefined; return ( ); } diff --git a/tests/auth-panel-product-truth.dom.test.tsx b/tests/auth-panel-product-truth.dom.test.tsx new file mode 100644 index 000000000..cade2a38a --- /dev/null +++ b/tests/auth-panel-product-truth.dom.test.tsx @@ -0,0 +1,50 @@ +/** @vitest-environment jsdom */ + +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const authSession = vi.hoisted(() => ({ + status: "signed_out" as const, + error: null, + notice: null, + isConfigured: true, + signInWithEmail: vi.fn(), + signInWithOAuth: vi.fn(), + signOut: vi.fn(), + session: null, +})); + +vi.mock("@/lib/supabase/client", () => ({ + AUTH_EMAIL_STORAGE_KEY: "clinical-kb-auth-email", + useAuthSession: () => authSession, +})); + +import { AuthPanel } from "@/components/clinical-dashboard/auth-panel"; + +describe("AuthPanel product truth", () => { + beforeEach(() => { + window.localStorage.clear(); + vi.clearAllMocks(); + }); + + it("distinguishes account-synced data from browser-session recents", () => { + render(); + + expect(screen.getByText(/Save favourites and clinical defaults across signed-in devices/i)).toBeVisible(); + expect(screen.getByText("Saved favourites")).toBeVisible(); + expect(screen.getByText("Local recents")).toBeVisible(); + expect(screen.getByText(/Recent searches stay in this browser session/i)).toBeVisible(); + expect(screen.queryByText("Source history")).toBeNull(); + expect(screen.queryByText("Saved sources")).toBeNull(); + }); + + it("disables Apple while leaving configured OAuth providers available", () => { + render(); + + const apple = screen.getByRole("button", { name: "Apple sign-in unavailable" }); + expect(apple).toBeDisabled(); + expect(apple).toHaveAttribute("title", "Apple sign-in is unavailable. Continue with email, Google, or Microsoft."); + expect(screen.getByRole("button", { name: "Continue with Google" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "Continue with Microsoft" })).toBeEnabled(); + }); +}); diff --git a/tests/favourites-auth-gate.dom.test.tsx b/tests/favourites-auth-gate.dom.test.tsx index 413e989c8..9b024da50 100644 --- a/tests/favourites-auth-gate.dom.test.tsx +++ b/tests/favourites-auth-gate.dom.test.tsx @@ -141,6 +141,22 @@ describe("favourites auth gate DOM", () => { expect(screen.getByText("Saved favourites")).toBeVisible(); }); + it("describes the actual account persistence and disables unavailable social sign-in", () => { + render( undefined} />); + + expect(screen.getByRole("heading", { name: "What your account saves" })).toBeVisible(); + expect(screen.getByText(/Recent questions stay in this browser session/i)).toBeVisible(); + expect(screen.getByText("Account-scoped saves")).toBeVisible(); + expect(screen.queryByText(/Everything syncs across your devices/i)).toBeNull(); + expect(screen.queryByText(/never shared/i)).toBeNull(); + + for (const provider of ["Apple", "Google", "Microsoft"]) { + const button = screen.getByRole("button", { name: `${provider} sign-in unavailable` }); + expect(button).toBeDisabled(); + expect(button).toHaveAttribute("title", `${provider} sign-in is unavailable. Continue with email.`); + } + }); + it("blacks out Tools Saved workflows and Favourites shortcuts for guests", () => { authSession.status = "signed_out"; render(); diff --git a/tests/settings-dialog-actions.dom.test.tsx b/tests/settings-dialog-actions.dom.test.tsx index a0a9d4ee0..c055e95dd 100644 --- a/tests/settings-dialog-actions.dom.test.tsx +++ b/tests/settings-dialog-actions.dom.test.tsx @@ -139,4 +139,15 @@ describe("SettingsDialog — destructive and account actions", () => { expect(signInWithEmail).toHaveBeenCalledWith("clinician@clinic.example"); }); + + it("clearly disables unavailable Apple sign-in", () => { + renderDialog({ signedIn: false }); + fireEvent.click(screen.getAllByRole("button", { name: "Sign in" })[0]); + + const apple = screen.getByRole("button", { name: "Apple sign-in unavailable" }); + expect(apple).toBeDisabled(); + expect(apple).toHaveAttribute("title", "Apple sign-in is unavailable. Continue with email, Google, or Microsoft."); + expect(screen.getByRole("button", { name: "Google" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "Microsoft" })).toBeEnabled(); + }); }); diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index a58b00f3c..22d1b6997 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -835,10 +835,11 @@ async function expectAccountSetupSurface(setup: Locator) { await expect(setup.getByRole("heading", { name: "Set up your workspace" })).toBeVisible(); await expect(setup.getByLabel("Email address")).toBeVisible(); await expect(setup.getByRole("button", { name: "Continue", exact: true })).toBeVisible(); - await expect(setup.getByRole("button", { name: "Apple" })).toBeVisible(); - await expect(setup.getByRole("button", { name: "Google" })).toBeVisible(); - await expect(setup.getByRole("button", { name: "Microsoft" })).toBeVisible(); - await expect(setup.getByRole("heading", { name: "Everything syncs across your devices" })).toBeVisible(); + await expect(setup.getByRole("button", { name: "Apple sign-in unavailable" })).toBeDisabled(); + await expect(setup.getByRole("button", { name: "Google sign-in unavailable" })).toBeDisabled(); + await expect(setup.getByRole("button", { name: "Microsoft sign-in unavailable" })).toBeDisabled(); + await expect(setup.getByRole("heading", { name: "What your account saves" })).toBeVisible(); + await expect(setup.getByText(/Recent questions stay in this browser session/i)).toBeVisible(); await expect(setup.getByRole("heading", { name: "Security summary" })).toBeVisible(); await expect(setup.getByText("No PHI required")).toBeVisible(); await expect(setup).toContainText("Do not enter patient-identifying information."); From edbc2260fef59ca2fa7c6973dffb85e32354bce1 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:02:32 +0800 Subject: [PATCH 2/2] fix: align disabled sign-in placeholders Use the repository's coming-soon tooltip and connected screen-reader description pattern for unavailable providers. Focused DOM tests, ESLint, and typecheck pass. --- .../clinical-dashboard/account-setup-dialog.tsx | 12 ++++++++---- src/components/clinical-dashboard/auth-panel.tsx | 4 ++-- .../clinical-dashboard/settings-dialog.tsx | 2 +- tests/auth-panel-product-truth.dom.test.tsx | 5 ++++- tests/favourites-auth-gate.dom.test.tsx | 3 ++- tests/settings-dialog-actions.dom.test.tsx | 8 +++++++- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/clinical-dashboard/account-setup-dialog.tsx b/src/components/clinical-dashboard/account-setup-dialog.tsx index fe1be5c77..a3b9f5798 100644 --- a/src/components/clinical-dashboard/account-setup-dialog.tsx +++ b/src/components/clinical-dashboard/account-setup-dialog.tsx @@ -191,7 +191,7 @@ export function AccountSetupDialog({ ))}
-

+

Continue with email. Social sign-in is not available in this setup.

@@ -289,18 +289,22 @@ export function AccountSetupDialog({ } function ProviderButton({ provider }: { provider: SsoProvider }) { + const descriptionId = `account-${provider.toLowerCase()}-sign-in-unavailable`; + return ( ); } diff --git a/src/components/clinical-dashboard/auth-panel.tsx b/src/components/clinical-dashboard/auth-panel.tsx index 437108286..0b99a50d0 100644 --- a/src/components/clinical-dashboard/auth-panel.tsx +++ b/src/components/clinical-dashboard/auth-panel.tsx @@ -222,7 +222,7 @@ export function AuthPanel() { chooseProvider("Microsoft")} /> -

+

Apple sign-in is unavailable. Continue with email, Google, or Microsoft.

@@ -258,7 +258,7 @@ function ProviderButton({ type="button" onClick={onClick} disabled={disabled} - title={disabled ? "Apple sign-in is unavailable. Continue with email, Google, or Microsoft." : undefined} + title={disabled ? "Apple sign-in is unavailable — coming soon" : undefined} aria-describedby={disabled ? "auth-apple-sign-in-unavailable" : undefined} className="flex min-h-tap w-full items-center gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-lux)] px-3 text-left text-sm font-semibold text-[color:var(--text-heading)] shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] disabled:cursor-not-allowed disabled:bg-[color:var(--surface-inset)] disabled:text-[color:var(--disabled)] disabled:opacity-75 disabled:shadow-none" > diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index 17ea640cd..a364be58a 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -1158,7 +1158,7 @@ function SettingsProviderRow({ type="button" onClick={onClick} disabled={Boolean(disabledReason)} - title={disabledReason} + title={disabledReason ? `${disabledReason.replace(/\.$/, "")} — coming soon` : undefined} aria-label={label} aria-describedby={descriptionId} className="flex min-h-12 w-full items-center gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] px-3 text-left text-sm font-semibold text-[color:var(--text-heading)] shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] disabled:cursor-not-allowed disabled:bg-[color:var(--surface-inset)] disabled:text-[color:var(--disabled)] disabled:opacity-75 disabled:shadow-none" diff --git a/tests/auth-panel-product-truth.dom.test.tsx b/tests/auth-panel-product-truth.dom.test.tsx index cade2a38a..7ee4c5a32 100644 --- a/tests/auth-panel-product-truth.dom.test.tsx +++ b/tests/auth-panel-product-truth.dom.test.tsx @@ -43,7 +43,10 @@ describe("AuthPanel product truth", () => { const apple = screen.getByRole("button", { name: "Apple sign-in unavailable" }); expect(apple).toBeDisabled(); - expect(apple).toHaveAttribute("title", "Apple sign-in is unavailable. Continue with email, Google, or Microsoft."); + expect(apple).toHaveAttribute("title", "Apple sign-in is unavailable — coming soon"); + expect(apple).toHaveAccessibleDescription( + "Apple sign-in is unavailable. Continue with email, Google, or Microsoft.", + ); expect(screen.getByRole("button", { name: "Continue with Google" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Continue with Microsoft" })).toBeEnabled(); }); diff --git a/tests/favourites-auth-gate.dom.test.tsx b/tests/favourites-auth-gate.dom.test.tsx index 9b024da50..bb6652bd9 100644 --- a/tests/favourites-auth-gate.dom.test.tsx +++ b/tests/favourites-auth-gate.dom.test.tsx @@ -153,7 +153,8 @@ describe("favourites auth gate DOM", () => { for (const provider of ["Apple", "Google", "Microsoft"]) { const button = screen.getByRole("button", { name: `${provider} sign-in unavailable` }); expect(button).toBeDisabled(); - expect(button).toHaveAttribute("title", `${provider} sign-in is unavailable. Continue with email.`); + expect(button).toHaveAttribute("title", `${provider} sign-in is unavailable — coming soon`); + expect(button).toHaveAccessibleDescription(`${provider} sign-in is unavailable. Continue with email.`); } }); diff --git a/tests/settings-dialog-actions.dom.test.tsx b/tests/settings-dialog-actions.dom.test.tsx index c055e95dd..4747fd54d 100644 --- a/tests/settings-dialog-actions.dom.test.tsx +++ b/tests/settings-dialog-actions.dom.test.tsx @@ -146,7 +146,13 @@ describe("SettingsDialog — destructive and account actions", () => { const apple = screen.getByRole("button", { name: "Apple sign-in unavailable" }); expect(apple).toBeDisabled(); - expect(apple).toHaveAttribute("title", "Apple sign-in is unavailable. Continue with email, Google, or Microsoft."); + expect(apple).toHaveAttribute( + "title", + "Apple sign-in is unavailable. Continue with email, Google, or Microsoft — coming soon", + ); + expect(apple).toHaveAccessibleDescription( + "Apple sign-in is unavailable. Continue with email, Google, or Microsoft.", + ); expect(screen.getByRole("button", { name: "Google" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Microsoft" })).toBeEnabled(); });