Skip to content

Unblock the host room page after room creation - #32

Draft
posthog-eu[bot] wants to merge 1 commit into
developfrom
posthog-self-driving/fixhost-unblock-the-room-page-after-1f0b04
Draft

Unblock the host room page after room creation#32
posthog-eu[bot] wants to merge 1 commit into
developfrom
posthog-self-driving/fixhost-unblock-the-room-page-after-1f0b04

Conversation

@posthog-eu

@posthog-eu posthog-eu Bot commented Aug 11, 2026

Copy link
Copy Markdown

Problem

  • A first-time host creates a room, then gets stuck on a spinner over a black overlay and never reaches the host screen with the join QR code — the room they just made is unusable.
  • CreateRoomForm passes handleCreateRoom straight to <form action={...}> and calls router.push from inside it. React keeps the form action pending for the whole route transition, so SubmitButton keeps spinning and the dialog never closes. Its bg-black/50 overlay stays over the old /host page.
  • The destination is a server component that blocks on sequential Convex round trips (room preload, then auth token, then isHost), and there was no loading boundary anywhere in apps/web/src/app, so nothing covered the wait.

Changes

Change File Effect
Close the dialog before navigating components/host/create-room.tsx The overlay and submit spinner cannot outlive the mutation.
Add a loading boundary app/host/[code]/loading.tsx (new) The transition renders the room shell, not the previous page.
Parallelize independent awaits app/host/[code]/page.tsx Room preload and auth token run together, shortening the block before first paint.

Notes

  • The dialog is now controlled (open / onOpenChange), so router.push closes it before the route change starts.
  • isHost still runs after the room resolves, because it needs the room id — only the room preload and the token fetch are parallel.
  • Out of scope: the older session also showed an unstyled render that lines up with deployment skew (no deploymentId in next.config.ts, no skew protection in vercel.json). That is a separate issue and is not addressed here.

Created with PostHog Desktop from this inbox report.

Close the create-room dialog before navigating so its overlay and the
submit spinner cannot outlive the mutation. Add a loading.tsx for
/host/[code] so the route transition renders the room shell instead of
the previous page. Parallelize the room preload and the auth token fetch
in page.tsx to shorten the block before first paint.

Generated-By: PostHog Code
Task-Id: 10328b32-44d1-4998-bb84-dd4300bb0405
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
songup Ready Ready Preview Aug 11, 2026 8:35am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
songup-blog Skipped Skipped Aug 11, 2026 8:35am
songup-docs Skipped Skipped Aug 11, 2026 8:35am

@motz0815 motz0815 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review: automated, review-only (no code changes)

What this PR actually changes

Three small changes to the host-room creation flow (+32/−8 across 3 files):

  1. CreateRoomForm's Radix Dialog becomes controlled (open/onOpenChange), with setOpen(false) called right before router.push(/host/${code}) inside the form action.
  2. A new app/host/[code]/loading.tsx renders a spinner over the same HostBackground used by the real host page.
  3. app/host/[code]/page.tsx runs preloadQuery(getRoomByCode) and convexAuthNextjsToken() in a Promise.all instead of sequentially, keeping the isHost fetch after the room resolves.

Verdict: looks good

The diagnosis is accurate and the fix is real. No correctness bugs, races, or security issues in the diff. Two low-severity nits and one pre-existing gap worth a follow-up are below.


