From c170bc1ae0e03ad8be63be70f82310833dcc7d12 Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:56:22 +0530
Subject: [PATCH 01/19] feat(auth): establish server-trusted Google
registration consent
---
src/lib/google-signup-consent.ts | 37 ++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 src/lib/google-signup-consent.ts
diff --git a/src/lib/google-signup-consent.ts b/src/lib/google-signup-consent.ts
new file mode 100644
index 00000000..175e4a96
--- /dev/null
+++ b/src/lib/google-signup-consent.ts
@@ -0,0 +1,37 @@
+import { TERMS_VERSION } from "@/content/legal";
+
+// A Google registration is explicitly requested, with the same legal consent as email signup.
+// This data is promoted into Better Auth's signed, server-only OAuth state, never accepted
+// directly from provider profile fields or a callback query string.
+export function googleSignupServerContext(additionalData: unknown, now = new Date()) {
+ if (
+ !additionalData ||
+ typeof additionalData !== "object" ||
+ Array.isArray(additionalData) ||
+ (additionalData as Record).acceptTerms !== true ||
+ (additionalData as Record).termsVersion !== TERMS_VERSION
+ )
+ return null;
+ return {
+ googleSignupTermsVersion: TERMS_VERSION,
+ googleSignupAcceptedAt: now.toISOString(),
+ };
+}
+
+export function googleSignupConsentFromState(state: unknown, now = Date.now()) {
+ if (!state || typeof state !== "object" || Array.isArray(state)) return null;
+ const serverContext = (state as Record).serverContext;
+ if (!serverContext || typeof serverContext !== "object" || Array.isArray(serverContext))
+ return null;
+ const consent = serverContext as Record;
+ if (
+ consent.googleSignupTermsVersion !== TERMS_VERSION ||
+ typeof consent.googleSignupAcceptedAt !== "string"
+ )
+ return null;
+ const acceptedAt = Date.parse(consent.googleSignupAcceptedAt);
+ // Never trust a client-provided timestamp, stale OAuth state or future acceptance.
+ if (!Number.isFinite(acceptedAt) || acceptedAt > now || now - acceptedAt > 15 * 60 * 1000)
+ return null;
+ return { termsVersion: TERMS_VERSION, termsAcceptedAt: new Date(acceptedAt) };
+}
From 3b4976e2e7f8defd6a61052465f6eaf35b3ce11a Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:56:25 +0530
Subject: [PATCH 02/19] feat(auth): establish server-trusted Google
registration consent
---
tests/google-signup-consent.test.ts | 32 +++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 tests/google-signup-consent.test.ts
diff --git a/tests/google-signup-consent.test.ts b/tests/google-signup-consent.test.ts
new file mode 100644
index 00000000..7f91de39
--- /dev/null
+++ b/tests/google-signup-consent.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, it } from "vitest";
+import { TERMS_VERSION } from "../src/content/legal";
+import {
+ googleSignupConsentFromState,
+ googleSignupServerContext,
+} from "../src/lib/google-signup-consent";
+
+describe("explicit Google registration legal consent", () => {
+ const now = new Date("2026-11-11T04:30:00.000Z");
+ it("only stamps the current policy after affirmative consent", () => {
+ expect(googleSignupServerContext(undefined, now)).toBeNull();
+ expect(googleSignupServerContext({ acceptTerms: "true", termsVersion: TERMS_VERSION }, now)).toBeNull();
+ expect(googleSignupServerContext({ acceptTerms: true, termsVersion: "old" }, now)).toBeNull();
+ expect(googleSignupServerContext({ acceptTerms: true, termsVersion: TERMS_VERSION }, now)).toEqual({
+ googleSignupTermsVersion: TERMS_VERSION,
+ googleSignupAcceptedAt: now.toISOString(),
+ });
+ });
+ it("accepts only fresh server-side OAuth state", () => {
+ const context = googleSignupServerContext({ acceptTerms: true, termsVersion: TERMS_VERSION }, now)!;
+ const state = { serverContext: context };
+ expect(googleSignupConsentFromState(state, now.getTime() + 60_000)).toEqual({
+ termsVersion: TERMS_VERSION,
+ termsAcceptedAt: now,
+ });
+ expect(googleSignupConsentFromState({ additionalData: context }, now.getTime())).toBeNull();
+ expect(googleSignupConsentFromState({ serverContext: { ...context, googleSignupTermsVersion: "old" } }, now.getTime())).toBeNull();
+ expect(googleSignupConsentFromState(state, now.getTime() + 15 * 60_000 + 1)).toBeNull();
+ expect(googleSignupConsentFromState(state, now.getTime() - 1)).toBeNull();
+ expect(googleSignupConsentFromState({ serverContext: { ...context, googleSignupAcceptedAt: "not-a-date" } }, now.getTime())).toBeNull();
+ });
+});
From fe460e27e669350031b9a67c7759650bbc2e0aa0 Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:56:27 +0530
Subject: [PATCH 03/19] feat(auth): establish server-trusted Google
registration consent
---
.../android-release/google-auth-acceptance.md | 44 +++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 docs/android-release/google-auth-acceptance.md
diff --git a/docs/android-release/google-auth-acceptance.md b/docs/android-release/google-auth-acceptance.md
new file mode 100644
index 00000000..ec55e3a0
--- /dev/null
+++ b/docs/android-release/google-auth-acceptance.md
@@ -0,0 +1,44 @@
+# Android / web Google authentication acceptance
+
+MenuMinute's Play app is an Android Trusted Web Activity (Chrome), not a WebView.
+The registered Google OAuth client is a **Web application** pointing at
+`https://food.bababuilds.com/api/auth/callback/google`. Do not use an Android-only
+OAuth redirect, and do not publish Google client secrets in app resources.
+
+## Supported journeys
+
+- Existing account, already linked to Google: sign in from the first screen; return to the
+ validated `next` path (the TWA uses `/app`).
+- Existing email/password-only account: do **not** silently link by matching email.
+ Authenticate with password first, then explicitly link under Account Security.
+- New owner: first-screen Create free account -> registration, check current Terms/Privacy
+ acceptance -> Continue with Google -> Google identity -> verified session -> first outlet
+ onboarding. A password is not required for a Google-created account.
+- New owner via email/password: the existing signup, deferred verification and onboarding
+ remain available.
+- A user protected by TOTP cannot bypass the authenticator with Google. Continue with
+ email/password and TOTP instead.
+
+The backend requires `requestSignUp` and signed server-side OAuth consent state before
+creating an account through Google. Linked-account sign-in never creates users implicitly.
+No new database migration or Android binary change is needed.
+
+## Production verification (not proven by CI)
+
+1. Confirm `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` exist on the production web
+ service and the Google Cloud OAuth consent screen is published/available to testers.
+2. Confirm the **Authorized redirect URI** is exactly
+ `https://food.bababuilds.com/api/auth/callback/google`.
+ This is unrelated to Android App Links / `assetlinks.json`.
+3. Test on the installed Play closed-testing TWA using a fresh Google identity. Consent,
+ browser return, authenticated session, welcome onboarding and app relaunch must work.
+4. Test a previously linked account and an email/password-only account with the same email:
+ the latter must remain unlinked until authenticated account-security linking.
+5. Verify declining the legal checkbox blocks Google registration; manipulation of
+ `additionalData` and a missing or expired consent state cannot create a user.
+6. Test both light/dark, narrow phone widths, keyboard accessibility and failure messages.
+7. Verify an MFA-enabled account remains blocked by Google sign-in.
+8. Record the deployed release SHA, Chrome version, device/Android version, and results.
+
+Google rejects sign-in inside some embedded WebViews. The Play client uses Chrome TWA:
+do **not** add an unreviewed native WebView or pass Google tokens to Android Java code.
From 9a3f039c9125c4ccd057ad275551ae6db4f8c30f Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:56:56 +0530
Subject: [PATCH 04/19] feat(auth): allow explicit Google signup without
weakening MFA or linking
---
src/lib/auth.ts | 40 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index e637648e..bb731902 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -10,7 +10,12 @@ import { env } from "@/lib/config/env";
import { hashPassword, verifyPassword } from "better-auth/crypto";
import bcrypt from "bcryptjs";
import { twoFactor, captcha } from "better-auth/plugins";
-import { APIError } from "better-auth/api";
+import { APIError, addOAuthServerContext, createAuthMiddleware, getOAuthState } from "better-auth/api";
+import { TERMS_VERSION } from "@/content/legal";
+import {
+ googleSignupConsentFromState,
+ googleSignupServerContext,
+} from "@/lib/google-signup-consent";
import { recordAuthEvent, alertSecurityEvent } from "@/lib/security-events";
import { sendAuthEmail } from "@/lib/auth-email";
import { isDemoActor } from "@/modules/demo/guard";
@@ -35,6 +40,14 @@ export const authServer = betterAuth({
error: "mfa_requires_password",
errorDescription: "This account requires password and authenticator sign-in.",
};
+ if (source.action === "create-user") {
+ const state = await getOAuthState().catch(() => null);
+ if (!googleSignupConsentFromState(state))
+ return {
+ error: "terms_required",
+ errorDescription: "Accept the current terms on the registration page before continuing.",
+ };
+ }
},
additionalFields: {
termsVersion: { type: "string", required: true, input: true },
@@ -45,14 +58,37 @@ export const authServer = betterAuth({
},
},
trustedOrigins: [new URL(env.APP_URL).origin],
+ hooks: {
+ before: createAuthMiddleware(async (ctx) => {
+ if (ctx.path !== "/sign-in/social") return;
+ const body = ctx.body as
+ | { provider?: unknown; requestSignUp?: unknown; additionalData?: unknown }
+ | undefined;
+ if (body?.provider !== "google" || body.requestSignUp !== true) return;
+ const consent = googleSignupServerContext(body.additionalData);
+ if (!consent)
+ throw new APIError("BAD_REQUEST", {
+ message: "Accept the current Terms and Privacy Policy before registering with Google.",
+ });
+ // Only server-validated consent is persisted into OAuth state. The client cannot
+ // forge serverContext, and the callback cannot skip this user-creation gate.
+ await addOAuthServerContext(consent);
+ }),
+ },
socialProviders:
env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
? {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
- disableSignUp: true,
+ // Returning sign-ins are never allowed to silently create a new user.
+ // Explicit Google registration supplies requestSignUp + server-trusted consent.
+ disableImplicitSignUp: true,
disableIdTokenSignIn: true,
+ mapProfileToUser: async () => {
+ const state = await getOAuthState().catch(() => null);
+ return googleSignupConsentFromState(state) ?? {};
+ },
requireEmailVerification: true,
},
}
From a3353bca87fd181194f1129cd3b8052e211f7e31 Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:57:35 +0530
Subject: [PATCH 05/19] fix(mobile): surface Google sign-in and free signup
before email form
---
src/app/login/login-form.tsx | 94 ++++++++++++++++++------------------
1 file changed, 46 insertions(+), 48 deletions(-)
diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx
index d31dbd2f..ece0b97c 100644
--- a/src/app/login/login-form.tsx
+++ b/src/app/login/login-form.tsx
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useState, useSyncExternalStore } from "react";
-import { ArrowRight, ShieldCheck, UserRound, X } from "lucide-react";
+import { ArrowRight, ShieldCheck, UserRound, UserRoundPlus, X } from "lucide-react";
import {
clearRememberedAccount,
completePendingLoginPreference,
@@ -52,15 +52,47 @@ export function LoginForm({ googleEnabled = false }: { googleEnabled?: boolean }
eyebrow="SIGN IN / WELCOME BACK"
title="Good to have you back."
description="Pick up where you left off. Your restaurant stays yours, across every device."
- extra={
-
- New to MenuMinute?
-
- Create an account
-
-
- }
>
+
+ {googleEnabled && (
+
+ )}
+
+
+
Create a free account
+
+
+
New restaurant? Start free — no payment card needed.
+
+ Or sign in with email
)}
{error && (
@@ -230,44 +266,6 @@ export function LoginForm({ googleEnabled = false }: { googleEnabled?: boolean }
{!busy && }
- {googleEnabled && (
-
-
Or sign in with a connected account
-
-
- New here? Create your account with email first, then connect Google in Account Security.
-
-
- )}
);
}
From 812f587da29cb5210b9ff371c2ede96711880afc Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:58:18 +0530
Subject: [PATCH 06/19] feat(auth): add explicit consent-gated Google signup to
registration
---
src/app/register/register-form.tsx | 254 +++++++++++++++++++++++++++++
1 file changed, 254 insertions(+)
create mode 100644 src/app/register/register-form.tsx
diff --git a/src/app/register/register-form.tsx b/src/app/register/register-form.tsx
new file mode 100644
index 00000000..62f89777
--- /dev/null
+++ b/src/app/register/register-form.tsx
@@ -0,0 +1,254 @@
+"use client";
+import { useState } from "react";
+import Link from "next/link";
+import { useRouter, useSearchParams } from "next/navigation";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { authClient } from "@/lib/auth-client";
+import { AuthShell } from "@/components/auth/auth-shell";
+import { RegistrationChallenge } from "@/components/auth/turnstile";
+import { safeReturnPath } from "@/lib/auth-return";
+import { TERMS_VERSION } from "@/content/legal";
+const schema = z
+ .object({
+ name: z.string().trim().min(2, "Enter your name (at least 2 characters).").max(100),
+ email: z.email("Enter a valid email address.").max(254),
+ password: z.string().min(8, "Use at least 8 characters.").max(128),
+ confirmPassword: z.string().min(1, "Confirm your password."),
+ acceptTerms: z.literal(true, { error: "Accept the Terms and Privacy Policy to continue." }),
+ })
+ .refine((v) => v.password === v.confirmPassword, {
+ message: "Passwords do not match.",
+ path: ["confirmPassword"],
+ });
+type Values = z.infer;
+export function RegistrationForm({ googleEnabled = false }: { googleEnabled?: boolean }) {
+ const router = useRouter(),
+ params = useSearchParams(),
+ next = safeReturnPath(params.get("next"), "");
+ const [busy, setBusy] = useState(false),
+ [error, setError] = useState(""),
+ [challenge, setChallenge] = useState("");
+ const [challengeRetry, setChallengeRetry] = useState(0);
+ const [googleBusy, setGoogleBusy] = useState(false);
+ const [showPassword, setShowPassword] = useState(false);
+ const {
+ register,
+ handleSubmit,
+ watch,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(schema),
+ shouldFocusError: true,
+ });
+ const termsAccepted = watch("acceptTerms") === true;
+ return (
+
+ Already have an account?
+ Sign in
+
+ }
+ >
+
+
+ );
+}
From 1f8afbdc3beaa575413b74336dea6041a23956fd Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:58:20 +0530
Subject: [PATCH 07/19] refactor(auth): render registration capabilities from
server config
---
src/app/register/page.tsx | 215 ++------------------------------------
1 file changed, 10 insertions(+), 205 deletions(-)
diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx
index ed2552c5..7b200766 100644
--- a/src/app/register/page.tsx
+++ b/src/app/register/page.tsx
@@ -1,211 +1,16 @@
-"use client";
-import { Suspense, useState } from "react";
-import Link from "next/link";
-import { useRouter, useSearchParams } from "next/navigation";
-import { useForm } from "react-hook-form";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { z } from "zod";
-import { authClient } from "@/lib/auth-client";
-import { AuthShell } from "@/components/auth/auth-shell";
-import { RegistrationChallenge } from "@/components/auth/turnstile";
-import { safeReturnPath } from "@/lib/auth-return";
-import { TERMS_VERSION } from "@/content/legal";
-const schema = z
- .object({
- name: z.string().trim().min(2, "Enter your name (at least 2 characters).").max(100),
- email: z.email("Enter a valid email address.").max(254),
- password: z.string().min(8, "Use at least 8 characters.").max(128),
- confirmPassword: z.string().min(1, "Confirm your password."),
- acceptTerms: z.literal(true, { error: "Accept the Terms and Privacy Policy to continue." }),
- })
- .refine((v) => v.password === v.confirmPassword, {
- message: "Passwords do not match.",
- path: ["confirmPassword"],
- });
-type Values = z.infer;
-function RegistrationForm() {
- const router = useRouter(),
- params = useSearchParams(),
- next = safeReturnPath(params.get("next"), "");
- const [busy, setBusy] = useState(false),
- [error, setError] = useState(""),
- [challenge, setChallenge] = useState("");
- const [challengeRetry, setChallengeRetry] = useState(0);
- const [showPassword, setShowPassword] = useState(false);
- const {
- register,
- handleSubmit,
- formState: { errors },
- } = useForm({
- resolver: zodResolver(schema),
- shouldFocusError: true,
- });
- return (
-
- Already have an account?
- Sign in
-
- }
- >
-
-
- );
-}
+import { Suspense } from "react";
+import { env } from "@/lib/config/env";
+import { RegistrationForm } from "./register-form";
+
+export const metadata = {
+ title: "Create an account — MenuMinute",
+ robots: { index: false, follow: false },
+};
+
export default function RegisterPage() {
return (
-
+
);
}
From 7055932204f147bd6c2e1d17623ac8752f839722 Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:59:11 +0530
Subject: [PATCH 08/19] style(auth): mobile-first signup and Google action
hierarchy
---
src/components/auth/auth-experience.css | 104 ++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/src/components/auth/auth-experience.css b/src/components/auth/auth-experience.css
index 6dea2be0..d31b02c5 100644
--- a/src/components/auth/auth-experience.css
+++ b/src/components/auth/auth-experience.css
@@ -543,3 +543,107 @@
background: var(--paper);
border-radius: 50%;
}
+
+
+/* Mobile-first entry choices: first-time owners must never hunt below the email form. */
+.mm-auth-entry {
+ display: grid;
+ gap: 11px;
+ margin-bottom: 16px;
+}
+.mm-auth-entry-help {
+ margin: 0;
+ color: var(--auth-muted);
+ font-size: 12px;
+ line-height: 1.5;
+ text-align: center;
+}
+.mm-auth a.mm-auth-create-account {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ min-height: 48px;
+ padding: 11px 16px;
+ border: 1.5px solid var(--auth-border);
+ border-radius: 12px;
+ background: var(--auth-soft);
+ color: var(--auth-text);
+ font-size: 14px;
+ font-weight: 800;
+ text-align: center;
+ text-decoration: none;
+}
+.mm-auth-create-account svg:first-child {
+ color: var(--auth-accent);
+}
+.mm-auth-create-account svg:last-child {
+ margin-left: auto;
+}
+.mm-auth a.mm-auth-create-account:hover {
+ border-color: var(--auth-accent);
+}
+.mm-auth .mm-auth-google {
+ min-height: 48px;
+ background: var(--auth-paper);
+ color: var(--auth-text);
+ border-color: var(--auth-border);
+}
+.mm-auth .mm-auth-google:disabled {
+ cursor: not-allowed;
+ opacity: 0.55;
+}
+.mm-auth-google-g {
+ display: inline-grid;
+ place-items: center;
+ font-weight: 900;
+ font-size: 20px;
+ line-height: 1;
+ color: #4285f4;
+}
+.mm-auth-email-divider {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 2px 0 18px;
+ color: var(--auth-muted);
+ font-size: 12px;
+ text-align: center;
+}
+.mm-auth-email-divider::before,
+.mm-auth-email-divider::after {
+ content: "";
+ height: 1px;
+ min-width: 12px;
+ flex: 1;
+ background: var(--auth-border);
+}
+.mm-auth-register-google {
+ display: grid;
+ gap: 12px;
+}
+.mm-auth-register-google .mm-auth-email-divider {
+ margin: 4px 0 0;
+}
+.mm-auth-form > .mm-auth-secondary:first-of-type {
+ justify-content: flex-start;
+ padding: 12px 0;
+ line-height: 1.6;
+}
+.mm-auth-form > .mm-auth-secondary:first-of-type input[type="checkbox"] {
+ width: 20px;
+ height: 20px;
+ flex: none;
+ accent-color: var(--auth-accent);
+}
+@media (max-width: 440px) {
+ .mm-auth-card h1 {
+ font-size: clamp(27px, 7.8vw, 34px);
+ }
+ .mm-auth-description {
+ margin-bottom: 20px;
+ }
+ .mm-auth-main {
+ gap: 18px;
+ }
+}
From 987b556256a4b5c24facde11be006514743bf869 Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 10:59:33 +0530
Subject: [PATCH 09/19] test(auth): cover signup discovery, consent gate and
phone widths
---
tests/e2e/auth-experience.spec.ts | 34 +++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/tests/e2e/auth-experience.spec.ts b/tests/e2e/auth-experience.spec.ts
index 6114c64e..98257e5b 100644
--- a/tests/e2e/auth-experience.spec.ts
+++ b/tests/e2e/auth-experience.spec.ts
@@ -36,3 +36,37 @@ test("reduced motion and narrow view retain accessible form without horizontal o
await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)
).toBe(true);
});
+
+
+test("Android-sized login exposes signup above the fold and keeps the app return target", async ({ page }) => {
+ await page.setViewportSize({ width: 360, height: 740 });
+ await page.goto("/login?next=%2Fapp");
+ const create = page.getByRole("link", { name: "Create a free account" });
+ await expect(create).toBeVisible();
+ await expect(create).toBeInViewport();
+ await expect(create).toHaveAttribute("href", "/register?next=%2Fapp");
+ await create.click();
+ await expect(page).toHaveURL(/\/register\?next=%2Fapp/);
+ await expect(page.getByRole("heading", { name: "Your next chapter starts here." })).toBeVisible();
+ const consent = page.getByRole("checkbox", { name: /I agree to the/ });
+ await expect(consent).toBeVisible();
+ const google = page.getByRole("button", { name: "Continue with Google" });
+ if (await google.count()) {
+ await expect(google).toBeDisabled();
+ await consent.check();
+ await expect(google).toBeEnabled();
+ }
+ await expect(page.getByRole("button", { name: "Create free account" })).toBeVisible();
+});
+
+test("Google signup stays accessible on small and large phone screens", async ({ page }) => {
+ for (const width of [320, 393, 430]) {
+ await page.setViewportSize({ width, height: 812 });
+ await page.goto("/login");
+ const create = page.getByRole("link", { name: "Create a free account" });
+ await expect(create).toBeInViewport();
+ const bounds = await create.boundingBox();
+ expect(bounds?.height ?? 0).toBeGreaterThanOrEqual(44);
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)).toBe(true);
+ }
+});
From f611023498cf2067eed311c97d5dad90aff35a0b Mon Sep 17 00:00:00 2001
From: "Sameer .I. Siddiqui"
Date: Wed, 11 Nov 2026 11:02:37 +0530
Subject: [PATCH 10/19] style(auth): apply pinned Prettier formatting and
remove blank mobile story strip
---
src/app/login/login-form.tsx | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx
index ece0b97c..dffef97b 100644
--- a/src/app/login/login-form.tsx
+++ b/src/app/login/login-form.tsx
@@ -78,7 +78,9 @@ export function LoginForm({ googleEnabled = false }: { googleEnabled?: boolean }
}
}}
>
- G
+
+ G
+
{googleBusy ? "Connecting to Google…" : "Continue with Google"}
)}
@@ -92,7 +94,9 @@ export function LoginForm({ googleEnabled = false }: { googleEnabled?: boolean }
New restaurant? Start free — no payment card needed.
- Or sign in with email
+
+ Or sign in with email
+