fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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

fix(hackathons): línea de tiempo calculada desde la fecha actual - #76

Merged
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475
Aug 4, 2026
Merged

fix(hackathons): línea de tiempo calculada desde la fecha actual#76
agustinkassis merged 2 commits into
mainfrom
claude/hackathons-timeline-dynamic-8d1475

Conversation

@agustinkassis

@agustinkassisagustinkassis commented Aug 4, 2026

Copy link
Copy Markdown
Member

El marcador de "hoy" en /hackathons se veía parado en julio estando en agosto, y los meses vacíos del riel estaban hardcodeados.

El bug

todayPct salía de closedCount / n. Con 5 hackatones cerrados sobre 8 nodos eso da 62.5%, que es exactamente el punto medio entre el nodo JUL (56.25%) y el AGO (68.75%) — visualmente, pegado a julio. El número no depende de la fecha en absoluto: solo cambia cuando un hackatón cierra, así que el marcador se queda clavado ahí todo agosto y todo septiembre.

Efecto lateral del mismo cálculo: mientras un hackatón está en curso el marcador sí avanza hacia el nodo siguiente, y al cerrarse salta para atrás al punto medio. Era no monótono.

Y los slots vacíos se insertaban buscando el nodo de julio por su etiqueta:

constjulyIndex=items.findIndex((item)=>item.monthShort==="JUL");items.splice(julyIndex+1,0,/* Agosto, Septiembre, year: 2026 */);

Qué cambió

El riel es un calendario derivado de las fechas. Un slot por mes entre el primer y el último hackatón; los meses que nadie programó se rellenan solos. Agregar un hackatón en noviembre extiende el riel sin tocar este código.

La posición de hoy se interpola sobre ese eje.railIndex = índice fraccionario del mes actual + la fracción del mes transcurrida; el + 0.5 lo recentra sobre los puntos, que viven en el centro de cada slice 1/n. Hoy arranca sobre el punto de su propio mes y viaja hacia el siguiente a medida que pasa el mes, así que siempre se lee como "estamos en <mes>" y solo avanza. Al 4 de agosto: ~70%, apenas pasado el nodo AGO.

El día se saca del mismo toISOString().slice(0, 10) contra el que compara hackathonStatus(), así el marcador y los puntos de estado no pueden discrepar sobre qué día es. Los meses se parsean del string ISO y no con new Date(iso), que lee UTC medianoche y se corre al mes anterior al oeste de Greenwich.

El encabezado "Marzo → Octubre 2026" ahora sale de los mismos datos.

Meses vacíos: nodos sí, tarjetas no

Segundo pedido: las tarjetas "Sin hackatón programado" molestaban y las flechas obligaban a pasar por dos de ellas para ir de AI AGENTS a LABITCONF.

Los meses vacíos siguen en el riel — sacarlos comprimiría el hueco JUL→OCT y volvería a descolocar el marcador — pero salen del carrusel:

constslides=useMemo(()=>items.filter((h)=>!h.placeholder),[items]);

El índice de estado pasa a ser sobre slides. Como la posición en el riel ya no coincide con la del carrusel, la selección se resuelve por id (slideIndexById) en vez de por índice — sin eso el anillo activo se corría dos nodos a partir de agosto. Los nodos AGO/SEP ya eran <div> no interactivos, así que no cambian. Quedaron muertas y se borraron las ramas if (h.placeholder) de StageCard y PeekCard.

initialIndex se calcula sobre la lista filtrada. Antes elegía el primer item upcoming de items, que hoy era el placeholder de agosto: por eso la página abría en una tarjeta vacía.

Verificado en el navegador

antesahora
marcador de hoy62.5% (borde JUL/AGO)~70%, sobre AGO
tarjeta inicial"Agosto 2026 · Sin hackatón programado"LABITCONF
contador6 / 86 / 6
"anterior" desde LABITCONFAGO → SEP → AI AGENTSAI AGENTS, un clic

El árbol de accesibilidad lista solo 6 botones de nodo, "Sin hackatón programado" ya no aparece en el DOM y el pip amarillo queda sobre AGO con el fill llegando hasta ahí. Sin errores de consola; tsc --noEmit y pnpm build limpios.

Para el reviewer

  • placeholderMonth() deriva su status del calendario aunque hoy nadie lo lea (el riel renderiza placeholders sin status). Es deliberado: fijarlo en "upcoming" dejaba una trampa para un lector futuro, porque un mes hueco ya pasado seguiría diciendo "upcoming" — que es justo el bug que tenía initialIndex.
  • El caso "dos hackatones en el mismo mes" no existe en hackathons.json hoy, pero el riel lo parte en dos slices y railIndex los recorre a lo largo del mes en vez de romperse.
  • now sigue leyéndose dentro de "use cache". El perfil nostr anidado (revalidate 300) se propaga a la página, así que el marcador queda como mucho ~5 min viejo — irrelevante en una escala mensual.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Updated the hackathon timeline to align events with their calendar months.
    • Empty months remain visible on the calendar rail without becoming selectable carousel cards.
    • Improved navigation, previews, keyboard controls, and event selection.
    • Added a dynamic date-range label based on the displayed hackathon schedule.
    • The “Today” marker now reflects the correct month and day position.