Verification of the PR's claims

  • CONFIRMED — no loading boundary existed. apps/web/src/app contained zero loading.tsx files before this PR (only room/not-found.tsx). A router.push into /host/[code] had nothing to suspend into, so the transition stayed pending for the whole server round trip.
  • CONFIRMED — the spinner is transition-bound. packages/ui/src/components/submit-button.tsx uses useFormStatus().pending, which React clears only when the form-action transition commits. Since router.push and the action's pending-reset land on entangled transition lanes, the spinner and the bg-black/50 DialogOverlay (packages/ui/src/components/dialog.tsx:40) really do survive the whole navigation.
  • CONFIRMED — loading.tsx is the load-bearing fix. A newly mounted Suspense boundary lets the transition commit immediately with the fallback. setOpen(false) alone would not have fixed it — it's scheduled in the same transition and would be deferred identically. Worth stating explicitly so nobody later "simplifies" by deleting loading.tsx and keeping only the dialog change.
  • setOpen(false) is redundant but not useless. Navigating away unmounts /host's manage.tsx and with it the dialog, so the overlay would go regardless. But closing explicitly avoids Radix's known abrupt-unmount-while-open cleanup issue (stale pointer-events: none on <body> / scroll lock), so it's worth keeping. Making the dialog controlled is safe — Dialog at packages/ui/src/components/dialog.tsx:9 spreads props straight to DialogPrimitive.Root, and the two CreateRoomForm instances in apps/web/src/app/host/manage.tsx:190,202 are mutually exclusive branches with independent state.
  • CONFIRMED — the Promise.all is safe. preloadQuery(api.rooms.getRoomByCode, …) is deliberately unauthenticated (the query in packages/backend/convex/rooms.ts takes no identity), and convexAuthNextjsToken() only reads the cookie set by apps/web/src/proxy.ts's convexAuthNextjsMiddleware. Ordering carries no auth semantics; both rejections are awaited, so there's no unhandled rejection. The only behavior change is that the cookie is now read even for nonexistent rooms — harmless, and the route was already dynamic.

Findings

1. apps/web/src/app/host/[code]/loading.tsx:11 — mobile viewport overflows by the parent's padding (cosmetic).

The outer div is min-h-screen … p-4 and the inner div is h-full min-h-screen … lg:min-h-0. Below the lg breakpoint the inner element's min-height: 100vh sits inside 1rem of parent padding, so the page is 100vh + 2rem tall and gets a ~32px scroll. The real page (apps/web/src/app/host/[code]/host.tsx:126) uses a plain flex h-full w-full main and does not do this, so the fallback→content transition will visibly jump on phones.

Fix: drop min-h-screen from the inner div and add min-h-[calc(100vh-2rem)], or just let the outer min-h-screen + flex do the centering.

2. apps/web/src/app/host/[code]/loading.tsx:14 — the fallback copy assumes the create-room path.

/host/[code] is also reached from the "Open" links on the manage page (apps/web/src/app/host/manage.tsx:178) and by anyone pasting a host URL. A non-host now sees "Setting up your room…" and then Next's default 404 (there is no host/not-found.tsx; only app/room/not-found.tsx exists). "Loading room…" would be accurate for all entry points.

3. apps/web/src/components/host/create-room.tsx:53-102 — no error handling on the action (pre-existing, but adjacent to the bug this PR fixes).

handleCreateRoom has no try/catch, and there is no error.tsx or global-error.tsx anywhere in apps/web/src/app.

Failure scenario: a first-time host (anonymous) submits; useAuthedMutation (apps/web/src/lib/auth.ts) calls signIn("anonymous") then waitForAuthentication(), which rejects with new Error("Authentication timeout") after 5s on a slow connection. The rejected form action propagates to the root error boundary and blanks the app instead of showing a toast in the still-open dialog.

Given the inbox report was specifically "first-time host, room unusable", this is a plausible second root cause of the same symptom that this PR does not cover. A .catch(err => { toast.error(...) }) around the mutation would close it.

4. PLAUSIBLE — the fix may convert "infinite spinner" into "404" for a freshly-signed-in anonymous host.

api.rooms.isHost (packages/backend/convex/rooms.ts:85-101) returns false when getAuthUserId is empty, and the page then calls notFound(). If the anonymous session's JWT cookie has not yet been written by the Convex auth middleware when the RSC render runs, the host lands on a 404 for a room they just created.

Not confirmed — @convex-dev/auth's client storage does appear to await the server cookie write inside signIn, so it's probably fine. Worth a manual check on the Vercel preview with a fresh incognito session, since it would look like the PR "didn't fix it".

CI

All 4 checks green: CodeQL (javascript-typescript and python), Vercel Preview Comments, and the Vercel songup preview deploy is Ready — so the build compiles. Note there is no lint or type-check workflow in .github/workflows; bun run lint / check-types exist in package.json but are not run in CI, so the Vercel build is the only real gate. The new file matches the repo's Prettier config (4-space, no semicolons, sorted imports, sorted Tailwind classes), and Loader2 is a valid lucide-react export already imported in packages/ui/src/components/button.tsx:3 and elsewhere.


Generated by Claude Code

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