feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(home): featurear la ronda de votación que está abierta - #75

Merged
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27
Aug 3, 2026
Merged

feat(home): featurear la ronda de votación que está abierta#75
agustinkassis merged 1 commit into
mainfrom
claude/hackaton-voting-status-f7da27

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problema

El hero de votación del home estaba clavado a getHackathon("gaming") (app/page.tsx). Esa ronda cerró el 2026-06-30 y VotingHero esconde los resultados en el home 7 días después del cierre, así que el home no muestra nada — ni siquiera la votación de ai-agents, que está abierta ahora mismo:

ai-agents open · openedAt 1785271964 · closedAt — · 7 proyectos · 18 elegibles
zaps closed · openedAt 1782415186 · closedAt 1782856190
commerce sin ronda

Segundo problema, más silencioso: HomeGate reemplaza el home por HomeDashboard cuando hay sesión, y el dashboard no tenía ranura para el hero. Es decir, los únicos que tienen votos para gastar eran justo los que no veían el estado de la votación.

Cambio

  • lib/votingCache.ts — nuevo getFeaturedVotingRound(): escanea las 3 hackatones más recientes que ya arrancaron (una ronda no existe antes de la apertura), prefiere la que está abierta y si no hay ninguna cae a la cerrada más reciente. Registra el cacheTag de votación de todas las hackatones: los tags de un "use cache" anidado no burbujean en Next 16, y tagear sólo las candidatas dejaría a la apertura de una hackatón nueva sin revalidar el home.
  • app/page.tsx — el hero se resuelve desde los relays en vez de estar pineado a un id, con cacheLife("nostr").
  • components/home/HomeGate.tsx + HomeDashboard.tsx — el hero baja al dashboard, debajo de la banda de saludo. La copia del dashboard va con initialPeriod={null} a propósito: sólo renderiza en el cliente y useVotingLive relee el período al montar, así no se manda el snapshot dos veces.
  • components/voting/VotingHero.tsx — prop inline que saca el padding que despeja el header fijo cuando el hero va dentro del dashboard.

Verificación

Resolución corrida contra los relays reales con la pubkey publisher de producción → → el home mostraría: AI AGENTS (open).

Render verificado en un entorno local aislado (relay local + claves descartables, dos rondas sembradas: zaps cerrada hace 3 días + ai-agents):

  • Ronda abierta, visitante sin sesión → "VOTACIÓN EN PROGRESO · La comunidad está votando a los ganadores de AI AGENTS".
  • Ronda abierta, votante elegible logueado → hero completo dentro del dashboard: "VOTACIÓN ABIERTA", "5/5 votos disponibles", medidor de participación en vivo, CTA "Votar ahora". Sin superposición ni doble padding (banda 32→375, hero 375→816, contenido 816→…).
  • Sin ronda abierta → toma la cerrada más reciente: "RESULTADOS PUBLICADOS · Hackatón de AI AGENTS" con el podio, no la de GAMING que cerró antes.

tsc --noEmit limpio y pnpm build OK (/ queda estático, revalidate 5m / expire 1w).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • The home page now highlights the currently featured community voting round.
    • Voting details dynamically display the round name, identifier, and voting period.
    • Authenticated users see the live voting section integrated into their dashboard.
    • The featured round is selected from active or most recently completed voting rounds.
  • Bug Fixes
    • Removed reliance on a fixed voting event, ensuring the displayed round stays current.
    • The voting section is omitted when no eligible round is available.

El hero de votación del home estaba clavado a `getHackathon("gaming")`, así
que quedó invisible en cuanto esa ronda cerró (VotingHero esconde los
resultados en el home 7 días después del cierre) y nunca mostró la ronda
siguiente. Hoy la votación abierta es `ai-agents` y el home no la anunciaba.
Ahora `getFeaturedVotingRound()` resuelve la ronda desde los relays: escanea
las 3 hackatones más recientes que ya arrancaron, prefiere la que está
abierta y si no hay ninguna cae a la cerrada más reciente (que el hero sigue
mostrando durante su semana de anuncio). Registra el cacheTag de votación de
todas las hackatones, porque los tags de un `"use cache"` anidado no burbujean
en Next 16 y tagear sólo las candidatas dejaría fuera la apertura de una
hackatón nueva.
El hero además baja al dashboard de usuario logueado: HomeGate reemplaza el
home por HomeDashboard cuando hay sesión, así que los que realmente tienen
votos para gastar no veían el estado de la votación. La copia del dashboard
va con `initialPeriod={null}` a propósito — sólo renderiza en el cliente y
useVotingLive relee el período al montar, no hace falta mandar el snapshot
dos veces. El prop `inline` saca el padding que despeja el header fijo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 3, 2026 2:56pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The home page now resolves the featured voting round from cached relay data. Authenticated users see the round in an inline dashboard hero. The hero supports adjusted spacing, while unauthenticated and marketing-home rendering remain unchanged.

