Skip to content

Commit a2b7392

Browse files
authored
Fix production vertical overflow in app shell (#27)
1 parent bd3817b commit a2b7392

11 files changed

Lines changed: 86 additions & 18 deletions

File tree

.claude/napkin.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,14 @@
3232
- Dev-only third-party scripts should be opt-in; avoid `beforeInteractive` for non-critical tooling (e.g., `react-grab`)
3333
| 2026-02-13 | self | Assumed `request.json()` in Convex HTTP actions returned typed JSON; TS now treats it as `unknown` | Add runtime type guards (or explicit schema validation) before accessing webhook payload fields |
3434
- Convex `httpAction` webhook handlers are safer with explicit type guards before deriving event keys (`object_type`, `aspect_type`) from `request.json()`
35+
- `apps/web` dev script runs `@react-grab/cursor` before `next dev`; this can create behavior differences vs production. Prefer validating layout bugs with `next build && next start` too.
36+
- Overflow debug scripts that add `window.scrollY` to fixed-position elements can produce misleading `top/bottom` values; filter out `position: fixed` when diagnosing document-flow overflow.
37+
- When user asks to branch with dirty worktree, preserve unrelated local modifications and scope edits to requested files only.
38+
| 2026-02-13 | self | Forgot to quote path containing parentheses (`(marketing)`) so zsh globbing failed | Quote paths with `()`, `[]`, and other glob chars in shell commands |
39+
- If user asks to open a PR, commit only task-relevant files and keep unrelated dirty files unstaged.
40+
| 2026-02-13 | self | Used backticks inside a double-quoted `gh pr create --body` string, triggering shell command substitution and noisy side effects | Use a heredoc/file for PR body or avoid backticks in shell-quoted strings |
41+
## User Preferences
42+
- Always commit `.claude/napkin.md` with related task commits.
43+
- Avoid module-scope `new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!)` in route handlers; lazy-init inside request handlers with env guards to prevent build-time crashes.
44+
- Root layout auth preloads can execute during static prerender (`/_not-found`); guard token preload and fail open when auth env vars are absent at build time.
45+
- Vercel can ignore root `vercel.json` when project Root Directory is `apps/web`; keep an `apps/web/vercel.json` with the Convex deploy build command to avoid accidental `pnpm build` fallback.

apps/web/app/api/strava/callback/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { api } from "@repo/backend";
44

55
import { getServerAuth } from "@/lib/server-auth";
66

7-
const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
87
const STATE_COOKIE = "strava_oauth_state";
98
const DEFAULT_SUCCESS_URL = "/integrations?success=strava_connected";
109
const DEFAULT_ERROR_URL = "/integrations?error=strava_auth_failed";
@@ -38,6 +37,13 @@ interface StravaTokenResponse {
3837
}
3938

4039
export async function GET(request: NextRequest) {
40+
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL;
41+
if (!convexUrl) {
42+
console.error("Missing NEXT_PUBLIC_CONVEX_URL for Strava callback route");
43+
return redirectWithCookieClear(request, DEFAULT_ERROR_URL, true);
44+
}
45+
const convex = new ConvexHttpClient(convexUrl);
46+
4147
const { userId, convexToken } = await getServerAuth();
4248

4349
if (!userId) {

apps/web/app/challenges/[id]/admin/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export default async function ChallengeAdminLayout({
8585
];
8686

8787
return (
88-
<div className="flex h-screen flex-col bg-zinc-950 text-zinc-100">
88+
<div className="flex h-dvh flex-col overflow-hidden bg-zinc-950 text-zinc-100">
8989
{/* Dense Header Bar */}
9090
<header className="flex-shrink-0 border-b border-zinc-800 bg-zinc-900">
9191
<div className="flex items-center justify-between px-3 py-2">
@@ -143,7 +143,7 @@ export default async function ChallengeAdminLayout({
143143
</header>
144144

145145
{/* Main Content - Scrollable */}
146-
<main className="flex-1 overflow-y-auto">
146+
<main className="min-h-0 flex-1 overflow-y-auto">
147147
<div className="p-3">
148148
{children}
149149
</div>

apps/web/app/layout.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,18 @@ export default async function RootLayout({
3939
const enableDevCursorScripts =
4040
process.env.NODE_ENV === "development" &&
4141
process.env.NEXT_PUBLIC_ENABLE_REACT_GRAB === "1";
42+
const canUseAuth = Boolean(process.env.NEXT_PUBLIC_CONVEX_URL);
4243

43-
// getToken() is fast (cookie read) — safe to await in layout.
44-
// The expensive preloadAuthQuery is deferred into Suspense via HeaderContent.
45-
const token = await getToken();
44+
// getToken() is fast (cookie read). During static prerender (e.g. /_not-found)
45+
// auth env vars may be unavailable, so fail open to avoid build-time crashes.
46+
let token: string | null = null;
47+
if (canUseAuth) {
48+
try {
49+
token = (await getToken()) ?? null;
50+
} catch (error) {
51+
console.error("[layout] failed to preload auth token:", error);
52+
}
53+
}
4654

4755
return (
4856
<ConvexProviderWrapper initialToken={token ?? null}>
@@ -66,13 +74,11 @@ export default async function RootLayout({
6674
className={`${geistSans.variable} ${geistMono.variable} bg-black text-white antialiased`}
6775
>
6876
<div className="relative min-h-screen">
69-
<div className="pointer-events-none fixed inset-0 overflow-hidden">
70-
<div className="absolute -left-20 -top-40 h-96 w-96 rounded-full bg-indigo-500/20 blur-[120px]" />
71-
<div className="absolute bottom-0 right-0 h-80 w-80 translate-x-1/4 translate-y-1/4 rounded-full bg-fuchsia-500/15 blur-[100px]" />
72-
</div>
73-
<Suspense fallback={<HeaderSkeleton />}>
74-
<HeaderContent />
75-
</Suspense>
77+
{canUseAuth && (
78+
<Suspense fallback={<HeaderSkeleton />}>
79+
<HeaderContent />
80+
</Suspense>
81+
)}
7682
<div className="relative z-10">
7783
{children}
7884
</div>

apps/web/components/dashboard/dashboard-layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function DashboardLayout({
4040
}, []);
4141

4242
return (
43-
<div className="flex h-screen bg-black text-white">
43+
<div className="flex h-dvh overflow-hidden bg-black text-white">
4444
{/* Left Sidebar - Collapsed (lg) */}
4545
<aside onWheel={forwardScroll} className="hidden w-[72px] flex-shrink-0 flex-col border-r border-zinc-800 lg:flex xl:hidden">
4646
<div className="flex h-full flex-col items-center py-4">
@@ -130,15 +130,15 @@ export function DashboardLayout({
130130
</aside>
131131

132132
{/* Main Content - Scrollable */}
133-
<main ref={mainRef} className="flex-1 overflow-y-auto overscroll-contain scrollbar-hide pb-20 lg:pb-0">
133+
<main ref={mainRef} className="min-h-0 flex-1 overflow-y-auto overscroll-contain scrollbar-hide pb-20 lg:pb-0">
134134
<PaymentRequiredBanner challengeId={challenge.id} />
135135
<AnnouncementBanner challengeId={challenge.id} />
136136
{children}
137137
</main>
138138

139139
{/* Right Sidebar - Fixed (xl only) */}
140140
{!hideRightSidebar && (
141-
<aside onWheel={forwardScroll} className="hidden w-96 flex-shrink-0 flex-col border-l border-zinc-800 lg:flex">
141+
<aside onWheel={forwardScroll} className="hidden min-h-0 w-96 flex-shrink-0 flex-col border-l border-zinc-800 lg:flex">
142142
<div className="p-4">
143143
<UserSearch challengeId={challenge.id} />
144144
</div>

apps/web/lib/server-auth.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,30 @@ type ServerAuthResult = {
2222
// Lazy initialization to avoid build-time errors when env vars aren't available
2323
let _betterAuthUtils: ReturnType<typeof convexBetterAuthNextJs> | null = null;
2424

25+
function resolveConvexSiteUrl(convexUrl: string): string {
26+
if (process.env.NEXT_PUBLIC_CONVEX_SITE_URL) {
27+
return process.env.NEXT_PUBLIC_CONVEX_SITE_URL;
28+
}
29+
30+
// Convex cloud deployments use a corresponding ".convex.site" host for auth.
31+
if (convexUrl.includes(".convex.cloud")) {
32+
return convexUrl.replace(".convex.cloud", ".convex.site");
33+
}
34+
35+
// For self-hosted or already-site URLs, reuse the same host.
36+
return convexUrl;
37+
}
38+
2539
function getBetterAuthUtils() {
2640
if (!_betterAuthUtils) {
41+
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL;
42+
if (!convexUrl) {
43+
throw new Error("NEXT_PUBLIC_CONVEX_URL is not set.");
44+
}
45+
2746
_betterAuthUtils = convexBetterAuthNextJs({
28-
convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
29-
convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
47+
convexUrl,
48+
convexSiteUrl: resolveConvexSiteUrl(convexUrl),
3049
});
3150
}
3251
return _betterAuthUtils;

apps/web/vercel.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$schema": "https://openapi.vercel.sh/vercel.json",
3+
"buildCommand": "cd ../../packages/backend && npx convex deploy --cmd 'cd ../../apps/web && npx next build' --cmd-url-env-var-name NEXT_PUBLIC_CONVEX_URL",
4+
"installCommand": "pnpm install",
5+
"framework": "nextjs"
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# 2026-02-13 Investigate CONVEX_SITE_URL prerender regression
2+
3+
- [x] Locate source of `CONVEX_SITE_URL is not set` throw
4+
- [x] Identify commit/PR introducing the behavior
5+
- [x] Propose/implement fix
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# 2026-02-13 Reproduce production-only page scrollbar
2+
3+
- [x] Confirm root/web production scripts
4+
- [x] Provide exact commands to run a production build locally
5+
- [x] Explain likely reasons scrollbar appears in production but not in dev
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# 2026-02-13 Align Vercel build command for Convex preview env injection
2+
3+
- [x] Confirm current root `vercel.json` build command
4+
- [x] Add `apps/web/vercel.json` for `apps/web` root-directory deployments
5+
- [x] Push PR update with deployment config alignment

0 commit comments

Comments
 (0)