diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 0000000..6f26bab --- /dev/null +++ b/PR_DESCRIPTION.md @@ -0,0 +1,277 @@ +## Title + +fix: address review feedback — params, theme flash, Monaco theme, auth race, socket cleanup, gradient tokens + merge upstream/main + +--- + +## 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. `params` not awaited in ModulePage (Next.js 15 compat) + +**Commit:** `9e47097` | **File:** `apps/web/src/app/app/tracks/[trackId]/modules/[moduleId]/page.tsx` + +**Problem:** `ModulePage` was a Client Component destructuring `params` synchronously: +```tsx +export default function ModulePage({ params }: { params: { trackId: string; moduleId: string } }) { +``` + +In Next.js 15, `params` becomes a Promise. This code would break on upgrade. + +**Fix:** Since we're on React 18 (`use()` not available), we can't use `use(params)` in a Client Component. Instead, the page was split: +- `page.tsx` → async Server Component that `await`s params and passes them as plain props +- `module-page-content.tsx` → new Client Component (`"use client"`) receiving `{ trackId, moduleId }` as regular props + +This 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** destructuring `params` or `searchParams` — 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.tsx` + +**Problem:** `` had a hardcoded `className="dark"`. The `ThemeProvider` toggles the `dark` class via `useEffect` — 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:** +- Theme preference is persisted via Zustand store → `localStorage("unvibe-theme")` → values `"dark"` or `"light"` +- Zustand initialized on client only (hydration), so the server always rendered `className="dark"` +- No inline script existed to read the stored preference before first paint + +**Fix (3-part):** + +1. **Removed `className="dark"`** from `` — the server no longer hardcodes dark mode +2. **Added `suppressHydrationWarning`** to `` — 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 — `suppressHydrationWarning` silences just that one attribute on ``. +3. **Added blocking inline `