Changes

Featured voting home

Layer / File(s)Summary
Featured round resolution
lib/votingCache.ts
Adds getFeaturedVotingRound(), which evaluates recent non-upcoming hackathons, loads voting periods concurrently, and selects the latest open or closed round.
Inline hero rendering
app/page.tsx, components/voting/VotingHero.tsx
Passes featured round data to VotingHero. The optional inline prop applies reduced spacing to home hero variants.
Authenticated dashboard placement
components/home/HomeGate.tsx, components/home/HomeDashboard.tsx, app/page.tsx
Passes the suspense-wrapped hero through HomeGate and renders it between the dashboard greeting and content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant Home
participant HomeGate
participant HomeDashboard
participant HomeVotingHero
participant getFeaturedVotingRound
Home->>HomeVotingHero: render inline hero
HomeVotingHero->>getFeaturedVotingRound: resolve featured round
getFeaturedVotingRound-->>HomeVotingHero: return round or null
Home->>HomeGate: pass votingHero
HomeGate->>HomeDashboard: forward votingHero when authenticated
HomeDashboard-->>Home: render voting hero above dashboard content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedEl título describe de forma clara el cambio principal: mostrar en el home la ronda de votación abierta destacada.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackaton-voting-status-f7da27

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/page.tsx`:
- Around line 16-33: Distinguish “no voting period” from “period fetch pending”
for the inline path. Update useVotingLive to expose a first-fetch/pending state
when initialPeriod is null, and update VotingHero’s early-return logic to render
a lightweight inline placeholder while that fetch is pending, retaining null
only when no period exists and preserving the existing home behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29f944a9-ef1c-44ee-b9b5-a745995bb222

📥 Commits

Reviewing files that changed from the base of the PR and between 5c784ee and 4e003da.

📒 Files selected for processing (5)
  • app/page.tsx
  • components/home/HomeDashboard.tsx
  • components/home/HomeGate.tsx
  • components/voting/VotingHero.tsx
  • lib/votingCache.ts

Comment threadapp/page.tsx
Comment on lines +16 to 33
async function HomeVotingHero({ inline = false }: { inline?: boolean }) {
"use cache";
const gaming = getHackathon("gaming");
if (!gaming) return null;
cacheTag(nostrVotingTag(gaming.id));
const period = await getCachedVotingPeriod(gaming.id);
if (!period) return null;
cacheLife("nostr");
const featured = await getFeaturedVotingRound();
if (!featured) return null;
return (
<VotingHero
hackathonId={gaming.id}
hackathonName={gaming.name}
initialPeriod={period}
hackathonId={featured.hackathon.id}
hackathonName={featured.hackathon.name}
// The dashboard copy only ever renders on the client (HomeGate swaps it in
// once auth resolves) and `useVotingLive` re-reads the period on mount —
// shipping the SSR snapshot twice would be dead payload.
initialPeriod={inline ? null : featured.period}
variant="home"
inline={inline}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Inline hero renders nothing until the client-side period fetch resolves, even when a round is open.

Line 28 sets initialPeriod={null} for the inline (dashboard) case. In VotingHero (components/voting/VotingHero.tsx, lines 103-110), if (!period) return null; runs before any state update from useVotingLive's mount-time fetch (lib/useVotingLive.ts, the /api/hackathons/${hackathonId}/voting effect). Until that fetch resolves, the component returns null, so the dashboard hero is invisible for every authenticated user on every load with an open round, not just the first paint.

useVotingLive's loading flag does not cover this case: it reduces to !ready while period is null (since votingOpenNow is false), so ready can turn true before the period fetch completes, leaving a window where nothing is rendered and no skeleton exists to reserve space (a layout shift on every load).

Before this PR, variant="home" always shipped the real period, so !period meant "no round exists" — a stable, correct case. This PR overloads the same null state to also mean "round exists, not fetched yet," which VotingHero's early return and useVotingLive's loading computation don't distinguish.

Consider exposing a distinct "period pending" state from useVotingLive (e.g., initialPeriod === null && !firstFetchDone) and having VotingHero render a lightweight placeholder instead of null while pending, only for the inline case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/page.tsx` around lines 16 - 33, Distinguish “no voting period” from
“period fetch pending” for the inline path. Update useVotingLive to expose a
first-fetch/pending state when initialPeriod is null, and update VotingHero’s
early-return logic to render a lightweight inline placeholder while that fetch
is pending, retaining null only when no period exists and preserving the
existing home behavior.

@agustinkassis
agustinkassis merged commit 56aa8a4 into mainAug 3, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackaton-voting-status-f7da27 branch August 3, 2026 19:49
Sign up for freeto 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

@agustinkassis