Personal live dashboard: production hardening - #1
Merged
Conversation
Server-synced personal card in a top-right dropdown over the leaderboard, with a whole-page tier-tinted interactive particle background and an accurate online signal (new sync `active` flag + lastSeenAt fallback). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-in users get a dropdown dashboard over the leaderboard: a frosted-glass card showing tier, active hours, combos, XP, next-tier progress, per-tool breakdown, plan, rank against the whole field, and a live online state. A whole-page particle background (OGL/WebGL) is black when signed out and takes birth in a staggered swirl once authenticated. Particle color maps to the user's tier, shifts hue with the cursor, and energizes toward phosphor green while a tool is actively running. GSAP drives the birth, energy and hue as shader uniforms so nothing re-renders per frame. Respects prefers-reduced-motion and prefers-reduced-transparency, degrades to the plain black background when WebGL is unavailable, and never intercepts board clicks or scroll. Presence is a new accurate signal: the agent reports an `active` flag plus the tool ids running now (agent 0.3.0); the server stamps last_active_at only on active syncs, so freshness alone means "coding now", with the existing heartbeat as fallback for older agents. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…opack root Fixes found while reviewing the live-dashboard branch for shipping: - db: retry connection ACQUISITION only (never a statement) so a Neon cold start doesn't 500 the first request, while a mid-query drop is never re-sent and cannot double-write. Bounded to ~13s, with a connect timeout so a black-holed address fails fast instead of hanging on the OS TCP timeout. An idle-connection 'error' listener keeps a pooled death from taking down the whole serverless instance. Regression test guards the retry budget. - particles: a browser with no WebGL (GPU-less VM, blocklisted driver, hardware acceleration off) made OGL log an error and throw on every mount. Probe first via lib/webgl, and give signed-in users a still tier-colored wash instead of the black void a signed-out visitor sees. Release the GL context on teardown — OGL never does, and past Chrome's ~16-context cap the field silently dies for the rest of the session. - particles: preserve reveal progress across a rebuild and re-arm the tween against the new uniforms, so a tier change mid-birth resumes instead of stranding the field invisible. - user-menu: reset the pending state on bfcache restore, so backing out of Discord doesn't leave a dead pointer-events:none spinner. - next.config: pin turbopack.root to this file's own directory. Competing lockfiles made root inference key client modules under a root the React Client Manifest didn't share, 500ing renders at random. - remove the temp /particle-demo harness, which was compiling into a public production route. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…xt back A WebGL context is a loan. The browser reclaims it on a GPU reset, a driver swap, a wake from sleep, or when too many other tabs want one — and the canvas goes dead and stays dead. We ignored `webglcontextlost`, so any of those cost the visitor the field for the rest of their session. - Prevent the default on `webglcontextlost` (an unprevented loss is final) and rebuild the scene: restoration hands back a blank context, so every program, buffer, and attribute OGL uploaded has to be re-uploaded anyway. Reveal progress already survives a rebuild, so the field picks up where it left off. - Retry a failed rebuild on a widening delay, bounded, since the GPU may still be coming back from the very reset that triggered it. A first-build failure is not retried: that browser simply has no WebGL, and there is nothing to wait for. - Detach the loss handler before the deliberate loseContext() in teardown, or unmounting trips our own recovery and every unmount spawns a replacement. - Probe `experimental-webgl` too, so browsers that only answer to the old name get the field instead of the fallback background. Verified against a real context loss in headless Chrome with a live SwiftShader context: after loseContext(), the component mounts a new canvas with a live context (1280x657), exactly one canvas, no rebuild loop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Production had BASE_URL set to an empty string. `??` only guards null and undefined, so "" sailed through as a legitimate value: baseUrl became "", the Discord redirect_uri degraded to a bare "/auth/callback", and sign-in broke on grindeasy.tech while every other page looked healthy. Had the variable simply been absent, the VERCEL_PROJECT_PRODUCTION_URL fallback on the next line would have produced the right answer all along. Blank now means absent, so the fallback does its job. Per Vercel's docs VERCEL_PROJECT_PRODUCTION_URL is the project's shortest production custom domain (grindeasy.tech), set on every deployment, protocol not included. Tests cover the set, blank, whitespace-only, trailing-slash, and unset cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shashank03-dev
added a commit
that referenced
this pull request
Jul 16, 2026
Server-synced personal card in a top-right dropdown over the leaderboard, with a whole-page tier-tinted interactive particle background and an accurate online signal (new sync `active` flag + lastSeenAt fallback). * feat: personal live dashboard with tier-colored particle background Signed-in users get a dropdown dashboard over the leaderboard: a frosted-glass card showing tier, active hours, combos, XP, next-tier progress, per-tool breakdown, plan, rank against the whole field, and a live online state. A whole-page particle background (OGL/WebGL) is black when signed out and takes birth in a staggered swirl once authenticated. Particle color maps to the user's tier, shifts hue with the cursor, and energizes toward phosphor green while a tool is actively running. GSAP drives the birth, energy and hue as shader uniforms so nothing re-renders per frame. Respects prefers-reduced-motion and prefers-reduced-transparency, degrades to the plain black background when WebGL is unavailable, and never intercepts board clicks or scroll. Presence is a new accurate signal: the agent reports an `active` flag plus the tool ids running now (agent 0.3.0); the server stamps last_active_at only on active syncs, so freshness alone means "coding now", with the existing heartbeat as fallback for older agents. * fix: harden dashboard for production — db retry, WebGL fallback, Turbopack root Fixes found while reviewing the live-dashboard branch for shipping: - db: retry connection ACQUISITION only (never a statement) so a Neon cold start doesn't 500 the first request, while a mid-query drop is never re-sent and cannot double-write. Bounded to ~13s, with a connect timeout so a black-holed address fails fast instead of hanging on the OS TCP timeout. An idle-connection 'error' listener keeps a pooled death from taking down the whole serverless instance. Regression test guards the retry budget. - particles: a browser with no WebGL (GPU-less VM, blocklisted driver, hardware acceleration off) made OGL log an error and throw on every mount. Probe first via lib/webgl, and give signed-in users a still tier-colored wash instead of the black void a signed-out visitor sees. Release the GL context on teardown — OGL never does, and past Chrome's ~16-context cap the field silently dies for the rest of the session. - particles: preserve reveal progress across a rebuild and re-arm the tween against the new uniforms, so a tier change mid-birth resumes instead of stranding the field invisible. - user-menu: reset the pending state on bfcache restore, so backing out of Discord doesn't leave a dead pointer-events:none spinner. - next.config: pin turbopack.root to this file's own directory. Competing lockfiles made root inference key client modules under a root the React Client Manifest didn't share, 500ing renders at random. - remove the temp /particle-demo harness, which was compiling into a public production route. * fix(particles): recover the field when the browser takes the GL context back A WebGL context is a loan. The browser reclaims it on a GPU reset, a driver swap, a wake from sleep, or when too many other tabs want one — and the canvas goes dead and stays dead. We ignored `webglcontextlost`, so any of those cost the visitor the field for the rest of their session. - Prevent the default on `webglcontextlost` (an unprevented loss is final) and rebuild the scene: restoration hands back a blank context, so every program, buffer, and attribute OGL uploaded has to be re-uploaded anyway. Reveal progress already survives a rebuild, so the field picks up where it left off. - Retry a failed rebuild on a widening delay, bounded, since the GPU may still be coming back from the very reset that triggered it. A first-build failure is not retried: that browser simply has no WebGL, and there is nothing to wait for. - Detach the loss handler before the deliberate loseContext() in teardown, or unmounting trips our own recovery and every unmount spawns a replacement. - Probe `experimental-webgl` too, so browsers that only answer to the old name get the field instead of the fallback background. Verified against a real context loss in headless Chrome with a live SwiftShader context: after loseContext(), the component mounts a new canvas with a live context (1280x657), exactly one canvas, no rebuild loop. * fix(env): treat a blank BASE_URL as absent, not as a valid base URL Production had BASE_URL set to an empty string. `??` only guards null and undefined, so "" sailed through as a legitimate value: baseUrl became "", the Discord redirect_uri degraded to a bare "/auth/callback", and sign-in broke on grindeasy.tech while every other page looked healthy. Had the variable simply been absent, the VERCEL_PROJECT_PRODUCTION_URL fallback on the next line would have produced the right answer all along. Blank now means absent, so the fallback does its job. Per Vercel's docs VERCEL_PROJECT_PRODUCTION_URL is the project's shortest production custom domain (grindeasy.tech), set on every deployment, protocol not included. Tests cover the set, blank, whitespace-only, trailing-slash, and unset cases. ---------
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reviews and hardens the live-dashboard branch for production.
What was wrong
/particle-demowas a public production route. A temp harness marked "delete when done" was compiling into the build output. Removed.unable to create webgl contextand throws on a null context. GPU-less VMs, blocklisted drivers, and hardware acceleration switched off all hit this.TODO: Handle context lossinRenderer.js), so past Chrome's ~16-context cap the field silently dies for the rest of the session.pointer-events: none.Notable decisions
lib/db/index.test.tsguards that budget.Before merging
BASE_URLmust be set in Vercel's production environment.lib/env.tsfalls back tohttp://localhost:3000when it's unset, and the Discord OAuthstatecookie is host-scoped — written on one host, read on another, the callback correctly 400s.Verification
npm run lint,npm run typecheck,npm test(50/50), andnpm run buildall pass. The no-WebGL fallback was confirmed rendering in a real signed-in browser session that cannot create a WebGL context.🤖 Generated with Claude Code