Skip to content
This repository was archived by the owner on Apr 21, 2026. It is now read-only.

feat: Refresh marketing with live ecosystem sync#421

Merged
LucasSantana-Dev merged 2 commits intomainfrom
feature/siza-marketing-refresh
Mar 10, 2026
Merged

feat: Refresh marketing with live ecosystem sync#421
LucasSantana-Dev merged 2 commits intomainfrom
feature/siza-marketing-refresh

Conversation

@LucasSantana-Dev
Copy link
Copy Markdown
Member

@LucasSantana-Dev LucasSantana-Dev commented Mar 10, 2026

Summary

  • Add a server-only marketing ecosystem sync module (apps/web/src/lib/marketing/ecosystem-data.ts) using GitHub REST with the canonical 11-repo allowlist
  • Wire homepage StatsBar and EcosystemSection to live snapshot data and remove stale hardcoded ecosystem counts
  • Convert marketing About and Roadmap pages to server wrappers + client renderers driven by live ecosystem metadata
  • Refresh roadmap/about messaging to reflect shipped differentiators without over-claiming unreleased work
  • Update README/CHANGELOG with the new live-sync behavior and env var contract (FORGE_SPACE_GITHUB_TOKEN preferred, GITHUB_TOKEN fallback)
  • Update landing Playwright assertions to remove stale “seven repos” expectation

Verification

  • npm run lint --workspace=apps/web
  • npm run test --workspace=apps/web
  • npm run build --workspace=apps/web
  • npm audit --audit-level=high

Notes

  • Playwright run command (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)
  • High vulnerabilities reported by npm audit are in transitive tooling deps (not newly introduced by this PR), mainly tar in the Electron builder chain

Summary by CodeRabbit

  • New Features

    • Added a new About page featuring company philosophy, technology stack, and project credits.
    • Integrated live GitHub ecosystem data synchronization on the landing page and roadmap.
    • Repository cards now display latest release information and last updated dates.
  • Tests

    • Added comprehensive test suite for ecosystem data integration.
    • Updated end-to-end tests for landing page text updates.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 10, 2026

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

Project Deployment Actions Updated (UTC)
siza-web Ready Ready Preview, Comment Mar 10, 2026 8:11pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

Warning

Rate limit exceeded

@LucasSantana-Dev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b2ad7ca5-4e5d-4086-976c-08fa6dd0a82e

📥 Commits

Reviewing files that changed from the base of the PR and between 8218fb0 and 4649b7c.

📒 Files selected for processing (1)
  • apps/web/e2e/landing.spec.ts

Walkthrough

This 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

Cohort / File(s) Summary
E2E Test Updates
apps/web/e2e/landing.spec.ts
Updated visible text assertions to match new UI copy: "live github ecosystem sync" and "product repos" in stats bar; "11 repos. one vision." and "ui-mcp" in ecosystem section.
Ecosystem Data Layer
apps/web/src/lib/marketing/ecosystem-data.ts
New module providing GitHub API integration for fetching repositories and release data. Includes type definitions (EcosystemSnapshot, EcosystemRepo), utility functions for API calls with fallback handling, and public export of getEcosystemSnapshot() and getFallbackEcosystemSnapshot().
Ecosystem Data Tests
apps/web/src/__tests__/lib/marketing/ecosystem-data.test.ts
New test suite validating ecosystem data module: mocks GitHub API, tests snapshot building with live release data, fallback behavior on API errors, and repo list failure scenarios.
About Page Refactor
apps/web/src/app/(marketing)/about/page.tsx, apps/web/src/app/(marketing)/about/about-client.tsx
Split into server-side page component (fetches ecosystem snapshot) and new client-side AboutPageClient (renders styled UI with hero, philosophy cards, repo cards, tech stack, credits, and animations with snapshot data).
Roadmap Page Refactor
apps/web/src/app/(marketing)/roadmap/page.tsx, apps/web/src/app/(marketing)/roadmap/roadmap-client.tsx, apps/web/src/components/roadmap/data.ts
Refactored roadmap into server component (fetches ecosystem data) and new RoadmapClientPage (manages state and renders phases). Replaced static phases constant with buildPhases(repoCount) factory function for dynamic content.
Landing Components
apps/web/src/app/page.tsx, apps/web/src/components/landing/StatsBar.tsx, apps/web/src/components/landing/EcosystemSection.tsx
Updated landing page to fetch ecosystem snapshot and pass to StatsBar and EcosystemSection. Both components now accept snapshot props and render dynamic data (repo counts, release tags, updated dates) instead of hardcoded values with improved metadata display and animations.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Refresh marketing with live ecosystem sync' directly describes the main change—integrating live GitHub ecosystem data into marketing pages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/siza-marketing-refresh

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 and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 10, 2026

Project Scorecard


Scorecard: 84/100 (B)
────────────────────────────────────────
  security: 100/100 (A)
  quality: 80/100 (B) — 1 violations
  performance: 67/100 (D) — 1 violations
  compliance: 75/100 (C) — 1 violations
  dependency: 100/100 (A)

Recommendations:
  - Increase test coverage to meet the 80% threshold
  - Extend log retention to at least 90 days for compliance

Comment on lines +329 to +332
let release = {
tag: metadata.fallbackReleaseTag ?? null,
publishedAt: metadata.fallbackReleaseDate ?? null,
};

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

The initial value of release is unused, since it is always overwritten.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (9)
apps/web/src/components/landing/EcosystemSection.tsx (2)

12-19: Consider extracting formatDate to 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 FadeIn implementation is similar to apps/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: Duplicate formatDate function.

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 Readonly utility 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

📥 Commits

Reviewing files that changed from the base of the PR and between abb7db4 and 8218fb0.

⛔ Files ignored due to path filters (2)
  • CHANGELOG.md is excluded by !**/*.md
  • README.md is excluded by !**/*.md
📒 Files selected for processing (11)
  • apps/web/e2e/landing.spec.ts
  • apps/web/src/__tests__/lib/marketing/ecosystem-data.test.ts
  • apps/web/src/app/(marketing)/about/about-client.tsx
  • apps/web/src/app/(marketing)/about/page.tsx
  • apps/web/src/app/(marketing)/roadmap/page.tsx
  • apps/web/src/app/(marketing)/roadmap/roadmap-client.tsx
  • apps/web/src/app/page.tsx
  • apps/web/src/components/landing/EcosystemSection.tsx
  • apps/web/src/components/landing/StatsBar.tsx
  • apps/web/src/components/roadmap/data.ts
  • apps/web/src/lib/marketing/ecosystem-data.ts

Comment on lines +1 to 7
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} />;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +39 to +46
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);
}, []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

@LucasSantana-Dev
Copy link
Copy Markdown
Member Author

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

@LucasSantana-Dev
Copy link
Copy Markdown
Member Author

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)
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 10, 2026

Deployment failed with the following error:

Resource is limited - try again in 4 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/luksantanas-projects?upgradeToPro=build-rate-limit

@sonarqubecloud
Copy link
Copy Markdown

@LucasSantana-Dev
Copy link
Copy Markdown
Member Author

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).

@LucasSantana-Dev LucasSantana-Dev merged commit c4005eb into main Mar 10, 2026
26 of 27 checks passed
@LucasSantana-Dev LucasSantana-Dev deleted the feature/siza-marketing-refresh branch March 10, 2026 21:40
LucasSantana-Dev added a commit that referenced this pull request Mar 11, 2026
* 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
LucasSantana-Dev added a commit that referenced this pull request Mar 11, 2026
* 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)
LucasSantana-Dev added a commit that referenced this pull request Mar 11, 2026
* 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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants