Skip to content

Redesign auth as single split-screen route#45

Merged
TraderSamwise merged 3 commits into
masterfrom
feat/fresh-auth
May 29, 2026
Merged

Redesign auth as single split-screen route#45
TraderSamwise merged 3 commits into
masterfrom
feat/fresh-auth

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace /landing, /sign-in, /sign-up with a single /auth route hosting sign-in, sign-up, forgot-password, and email verification behind a segmented toggle.
  • Split-screen layout: branded landing panel on wide web, compact header above the card on iOS / narrow web.
  • Apple / Google / GitHub OAuth buttons coded in but disabled by OAUTH_ENABLED flag (Clerk useSSO wiring ready behind the gate).
  • Forgot-password flow added via Clerk reset_password_email_code.
  • Extract clerkErrorMessage + sanitizeRedirect into lib/clerk-errors.ts, drop hand-rolled <TextInput> styling in favour of the Input primitive, add show/hide password toggle, iOS keychain autoComplete / textContentType.

Test plan

  • yarn typecheck clean (one unrelated expo-notifications types miss is pre-existing).
  • yarn lint clean for new files.
  • Run yarn dev:web:relay with EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY set and confirm the wide-web split layout, segmented toggle, and email + password sign-in.
  • Confirm narrow web (<880px) collapses to the compact header.
  • iOS simulator via yarn dev:ios:relay: layout, keyboard avoidance, password toggle.
  • Forgot password flow: email → reset code + new password → signed-in.
  • Email verification flow on sign-up.
  • Resend on both verification surfaces.
  • Disabled OAuth buttons render with soon tag and ignore taps.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Unified authentication screen combining sign-in, sign-up, verification, and password reset (with resend)
    • New branded auth layouts and modular auth form components; OAuth buttons scaffolded
  • Refactor

    • Routing consolidated to a single /auth endpoint; auth navigation and CLI sign-in redirect updated
    • Invite flows updated to point to /auth
  • Removed

    • Public landing screen content removed from the app

Review Change Stack

Replace the separate landing / sign-in / sign-up screens with a single
/auth route that hosts sign-in, sign-up, forgot-password, and email
verification behind a segmented toggle. Wide-web shows the merged
landing as a side panel; iOS and narrow web collapse to a compact
header above the card.

Apple, Google, and GitHub OAuth buttons are coded in but gated by an
OAUTH_ENABLED flag so they ship disabled on day one. Extracts the
shared clerkErrorMessage / sanitizeRedirect helpers into
lib/clerk-errors.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d828e68-3920-404b-a2c7-8e7f57ddc57a

📥 Commits

Reviewing files that changed from the base of the PR and between 4777d91 and 260d4fe.

📒 Files selected for processing (2)
  • app/app/auth.tsx
  • app/components/auth/OAuthRow.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/components/auth/OAuthRow.tsx
  • app/app/auth.tsx

📝 Walkthrough

Walkthrough

Consolidates landing/sign-in/sign-up into a single multi‑phase /auth screen; adds reusable auth UI components and Clerk error/redirect helpers; updates routing and callers to /auth; and reformats a couple of event type exports.

Changes

Auth Screen Consolidation

Layer / File(s) Summary
Routing foundation
app/app/_layout.tsx, app/app/cli-auth.tsx, app/app/shares/invite/[ownerUserId]/[token]/accept.tsx, app/components/AuthMenu.tsx
AuthGate now treats the "auth" segment as the auth route and redirects unauthenticated users to "/auth". RootLayout replaces landing, sign-in (modal), and sign-up (modal) with a single auth screen. Callers (cli-auth, invite accept, AuthMenu) now navigate to /auth with mode/redirect params where applicable.
Auth helper utilities
app/lib/clerk-errors.ts
clerkErrorMessage() extracts the first Clerk API error message (with fallback). sanitizeRedirect() validates redirect destinations to allow only single‑slash‑prefixed paths and rejects empty or //... values.
Reusable form components
app/components/auth/BrandPanel.tsx, app/components/auth/EmailPasswordForm.tsx, app/components/auth/ForgotPasswordForm.tsx, app/components/auth/OAuthRow.tsx, app/components/auth/VerificationForm.tsx
Adds BrandPanel (branding variants), EmailPasswordForm (sign-in/sign-up credential entry with visibility toggle), ForgotPasswordForm (password reset email entry), OAuthRow (provider buttons, currently disabled), and VerificationForm (code entry with resend/back actions).
Unified auth screen
app/app/auth.tsx
New default-exported /auth screen parsing mode and redirect, managing phases (form, verify, forgot, forgot-verify), wiring Clerk sign-in/sign-up/verification/reset flows, activating sessions on success, and navigating to sanitized redirects.
Type export formatting
app/lib/events.ts, app/lib/heartbeat.ts
Reformats event-type union and re-export statements from multi-line blocks to single-line expressions; no semantic changes.

