From 8a47711e04fca8faa3ed3d3f9dcdfa20c86fe511 Mon Sep 17 00:00:00 2001 From: Kevin Kern Date: Sat, 18 Jul 2026 17:32:10 +0200 Subject: [PATCH] feat: simplify agent install actions --- apps/docs/app/global.css | 60 ++------- apps/docs/components/agent-setup-panel.tsx | 143 +++++++++------------ apps/docs/scripts/verify-agent-landing.mjs | 61 ++++----- apps/docs/scripts/verify-shell.mjs | 115 ++++++++--------- 4 files changed, 153 insertions(+), 226 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 5820645..3a21f6e 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -308,17 +308,17 @@ .agent-setup-heading h2 { margin-top: .6rem; font-size: clamp(2rem, 4.5vw, 3.65rem); font-weight: 720; letter-spacing: -.055em; line-height: 1.02; text-wrap: balance; } .agent-setup-heading > span { display: block; max-width: 700px; margin-top: 1rem; color: var(--color-fd-muted-foreground); font-size: .98rem; line-height: 1.65; } -.agent-setup-tabs { +.agent-setup-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .7rem; margin-top: 2rem; } -.agent-setup-tabs > button { +.agent-setup-actions > button { display: grid; min-width: 0; - grid-template-columns: auto 1fr; + grid-template-columns: auto 1fr auto; align-items: center; gap: .75rem; border: 1px solid var(--color-fd-border); @@ -329,52 +329,18 @@ transition: border-color 160ms ease, background-color 160ms ease, transform 160ms ease; } -.agent-setup-tabs > button:hover { transform: translateY(-2px); border-color: color-mix(in oklab, var(--color-fd-primary) 50%, transparent); } -.agent-setup-tabs > button[aria-selected="true"] { border-color: var(--color-fd-primary); background: color-mix(in oklab, var(--color-fd-primary) 10%, var(--color-fd-background)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-fd-primary) 20%, transparent); } -.agent-setup-tab__mark { display: grid; width: 2.7rem; height: 2.7rem; place-items: center; border-radius: .65rem; background: #f6f5f1; } -.agent-setup-tab__mark img { width: 1.8rem; height: 1.8rem; object-fit: contain; } -.agent-setup-tabs strong, .agent-setup-tabs small { display: block; } -.agent-setup-tabs strong { font-size: .9rem; } -.agent-setup-tabs small { overflow: hidden; margin-top: .1rem; color: var(--color-fd-muted-foreground); font-size: .68rem; line-height: 1.3; text-overflow: ellipsis; white-space: nowrap; } +.agent-setup-actions > button:hover { transform: translateY(-2px); border-color: color-mix(in oklab, var(--color-fd-primary) 50%, transparent); } +.agent-setup-actions > button[data-copy-state="copied"] { border-color: var(--color-fd-primary); background: color-mix(in oklab, var(--color-fd-primary) 10%, var(--color-fd-background)); } +.agent-setup-actions > button > svg { width: 1rem; height: 1rem; color: var(--color-fd-primary); } +.agent-setup-action__mark { display: grid; width: 2.7rem; height: 2.7rem; place-items: center; border-radius: .65rem; background: #f6f5f1; } +.agent-setup-action__mark img { width: 1.8rem; height: 1.8rem; object-fit: contain; } +.agent-setup-action__label strong, .agent-setup-action__label small { display: block; } +.agent-setup-action__label strong { margin-top: .1rem; font-size: .78rem; } +.agent-setup-action__label small { color: var(--color-fd-muted-foreground); font-size: .68rem; } -.agent-setup-panel { - display: grid; - grid-template-columns: minmax(0, 1.15fr) minmax(17rem, .85fr); - gap: 1rem; - margin-top: 1rem; -} - -.agent-setup-panel[hidden] { display: none; } - -.agent-setup-prompt, -.agent-setup-contract { - min-width: 0; - border: 1px solid var(--color-fd-border); - border-radius: 1rem; - padding: 1rem; - background: color-mix(in oklab, var(--color-fd-background) 86%, transparent); -} - -.agent-setup-prompt__heading { display: flex; align-items: center; gap: .8rem; margin-bottom: .85rem; } -.agent-setup-prompt__logo { display: grid; width: 3.25rem; height: 3.25rem; flex: none; place-items: center; border-radius: .72rem; background: #f6f5f1; } -.agent-setup-prompt__logo img { width: 2rem; height: 2rem; object-fit: contain; } -.agent-setup-prompt__heading p { color: var(--color-fd-primary); font-size: .67rem; font-weight: 750; letter-spacing: .09em; text-transform: uppercase; } -.agent-setup-prompt__heading h3 { margin-top: .15rem; font-size: 1.15rem; font-weight: 720; letter-spacing: -.02em; } -.agent-setup-prompt .command-block pre { max-height: 21rem; overflow: auto; font-size: .73rem; line-height: 1.65; } .agent-setup-links { display: flex; flex-wrap: wrap; align-items: center; gap: .65rem 1rem; margin-top: .85rem; font-size: .78rem; font-weight: 650; } .agent-setup-links a { display: inline-flex; align-items: center; gap: .35rem; color: var(--color-fd-primary); text-decoration: underline; text-underline-offset: .25rem; } .agent-setup-links svg { width: .85rem; height: .85rem; } - -.agent-setup-contract { display: grid; align-content: start; gap: 1rem; } -.agent-setup-contract > div { border-bottom: 1px solid var(--color-fd-border); padding-bottom: 1rem; } -.agent-setup-contract__icon { display: grid; width: 2rem; height: 2rem; place-items: center; border-radius: .55rem; background: color-mix(in oklab, var(--color-fd-primary) 13%, transparent); color: var(--color-fd-primary); } -.agent-setup-contract__icon svg { width: 1.05rem; height: 1.05rem; } -.agent-setup-contract h3 { margin-top: .65rem; font-size: .9rem; font-weight: 720; } -.agent-setup-contract ul { display: grid; gap: .38rem; margin-top: .55rem; color: var(--color-fd-muted-foreground); font-size: .72rem; line-height: 1.45; } -.agent-setup-contract li { position: relative; padding-left: .8rem; } -.agent-setup-contract li::before { position: absolute; left: 0; color: var(--color-fd-primary); content: "•"; } -.agent-setup-reload { color: var(--color-fd-muted-foreground); font-size: .75rem; line-height: 1.5; } -.agent-setup-reload strong { color: var(--color-fd-foreground); } .agent-setup-noscript { margin-top: 1rem; border: 1px solid var(--color-fd-border); border-radius: .75rem; padding: .8rem; color: var(--color-fd-muted-foreground); font-size: .8rem; } .agent-setup-noscript a, .agent-setup-manual a { color: var(--color-fd-primary); font-weight: 650; text-decoration: underline; text-underline-offset: .25rem; } .agent-setup-manual { margin-top: 1rem; color: var(--color-fd-muted-foreground); font-size: .82rem; text-align: center; } @@ -475,7 +441,6 @@ @media (max-width: 900px) { .hero-shell { min-height: auto; grid-template-columns: 1fr; padding-top: 6.5rem; } .hero-terminal { width: min(620px, 100%); transform: none; } - .agent-setup-panel { grid-template-columns: 1fr; } .agent-shell { grid-template-columns: 1fr; } .path-grid { grid-template-columns: 1fr; } .path-card { min-height: 14rem; } @@ -490,8 +455,7 @@ .button-tertiary { width: 100%; } .lifecycle-preview li { grid-template-columns: 1.5rem 3.5rem 1fr; padding-inline: .3rem; } .agent-grid { grid-template-columns: 1fr; } - .agent-setup-tabs { grid-template-columns: 1fr; } - .agent-setup-tabs > button { grid-template-columns: auto 1fr; } + .agent-setup-actions { grid-template-columns: 1fr; } .agent-card { grid-template-columns: auto 1fr auto; align-items: center; } .agent-card__mark { grid-column: auto; width: 4rem; aspect-ratio: 1; } .agent-card__mark img { width: 2.35rem; height: 2.35rem; } diff --git a/apps/docs/components/agent-setup-panel.tsx b/apps/docs/components/agent-setup-panel.tsx index 42bb1bd..6201de1 100644 --- a/apps/docs/components/agent-setup-panel.tsx +++ b/apps/docs/components/agent-setup-panel.tsx @@ -1,10 +1,9 @@ 'use client'; -import { ArrowRight, FileCheck2, ShieldCheck } from 'lucide-react'; +import { ArrowRight, Check, Copy } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; -import { useState, type KeyboardEvent } from 'react'; -import { CommandBlock } from '@/components/command-block'; +import { useState } from 'react'; import { agentClientIds, agentRecipes, @@ -12,21 +11,36 @@ import { } from '@/lib/agent-recipes'; export function AgentSetupPanel() { - const [selectedClient, setSelectedClient] = useState('codex'); + const [copyState, setCopyState] = useState<{ + client: AgentClientId | null; + status: 'idle' | 'copied' | 'error'; + }>({ client: null, status: 'idle' }); - function selectFromKeyboard(event: KeyboardEvent, client: AgentClientId) { - const currentIndex = agentClientIds.indexOf(client); - let nextIndex: number | undefined; - if (event.key === 'ArrowRight' || event.key === 'ArrowDown') nextIndex = (currentIndex + 1) % agentClientIds.length; - if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') nextIndex = (currentIndex - 1 + agentClientIds.length) % agentClientIds.length; - if (event.key === 'Home') nextIndex = 0; - if (event.key === 'End') nextIndex = agentClientIds.length - 1; - if (nextIndex === undefined) return; + async function copyInstallPrompt(client: AgentClientId) { + const prompt = agentRecipes[client].setupPrompt; + try { + await navigator.clipboard.writeText(prompt); + } catch { + try { + const fallback = document.createElement('textarea'); + fallback.value = prompt; + fallback.setAttribute('readonly', ''); + fallback.style.position = 'fixed'; + fallback.style.opacity = '0'; + document.body.append(fallback); + fallback.select(); + const copied = document.execCommand('copy'); + fallback.remove(); + if (!copied) throw new Error('Fallback copy was rejected'); + } catch { + setCopyState({ client, status: 'error' }); + window.setTimeout(() => setCopyState({ client: null, status: 'idle' }), 2400); + return; + } + } - event.preventDefault(); - const nextClient = agentClientIds[nextIndex]; - setSelectedClient(nextClient); - document.getElementById(`agent-tab-${nextClient}`)?.focus(); + setCopyState({ client, status: 'copied' }); + window.setTimeout(() => setCopyState({ client: null, status: 'idle' }), 1600); } return ( @@ -35,93 +49,52 @@ export function AgentSetupPanel() {

Set up with your agent

One prompt. Project-scoped by default.

- Pick your client and copy its complete setup contract. Your agent previews repository - changes, preserves existing configuration, verifies diagnostics, and stops before product work. + Choose your coding agent, copy the install prompt, and hand off setup in one click. -
+
{agentClientIds.map((client) => { const option = agentRecipes[client]; - const selected = selectedClient === client; + const currentStatus = copyState.client === client ? copyState.status : 'idle'; return ( ); })}
- {agentClientIds.map((client) => { - const recipe = agentRecipes[client]; - const selected = selectedClient === client; - return ( - - ); - })} +
+ + Full Agent Quickstart