El marcador de "hoy" salía de `closedCount / n`, que en agosto cae justo
en el límite JUL/AGO — se veía parado en julio. Y los meses vacíos se
insertaban a mano buscando el nodo "JUL" en la lista.
Ahora el riel es un calendario derivado de las fechas: un slot por mes
entre el primer y el último hackatón, y la posición de hoy se interpola
sobre ese eje, así que arranca sobre el punto del mes en curso y avanza
hacia el siguiente a medida que pasa el mes. Solo se mueve hacia adelante;
antes, al cerrar un hackatón, el marcador saltaba para atrás al punto
medio entre dos nodos. El rango del encabezado ("Marzo → Octubre 2026")
también sale de los datos.
Los meses sin hackatón dejan de ser tarjetas: siguen como nodos del riel
(sacarlos comprimiría el hueco y volvería a descolocar el marcador), pero
el carrusel solo tiene hackatones, así que las flechas, el teclado y el
swipe saltan derecho al siguiente en vez de caer en "Sin hackatón
programado". Como la posición en el riel ya no coincide con la del
carrusel, la selección se resuelve por id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
lacrypta-devReadyReadyPreviewAug 4, 2026 6:14pm

Request Review

@coderabbitai

coderabbitaiBot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agustinkassis, you've reached your PR review limit, so we couldn't start this review.

Next review available in:52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 051d91a8-ebc6-441b-9748-273a0f92e030

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff1535 and eb74119.

📒 Files selected for processing (1)
  • app/hackathons/page.tsx
📝 Walkthrough

Walkthrough

The hackathon timeline now generates calendar-based rail months, keeps empty months as rail-only placeholders, and separates carousel navigation from rail positions. The page also displays a computed date range.

Changes

Hackathon timeline

Layer / File(s)Summary
Calendar rail generation
app/hackathons/page.tsx
The timeline creates month metadata and placeholders for unoccupied months. It computes calendar positions, the today marker, and a dynamic range label.
Carousel and rail mapping
app/hackathons/HackathonTimeline.tsx
The timeline filters placeholders from carousel slides, maps hackathon IDs to carousel indices, and uses those indices for cards, selection, and navigation.

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

Sequence Diagram(s)

sequenceDiagram
participant Page
participant buildTimeline
participant HackathonTimeline
participant RailButton
participant StageCard
Page->>buildTimeline: Build calendar rail and rangeLabel
buildTimeline-->>Page: Return hackathons and placeholders
Page->>HackathonTimeline: Provide timeline nodes
HackathonTimeline->>RailButton: Render calendar rail
RailButton->>HackathonTimeline: Select mapped hackathon index
HackathonTimeline->>StageCard: Render active hackathon
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 con precisión el cambio principal: calcular la línea de tiempo de hackatones según la fecha actual.
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/hackathons-timeline-dynamic-8d1475

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/hackathons/page.tsx`:
- Around line 233-235: Update the firstMonth and lastMonth bounds in the
hackathon rail construction to derive the minimum and maximum key values across
all hackathonSlots, rather than relying on the first and last array elements.
Preserve todayMonth as the fallback for an empty slot list so the loop always
covers configured months regardless of ordering.
🪄 Autofix

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: 8a48428d-5515-4d92-8e54-4f24df58bd4c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa8a4 and 9ff1535.

📒 Files selected for processing (2)
  • app/hackathons/HackathonTimeline.tsx
  • app/hackathons/page.tsx

Comment threadapp/hackathons/page.tsx Outdated
`ordered` ordena por primera fecha, así que un hackatón anunciado antes de
tener fechas (clave de orden "") queda primero, pero su posición en el riel
sale del mes declarado en el JSON. Leer los extremos del array invertía los
límites, el loop no emitía ningún nodo y `rangeLabel` desreferenciaba
`items[0]` sobre un array vacío — justo el caso que el comentario del
fallback dice soportar.
Ahora los límites salen del mínimo y el máximo de las claves. Con
`hackathonSlots` vacío el fallback a `todayMonth` sigue emitiendo un slot,
así que `items` nunca queda vacío.
Reportado por CodeRabbit en #76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 4a552dd into mainAug 4, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-timeline-dynamic-8d1475 branch August 4, 2026 19:02
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