Sequence Diagram

sequenceDiagram
  participant User
  participant AuthScreen
  participant EmailPasswordForm
  participant VerificationForm
  participant ClerkAPI
  participant Router
  User->>AuthScreen: Navigate to /auth?mode=sign-in&redirect=/home
  AuthScreen->>EmailPasswordForm: Render email/password form
  User->>EmailPasswordForm: Enter email and password
  EmailPasswordForm->>AuthScreen: onSubmit({email, password})
  AuthScreen->>ClerkAPI: useSignIn.create({email, password})
  alt Second Factor Required
    ClerkAPI-->>AuthScreen: status=needs_second_factor (pendingEmail)
    AuthScreen->>VerificationForm: Render code entry
    User->>VerificationForm: Enter verification code
    VerificationForm->>AuthScreen: onSubmit(code)
    AuthScreen->>ClerkAPI: useSignIn.attemptEmailCodeVerification({code})
  else Sign-In Complete
    ClerkAPI-->>AuthScreen: status=complete
  end
  ClerkAPI-->>AuthScreen: Session created
  AuthScreen->>ClerkAPI: setActive({session})
  AuthScreen->>Router: router.replace(/home)
  Router-->>User: Navigate to redirect destination
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • TraderSamwise/aimux#3: Also modifies Expo Router auth routing and AuthGate behavior; strongly related to the routing changes consolidated here.

Poem

🐰 One auth path now hops with grace,
Forms and codes in tidy place,
Clerk hums softly, sessions wake,
Redirects safe for journeys' sake,
A bunny cheers this single space.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main architectural change: replacing three separate auth routes (/landing, /sign-in, /sign-up) with a unified /auth route featuring a split-screen design.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fresh-auth

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/app/auth.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

app/components/auth/OAuthRow.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/components/auth/OAuthRow.tsx (1)

32-32: ⚡ Quick win

Make disabled styling conditional on OAUTH_ENABLED.

Line [32] keeps opacity-50 even when OAuth is enabled, so the buttons still look disabled after the flag is flipped.

