fix: address review feedback — params, theme flash, Monaco theme, auth race, socket cleanup, gradient tokens + merge upstream/main - #18
Conversation
… 15 compat) ModulePage was a Client Component destructuring params synchronously. In Next.js 15, params is a Promise. Since React 18 doesn't have use(), wrap the client logic in a thin async Server Component that awaits params and passes them as plain props to the inner Client Component. No other files in the app/ tree destructure params or searchParams — this was the only instance.
Remove hardcoded className="dark" from <html>. Add blocking inline script
that reads localStorage("unvibe-theme") and sets the class before first
paint, matching the Zustand store's persistence strategy. Add
suppressHydrationWarning to <html> to silence the false-positive mismatch
warning since the DOM is intentionally mutated before hydration.
Replace hardcoded theme="vs-dark" with a dynamic value read from useUIStore.darkMode so the editor matches the app's light/dark setting.
Button with asChild wrapping a Link could cause onClick (signIn) to not fire before navigation. Replace with a plain Button that calls signIn() then router.push() in the handler. Add TODO noting this changes when real NextAuth wiring lands.
Socket connect/disconnect was called but no socket events were listened to — only a setInterval drove mock messages. Remove the socket calls and unused getSocket import. Leave a TODO to wire real socket events when the War Room backend lands.
Define --gradient-radial in :root (light) and .dark (dark) in globals.css. Replace the conditional inline style in ThemeProvider with a single var(--gradient-radial) reference, which resolves automatically based on the .dark class on <html>.
|
Tick the box to add this pull request to the merge queue (same as
|
…hell # Conflicts: # apps/web/src/app/app/tracks/[trackId]/modules/[moduleId]/page.tsx # apps/web/src/app/auth/signin/page.tsx # apps/web/src/app/auth/signup/page.tsx # apps/web/src/app/layout.tsx # apps/web/src/components/app/theme-provider.tsx # apps/web/src/components/features/code-editor.tsx # apps/web/src/components/features/war-room-live.tsx
|
@Yuvraj-Sarathe @SharanyoBanerjee Ready for review whenever you have a moment. |
|
@SourabhX16 your branch isn't up-to-date with current state of main branch Sync the branch Add SS of UI changes in PR. If it is better than what it is currently I will merge it. Add a loading state and matching scrollbar in the UI; this is a must! Currently the website loads pages, but the user does not see if the page is being loaded or not. Only once the page is rendered do they see the page. Also add a logo and favicon for the same. Your TODOs:
Side Note: Make sure the logo is not green or environment type but is techy but also not overly complicated. Try not to use blue or purple colors. |
|
@SourabhX16 Once you do the above changes, merge conflicts would be solved by themselves only if they still exist. MAke changes until they are resolved and make sure your ss are after the conflicts have been resolved! |
|
@Yuvraj-Sarathe noted 👍 will solve it asap. |
Merge upstream/main (71 commits) into feat/frontend-app-shell. Conflicts resolved: - apps/web/src/app/app/tracks/[trackId]/modules/[moduleId]/page.tsx Keep our async Server Component wrapper (Next.js 15 compat) - apps/web/src/app/globals.css Merge: keep --gradient-radial tokens + upstream --success/--warning tokens - apps/web/src/app/layout.tsx Keep our dark-mode flash-fix inline script + add metadata.icons - apps/web/src/components/app/theme-provider.tsx Keep our CSS-variable gradient approach (var(--gradient-radial)) New additions (reviewer feedback): feat(ux): add SkeletonLoader component with page variants - apps/web/src/components/ui/skeleton-loader.tsx Wraps skeleton primitives with dashboard/tracks/war-room/module variants - apps/web/src/app/app/tracks/page.tsx Replace LoadingPanel with SkeletonLoader variant='tracks' - apps/web/src/app/app/tracks/[trackId]/modules/[moduleId]/module-page-content.tsx Replace LoadingPanel with SkeletonLoader variant='module' feat(ux): add themed scrollbar styling (light + dark mode) - apps/web/src/app/globals.css Add ::-webkit-scrollbar rules outside @layer for light + .dark modes feat(brand): add logo and favicon - apps/web/public/logo.png (512x512, coral/teal geometric UV mark) - apps/web/public/favicon.ico (copied from src/app/favicon.ico) - apps/web/src/app/layout.tsx: add metadata.icons for /favicon.ico + /logo.png
…ntent Upstream removed the eyebrow prop from PageHeader component. Replace eyebrow + description with single description that includes track title as context prefix. Fixes TypeScript build error.
|
@SourabhX16 is attempting to deploy a commit to the Yuvraj Sarathe's projects Team on Vercel. A member of the Team first needs to authorize it. |
ThemeController conditionally renders Moon vs Sun based on Zustand darkMode state. Zustand only initializes on the client, so the server always renders the default while the client may render a different icon. This caused a React hydration error: 'Expected server HTML to contain a matching <circle> in <svg>' Fix: add a mounted guard that defers the conditional icon to after first useEffect, rendering Sun as a neutral SSR placeholder in the meantime. Both server and client agree on Sun until mount; then the client silently swaps to the correct icon.
Summary
Six fixes addressing the merge review feedback, plus a merge of upstream/main (7 file conflicts resolved). All six fixes are preserved after the merge. The branch is up to date with
upstream/main, no remaining conflicts.Must-fix (blocking)
1.
paramsnot awaited in ModulePage (Next.js 15 compat)Commit:
9e47097| File:apps/web/src/app/app/tracks/[trackId]/modules/[moduleId]/page.tsxProblem:
ModulePagewas a Client Component destructuringparamssynchronously:In Next.js 15,
paramsbecomes a Promise. This code would break on upgrade.Fix: Since we're on React 18 (
use()not available), we can't useuse(params)in a Client Component. Instead, the page was split:page.tsx→ async Server Component thatawaits params and passes them as plain propsmodule-page-content.tsx→ new Client Component ("use client") receiving{ trackId, moduleId }as regular propsThis works identically on Next.js 14 today (awaiting a plain object resolves immediately) and will work on Next.js 15 after upgrade with zero changes.
Scope: Grep of the entire
apps/web/src/app/tree confirmed this was the only file destructuringparamsorsearchParams— no other fixes needed.Post-merge update: After merging upstream/main, the inner Client Component was updated to use upstream's tRPC queries (
trpc.tracks.getById.useQuery,trpc.modules.getById.useQuery) instead of the old mock data hooks, since upstream had already replaced the mock data layer.2. Dark-mode flash on light-mode load
Commit:
6e0d392| File:apps/web/src/app/layout.tsxProblem:
<html>had a hardcodedclassName="dark". TheThemeProvidertoggles thedarkclass viauseEffect— which runs after hydration. A user with a saved light-mode preference would see a dark page flash before React corrected it.Root cause analysis:
localStorage("unvibe-theme")→ values"dark"or"light"className="dark"Fix (3-part):
className="dark"from<html lang="en">— the server no longer hardcodes dark modesuppressHydrationWarningto<html>— silences the false-positive hydration mismatch warning. The warning would fire because our inline script (step 3) mutates the DOM before React hydrates, causing the server-rendered class and client class to differ. This is intentional and harmless —suppressHydrationWarningsilences just that one attribute on<html>.<script>before<body>content:<html>immediately — zero flash.What didn't change: The existing
useEffectinThemeProviderstill handles class toggling when the user changes theme after load. The inline script only covers the initial paint.Verification: Hard-refresh the page in light mode — the page loads directly in light mode with no dark flash.
3. Monaco editor ignores theme setting
Commit:
4c8994c| File:apps/web/src/components/features/code-editor.tsxProblem: The Monaco editor had
theme="vs-dark"hardcoded. Toggling the app to light mode left the code editor in dark mode — a visual inconsistency.Fix:
Now the editor's theme tracks the app's theme toggle in real time.
Post-merge formatting: Upstream/main reformatted the component's function signature to multiline. Both changes are preserved in the merged result.
4.
asChild+onClickrace condition in auth flowCommit:
4ac870f+ merged via1269c6f| Files:apps/web/src/app/auth/signin/page.tsx,apps/web/src/app/auth/signup/page.tsxOriginal problem: Both auth pages used:
With
asChild, the Button renders as a<Link>. TheonClick(callingsignIn()from the mock auth store) could race with the Link's native navigation —signInmight not fire before the browser navigates.Fix (original): Removed the
asChild/Linkwrapper. Used a plain<Button>withonClick={() => { signIn(); router.push('/app/dashboard'); }}. Added a TODO for when real NextAuth wiring replaces the mock.Post-merge resolution: Upstream/main had already replaced both pages with real auth forms (email/password inputs, validation, loading state, error display, real
next-auth/reactsignInfor OAuth). The upstream version naturally avoids theasChildrace — it usesonClick={handleSignIn}on a plain<Button>with no Link wrapper. We accepted upstream's version in the merge. Our TODO was retired since real auth is already wired.The merged result:
signIn("github"),signIn("google")withnext-auth/react)handleSignIn/handleSignUp)LoadingPanelasChild/Link patterns eliminatedMinor (cleanup)
5. Dead socket wiring in war-room-live
Commit:
9aa842f| File:apps/web/src/components/features/war-room-live.tsxProblem:
useEffectcalledgetSocket().connect()andsocket.disconnect()in cleanup, but no socket event listeners were ever registered. The only data coming into the component was from asetIntervalpushing mock messages. The socket calls were dead code.Fix:
import { getSocket } from "@/lib/socket/client"(eliminates an ESLintno-unused-varswarning)const socket = getSocket()andsocket.connect()from the effect bodysocket.disconnect()from the cleanup function// TODO: wire real socket events once War Room backend landscomment above thesetIntervalso the intent is clear when the backend arrivesPost-merge update: Accepted upstream's import path change (
@unvibe/typesinstead of@/lib/mock-data/types). The socket removal and TODO are preserved.6. Hardcoded gradient hex values in ThemeProvider
Commit:
f56b01d| Files:apps/web/src/app/globals.css,apps/web/src/components/app/theme-provider.tsxProblem:
ThemeProvidercontained raw hex values inline:radial-gradient(125% 125% at 50% 100%, #000000 40%, #010133 100%)radial-gradient(125% 125% at 50% 90%, #ffffff 40%, #ec4899 100%)These weren't tied to the design token system. If the palette changed, they'd need manual updates.
Fix:
--gradient-radialto:root(light) and.dark(dark) inglobals.css, alongside the existing CSS custom propertiesThemeProviderto:style={{ background: "var(--gradient-radial)" }}.darkclass on<html>— no conditional logic needed in the componentMerge Details
Commit:
1269c6fMerged
upstream/mainintofeat/frontend-app-shellwith 7 file conflicts, all resolved manually:page.tsx(module)signin/page.tsxsignup/page.tsxlayout.tsxclassName="dark")theme-provider.tsxcode-editor.tsxwar-room-live.tsxAlso fixed 4 pre-existing build blockers found during merge verification (all from upstream's new code):
dotenvdependency (missing innext.config.mjs)bcryptjs+@types/bcryptjsfor upstream's new auth router (api)prisma generate(stale client after schema changes)@unvibe/types(unbuilt after type additions)Verification
Route table:
No regressions in route structure, page rendering, or type safety.
How to review
Each of the 6 original fixes is in its own commit for focused review:
9e470976e0d3924c8994c4ac870f9aa842ff56b01dThe merge commit
1269c6fshows how each conflict was resolved.