From 32101ec44f2b53c2d9ecf955defdaa7072c42fe5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 19:32:55 +0000 Subject: [PATCH 1/3] feat(web): sticky install CTA above the fold on agent pages Put a sticky npx install command with a labeled Copy command button directly under the agent title so visitors can copy without scrolling. Package-manager tabs stay on a secondary card; PostHog uses surface sticky_install_cta. File list heading is now Files this command writes. Co-authored-by: Tommaso --- apps/web/app/(main)/agents/[slug]/page.tsx | 24 +++++-- apps/web/components/agent-file-viewer.tsx | 4 +- apps/web/components/sticky-install-cta.tsx | 75 ++++++++++++++++++++++ apps/web/lib/package-managers.ts | 9 +++ apps/web/tests/package-managers.test.ts | 47 ++++++++++++++ 5 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 apps/web/components/sticky-install-cta.tsx create mode 100644 apps/web/tests/package-managers.test.ts diff --git a/apps/web/app/(main)/agents/[slug]/page.tsx b/apps/web/app/(main)/agents/[slug]/page.tsx index c150a5a6..3cc519ae 100644 --- a/apps/web/app/(main)/agents/[slug]/page.tsx +++ b/apps/web/app/(main)/agents/[slug]/page.tsx @@ -15,6 +15,7 @@ import { FavoriteButton } from '@/components/favorite-button' import { InstallCommand } from '@/components/install-command' import { JsonLd } from '@/components/json-ld' import { MobileInstallBar } from '@/components/mobile-install-bar' +import { StickyInstallCta } from '@/components/sticky-install-cta' import { compareRelatedAgents, countFilesByKind, @@ -209,10 +210,6 @@ async function AgentDetailContent({ agent }: { agent: AgentWithAuthor }) { /> - -

- {agent.description} -

{agent.authorUsername ? (
+ + + +

+ {agent.description} +

+ -

Install

+

+ Other package managers +

- Run this command in your eve app to add the agent. + Prefer pnpm, yarn, or bun? Copy the matching install command.

+ diff --git a/apps/web/components/agent-file-viewer.tsx b/apps/web/components/agent-file-viewer.tsx index 96840b62..c479b793 100644 --- a/apps/web/components/agent-file-viewer.tsx +++ b/apps/web/components/agent-file-viewer.tsx @@ -278,7 +278,9 @@ export function AgentFileViewer({ files }: { files: AgentRegistryFile[] }) {
-

Files

+

+ Files this command writes +

{files.length} diff --git a/apps/web/components/sticky-install-cta.tsx b/apps/web/components/sticky-install-cta.tsx new file mode 100644 index 00000000..14f129ec --- /dev/null +++ b/apps/web/components/sticky-install-cta.tsx @@ -0,0 +1,75 @@ +'use client' + +import { Button } from '@evex/ui/button' +import { cn } from '@evex/ui/lib/utils' +import { Check, Copy } from 'lucide-react' +import posthog from 'posthog-js' +import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' +import { buildInstallCommand } from '@/lib/package-managers' + +/** + * Above-the-fold install affordance pinned under the agent title. Always shows + * the canonical `npx shadcn@latest add @evex/` form — package-manager + * tabs live on the secondary install card below, not here. + */ +export function StickyInstallCta({ + slug, + agentAuthor, + viewerIsAuthor, + className, +}: { + slug: string + agentAuthor: string | null + viewerIsAuthor: boolean | null + className?: string +}) { + const command = buildInstallCommand(slug) + const { copied, copy } = useCopyToClipboard() + + const handleCopy = async () => { + const didCopy = await copy(command, { + successMessage: 'Copied install command', + }) + if (didCopy) { + posthog.capture('agent_install_command_copied', { + agent_author: agentAuthor, + agent_slug: slug, + package_manager: 'npm', + surface: 'sticky_install_cta', + viewer_is_author: viewerIsAuthor, + }) + } + } + + return ( + + ) +} diff --git a/apps/web/lib/package-managers.ts b/apps/web/lib/package-managers.ts index 42278186..b1f4b685 100644 --- a/apps/web/lib/package-managers.ts +++ b/apps/web/lib/package-managers.ts @@ -21,10 +21,19 @@ export function parsePackageManager( return match ? match.id : DEFAULT_PACKAGE_MANAGER } +// Canonical public install command. Always the npx form — product surfaces +// that must not rotate package managers (hero, sticky CTA, SEO) use this. +export function buildInstallCommand(slug: string): string { + return `npx shadcn@latest add @evex/${slug}` +} + export function buildInstallCommandForManager( manager: PackageManagerId, slug: string, ): string { + if (manager === 'npm') { + return buildInstallCommand(slug) + } const match = PACKAGE_MANAGERS.find((entry) => entry.id === manager) const runner = match?.runner ?? 'npx' return `${runner} shadcn@latest add @evex/${slug}` diff --git a/apps/web/tests/package-managers.test.ts b/apps/web/tests/package-managers.test.ts new file mode 100644 index 00000000..6a099593 --- /dev/null +++ b/apps/web/tests/package-managers.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest' +import { + buildInstallCommand, + buildInstallCommandForManager, +} from '@/lib/package-managers' + +describe('buildInstallCommand', () => { + it('always returns the canonical npx form', () => { + expect(buildInstallCommand('code-reviewer')).toBe( + 'npx shadcn@latest add @evex/code-reviewer', + ) + expect(buildInstallCommand('brand-visual-asset-generator')).toBe( + 'npx shadcn@latest add @evex/brand-visual-asset-generator', + ) + }) + + it('matches the server-side helper in site-url', async () => { + // Keep the client-safe and server-only copies in lockstep without a + // barrel re-export (ultracite bans those). + const { buildInstallCommand: serverBuildInstallCommand } = await import( + '@/lib/site-url' + ) + expect(buildInstallCommand('code-reviewer')).toBe( + serverBuildInstallCommand('code-reviewer'), + ) + }) +}) + +describe('buildInstallCommandForManager', () => { + it('matches the canonical command for npm', () => { + expect(buildInstallCommandForManager('npm', 'openui')).toBe( + buildInstallCommand('openui'), + ) + }) + + it('uses the selected runner for other managers', () => { + expect(buildInstallCommandForManager('pnpm', 'openui')).toBe( + 'pnpm dlx shadcn@latest add @evex/openui', + ) + expect(buildInstallCommandForManager('yarn', 'openui')).toBe( + 'yarn dlx shadcn@latest add @evex/openui', + ) + expect(buildInstallCommandForManager('bun', 'openui')).toBe( + 'bunx --bun shadcn@latest add @evex/openui', + ) + }) +}) From ff8e25d55aac3bc5278154b5bfc996a63d8e8bc5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 18 Aug 2026 08:00:39 +0000 Subject: [PATCH 2/3] fix(web): keep sticky install CTA desktop-only On small viewports the install CTA stays in document flow so MobileInstallBar remains the only sticky copy control. Co-authored-by: Tommaso --- apps/web/components/sticky-install-cta.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/web/components/sticky-install-cta.tsx b/apps/web/components/sticky-install-cta.tsx index 14f129ec..db1bde48 100644 --- a/apps/web/components/sticky-install-cta.tsx +++ b/apps/web/components/sticky-install-cta.tsx @@ -8,9 +8,11 @@ import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' import { buildInstallCommand } from '@/lib/package-managers' /** - * Above-the-fold install affordance pinned under the agent title. Always shows - * the canonical `npx shadcn@latest add @evex/` form — package-manager - * tabs live on the secondary install card below, not here. + * Above-the-fold install affordance under the agent title. Sticky on sm+ only — + * on small viewports it stays in document flow so MobileInstallBar remains the + * sole sticky install control. Always shows the canonical + * `npx shadcn@latest add @evex/` form; package-manager tabs live on the + * secondary install card below, not here. */ export function StickyInstallCta({ slug, @@ -45,7 +47,8 @@ export function StickyInstallCta({
- +