Proposed diff
-          className="h-11 flex-row items-center justify-center rounded-lg border border-border bg-background opacity-50"
+          className={`h-11 flex-row items-center justify-center rounded-lg border border-border bg-background ${
+            OAUTH_ENABLED ? "" : "opacity-50"
+          }`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/auth/OAuthRow.tsx` at line 32, The OAuthRow component
currently always applies the "opacity-50" class in the className string (making
buttons look disabled) even when OAUTH_ENABLED is true; update OAuthRow (the JSX
that includes className="... opacity-50") to conditionally include "opacity-50"
only when the OAUTH_ENABLED flag is falsy (or when OAuth is actually disabled).
Implement the condition by building the className dynamically (e.g., template
literal or classnames helper) so that "opacity-50" is appended only when
!OAUTH_ENABLED, leaving the rest of the classes unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/app/auth.tsx`:
- Around line 209-319: The memoized `card` subtree (const card = useMemo(...))
is capturing stale handler closures because `isLoaded` and the auth handler
functions (e.g., handleVerify, handleForgotVerify, handleResendVerify,
handleResendForgot, handleForgotStart, handleSignIn, handleSignUp, switchMode,
reset) are not included in its dependency array; update the memo to either
remove useMemo entirely (so handlers always reflect latest state) or include
`isLoaded` plus all referenced handler functions in the dependency array so the
JSX is recomputed when loading state or handlers change, ensuring handlers like
handleVerify/handleForgotVerify no longer early-return incorrectly.

---

Nitpick comments:
In `@app/components/auth/OAuthRow.tsx`:
- Line 32: The OAuthRow component currently always applies the "opacity-50"
class in the className string (making buttons look disabled) even when
OAUTH_ENABLED is true; update OAuthRow (the JSX that includes className="...
opacity-50") to conditionally include "opacity-50" only when the OAUTH_ENABLED
flag is falsy (or when OAuth is actually disabled). Implement the condition by
building the className dynamically (e.g., template literal or classnames helper)
so that "opacity-50" is appended only when !OAUTH_ENABLED, leaving the rest of
the classes unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 32bc7c5f-e5cd-4ec1-a53b-5f3c06b92cd4

📥 Commits

Reviewing files that changed from the base of the PR and between 0c063e6 and fb5fed8.

📒 Files selected for processing (16)
  • app/app/_layout.tsx
  • app/app/auth.tsx
  • app/app/cli-auth.tsx
  • app/app/landing.tsx
  • app/app/shares/invite/[ownerUserId]/[token]/accept.tsx
  • app/app/sign-in.tsx
  • app/app/sign-up.tsx
  • app/components/AuthMenu.tsx
  • app/components/auth/BrandPanel.tsx
  • app/components/auth/EmailPasswordForm.tsx
  • app/components/auth/ForgotPasswordForm.tsx
  • app/components/auth/OAuthRow.tsx
  • app/components/auth/VerificationForm.tsx
  • app/lib/clerk-errors.ts
  • app/lib/events.ts
  • app/lib/heartbeat.ts
💤 Files with no reviewable changes (3)
  • app/app/sign-up.tsx
  • app/app/landing.tsx
  • app/app/sign-in.tsx

Comment thread app/app/auth.tsx Outdated
CodeRabbit flagged a stale-closure risk: when Clerk hadn't loaded yet
on first render, the memoized JSX retained handlers closing over
isLoaded=false, causing later auth actions to silently no-op.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/app/auth.tsx (1)

276-284: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disable mode switches while an auth request is in flight.

mode decides which verification branch runs later, but these two controls stay clickable during loading. If a user starts sign-up and flips to sign-in before Clerk responds, the request can still land in phase="verify" with mode="sign-in", and handleVerify will call the sign-in path against a sign-up attempt.

Suggested fix
 function switchMode(next: AuthMode) {
+  if (loading) return;
   setMode(next);
   setPhase("form");
   reset();
 }
         <SegmentedControl
           fullWidth
           options={[
             { value: "sign-in", label: "Sign in" },
             { value: "sign-up", label: "Sign up" },
           ]}
           value={mode}
           onChange={switchMode}
         />
@@
             {phase === "form" ? (
               <Button
                 className="mt-4"
                 variant="ghost"
                 label={
                   mode === "sign-in" ? "Don’t have an account? Sign up" : "Have an account? Sign in"
                 }
                 onPress={() => switchMode(mode === "sign-in" ? "sign-up" : "sign-in")}
+                disabled={loading}
               />
             ) : null}

Also applies to: 337-345

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/app/auth.tsx` around lines 276 - 284, Disable the SegmentedControl (and
the similar control around lines 337-345) while an auth request is in flight by
wiring its disabled prop to the request loading state; specifically, update the
SegmentedControl instances that use value={mode} and onChange={switchMode} to
also accept disabled={loading} (or the relevant boolean like isLoading) so users
cannot toggle mode during an ongoing Clerk request, and ensure switchMode and
handleVerify still correctly read the mode/phase values after the request
completes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/app/auth.tsx`:
- Around line 276-284: Disable the SegmentedControl (and the similar control
around lines 337-345) while an auth request is in flight by wiring its disabled
prop to the request loading state; specifically, update the SegmentedControl
instances that use value={mode} and onChange={switchMode} to also accept
disabled={loading} (or the relevant boolean like isLoading) so users cannot
toggle mode during an ongoing Clerk request, and ensure switchMode and
handleVerify still correctly read the mode/phase values after the request
completes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: efa3d6cc-632f-4ba0-a4f3-4fd4831f464c

📥 Commits

Reviewing files that changed from the base of the PR and between fb5fed8 and 4777d91.

📒 Files selected for processing (1)
  • app/app/auth.tsx

- Disable the sign-in/sign-up mode toggles while a Clerk request is
  in flight so users can't flip mode mid-request and land in the
  wrong verify branch.
- Tie the OAuth row's opacity-50 to !OAUTH_ENABLED so flipping the
  flag visually enables the buttons too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@TraderSamwise

TraderSamwise commented May 29, 2026

Copy link
Copy Markdown
Owner Author

Addressed CodeRabbit's out-of-diff feedback in 260d4fe:

  • OAuthRow opacity-50: now conditional on !OAUTH_ENABLED so the buttons render at full opacity once the flag flips.
  • Mode-switch race: switchMode early-returns when loading and the bottom "Don't have an account?" toggle now disables during requests, so the segmented control and inline link can no longer change mode mid-flight.

@TraderSamwise
TraderSamwise merged commit f295c80 into master May 29, 2026
1 check passed
@TraderSamwise
TraderSamwise deleted the feat/fresh-auth branch May 29, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant