feat: Refresh marketing with live ecosystem sync#421
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThis PR refactors multiple pages to separate server and client concerns, introduces a new GitHub ecosystem data module that fetches repository and release information with fallback handling, updates landing components to render dynamic data from the ecosystem source, and adds comprehensive test coverage for data layer functionality. Changes
Sequence Diagram(s)sequenceDiagram
participant Server as Server Component
participant EcosystemModule as getEcosystemSnapshot()
participant GitHubAPI as GitHub API
participant Cache as Fallback Cache
participant Client as Client Component
Server->>EcosystemModule: Call getEcosystemSnapshot()
EcosystemModule->>GitHubAPI: Fetch org repos
alt API Success
GitHubAPI-->>EcosystemModule: Repo list
EcosystemModule->>GitHubAPI: Fetch latest release per repo
alt Release Found
GitHubAPI-->>EcosystemModule: Release data
else Release 404
GitHubAPI-->>EcosystemModule: 404
EcosystemModule->>EcosystemModule: Use fallback release data
end
EcosystemModule->>EcosystemModule: Build snapshot
EcosystemModule-->>Server: EcosystemSnapshot
else API Error
GitHubAPI--X EcosystemModule: Error
EcosystemModule->>Cache: Get fallback snapshot
Cache-->>EcosystemModule: Fallback data
EcosystemModule-->>Server: EcosystemSnapshot (fallback)
end
Server->>Client: Pass snapshot prop
Client->>Client: Render with dynamic data
Client-->>User: Animated UI with repos & stats
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Project Scorecard |
| let release = { | ||
| tag: metadata.fallbackReleaseTag ?? null, | ||
| publishedAt: metadata.fallbackReleaseDate ?? null, | ||
| }; |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (9)
apps/web/src/components/landing/EcosystemSection.tsx (2)
12-19: Consider extractingformatDateto a shared utility.This exact function is duplicated in
apps/web/src/app/(marketing)/about/about-client.tsx(lines 106-113). Extracting it to a shared utility (e.g.,@/lib/utils/date.ts) would reduce duplication and ensure consistent date formatting across the marketing pages.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/landing/EcosystemSection.tsx` around lines 12 - 19, Extract the duplicated formatDate function into a shared utility (e.g., export from a new date util module) and replace the two inline implementations with imports of that utility; specifically, create a reusable function named formatDate in a shared file and update the usages in EcosystemSection (formatDate) and the about-client component (previously duplicated formatDate) to import and call the new shared function so date formatting is centralized and duplication removed.
8-10: Mark props interface as readonly.Per SonarCloud analysis, marking props as readonly prevents accidental mutations.
Proposed fix
interface EcosystemSectionProps { - snapshot: EcosystemSnapshot; + readonly snapshot: EcosystemSnapshot; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/landing/EcosystemSection.tsx` around lines 8 - 10, The props interface allows mutation; mark it readonly by changing EcosystemSectionProps so the snapshot property is readonly (e.g., change "snapshot: EcosystemSnapshot" to "readonly snapshot: EcosystemSnapshot" or "readonly snapshot: Readonly<EcosystemSnapshot>" for deep immutability) to prevent accidental mutations in the EcosystemSection component.apps/web/src/app/(marketing)/about/about-client.tsx (3)
28-30: Mark props interface as readonly.Per SonarCloud analysis, marking props as readonly prevents accidental mutations.
Proposed fix
interface AboutPageClientProps { - snapshot: EcosystemSnapshot; + readonly snapshot: EcosystemSnapshot; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(marketing)/about/about-client.tsx around lines 28 - 30, The props interface AboutPageClientProps is mutable; mark it readonly to prevent accidental mutation by changing the declaration to use readonly on the snapshot property (i.e., update AboutPageClientProps so snapshot: EcosystemSnapshot becomes readonly snapshot: EcosystemSnapshot) or make the entire interface readonly (e.g., declare interface AboutPageClientProps { readonly snapshot: EcosystemSnapshot }) and ensure any usages of AboutPageClientProps or the snapshot parameter respect this immutability.
32-58: Consider reusing the existing FadeIn component.This
FadeInimplementation is similar toapps/web/src/components/landing/FadeIn.tsx. If the implementations are compatible, importing the existing component would reduce duplication. If there are intentional differences (e.g., different margin values), consider documenting why.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(marketing)/about/about-client.tsx around lines 32 - 58, The local FadeIn component duplicates behavior of the existing FadeIn in components/landing/FadeIn.tsx; replace the local definition by importing the shared FadeIn component (use the same props: children, className, delay) and remove the duplicate function, or if the local version intentionally differs (e.g., the useInView margin '-80px' or transition constants like EASE_SIZA), update the shared component to accept a prop (e.g., margin or prefersReducedMotion/transition override) and then import it here, or add a short code comment explaining the deliberate difference; ensure you reference the FadeIn component name and its margin/transition behavior when making changes.
106-113: DuplicateformatDatefunction.This exact implementation exists in
apps/web/src/components/landing/EcosystemSection.tsx(lines 12-19). Consider extracting to a shared utility like@/lib/utils/format.ts.Proposed shared utility
Create
apps/web/src/lib/utils/format.ts:export function formatDate(iso: string | null): string { if (!iso) return 'No release'; return new Intl.DateTimeFormat('en-US', { month: 'short', day: '2-digit', year: 'numeric', }).format(new Date(iso)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(marketing)/about/about-client.tsx around lines 106 - 113, There’s a duplicated function formatDate; extract it into a single shared utility module (exporting function formatDate(iso: string | null): string with the same implementation), then replace the local implementations in About client and EcosystemSection with an import of that shared formatDate, ensuring callers keep the same signature and behavior; update imports in the components that used the duplicate function and remove the local definitions.apps/web/src/lib/marketing/ecosystem-data.ts (2)
329-341: Simplify release fetch logic and add error logging.The initial assignment on lines 329-332 is redundant since it's immediately overwritten by the try/catch block. Also, the empty catch block silently swallows errors, which makes debugging difficult.
Proposed refactor
- let release = { - tag: metadata.fallbackReleaseTag ?? null, - publishedAt: metadata.fallbackReleaseDate ?? null, - }; - - try { - release = await fetchLatestRelease(name); - } catch { - release = { - tag: metadata.fallbackReleaseTag ?? null, - publishedAt: metadata.fallbackReleaseDate ?? null, - }; - } + let release: { tag: string | null; publishedAt: string | null }; + try { + release = await fetchLatestRelease(name); + } catch (error) { + console.warn(`[ecosystem-data] Failed to fetch release for ${name}:`, error); + release = { + tag: metadata.fallbackReleaseTag ?? null, + publishedAt: metadata.fallbackReleaseDate ?? null, + }; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/lib/marketing/ecosystem-data.ts` around lines 329 - 341, The initial pre-assignment of the local release object is redundant and the empty catch swallows errors; remove the first release initialization and instead set release inside the try, and in the catch assign the fallback values using metadata.fallbackReleaseTag and metadata.fallbackReleaseDate while logging the error (include the caught error message) so failures from fetchLatestRelease(name) are visible; update references to the release variable and use fetchLatestRelease(name) as the single source of truth, and ensure the catch logs with the same logger used in this module.
358-360: Add error logging for top-level fallback.When the entire snapshot fetch fails, the error is silently swallowed. Adding a warning log helps with observability and debugging production issues.
Proposed fix
- } catch { + } catch (error) { + console.warn('[ecosystem-data] Failed to fetch ecosystem snapshot, using fallback:', error); return getFallbackEcosystemSnapshot(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/lib/marketing/ecosystem-data.ts` around lines 358 - 360, The top-level catch currently swallows errors; change the empty catch block to capture the error (e.g., replace "catch {" with "catch (err) {"), log a warning including the error before returning the fallback, and then return getFallbackEcosystemSnapshot(); use the project's logger (logger.warn or processLogger.warn) if available, otherwise use console.warn, and include clear context like "Failed to fetch ecosystem snapshot" plus the err and any relevant identifiers.apps/web/src/components/landing/StatsBar.tsx (1)
7-9: Mark props interface as readonly for immutability.Per SonarCloud analysis, marking props as readonly prevents accidental mutations and aligns with React's unidirectional data flow.
Proposed fix
interface StatsBarProps { - snapshot: EcosystemSnapshot; + readonly snapshot: EcosystemSnapshot; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/landing/StatsBar.tsx` around lines 7 - 9, The StatsBarProps interface should mark its props as immutable: update the interface declaration for StatsBarProps so the snapshot property is readonly (i.e., change "snapshot: EcosystemSnapshot" to "readonly snapshot: EcosystemSnapshot") to prevent accidental mutations in the StatsBar component.apps/web/src/app/(marketing)/roadmap/roadmap-client.tsx (1)
13-15: Consider marking props as read-only.Per the static analysis hint, marking the props interface as read-only improves type safety and signals immutability intent.
✨ Suggested change
-interface RoadmapClientPageProps { - repoCount: number; -} +interface RoadmapClientPageProps { + readonly repoCount: number; +}Alternatively, use the
Readonlyutility type on the entire interface.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(marketing)/roadmap/roadmap-client.tsx around lines 13 - 15, The props interface RoadmapClientPageProps should be marked immutable: change its declaration to make properties read-only (e.g., add readonly to repoCount or use Readonly<RoadmapClientPageProps> / declare the interface as readonly) so consumers and static analysis know props are immutable; update the interface name RoadmapClientPageProps accordingly where it’s used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/app/`(marketing)/roadmap/page.tsx:
- Around line 1-7: Add a page-level ISR export so RoadmapPage uses the same
cache interval as getEcosystemSnapshot: export the shared REVALIDATE_SECONDS
constant (from the ecosystem-data module) and export it as revalidate in this
file; update apps/web/src/app/(marketing)/roadmap/page.tsx to import
REVALIDATE_SECONDS from '@/lib/marketing/ecosystem-data' and add an export const
revalidate = REVALIDATE_SECONDS alongside the existing default async function
RoadmapPage so the page-level ISR matches the fetch-level caching.
In `@apps/web/src/app/`(marketing)/roadmap/roadmap-client.tsx:
- Around line 39-46: The scrollToPhase callback starts a timeout that calls
setActivePhase(null) after 1500ms but never clears it on unmount; change
scrollToPhase to store the timeout id (e.g., in a ref like scrollTimeoutRef) and
add a useEffect cleanup that clears the timeout
(clearTimeout(scrollTimeoutRef.current)) on unmount, and ensure scrollToPhase
clears any existing timeout before setting a new one so setActivePhase is not
called after the component unmounts; reference the scrollToPhase function,
setActivePhase, setExpandedPhases, and the timeout handling ref in your changes.
In `@apps/web/src/lib/marketing/ecosystem-data.ts`:
- Line 5: Export the REVALIDATE_SECONDS constant so pages can import it for
Next.js ISR; specifically update the declaration of REVALIDATE_SECONDS in
ecosystem-data.ts to be an exported symbol (export const REVALIDATE_SECONDS =
21_600) so consuming pages (e.g., about/page.tsx, roadmap/page.tsx) can import {
REVALIDATE_SECONDS } and assign it to their exported revalidate value.
---
Nitpick comments:
In `@apps/web/src/app/`(marketing)/about/about-client.tsx:
- Around line 28-30: The props interface AboutPageClientProps is mutable; mark
it readonly to prevent accidental mutation by changing the declaration to use
readonly on the snapshot property (i.e., update AboutPageClientProps so
snapshot: EcosystemSnapshot becomes readonly snapshot: EcosystemSnapshot) or
make the entire interface readonly (e.g., declare interface AboutPageClientProps
{ readonly snapshot: EcosystemSnapshot }) and ensure any usages of
AboutPageClientProps or the snapshot parameter respect this immutability.
- Around line 32-58: The local FadeIn component duplicates behavior of the
existing FadeIn in components/landing/FadeIn.tsx; replace the local definition
by importing the shared FadeIn component (use the same props: children,
className, delay) and remove the duplicate function, or if the local version
intentionally differs (e.g., the useInView margin '-80px' or transition
constants like EASE_SIZA), update the shared component to accept a prop (e.g.,
margin or prefersReducedMotion/transition override) and then import it here, or
add a short code comment explaining the deliberate difference; ensure you
reference the FadeIn component name and its margin/transition behavior when
making changes.
- Around line 106-113: There’s a duplicated function formatDate; extract it into
a single shared utility module (exporting function formatDate(iso: string |
null): string with the same implementation), then replace the local
implementations in About client and EcosystemSection with an import of that
shared formatDate, ensuring callers keep the same signature and behavior; update
imports in the components that used the duplicate function and remove the local
definitions.
In `@apps/web/src/app/`(marketing)/roadmap/roadmap-client.tsx:
- Around line 13-15: The props interface RoadmapClientPageProps should be marked
immutable: change its declaration to make properties read-only (e.g., add
readonly to repoCount or use Readonly<RoadmapClientPageProps> / declare the
interface as readonly) so consumers and static analysis know props are
immutable; update the interface name RoadmapClientPageProps accordingly where
it’s used.
In `@apps/web/src/components/landing/EcosystemSection.tsx`:
- Around line 12-19: Extract the duplicated formatDate function into a shared
utility (e.g., export from a new date util module) and replace the two inline
implementations with imports of that utility; specifically, create a reusable
function named formatDate in a shared file and update the usages in
EcosystemSection (formatDate) and the about-client component (previously
duplicated formatDate) to import and call the new shared function so date
formatting is centralized and duplication removed.
- Around line 8-10: The props interface allows mutation; mark it readonly by
changing EcosystemSectionProps so the snapshot property is readonly (e.g.,
change "snapshot: EcosystemSnapshot" to "readonly snapshot: EcosystemSnapshot"
or "readonly snapshot: Readonly<EcosystemSnapshot>" for deep immutability) to
prevent accidental mutations in the EcosystemSection component.
In `@apps/web/src/components/landing/StatsBar.tsx`:
- Around line 7-9: The StatsBarProps interface should mark its props as
immutable: update the interface declaration for StatsBarProps so the snapshot
property is readonly (i.e., change "snapshot: EcosystemSnapshot" to "readonly
snapshot: EcosystemSnapshot") to prevent accidental mutations in the StatsBar
component.
In `@apps/web/src/lib/marketing/ecosystem-data.ts`:
- Around line 329-341: The initial pre-assignment of the local release object is
redundant and the empty catch swallows errors; remove the first release
initialization and instead set release inside the try, and in the catch assign
the fallback values using metadata.fallbackReleaseTag and
metadata.fallbackReleaseDate while logging the error (include the caught error
message) so failures from fetchLatestRelease(name) are visible; update
references to the release variable and use fetchLatestRelease(name) as the
single source of truth, and ensure the catch logs with the same logger used in
this module.
- Around line 358-360: The top-level catch currently swallows errors; change the
empty catch block to capture the error (e.g., replace "catch {" with "catch
(err) {"), log a warning including the error before returning the fallback, and
then return getFallbackEcosystemSnapshot(); use the project's logger
(logger.warn or processLogger.warn) if available, otherwise use console.warn,
and include clear context like "Failed to fetch ecosystem snapshot" plus the err
and any relevant identifiers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1e34a9fa-6cf1-4011-9944-6032cf8e7c51
⛔ Files ignored due to path filters (2)
CHANGELOG.mdis excluded by!**/*.mdREADME.mdis excluded by!**/*.md
📒 Files selected for processing (11)
apps/web/e2e/landing.spec.tsapps/web/src/__tests__/lib/marketing/ecosystem-data.test.tsapps/web/src/app/(marketing)/about/about-client.tsxapps/web/src/app/(marketing)/about/page.tsxapps/web/src/app/(marketing)/roadmap/page.tsxapps/web/src/app/(marketing)/roadmap/roadmap-client.tsxapps/web/src/app/page.tsxapps/web/src/components/landing/EcosystemSection.tsxapps/web/src/components/landing/StatsBar.tsxapps/web/src/components/roadmap/data.tsapps/web/src/lib/marketing/ecosystem-data.ts
| import RoadmapClientPage from './roadmap-client'; | ||
| import { getEcosystemSnapshot } from '@/lib/marketing/ecosystem-data'; | ||
|
|
||
| import { useState, useCallback, useMemo } from 'react'; | ||
| import { motion, useReducedMotion } from 'motion/react'; | ||
| import { phases } from '@/components/roadmap/data'; | ||
| import { PhaseCard } from '@/components/roadmap/PhaseCard'; | ||
| import { StatusFilter } from '@/components/roadmap/StatusFilter'; | ||
| import { PhaseNavigator } from '@/components/roadmap/PhaseNavigator'; | ||
| import { countByStatus } from '@/components/roadmap/utils'; | ||
| import { EASE_SIZA } from '@/components/landing/constants'; | ||
| import type { ItemStatus } from '@/components/roadmap/types'; | ||
|
|
||
| export default function RoadmapPage() { | ||
| const prefersReducedMotion = useReducedMotion(); | ||
| const [activeFilter, setActiveFilter] = useState<ItemStatus | 'all'>('all'); | ||
| const [scope, setScope] = useState<'all' | 'desktop'>('all'); | ||
| const [expandedPhases, setExpandedPhases] = useState<Set<number>>( | ||
| () => new Set(phases.filter((p) => p.status === 'active').map((p) => p.number)) | ||
| ); | ||
| const [activePhase, setActivePhase] = useState<number | null>(null); | ||
|
|
||
| const togglePhase = useCallback((n: number) => { | ||
| setExpandedPhases((prev) => { | ||
| const next = new Set(prev); | ||
| if (next.has(n)) next.delete(n); | ||
| else next.add(n); | ||
| return next; | ||
| }); | ||
| }, []); | ||
|
|
||
| const scrollToPhase = useCallback((n: number) => { | ||
| setActivePhase(n); | ||
| setExpandedPhases((prev) => new Set([...prev, n])); | ||
| document.getElementById(`phase-${n}`)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); | ||
| setTimeout(() => setActivePhase(null), 1500); | ||
| }, []); | ||
|
|
||
| const counts = useMemo( | ||
| () => ({ | ||
| all: countByStatus(phases, 'all'), | ||
| done: countByStatus(phases, 'done'), | ||
| 'in-progress': countByStatus(phases, 'in-progress'), | ||
| planned: countByStatus(phases, 'planned'), | ||
| }), | ||
| [] | ||
| ); | ||
|
|
||
| return ( | ||
| <div className="min-h-screen bg-background"> | ||
| <motion.div | ||
| initial={prefersReducedMotion ? false : { opacity: 0, y: 20 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| transition={prefersReducedMotion ? { duration: 0 } : { duration: 0.5, ease: EASE_SIZA }} | ||
| className="pt-24 pb-8 px-6 text-center" | ||
| > | ||
| <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-4">Roadmap</h1> | ||
| <p className="text-muted-foreground max-w-xl mx-auto mb-8"> | ||
| Where Siza is headed. Built in public, shaped by developer feedback. | ||
| </p> | ||
| <div className="space-y-4"> | ||
| <PhaseNavigator phases={phases} activePhase={activePhase} onSelect={scrollToPhase} /> | ||
| <div className="flex justify-center gap-2"> | ||
| <button | ||
| type="button" | ||
| onClick={() => setScope('all')} | ||
| className={ | ||
| 'px-3 py-1.5 rounded-full text-xs font-medium transition-colors ' + | ||
| (scope === 'all' | ||
| ? 'bg-primary text-primary-foreground' | ||
| : 'bg-card border border-border text-muted-foreground hover:text-foreground') | ||
| } | ||
| > | ||
| All Platforms | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={() => setScope('desktop')} | ||
| className={ | ||
| 'px-3 py-1.5 rounded-full text-xs font-medium transition-colors ' + | ||
| (scope === 'desktop' | ||
| ? 'bg-primary text-primary-foreground' | ||
| : 'bg-card border border-border text-muted-foreground hover:text-foreground') | ||
| } | ||
| > | ||
| Desktop | ||
| </button> | ||
| </div> | ||
| <StatusFilter active={activeFilter} onChange={setActiveFilter} counts={counts} /> | ||
| </div> | ||
| </motion.div> | ||
| <div className="max-w-2xl mx-auto px-6 pb-16"> | ||
| {phases.map((phase, i) => ( | ||
| <PhaseCard | ||
| key={phase.number} | ||
| phase={phase} | ||
| index={i} | ||
| totalPhases={phases.length} | ||
| expanded={expandedPhases.has(phase.number)} | ||
| onToggle={() => togglePhase(phase.number)} | ||
| activeFilter={activeFilter} | ||
| scope={scope} | ||
| /> | ||
| ))} | ||
| </div> | ||
| <div className="max-w-2xl mx-auto px-6 pb-20 text-center"> | ||
| <p className="text-sm text-muted-foreground"> | ||
| This roadmap evolves with the project.{' '} | ||
| <a | ||
| href="https://github.com/Forge-Space/siza/issues" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-primary hover:underline" | ||
| > | ||
| Share your feedback | ||
| </a>{' '} | ||
| to help shape what comes next. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| ); | ||
| export default async function RoadmapPage() { | ||
| const snapshot = await getEcosystemSnapshot(); | ||
| return <RoadmapClientPage repoCount={snapshot.repoCount} />; | ||
| } |
There was a problem hiding this comment.
Add revalidate export for ISR caching.
Without exporting a revalidate value, this page won't benefit from the 6-hour caching interval defined in the ecosystem data module. The fetch-level caching in getEcosystemSnapshot helps, but page-level ISR ensures consistent behavior.
Proposed fix
import RoadmapClientPage from './roadmap-client';
-import { getEcosystemSnapshot } from '@/lib/marketing/ecosystem-data';
+import { getEcosystemSnapshot, REVALIDATE_SECONDS } from '@/lib/marketing/ecosystem-data';
+
+export const revalidate = REVALIDATE_SECONDS;
export default async function RoadmapPage() {Note: This requires exporting REVALIDATE_SECONDS from ecosystem-data.ts as noted in that file's review.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/`(marketing)/roadmap/page.tsx around lines 1 - 7, Add a
page-level ISR export so RoadmapPage uses the same cache interval as
getEcosystemSnapshot: export the shared REVALIDATE_SECONDS constant (from the
ecosystem-data module) and export it as revalidate in this file; update
apps/web/src/app/(marketing)/roadmap/page.tsx to import REVALIDATE_SECONDS from
'@/lib/marketing/ecosystem-data' and add an export const revalidate =
REVALIDATE_SECONDS alongside the existing default async function RoadmapPage so
the page-level ISR matches the fetch-level caching.
| const scrollToPhase = useCallback((number: number) => { | ||
| setActivePhase(number); | ||
| setExpandedPhases((previous) => new Set([...previous, number])); | ||
| document | ||
| .getElementById(`phase-${number}`) | ||
| ?.scrollIntoView({ behavior: 'smooth', block: 'center' }); | ||
| setTimeout(() => setActivePhase(null), 1500); | ||
| }, []); |
There was a problem hiding this comment.
Clear timeout on unmount to prevent state update on unmounted component.
If the component unmounts before the 1.5-second timeout completes, setActivePhase(null) will attempt to update state on an unmounted component. While React 18+ handles this gracefully without errors, cleaning up the timeout is still good practice.
🛠️ Suggested fix using useEffect for cleanup
+import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
...
+ const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+ useEffect(() => {
+ return () => {
+ if (highlightTimeoutRef.current) {
+ clearTimeout(highlightTimeoutRef.current);
+ }
+ };
+ }, []);
+
const scrollToPhase = useCallback((number: number) => {
setActivePhase(number);
setExpandedPhases((previous) => new Set([...previous, number]));
document
.getElementById(`phase-${number}`)
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
- setTimeout(() => setActivePhase(null), 1500);
+ if (highlightTimeoutRef.current) {
+ clearTimeout(highlightTimeoutRef.current);
+ }
+ highlightTimeoutRef.current = setTimeout(() => setActivePhase(null), 1500);
}, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const scrollToPhase = useCallback((number: number) => { | |
| setActivePhase(number); | |
| setExpandedPhases((previous) => new Set([...previous, number])); | |
| document | |
| .getElementById(`phase-${number}`) | |
| ?.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| setTimeout(() => setActivePhase(null), 1500); | |
| }, []); | |
| import { useState, useCallback, useMemo, useEffect, useRef } from 'react'; | |
| // ... other imports and code ... | |
| const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); | |
| useEffect(() => { | |
| return () => { | |
| if (highlightTimeoutRef.current) { | |
| clearTimeout(highlightTimeoutRef.current); | |
| } | |
| }; | |
| }, []); | |
| const scrollToPhase = useCallback((number: number) => { | |
| setActivePhase(number); | |
| setExpandedPhases((previous) => new Set([...previous, number])); | |
| document | |
| .getElementById(`phase-${number}`) | |
| ?.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| if (highlightTimeoutRef.current) { | |
| clearTimeout(highlightTimeoutRef.current); | |
| } | |
| highlightTimeoutRef.current = setTimeout(() => setActivePhase(null), 1500); | |
| }, []); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/`(marketing)/roadmap/roadmap-client.tsx around lines 39 -
46, The scrollToPhase callback starts a timeout that calls setActivePhase(null)
after 1500ms but never clears it on unmount; change scrollToPhase to store the
timeout id (e.g., in a ref like scrollTimeoutRef) and add a useEffect cleanup
that clears the timeout (clearTimeout(scrollTimeoutRef.current)) on unmount, and
ensure scrollToPhase clears any existing timeout before setting a new one so
setActivePhase is not called after the component unmounts; reference the
scrollToPhase function, setActivePhase, setExpandedPhases, and the timeout
handling ref in your changes.
|
|
||
| const GITHUB_ORG = 'Forge-Space'; | ||
| const GITHUB_API_BASE = 'https://api.github.com'; | ||
| const REVALIDATE_SECONDS = 21_600; |
There was a problem hiding this comment.
Export REVALIDATE_SECONDS for page-level ISR configuration.
The constant is used in fetch options but consuming pages (about/page.tsx, roadmap/page.tsx) need to export revalidate for Next.js ISR to work at the page level. Without this, pages will either be fully dynamic or use default caching behavior, not the intended 6-hour interval.
Proposed fix
-const REVALIDATE_SECONDS = 21_600;
+export const REVALIDATE_SECONDS = 21_600;Then in consuming pages (e.g., apps/web/src/app/(marketing)/about/page.tsx):
import { getEcosystemSnapshot, REVALIDATE_SECONDS } from '@/lib/marketing/ecosystem-data';
export const revalidate = REVALIDATE_SECONDS;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const REVALIDATE_SECONDS = 21_600; | |
| export const REVALIDATE_SECONDS = 21_600; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/lib/marketing/ecosystem-data.ts` at line 5, Export the
REVALIDATE_SECONDS constant so pages can import it for Next.js ISR; specifically
update the declaration of REVALIDATE_SECONDS in ecosystem-data.ts to be an
exported symbol (export const REVALIDATE_SECONDS = 21_600) so consuming pages
(e.g., about/page.tsx, roadmap/page.tsx) can import { REVALIDATE_SECONDS } and
assign it to their exported revalidate value.
|
Follow-up: landing Playwright spec was rerun with Supabase env configured and is now green (). Because direct pushes to are blocked by repo rules, I opened a branch-targeted follow-up PR with the assertion fixes: #422 |
|
Follow-up correction: landing Playwright spec now passes (30 passed across Chromium/Firefox/WebKit). Direct pushes to feature/siza-marketing-refresh are blocked by repo rules, so the assertion fix is in follow-up PR #422 targeting that branch. |
* test: Stabilize landing e2e assertions * test: remove vulnerable regex from landing e2e matcher (#423)
|
Deployment failed with the following error: Learn More: https://vercel.com/luksantanas-projects?upgradeToPro=build-rate-limit |
|
|
Code review summary:\n- No blocking code defects found in current diff and follow-up E2E/Sonar fixes.\n- Required internal CI checks are green.\n- Remaining failing contexts are external Vercel rate-limit checks (non-code). |
* fix(ci): Remediate SonarCloud hotspot blockers (#420) * feat: Refresh marketing with live ecosystem sync (#421) * feat: Refresh marketing with live ecosystem sync * test: Stabilize landing e2e assertions (#422) * test: Stabilize landing e2e assertions * test: remove vulnerable regex from landing e2e matcher (#423) * feat: Refresh marketing with live ecosystem sync * fix: Harden generation reliability and stabilize web E2E * test: Fix Playwright fixture signature and ecosystem selector
* fix(ci): Remediate SonarCloud hotspot blockers (#420) * feat: Refresh marketing with live ecosystem sync (#421) * feat: Refresh marketing with live ecosystem sync * test: Stabilize landing e2e assertions (#422) * test: Stabilize landing e2e assertions * test: remove vulnerable regex from landing e2e matcher (#423)
* feat: Refresh marketing with live ecosystem sync * test: Stabilize landing e2e assertions * fix: Harden generation reliability and stabilize web E2E * chore: Sync rebased fixes into feature/siza-marketing-refresh (#425) * fix(ci): Remediate SonarCloud hotspot blockers (#420) * feat: Refresh marketing with live ecosystem sync (#421) * feat: Refresh marketing with live ecosystem sync * test: Stabilize landing e2e assertions (#422) * test: Stabilize landing e2e assertions * test: remove vulnerable regex from landing e2e matcher (#423) * feat: Refresh marketing with live ecosystem sync * fix: Harden generation reliability and stabilize web E2E * test: Fix Playwright fixture signature and ecosystem selector * chore: Merge main into feature/siza-marketing-refresh (#426) * fix(ci): Remediate SonarCloud hotspot blockers (#420) * feat: Refresh marketing with live ecosystem sync (#421) * feat: Refresh marketing with live ecosystem sync * test: Stabilize landing e2e assertions (#422) * test: Stabilize landing e2e assertions * test: remove vulnerable regex from landing e2e matcher (#423) * test: Reduce Sonar duplication in generation test suite (#429) * feat: Refresh marketing with live ecosystem sync * fix: Harden generation reliability and stabilize web E2E * test: Fix Playwright fixture signature and ecosystem selector * test: Reduce duplicated new-code in generation specs



Summary
apps/web/src/lib/marketing/ecosystem-data.ts) using GitHub REST with the canonical 11-repo allowlistStatsBarandEcosystemSectionto live snapshot data and remove stale hardcoded ecosystem countsFORGE_SPACE_GITHUB_TOKENpreferred,GITHUB_TOKENfallback)Verification
npm run lint --workspace=apps/webnpm run test --workspace=apps/webnpm run build --workspace=apps/webnpm audit --audit-level=highNotes
npm run test:e2e --workspace=apps/web -- landing.spec.ts) could not execute in this environment because runtime boot requires Supabase env vars (NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY)npm auditare in transitive tooling deps (not newly introduced by this PR), mainlytarin the Electron builder chainSummary by CodeRabbit
New Features
Tests