Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions website/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ScrollProgress } from "@/components/ScrollProgress";
import { Nav } from "@/components/Nav";
import { Hero } from "@/components/Hero";
import { FeaturedIn } from "@/components/FeaturedIn";
import { Stats } from "@/components/Stats";
import { Primitives } from "@/components/Primitives";
import { Features } from "@/components/Features";
Expand All @@ -21,7 +20,6 @@ export default function Page() {
<Nav />
<main id="top">
<Hero />
<FeaturedIn />
<Stats
mcpTools={meta.mcpTools}
hooks={meta.hooks}
Expand Down
9 changes: 9 additions & 0 deletions website/components/FeaturedIn.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
border-top: 1px solid var(--charcoal);
border-bottom: 1px solid var(--charcoal);
}
.wrapCompact {
padding: 36px 0 0;
background: transparent;
border: 0;
margin-top: 40px;
}
.wrapCompact .eyebrow {
margin-bottom: 4px;
}
.inner {
max-width: 1100px;
margin: 0 auto;
Expand Down
15 changes: 13 additions & 2 deletions website/components/FeaturedIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,20 @@ const ITEMS: Feature[] = [
},
];

export function FeaturedIn() {
interface FeaturedInProps {
// When true, render the bar without the outer `<section>` chrome
// (no border, no own padding) so it can be inlined inside the
// hero below the CTA stack.
compact?: boolean;
}

export function FeaturedIn({ compact = false }: FeaturedInProps = {}) {
const wrapClass = compact ? `${styles.wrap} ${styles.wrapCompact}` : styles.wrap;
return (
<section className={styles.wrap} aria-labelledby="featured-in-title">
<section
className={wrapClass}
aria-labelledby="featured-in-title"
>
<div className={styles.inner}>
<div id="featured-in-title" className={styles.eyebrow}>
AS FEATURED IN
Expand Down
16 changes: 12 additions & 4 deletions website/components/GitHubStarButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import styles from "./GitHubStarButton.module.css";

interface Props {
repo: string;
// SSR-fed initial count from Nav's server-side fetch. When present
// the badge renders the number on first paint; the client-side
// refetch below is a best-effort refresh that only fires if it
// succeeds (unauthenticated github.com api is rate-limited to
// ~60/hr/IP, which is why the count was invisible before).
initialStars?: number;
}

function formatStars(n: number): string {
Expand All @@ -15,8 +21,10 @@ function formatStars(n: number): string {
return String(n);
}

export function GitHubStarButton({ repo }: Props) {
const [stars, setStars] = useState<number | null>(null);
export function GitHubStarButton({ repo, initialStars }: Props) {
const [stars, setStars] = useState<number | null>(
typeof initialStars === "number" ? initialStars : null,
);

useEffect(() => {
let cancelled = false;
Expand Down Expand Up @@ -52,7 +60,7 @@ export function GitHubStarButton({ repo }: Props) {
}
}
} catch {
/* offline / blocked — keep cached or null */
/* offline / blocked — keep SSR / cached / null */
}
})();
return () => {
Expand All @@ -66,7 +74,7 @@ export function GitHubStarButton({ repo }: Props) {
target="_blank"
rel="noopener noreferrer"
className={`btn btn--ghost ${styles.starBtn}`}
aria-label={`Star ${repo} on GitHub`}
aria-label={`Star ${repo} on GitHub${stars !== null ? ` — ${stars.toLocaleString()} stars` : ""}`}
>
<svg
aria-hidden
Expand Down
43 changes: 32 additions & 11 deletions website/components/Hero.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
align-items: center;
justify-content: center;
overflow: hidden;
padding: 120px 24px 80px;
padding: 110px 24px 72px;
}
.vignette {
position: absolute;
Expand All @@ -32,23 +32,24 @@
text-align: center;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}

.chip {
display: inline-block;
padding: 8px 14px;
padding: 7px 13px;
font-size: 12px;
letter-spacing: 0.96px;
color: var(--gold-soft);
border: 1px solid var(--gold-soft);
text-transform: uppercase;
margin-bottom: 28px;
margin-bottom: 22px;
}

.title {
font-size: clamp(54px, 12vw, 160px);
font-size: clamp(44px, 9vw, 124px);
font-weight: 900;
line-height: 0.92;
line-height: 0.94;
letter-spacing: -0.02em;
margin: 0;
text-transform: uppercase;
Expand Down Expand Up @@ -79,24 +80,44 @@
}

.lede {
max-width: 780px;
margin: 28px auto 40px;
font-size: clamp(14px, 1.4vw, 18px);
max-width: 720px;
margin: 24px auto 32px;
font-size: clamp(13px, 1.2vw, 16px);
letter-spacing: 0.12px;
color: var(--mist);
text-transform: uppercase;
line-height: 1.56;
line-height: 1.6;
}

.cta {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
width: 100%;
}
.ctaSecondary {
display: inline-flex;
gap: 16px;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}

@media (max-width: 640px) {
@media (max-width: 760px) {
.hero {
padding: 96px 18px 56px;
min-height: 0;
}
.title {
flex-direction: column;
font-size: clamp(40px, 14vw, 72px);
}
.lede {
margin: 20px auto 24px;
}
.chip {
font-size: 10px;
padding: 6px 10px;
margin-bottom: 18px;
}
}
24 changes: 16 additions & 8 deletions website/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { MemoryGraph } from "./MemoryGraph";
import { GitHubStarButton } from "./GitHubStarButton";
import { HeroNpxCommand } from "./HeroNpxCommand";
import { FeaturedIn } from "./FeaturedIn";
import { getProjectMeta } from "@/lib/meta";
import { fetchRepoStats } from "@/lib/github";
import styles from "./Hero.module.css";

export function Hero() {
export async function Hero() {
const meta = getProjectMeta();
const stats = await fetchRepoStats();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the stats fetch to prevent render-path failure.

At Line 11, await fetchRepoStats() has no local fallback. If that call throws, the hero can fail to render. Add a safe fallback so the page still ships core content.

Proposed fix
 export async function Hero() {
   const meta = getProjectMeta();
-  const stats = await fetchRepoStats();
+  const stats = await fetchRepoStats().catch(() => ({ stars: 0 }));
📝 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 stats = await fetchRepoStats();
export async function Hero() {
const meta = getProjectMeta();
const stats = await fetchRepoStats().catch(() => ({ stars: 0 }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/components/Hero.tsx` at line 11, The call to fetchRepoStats() in the
Hero component can throw and break the render; wrap the await fetchRepoStats()
call in a try/catch (or use .catch) and assign a safe fallback (e.g., an empty
stats object or zeros) to the stats variable so rendering can proceed; also
guard any subsequent reads of stats in the Hero render logic (e.g., when
accessing stats.stars or stats.forks) to use the fallback values when
fetchRepoStats fails.

return (
<section className={styles.hero} aria-labelledby="hero-title">
<MemoryGraph />
Expand All @@ -22,14 +26,18 @@ export function Hero() {
CAPTURE EVERY SESSION. RECALL IN MILLISECONDS. RUN ANYWHERE.
</p>
<div className={styles.cta}>
<a href="#install" className="btn btn--accent">
START IN 30 SECONDS
</a>
<a href="#live" className="btn btn--ghost">
SEE IT MOVE
</a>
<GitHubStarButton repo="rohitg00/agentmemory" />
<HeroNpxCommand />
<div className={styles.ctaSecondary}>
<a href="#live" className="btn btn--ghost">
SEE IT MOVE
</a>
<GitHubStarButton
repo="rohitg00/agentmemory"
initialStars={stats.stars > 0 ? stats.stars : undefined}
/>
</div>
</div>
<FeaturedIn compact />
</div>
</section>
);
Expand Down
69 changes: 69 additions & 0 deletions website/components/HeroNpxCommand.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
.cmd {
display: inline-grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 14px;
padding: 16px 22px;
min-width: 460px;
max-width: 100%;
background: var(--iron);
border: 1px solid var(--gold);
color: var(--white);
font-family: var(--font-mono);
font-size: 16px;
letter-spacing: 0.02em;
cursor: pointer;
transition: background 0.12s ease, transform 0.12s ease, border-color 0.12s ease;
text-align: left;
}
.cmd:hover {
background: #1a1a1f;
transform: translateY(-1px);
}
.cmd:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 3px;
}
.copied {
background: var(--gold);
color: var(--ink);
border-color: var(--gold);
}
.copied .hint {
color: var(--ink);
}
.prompt {
color: var(--gold);
font-weight: 700;
}
.copied .prompt {
color: var(--ink);
}
.text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hint {
font-size: 10px;
letter-spacing: 1.4px;
color: var(--ash);
text-transform: uppercase;
padding-left: 12px;
border-left: 1px solid var(--charcoal);
}
.copied .hint {
border-left-color: rgba(0, 0, 0, 0.2);
}
@media (max-width: 640px) {
.cmd {
min-width: 0;
width: 100%;
font-size: 13px;
padding: 14px 16px;
gap: 10px;
}
.hint {
display: none;
}
}
33 changes: 33 additions & 0 deletions website/components/HeroNpxCommand.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

import { useState } from "react";
import styles from "./HeroNpxCommand.module.css";

const CMD = "npx @agentmemory/agentmemory";

export function HeroNpxCommand() {
const [copied, setCopied] = useState(false);

const onCopy = async () => {
try {
await navigator.clipboard.writeText(CMD);
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
} catch {
/* clipboard blocked — keep button visible */
}
};

return (
<button
type="button"
onClick={onCopy}
className={`${styles.cmd} ${copied ? styles.copied : ""}`}
aria-label="Copy install command"
>
<span className={styles.prompt}>$</span>
<span className={styles.text}>{CMD}</span>
<span className={styles.hint}>{copied ? "COPIED" : "CLICK TO COPY"}</span>
</button>
);
}
4 changes: 2 additions & 2 deletions website/lib/generated-meta.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"version": "0.9.15",
"version": "0.9.16",
"mcpTools": 51,
"hooks": 12,
"restEndpoints": 121,
"testsPassing": 975,
"generatedAt": "2026-05-15T18:11:06.382Z"
"generatedAt": "2026-05-16T09:00:42.889Z"
}