From e793e0de9a6022b4ad3fc26ed67624d473fa7628 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:20:29 -0700 Subject: [PATCH 01/30] feat(docs): redesign landing page Rework the home page: bg-background-200 surface, human/agent install switcher, ai-studio-style hero typography, and a card-based "An agent is a directory" file browser (geistdocs CodeBlock with shiki highlighting, geistcn icons, optional-file affordances). Redesign the "Three layers" and "production agents" sections with vendored geistcn icons and platform-agnostic copy. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../[lang]/(home)/components/architecture.tsx | 303 ++++++---- .../[lang]/(home)/components/feature-card.tsx | 41 -- .../[lang]/(home)/components/feature-grid.tsx | 81 ++- .../(home)/components/file-tree-view.tsx | 104 ++++ .../[lang]/(home)/components/file-tree.tsx | 541 +++++------------- .../(home)/components/install-switcher.tsx | 52 ++ .../(home)/components/visuals/channels.tsx | 51 -- .../(home)/components/visuals/durability.tsx | 42 -- .../(home)/components/visuals/evals.tsx | 48 -- .../[lang]/(home)/components/visuals/hitl.tsx | 53 -- .../(home)/components/visuals/sandbox.tsx | 39 -- .../(home)/components/visuals/subagents.tsx | 56 -- apps/docs/app/[lang]/(home)/layout.tsx | 2 +- apps/docs/app/[lang]/(home)/page.tsx | 26 +- .../components/geistcn-icons/base-icon.tsx | 64 +++ .../geistcn-icons/icon-acronym-ts.tsx | 18 + .../components/geistcn-icons/icon-agents.tsx | 16 + .../geistcn-icons/icon-arrow-up-right.tsx | 18 + .../components/geistcn-icons/icon-clock.tsx | 16 + .../geistcn-icons/icon-file-text.tsx | 16 + .../geistcn-icons/icon-folder-open.tsx | 18 + .../components/geistcn-icons/icon-linked.tsx | 18 + .../components/geistcn-icons/icon-logs.tsx | 18 + .../components/geistcn-icons/icon-message.tsx | 18 + .../geistcn-icons/icon-plus-circle.tsx | 18 + .../components/geistcn-icons/icon-robot.tsx | 18 + .../components/geistcn-icons/icon-sandbox.tsx | 16 + .../geistcn-icons/icon-sparkles.tsx | 18 + .../components/geistcn-icons/icon-user.tsx | 16 + .../geistcn-icons/icon-workflow.tsx | 16 + .../components/geistcn-icons/icon-wrench.tsx | 16 + apps/docs/components/geistcn-icons/index.ts | 19 + 32 files changed, 899 insertions(+), 897 deletions(-) delete mode 100644 apps/docs/app/[lang]/(home)/components/feature-card.tsx create mode 100644 apps/docs/app/[lang]/(home)/components/file-tree-view.tsx create mode 100644 apps/docs/app/[lang]/(home)/components/install-switcher.tsx delete mode 100644 apps/docs/app/[lang]/(home)/components/visuals/channels.tsx delete mode 100644 apps/docs/app/[lang]/(home)/components/visuals/durability.tsx delete mode 100644 apps/docs/app/[lang]/(home)/components/visuals/evals.tsx delete mode 100644 apps/docs/app/[lang]/(home)/components/visuals/hitl.tsx delete mode 100644 apps/docs/app/[lang]/(home)/components/visuals/sandbox.tsx delete mode 100644 apps/docs/app/[lang]/(home)/components/visuals/subagents.tsx create mode 100644 apps/docs/components/geistcn-icons/base-icon.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-acronym-ts.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-agents.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-arrow-up-right.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-clock.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-file-text.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-folder-open.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-linked.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-logs.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-message.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-plus-circle.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-robot.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-sandbox.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-sparkles.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-user.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-workflow.tsx create mode 100644 apps/docs/components/geistcn-icons/icon-wrench.tsx create mode 100644 apps/docs/components/geistcn-icons/index.ts diff --git a/apps/docs/app/[lang]/(home)/components/architecture.tsx b/apps/docs/app/[lang]/(home)/components/architecture.tsx index ecd638e94..a7863cfc5 100644 --- a/apps/docs/app/[lang]/(home)/components/architecture.tsx +++ b/apps/docs/app/[lang]/(home)/components/architecture.tsx @@ -1,136 +1,209 @@ -"use client"; +import Link from "next/link"; +import type { JSX, ReactNode } from "react"; +import { + IconArrowUpRight, + IconLinked, + IconMessage, + IconSandbox, + IconSparkles, + IconWorkflow, + IconWrench, +} from "@/components/geistcn-icons"; -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; +// Runtime primitives shown in a row (desktop) / stacked (mobile) below the +// full-width Durable Workflow card. +const RUNTIME_ITEMS: { + icon: ReactNode; + title: string; + description: string; + href?: string; +}[] = [ + { + icon: , + title: "AI SDK", + description: "Model calls, streaming", + href: "https://ai-sdk.dev/", + }, + { + icon: , + title: "Sandbox SDK", + description: "Isolated execution", + href: "https://vercel.com/docs/sandbox/sdk-reference", + }, + { + icon: , + title: "Connection SDK", + description: "MCP/HTTP endpoints", + href: "/docs/connections/overview", + }, + { + icon: , + title: "Tools & Subagents", + description: "Functions, child agents", + }, +]; -export function ArchitectureDiagram() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.2 }); +const CHANNELS = [ + "Slack", + "Discord", + "Web Chat", + "Google Chat", + "Microsoft Teams", + "WhatsApp", + "API", + "Cron", + "Twilio", + "Linear", +]; + +function SectionLabel({ children }: { children: string }): JSX.Element { + return ( + + {children} + + ); +} + +function PrimitiveCard({ + icon, + title, + description, + href, +}: { + icon: ReactNode; + title: string; + description: string; + href?: string; +}): JSX.Element { + const body = ( + <> + {icon} +
+ {title} + {description} +
+ + ); + + if (!href) { + return
{body}
; + } + + return ( + + {body} + + + ); +} +// Subtle gradient hairline border around the outer Runtime / Channel cards. +function GradientBorder(): JSX.Element { return ( -
+
+ ); +} + +export function ArchitectureDiagram() { + return ( +
-

+

Three layers, cleanly separated

- The runtime owns durability and - state. The harness executes AI work. - The channel is where your agent gets - surfaced. + The runtime owns durability and state. The channel is where your agent gets surfaced.

- - {/* Left column: Harness above Runtime */} -
- {/* Harness */} - -
-
-
- Harness -
-
-
- Executes one unit of AI work per workflow step -
- - - {/* Runtime (contains workflow + primitives) */} - -
-
-
- Runtime -
-
-
+
+ {/* Runtime */} +
+ +
+ Runtime + Durable execution, state persistence, event streaming -
+ +
- {/* Durable Workflow */} -
- -
-
Durable Workflow
-
- Checkpointed steps, park between messages, resume on delivery -
-
-
+ } + title="Durable Workflow" + description="Checkpointed steps, park between messages, resume on delivery" + href="https://workflow-sdk.dev/worlds" + /> - {/* Primitives (inside runtime) */} -
-
-
- AI SDK -
-
Model calls, streaming
-
-
-
- Sandbox SDK -
-
Isolated execution
-
-
-
- Connection SDK -
-
MCP/HTTP endpoints
-
-
-
Tools & Subagents
-
Functions, child agents
-
-
- +
+ {RUNTIME_ITEMS.map((item) => ( + + ))} +
- {/* Channel (right side, stretches full height) */} - -
-
-
- Channel -
+ {/* Channel */} +
+ +
+ Channel + Where your agent gets surfaced
-
Where your agent gets surfaced
-
- {["Slack", "Web Chat", "API", "Cron"].map((ch) => ( -
- {ch} + + {/* lg:h-0 + lg:grow lets the card fill the runtime-driven column + height and clip overflowing channels instead of dictating it. */} + + +
+ Chat SDK +
+ {CHANNELS.map((channel) => ( + + {channel} + + ))}
- ))} -
- other channels
-
- - + + {/* Fade the clipped channels out toward the bottom of the card. */} +
+ +
+
); diff --git a/apps/docs/app/[lang]/(home)/components/feature-card.tsx b/apps/docs/app/[lang]/(home)/components/feature-card.tsx deleted file mode 100644 index e6be1a029..000000000 --- a/apps/docs/app/[lang]/(home)/components/feature-card.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { ArrowRight } from "lucide-react"; -import Link from "next/link"; -import type { ReactNode } from "react"; - -export function FeatureCard({ - title, - description, - icon, - visual, - href, -}: { - title: string; - description: string; - icon: ReactNode; - visual: ReactNode; - href: string; -}) { - return ( -
-
-
- {icon} -
-
-

{title}

-

{description}

-
-
-
{visual}
-
- - Learn more - - -
-
- ); -} diff --git a/apps/docs/app/[lang]/(home)/components/feature-grid.tsx b/apps/docs/app/[lang]/(home)/components/feature-grid.tsx index 4a88e4dad..54198d9d6 100644 --- a/apps/docs/app/[lang]/(home)/components/feature-grid.tsx +++ b/apps/docs/app/[lang]/(home)/components/feature-grid.tsx @@ -1,78 +1,75 @@ -import { Bot, Database, FlaskConical, MessageSquare, Shield, Terminal } from "lucide-react"; -import { FeatureCard } from "./feature-card"; -import { ChannelsVisual } from "./visuals/channels"; -import { DurabilityVisual } from "./visuals/durability"; -import { EvalsVisual } from "./visuals/evals"; -import { HITLVisual } from "./visuals/hitl"; -import { SandboxVisual } from "./visuals/sandbox"; -import { SubagentsVisual } from "./visuals/subagents"; +import type { JSX, ReactNode } from "react"; +import { + IconLogs, + IconMessage, + IconRobot, + IconSandbox, + IconUser, + IconWorkflow, +} from "@/components/geistcn-icons"; -const features = [ +const FEATURES: { icon: ReactNode; label: string; description: string }[] = [ { - title: "Durable Execution", + icon: , + label: "Durable execution", description: "Workflows survive crashes and restarts. Every step is checkpointed. Agents park when waiting, resume on the next message.", - icon: , - visual: , - href: "/docs/concepts/sessions-runs-and-streaming", }, { - title: "Sandboxed Compute", + icon: , + label: "Sandboxed compute", description: - "Agents spin up isolated VMs on demand. File system access, bash execution, and code runs, all completely isolated.", - icon: , - visual: , - href: "/docs/sandbox", + "Agents run code in isolated sandboxes. File system access, bash execution, and code, all fully isolated.", }, { - title: "Multi-Channel Delivery", + icon: , + label: "Multi-channel delivery", description: "One agent codebase deploys to web chat, Slack, API, cron, CLI, and custom apps.", - icon: , - visual: , - href: "/docs/channels/overview", }, { - title: "Human-in-the-Loop", + icon: , + label: "Human-in-the-loop", description: "Tools that need confirmation trigger approval gates. Sessions park until resolved, then resume seamlessly.", - icon: , - visual: , - href: "/docs/tools", }, { - title: "Subagents", + icon: , + label: "Subagents", description: "Delegate specialized work to child agents with their own prompts, tools, and sandbox.", - icon: , - visual: , - href: "/docs/subagents", }, { - title: "Evaluations", + icon: , + label: "Evaluations", description: "Define test suites with scoring rubrics. Run evals on every deployment and on a schedule.", - icon: , - visual: , - href: "/docs/evals/overview", }, ]; -export function FeatureGrid() { +export function FeatureGrid(): JSX.Element { return (
-

+

Everything you need for production agents

- Enterprise governance, observability, and sandboxed compute come standard. Focus on - building, not infrastructure. + Durability, sandboxing, human-in-the-loop, and evals are built into the framework. Focus + on building your agent.

-
- {features.map((feature) => ( - +
    + {FEATURES.map((feature) => ( +
  • +
    + {feature.icon} + + {feature.label} + +
    +

    {feature.description}

    +
  • ))} -
+
); diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx new file mode 100644 index 000000000..4362800b7 --- /dev/null +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { type ReactNode, useState } from "react"; +import { IconPlusCircle } from "@/components/geistcn-icons"; +import { cn } from "@/lib/utils"; + +export interface FileTreeItem { + name: string; + fileName: string; + /** File-type icon shown beside the file name in the code panel header. */ + icon: ReactNode; + /** Pre-highlighted code, rendered on the server through the geistdocs CodeBlock. */ + code: ReactNode; +} + +export function FileTreeView({ items }: { items: FileTreeItem[] }) { + const [selectedIndex, setSelectedIndex] = useState(0); + // The first file is "added" by default; every other file is optional and + // only counts as added once the user clicks it. + const [visited, setVisited] = useState>(() => new Set([0])); + const selected = items[selectedIndex]; + + function select(index: number) { + setSelectedIndex(index); + setVisited((prev) => new Set(prev).add(index)); + } + + return ( + // Full-width container so the header divider can bleed to the page frame's + // vertical borders, forming a cross with the layout grid. +
+ {/* Grid line aligned exactly with the card header's border-b. */} +
+
+
+
+ {/* Sidebar */} +
+
+ agent/ + + {visited.size} {visited.size === 1 ? "file" : "files"} + +
+
+ {items.map((item, i) => { + const added = visited.has(i); + return ( + + ); + })} +
+
+ + {/* Code panel */} +
+
+ {selected.icon} + {selected.fileName} + {selectedIndex > 0 ? ( + + Optional + + ) : null} +
+ {/* Re-keyed per file so the code subtly flies in on selection. */} +
+ {selected.code} +
+
+
+
+
+
+ ); +} diff --git a/apps/docs/app/[lang]/(home)/components/file-tree.tsx b/apps/docs/app/[lang]/(home)/components/file-tree.tsx index 53b5838d9..ec684ceaa 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree.tsx @@ -1,443 +1,206 @@ -"use client"; - -import type { LucideIcon } from "lucide-react"; +import { CodeBlock } from "@vercel/geistdocs/components/code-block"; +import { geistShikiTheme } from "@vercel/geistdocs/shiki-theme"; +import { highlight } from "fumadocs-core/highlight"; +import type { ComponentProps, JSX } from "react"; import { - Bot, - Clock, - FileText, - MessageSquare, - Plug, - Settings, - Terminal, - Wrench, -} from "lucide-react"; -import { motion, useInView } from "motion/react"; -import type { ReactNode } from "react"; -import { useRef, useState } from "react"; + IconAcronymTs, + IconAgents, + IconClock, + IconFileText, + IconLinked, + IconMessage, + type IconProps, + IconSandbox, + IconWrench, +} from "@/components/geistcn-icons"; +import { cn } from "@/lib/utils"; +import { FileTreeView } from "./file-tree-view"; -function Kw({ children }: { children: ReactNode }) { - return {children}; -} -function Str({ children }: { children: ReactNode }) { - return {children}; -} -function Fn({ children }: { children: ReactNode }) { - return {children}; -} -function Ty({ children }: { children: ReactNode }) { - return {children}; -} -function Pl({ children, className }: { children: ReactNode; className?: string }) { - return {children}; -} - -interface TreeItem { +interface Snippet { name: string; fileName: string; - icon: LucideIcon; - color: string; - description: string; - codeHighlighted: ReactNode; - indent: number; + lang: string; + Icon: (props: IconProps) => JSX.Element; + code: string; } -const treeItems: TreeItem[] = [ - { - name: "agent.ts", - fileName: "agent.ts", - icon: Settings, - color: "text-purple-600", - description: "Model and runtime configuration.", - codeHighlighted: ( - <> - import {"{ "} - defineAgent - {" }"} from {'"eve"'} - ; - {"\n"} - {"\n"} - export default defineAgent - {"({"} - {"\n"} - {" "} - model - : {'"openai/gpt-5.4-mini"'} - , - {"\n"} - {"});"} - - ), - indent: 1, - }, +const snippets: Snippet[] = [ { name: "instructions.md", fileName: "instructions.md", - icon: FileText, - color: "text-green-600", - description: "Always-on instructions. The agent's core identity.", - codeHighlighted: ( - <> - {"# Identity"} - {"\n"} - {"\n"} - You are an expert weather assistant. - {"\n"} - You can fetch the weather for any - {"\n"} - city in the world. - - ), - indent: 1, + lang: "markdown", + Icon: IconFileText, + code: `# Identity + +You are an expert weather assistant. +You can fetch the weather for any +city in the world.`, + }, + { + name: "agent.ts", + fileName: "agent.ts", + lang: "typescript", + Icon: IconAcronymTs, + code: `import { defineAgent } from "eve"; + +export default defineAgent({ + model: "openai/gpt-5.4-mini", +});`, }, { name: "skills/", fileName: "skills/research.md", - icon: FileText, - color: "text-amber-600", - description: "On-demand procedures loaded only when relevant.", - codeHighlighted: ( - <> - {"---"} - {"\n"} - name - : research - {"\n"} - description - : Research unfamiliar topics - {"\n"} - {"---"} - {"\n"} - {"\n"} - When the task is novel or ambiguous, - {"\n"} - gather evidence first, then answer. - - ), - indent: 1, + lang: "markdown", + Icon: IconFileText, + code: `--- +name: research +description: Research unfamiliar topics +--- + +When the task is novel or ambiguous, +gather evidence first, then answer.`, }, { name: "tools/", fileName: "tools/get_weather.ts", - icon: Wrench, - color: "text-orange-600", - description: "Typed integrations exposed to the model. File name becomes the tool name.", - codeHighlighted: ( - <> - import {"{ "} - defineTool - {" }"} from {'"eve/tools"'} - ; - {"\n"} - import z from {'"zod"'} - ; - {"\n"} - {"\n"} - export default defineTool - {"({"} - {"\n"} - {" "} - description - : {'"Get the weather for a city"'} - , - {"\n"} - {" "} - inputSchema - : z. - object - {"({"} - {"\n"} - {" "} - cityName - : z. - string - (), - {"\n"} - {" "} - {"}),"} - {"\n"} - {" "} - async execute - {"(input) {"} - {"\n"} - {" "} - const res = await fetch - ( - {"\n"} - {" "} - {"`${"} - process.env.WEATHER_API_URL - {"}/current?city=${"} - input.cityName - {"}`"} - {"\n"} - {" "} - ); - {"\n"} - {" "} - const data = await res. - json - (); - {"\n"} - {" "} - return data.current_condition[ - 0 - ]; - {"\n"} - {" "} - {"},"} - {"\n"} - {"});"} - - ), - indent: 1, + lang: "typescript", + Icon: IconWrench, + code: `import { defineTool } from "eve/tools"; +import z from "zod"; + +export default defineTool({ + description: "Get the weather for a city", + inputSchema: z.object({ + cityName: z.string(), + }), + async execute(input) { + const res = await fetch( + \`\${process.env.WEATHER_API_URL}/current?city=\${input.cityName}\` + ); + const data = await res.json(); + return data.current_condition[0]; + }, +});`, }, { name: "sandbox/", fileName: "sandbox/sandbox.ts", - icon: Terminal, - color: "text-red-600", - description: "Isolated compute environments with lifecycle hooks.", - codeHighlighted: ( - <> - import {"{ "} - defineSandbox - {" }"} from - {"\n"} - {" "} - {'"eve/sandbox"'} - ; - {"\n"} - {"\n"} - export default defineSandbox - {"({"} - {"\n"} - {" "} - async bootstrap - {"({ sandbox }) {"} - {"\n"} - {" "} - await sandbox. - run - ( - {"\n"} - {" "} - {'"git clone repo /workspace"'} - {"\n"} - {" "} - ); - {"\n"} - {" "} - {"},"} - {"\n"} - {"});"} - - ), - indent: 1, + lang: "typescript", + Icon: IconSandbox, + code: `import { defineSandbox } from + "eve/sandbox"; + +export default defineSandbox({ + async bootstrap({ sandbox }) { + await sandbox.run( + "git clone repo /workspace" + ); + }, +});`, }, { name: "channels/", fileName: "channels/slack.ts", - icon: MessageSquare, - color: "text-cyan-600", - description: "HTTP, Slack, and custom delivery surfaces.", - codeHighlighted: ( - <> - import {"{ "} - slackChannel - {" }"} from - {"\n"} - {" "} - {'"eve/channels/slack"'} - ; - {"\n"} - {"\n"} - export default slackChannel - {"({"} - {"\n"} - {" "} - botName - : {'"my-agent"'} - , - {"\n"} - {"});"} - - ), - indent: 1, + lang: "typescript", + Icon: IconMessage, + code: `import { slackChannel } from + "eve/channels/slack"; + +export default slackChannel({ + botName: "my-agent", +});`, }, { name: "connections/", fileName: "connections/linear.ts", - icon: Plug, - color: "text-pink-600", - description: "External MCP services.", - codeHighlighted: ( - <> - import {"{ "} - defineMcpClientConnection - {" }"} - {"\n"} - {" "} - from {'"eve/connections"'} - ; - {"\n"} - {"\n"} - export default defineMcpClientConnection - {"({"} - {"\n"} - {" "} - url - : {'"https://mcp.linear.app/mcp"'} - , - {"\n"} - {"});"} - - ), - indent: 1, + lang: "typescript", + Icon: IconLinked, + code: `import { defineMcpClientConnection } + from "eve/connections"; + +export default defineMcpClientConnection({ + url: "https://mcp.linear.app/mcp", +});`, }, { name: "subagents/", fileName: "subagents/researcher/agent.ts", - icon: Bot, - color: "text-indigo-600", - description: "Specialist child agents.", - codeHighlighted: ( - <> - import {"{ "} - defineAgent - {" }"} from - {"\n"} - {" "} - {'"eve"'} - ; - {"\n"} - {"\n"} - export default defineAgent - {"({"} - {"\n"} - {" "} - description - : {'"Investigate questions"'} - , - {"\n"} - {" "} - model - : {'"openai/gpt-5.4"'} - , - {"\n"} - {"});"} - - ), - indent: 1, + lang: "typescript", + Icon: IconAgents, + code: `import { defineAgent } from + "eve"; + +export default defineAgent({ + description: "Investigate questions", + model: "openai/gpt-5.4", +});`, }, { name: "schedules/", fileName: "schedules/daily-report.md", - icon: Clock, - color: "text-emerald-600", - description: "Recurring cron jobs.", - codeHighlighted: ( - <> - {"---"} - {"\n"} - cron - : {'"0 8 * * *"'} - {"\n"} - {"---"} - {"\n"} - {"\n"} - Send the user a daily weather - {"\n"} - digest for their saved cities. - - ), - indent: 1, + lang: "markdown", + Icon: IconClock, + code: `--- +cron: "0 8 * * *" +--- + +Send the user a daily weather +digest for their saved cities.`, }, ]; -const CODE_BOX_HEIGHT = 280; - -export function FileTree() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.3 }); - const [selectedIndex, setSelectedIndex] = useState(0); +export async function FileTree() { + const rendered = await Promise.all( + snippets.map((snippet) => + highlight(snippet.code, { + lang: snippet.lang, + theme: geistShikiTheme, + components: { + // Render the highlighted output through the geistdocs CodeBlock so the + // snippets match the docs (line numbers, copy button, geist theme), + // with its default border/background stripped to sit flush in the panel. + pre: (props: ComponentProps<"pre">) => ( + + ), + }, + }), + ), + ); - const selected = treeItems[selectedIndex]; + const items = snippets.map((snippet, i) => ({ + name: snippet.name, + fileName: snippet.fileName, + icon: , + code: rendered[i], + })); return ( -
+
-

+

An agent is a directory

Define instructions and skills in markdown, tools in TypeScript, and deploy. The framework compiles the directory, wires up durable workflows, and connects channels.

-
-
-
agent/
- {treeItems.map((item, i) => ( - setSelectedIndex(i)} - className={`flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors ${ - selectedIndex === i ? "bg-gray-100" : "hover:bg-gray-100/40" - }`} - style={{ paddingLeft: `${item.indent * 16 + 4}px` }} - > - - - {item.name} - - - ))} -
- -
-
- -
- - - {selected.fileName} - -
-

{selected.description}

-
-
- - -
-                {selected.codeHighlighted}
-              
-
-
-
+ +
); } diff --git a/apps/docs/app/[lang]/(home)/components/install-switcher.tsx b/apps/docs/app/[lang]/(home)/components/install-switcher.tsx new file mode 100644 index 000000000..d3a238019 --- /dev/null +++ b/apps/docs/app/[lang]/(home)/components/install-switcher.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { track } from "@vercel/analytics"; +import { + CommandPromptContent, + CommandPromptCopy, + CommandPromptList, + CommandPromptPrefix, + CommandPromptRoot, + CommandPromptSurface, + CommandPromptTrigger, + CommandPromptTriggerDivider, + CommandPromptViewport, +} from "@vercel/geistdocs/components/command-prompt"; +import type { JSX } from "react"; +import { cn } from "@/lib/utils"; + +const HUMAN_COMMAND = "npx eve@latest init my-agent"; +const AGENT_COMMAND = "npx skills add vercel/eve"; + +/** + * Hero install prompt that toggles between the human-facing `eve init` command + * and the agent-facing skills command, with a copy-to-clipboard pill. + */ +export const InstallSwitcher = ({ className }: { className?: string }): JSX.Element => ( + { + track("Selected installer command", { target: value }); + }} + > + + For humans + + For agents + + + + $ + + + {HUMAN_COMMAND} + + + {AGENT_COMMAND} + + + + + +); diff --git a/apps/docs/app/[lang]/(home)/components/visuals/channels.tsx b/apps/docs/app/[lang]/(home)/components/visuals/channels.tsx deleted file mode 100644 index bbc37e28e..000000000 --- a/apps/docs/app/[lang]/(home)/components/visuals/channels.tsx +++ /dev/null @@ -1,51 +0,0 @@ -"use client"; - -import { Bot, Clock, Globe, MessageSquare, Radio, Terminal } from "lucide-react"; -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; - -const targets = [ - { icon: Globe, label: "Web", color: "text-cyan-600" }, - { icon: MessageSquare, label: "Slack", color: "text-purple-600" }, - { icon: Terminal, label: "CLI", color: "text-green-600" }, - { icon: Radio, label: "API", color: "text-blue-600" }, - { icon: Clock, label: "Cron", color: "text-amber-600" }, -]; - -export function ChannelsVisual() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.5 }); - - return ( -
- - - - -
- {targets.map((t, i) => ( - -
- - {t.label} - - ))} -
-
- ); -} diff --git a/apps/docs/app/[lang]/(home)/components/visuals/durability.tsx b/apps/docs/app/[lang]/(home)/components/visuals/durability.tsx deleted file mode 100644 index 968b1dfd4..000000000 --- a/apps/docs/app/[lang]/(home)/components/visuals/durability.tsx +++ /dev/null @@ -1,42 +0,0 @@ -"use client"; - -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; - -const steps = [ - { id: "fetch", label: "Step 1: Fetch data", width: "60%", color: "bg-green-600" }, - { id: "process-1", label: "Step 2: Process", width: "45%", color: "bg-green-600" }, - { id: "crash", label: "Crash", width: "15%", color: "bg-red-600" }, - { id: "resume", label: "Resume", width: "10%", color: "bg-amber-600" }, - { id: "process-2", label: "Step 2: Process", width: "45%", color: "bg-green-600" }, - { id: "respond", label: "Step 3: Respond", width: "30%", color: "bg-green-600" }, -]; - -export function DurabilityVisual() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.5 }); - - return ( -
- {steps.map((step, i) => ( -
- - {step.label} - -
- -
-
- ))} -
- ); -} diff --git a/apps/docs/app/[lang]/(home)/components/visuals/evals.tsx b/apps/docs/app/[lang]/(home)/components/visuals/evals.tsx deleted file mode 100644 index 5310a46d4..000000000 --- a/apps/docs/app/[lang]/(home)/components/visuals/evals.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; - -const scores = [ - { label: "run.didNotFail", value: 100, color: "bg-green-600" }, - { label: "text.includes", value: 85, color: "bg-green-600" }, - { label: "run.usedTool", value: 92, color: "bg-green-600" }, - { label: "run.maxToolCalls", value: 100, color: "bg-green-600" }, -]; - -export function EvalsVisual() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.5 }); - - return ( -
- {scores.map((score, i) => ( -
- - {score.label} - -
- -
- - {score.value}% - -
- ))} -
- ); -} diff --git a/apps/docs/app/[lang]/(home)/components/visuals/hitl.tsx b/apps/docs/app/[lang]/(home)/components/visuals/hitl.tsx deleted file mode 100644 index eaa5162c0..000000000 --- a/apps/docs/app/[lang]/(home)/components/visuals/hitl.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; - -const steps = [ - { label: "Tool called", status: "done" as const }, - { label: "Approval required", status: "waiting" as const }, - { label: "User approved", status: "done" as const }, - { label: "Execution resumed", status: "done" as const }, -]; - -export function HITLVisual() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.5 }); - - return ( -
- {steps.map((step, i) => ( - -
- {step.label} - {step.status === "waiting" && ( - - parked - - )} - - ))} -
- ); -} diff --git a/apps/docs/app/[lang]/(home)/components/visuals/sandbox.tsx b/apps/docs/app/[lang]/(home)/components/visuals/sandbox.tsx deleted file mode 100644 index 441392b77..000000000 --- a/apps/docs/app/[lang]/(home)/components/visuals/sandbox.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client"; - -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; - -const lines = [ - { id: "cmd", prompt: "$ ", text: "sandbox.run('npm test')", color: "text-blue-600" }, - { id: "pass", prompt: "", text: "PASS src/agent.test.ts", color: "text-green-600" }, - { - id: "t1", - prompt: "", - text: " \u2713 handles weather query (42ms)", - color: "text-gray-900", - }, - { id: "t2", prompt: "", text: " \u2713 retries on failure (118ms)", color: "text-gray-900" }, - { id: "total", prompt: "", text: "Tests: 2 passed, 2 total", color: "text-green-600" }, -]; - -export function SandboxVisual() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.5 }); - - return ( -
- {lines.map((line, i) => ( - - {line.prompt && {line.prompt}} - {line.text} - - ))} -
- ); -} diff --git a/apps/docs/app/[lang]/(home)/components/visuals/subagents.tsx b/apps/docs/app/[lang]/(home)/components/visuals/subagents.tsx deleted file mode 100644 index a2f7fbfaa..000000000 --- a/apps/docs/app/[lang]/(home)/components/visuals/subagents.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client"; - -import { Bot, Code, Database, Search } from "lucide-react"; -import { motion, useInView } from "motion/react"; -import { useRef } from "react"; - -const agents = [ - { icon: Search, label: "Researcher", color: "text-purple-600" }, - { icon: Code, label: "Coder", color: "text-blue-600" }, - { icon: Database, label: "Analyst", color: "text-green-600" }, -]; - -export function SubagentsVisual() { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, amount: 0.5 }); - - return ( -
- - - Parent Agent - - - - -
- {agents.map((agent, i) => ( - - - {agent.label} - - ))} -
-
- ); -} diff --git a/apps/docs/app/[lang]/(home)/layout.tsx b/apps/docs/app/[lang]/(home)/layout.tsx index 1e1c5298e..6d43d8706 100644 --- a/apps/docs/app/[lang]/(home)/layout.tsx +++ b/apps/docs/app/[lang]/(home)/layout.tsx @@ -7,7 +7,7 @@ const Layout = async ({ children, params }: LayoutProps<"/[lang]">) => { return ( -
{children}
+
{children}
); }; diff --git a/apps/docs/app/[lang]/(home)/page.tsx b/apps/docs/app/[lang]/(home)/page.tsx index 16dc6536e..b8563c8e8 100644 --- a/apps/docs/app/[lang]/(home)/page.tsx +++ b/apps/docs/app/[lang]/(home)/page.tsx @@ -1,17 +1,15 @@ import type { Metadata } from "next"; -import Link from "next/link"; -import { Installer } from "@/components/geistdocs/installer"; import { staticOgImage } from "@/lib/geistdocs/og"; import { ArchitectureDiagram } from "./components/architecture"; import { CTA } from "./components/cta"; import { FeatureGrid } from "./components/feature-grid"; import { FileTree } from "./components/file-tree"; +import { InstallSwitcher } from "./components/install-switcher"; const title = "eve"; const tagline = "Like Next.js for web apps, but for agents."; const description = "Markdown for instructions and skills, TypeScript for tools. Durable by default."; -const betaAgreementHref = "https://vercel.com/docs/release-phases/public-beta-agreement"; export const metadata: Metadata = { title, @@ -31,29 +29,17 @@ export const metadata: Metadata = { const HomePage = () => (
-
- - Beta - -

+
+

The Framework
for Building Agents

-

+

{tagline} {description}

-
- - - Read the Docs - +
+
diff --git a/apps/docs/components/geistcn-icons/base-icon.tsx b/apps/docs/components/geistcn-icons/base-icon.tsx new file mode 100644 index 000000000..f6ac4569d --- /dev/null +++ b/apps/docs/components/geistcn-icons/base-icon.tsx @@ -0,0 +1,64 @@ +import { type CSSProperties, memo, type ReactNode, type SVGProps } from "react"; + +/** Flexible size format: a number, or a numeric string with optional "px" suffix. */ +export type IconSize = number | `${number}` | `${number}px`; + +/** `currentColor` (inherit) or a design-system color token resolved to `--ds-`. */ +export type IconColor = string; + +type ReservedSVGProps = "color" | "size" | "width" | "height" | "fill" | "viewBox"; + +/** Consumer-facing props shared by every geistcn icon. */ +export interface IconProps extends Omit, ReservedSVGProps> { + color?: IconColor; + size?: IconSize; + style?: Omit; + className?: string; +} + +interface GeneratedIconProps { + height: number; + aspectRatio: number; + viewBox: string; +} + +interface BaseIconProps extends IconProps { + $generated: GeneratedIconProps; + children: ReactNode; +} + +function parseIconSize(size: IconSize): number { + return typeof size === "number" ? size : Number(size.replace("px", "")); +} + +/** + * Shared renderer for the hard-copied geistcn icons. `size` controls height and + * width is derived from the icon's aspect ratio to preserve proportions. + */ +export const BaseIcon = memo(function BaseIcon({ + $generated, + size, + style, + color = "currentColor", + children, + ...props +}: BaseIconProps) { + const height = size !== undefined ? parseIconSize(size) : $generated.height; + const width = height * $generated.aspectRatio; + + return ( + + {children} + + ); +}); diff --git a/apps/docs/components/geistcn-icons/icon-acronym-ts.tsx b/apps/docs/components/geistcn-icons/icon-acronym-ts.tsx new file mode 100644 index 000000000..6cbed5286 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-acronym-ts.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconAcronymTs from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconAcronymTs(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-agents.tsx b/apps/docs/components/geistcn-icons/icon-agents.tsx new file mode 100644 index 000000000..3a9ebe448 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-agents.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconAgents from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconAgents(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-arrow-up-right.tsx b/apps/docs/components/geistcn-icons/icon-arrow-up-right.tsx new file mode 100644 index 000000000..0acb35d29 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-arrow-up-right.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconArrowUpRight from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconArrowUpRight(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-clock.tsx b/apps/docs/components/geistcn-icons/icon-clock.tsx new file mode 100644 index 000000000..e17397b72 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-clock.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconClock from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconClock(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-file-text.tsx b/apps/docs/components/geistcn-icons/icon-file-text.tsx new file mode 100644 index 000000000..693562fe8 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-file-text.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconFileText from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconFileText(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-folder-open.tsx b/apps/docs/components/geistcn-icons/icon-folder-open.tsx new file mode 100644 index 000000000..ab0623165 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-folder-open.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconFolderOpen from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconFolderOpen(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-linked.tsx b/apps/docs/components/geistcn-icons/icon-linked.tsx new file mode 100644 index 000000000..2b74cb5c8 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-linked.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconLinked from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconLinked(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-logs.tsx b/apps/docs/components/geistcn-icons/icon-logs.tsx new file mode 100644 index 000000000..736793d6b --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-logs.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconLogs from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconLogs(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-message.tsx b/apps/docs/components/geistcn-icons/icon-message.tsx new file mode 100644 index 000000000..232fae210 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-message.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconMessage from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconMessage(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-plus-circle.tsx b/apps/docs/components/geistcn-icons/icon-plus-circle.tsx new file mode 100644 index 000000000..c448d6f38 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-plus-circle.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconPlusCircle from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconPlusCircle(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-robot.tsx b/apps/docs/components/geistcn-icons/icon-robot.tsx new file mode 100644 index 000000000..01a97d09b --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-robot.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconRobot from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconRobot(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-sandbox.tsx b/apps/docs/components/geistcn-icons/icon-sandbox.tsx new file mode 100644 index 000000000..600d35219 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-sandbox.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconSandbox from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconSandbox(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-sparkles.tsx b/apps/docs/components/geistcn-icons/icon-sparkles.tsx new file mode 100644 index 000000000..53c7ed736 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-sparkles.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconSparkles from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconSparkles(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-user.tsx b/apps/docs/components/geistcn-icons/icon-user.tsx new file mode 100644 index 000000000..226972ddc --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-user.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconUser from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconUser(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-workflow.tsx b/apps/docs/components/geistcn-icons/icon-workflow.tsx new file mode 100644 index 000000000..498984c74 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-workflow.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconWorkflow from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconWorkflow(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/icon-wrench.tsx b/apps/docs/components/geistcn-icons/icon-wrench.tsx new file mode 100644 index 000000000..e8de86819 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-wrench.tsx @@ -0,0 +1,16 @@ +// Hard copy of IconWrench from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconWrench(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/index.ts b/apps/docs/components/geistcn-icons/index.ts new file mode 100644 index 000000000..06212e229 --- /dev/null +++ b/apps/docs/components/geistcn-icons/index.ts @@ -0,0 +1,19 @@ +// Hard copies of the geistcn icons used by the home page sections, +// vendored from @vercel/geistcn-assets so the docs app has no dependency on it. +export type { IconColor, IconProps, IconSize } from "./base-icon"; +export { IconAcronymTs } from "./icon-acronym-ts"; +export { IconAgents } from "./icon-agents"; +export { IconArrowUpRight } from "./icon-arrow-up-right"; +export { IconClock } from "./icon-clock"; +export { IconFileText } from "./icon-file-text"; +export { IconFolderOpen } from "./icon-folder-open"; +export { IconLinked } from "./icon-linked"; +export { IconLogs } from "./icon-logs"; +export { IconMessage } from "./icon-message"; +export { IconPlusCircle } from "./icon-plus-circle"; +export { IconRobot } from "./icon-robot"; +export { IconSandbox } from "./icon-sandbox"; +export { IconSparkles } from "./icon-sparkles"; +export { IconUser } from "./icon-user"; +export { IconWorkflow } from "./icon-workflow"; +export { IconWrench } from "./icon-wrench"; From 880b5c611b1ecce6d5cd1174c0e916cc4b02c03e Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:24:46 -0700 Subject: [PATCH 02/30] fix(docs): pass children explicitly to CodeBlock pre override The shiki `pre` override spread ComponentProps<"pre"> (children optional) into CodeBlock, whose children is required. Destructure children and pass it as a JSX child so the type is satisfied. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree.tsx b/apps/docs/app/[lang]/(home)/components/file-tree.tsx index ec684ceaa..1151234b5 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree.tsx @@ -159,7 +159,7 @@ export async function FileTree() { // Render the highlighted output through the geistdocs CodeBlock so the // snippets match the docs (line numbers, copy button, geist theme), // with its default border/background stripped to sit flush in the panel. - pre: (props: ComponentProps<"pre">) => ( + pre: ({ children, ...props }: ComponentProps<"pre">) => ( + > + {children} + ), }, }), From 4e5f7b0c62eed2edc7edf9b238e2670442102cbd Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:35:21 -0700 Subject: [PATCH 03/30] feat(docs): add /home alias for the landing page Extract the landing page into a shared HomeContent component and expose it at /home (in addition to /), so the preview is reachable while the root domain is still forwarded to vercel.com/eve. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../[lang]/(home)/components/home-content.tsx | 52 ++++++++++++++++++ apps/docs/app/[lang]/(home)/home/page.tsx | 9 ++++ apps/docs/app/[lang]/(home)/page.tsx | 53 ++----------------- 3 files changed, 64 insertions(+), 50 deletions(-) create mode 100644 apps/docs/app/[lang]/(home)/components/home-content.tsx create mode 100644 apps/docs/app/[lang]/(home)/home/page.tsx diff --git a/apps/docs/app/[lang]/(home)/components/home-content.tsx b/apps/docs/app/[lang]/(home)/components/home-content.tsx new file mode 100644 index 000000000..e29f27251 --- /dev/null +++ b/apps/docs/app/[lang]/(home)/components/home-content.tsx @@ -0,0 +1,52 @@ +import type { Metadata } from "next"; +import { staticOgImage } from "@/lib/geistdocs/og"; +import { ArchitectureDiagram } from "./architecture"; +import { CTA } from "./cta"; +import { FeatureGrid } from "./feature-grid"; +import { FileTree } from "./file-tree"; +import { InstallSwitcher } from "./install-switcher"; + +const title = "eve"; +const tagline = "Like Next.js for web apps, but for agents."; +const description = + "Markdown for instructions and skills, TypeScript for tools. Durable by default."; + +export const homeMetadata: Metadata = { + title, + description: `${tagline} ${description}`, + openGraph: { + title, + description: `${tagline} ${description}`, + images: [staticOgImage], + }, + twitter: { + card: "summary_large_image", + title, + description: `${tagline} ${description}`, + images: [staticOgImage], + }, +}; + +export const HomeContent = () => ( +
+
+

+ The Framework +
+ for Building Agents +

+

+ {tagline} {description} +

+
+ +
+
+
+ + + + +
+
+); diff --git a/apps/docs/app/[lang]/(home)/home/page.tsx b/apps/docs/app/[lang]/(home)/home/page.tsx new file mode 100644 index 000000000..11a49dcd8 --- /dev/null +++ b/apps/docs/app/[lang]/(home)/home/page.tsx @@ -0,0 +1,9 @@ +import { HomeContent, homeMetadata } from "../components/home-content"; + +// Temporary alias of the landing page so the preview is reachable while `/` +// is being forwarded at the domain level. +export const metadata = homeMetadata; + +const HomeAliasPage = () => ; + +export default HomeAliasPage; diff --git a/apps/docs/app/[lang]/(home)/page.tsx b/apps/docs/app/[lang]/(home)/page.tsx index b8563c8e8..899a1e1b1 100644 --- a/apps/docs/app/[lang]/(home)/page.tsx +++ b/apps/docs/app/[lang]/(home)/page.tsx @@ -1,54 +1,7 @@ -import type { Metadata } from "next"; -import { staticOgImage } from "@/lib/geistdocs/og"; -import { ArchitectureDiagram } from "./components/architecture"; -import { CTA } from "./components/cta"; -import { FeatureGrid } from "./components/feature-grid"; -import { FileTree } from "./components/file-tree"; -import { InstallSwitcher } from "./components/install-switcher"; +import { HomeContent, homeMetadata } from "./components/home-content"; -const title = "eve"; -const tagline = "Like Next.js for web apps, but for agents."; -const description = - "Markdown for instructions and skills, TypeScript for tools. Durable by default."; +export const metadata = homeMetadata; -export const metadata: Metadata = { - title, - description: `${tagline} ${description}`, - openGraph: { - title, - description: `${tagline} ${description}`, - images: [staticOgImage], - }, - twitter: { - card: "summary_large_image", - title, - description: `${tagline} ${description}`, - images: [staticOgImage], - }, -}; - -const HomePage = () => ( -
-
-

- The Framework -
- for Building Agents -

-

- {tagline} {description} -

-
- -
-
-
- - - - -
-
-); +const HomePage = () => ; export default HomePage; From 9325740ce9acec2f9e09c015c629b8024312391c Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:50:13 -0700 Subject: [PATCH 04/30] feat(docs): tint footer to background-200 on home routes The global footer is shared across all routes, so scope the tint with a [data-home-route] marker (rendered only by the home layout) and a `body:has(...)` rule, leaving docs/other routes unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/layout.tsx | 3 +++ apps/docs/app/styles/geistdocs.css | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/apps/docs/app/[lang]/(home)/layout.tsx b/apps/docs/app/[lang]/(home)/layout.tsx index 6d43d8706..8f8717528 100644 --- a/apps/docs/app/[lang]/(home)/layout.tsx +++ b/apps/docs/app/[lang]/(home)/layout.tsx @@ -7,6 +7,9 @@ const Layout = async ({ children, params }: LayoutProps<"/[lang]">) => { return ( + {/* Marker so the global footer can be tinted on home routes only, + via the `body:has([data-home-route]) footer` rule in geistdocs.css. */} +

+ diff --git a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx new file mode 100644 index 000000000..3065277e5 --- /dev/null +++ b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx @@ -0,0 +1,101 @@ +import { CodeBlock } from "@vercel/geistdocs/components/code-block"; +import { geistShikiTheme } from "@vercel/geistdocs/shiki-theme"; +import { highlight } from "fumadocs-core/highlight"; +import type { ComponentProps } from "react"; +import { IconAcronymTs } from "@/components/geistcn-icons"; +import { cn } from "@/lib/utils"; + +interface InteropFile { + fileName: string; + lang: string; + code: string; +} + +const FILES: InteropFile[] = [ + { + fileName: "next.config.ts", + lang: "typescript", + code: `import { withEve } from "eve/next"; + +const nextConfig = {}; + +// Agent + app: one dev server, one deploy. +export default withEve(nextConfig);`, + }, + { + fileName: "app/chat.tsx", + lang: "tsx", + code: `"use client"; +import { useEveAgent } from "eve/react"; + +export function Chat() { + // Same-origin routes, found automatically. + const agent = useEveAgent(); + // agent.messages, agent.sendMessage, ... +}`, + }, +]; + +const BENEFITS = ["One dev server", "Same-origin, no CORS", "One deploy"]; + +async function renderCode(file: InteropFile) { + return highlight(file.code, { + lang: file.lang, + theme: geistShikiTheme, + components: { + pre: ({ children, ...props }: ComponentProps<"pre">) => ( + + {children} + + ), + }, + }); +} + +export async function NextjsInterop() { + const rendered = await Promise.all(FILES.map(renderCode)); + + return ( +
+
+

+ Runs inside your Next.js app +

+

+ Wrap your config with withEve() and the agent runs + alongside your app. useEveAgent() finds the + mounted routes on its own — no CORS to configure and no URL env vars to keep in sync. +

+ +
+ {FILES.map((file, i) => ( +
+
+ + {file.fileName} +
+
{rendered[i]}
+
+ ))} +
+ +
+ {BENEFITS.map((benefit) => ( + + {benefit} + + ))} +
+
+
+ ); +} diff --git a/apps/docs/next-env.d.ts b/apps/docs/next-env.d.ts index 9edff1c7c..c4b7818fb 100644 --- a/apps/docs/next-env.d.ts +++ b/apps/docs/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. From c398c615a611f23c751041b4aa4262e33fc4047e Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:34:09 -0700 Subject: [PATCH 06/30] refactor(docs): move Next.js interop section after architecture Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/home-content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/home-content.tsx b/apps/docs/app/[lang]/(home)/components/home-content.tsx index 86c6e6100..f62a19138 100644 --- a/apps/docs/app/[lang]/(home)/components/home-content.tsx +++ b/apps/docs/app/[lang]/(home)/components/home-content.tsx @@ -45,8 +45,8 @@ export const HomeContent = () => (
- +
From 06701e7bd15a0ef85e26ef432439bc0dbf1b243f Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:41:17 -0700 Subject: [PATCH 07/30] feat(docs): fade the file-tree card's lower edge into its background Add a full-width bottom gradient over the whole "agent is a directory" card so the lower sidebar entries and code dissolve into the background. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree-view.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 4362800b7..fe7870feb 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -97,6 +97,11 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) {
+ {/* Dissolve the lower edge of the card into its background. */} +
From 4dde3c63692204279cd45c85e64685314c0dd3fc Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:19:26 -0700 Subject: [PATCH 08/30] feat(docs): style Next.js interop benefits as geistdocs badges Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 20 ++++++++++--------- .../(home)/components/nextjs-interop.tsx | 10 ++++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index fe7870feb..66c01b2ad 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -35,8 +35,8 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { className="pointer-events-none absolute top-12 -left-4 -right-4 border-t sm:-left-12 sm:-right-12" />
-
-
+
+
{/* Sidebar */}
@@ -59,7 +59,10 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { )} > {item.name} @@ -67,7 +70,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { ) : null} @@ -97,13 +100,12 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) {
- {/* Dissolve the lower edge of the card into its background. */} -
+
); } diff --git a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx index 3065277e5..40f7ba632 100644 --- a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx +++ b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@vercel/geistdocs/components/badge"; import { CodeBlock } from "@vercel/geistdocs/components/code-block"; import { geistShikiTheme } from "@vercel/geistdocs/shiki-theme"; import { highlight } from "fumadocs-core/highlight"; @@ -85,14 +86,15 @@ export async function NextjsInterop() { ))}
-
+
{BENEFITS.map((benefit) => ( - {benefit} - + ))}
From 1c5aeae3376a0e79aab689983c05d5bb094360c5 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:43:31 -0700 Subject: [PATCH 09/30] style(docs): use sans title-case badge labels for Next.js interop Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx index 40f7ba632..be4170470 100644 --- a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx +++ b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx @@ -37,7 +37,7 @@ export function Chat() { }, ]; -const BENEFITS = ["One dev server", "Same-origin, no CORS", "One deploy"]; +const BENEFITS = ["One Dev Server", "Same-Origin, No CORS", "One Deploy"]; async function renderCode(file: InteropFile) { return highlight(file.code, { @@ -88,11 +88,7 @@ export async function NextjsInterop() {
{BENEFITS.map((benefit) => ( - + {benefit} ))} From bbb93e31990097a747f1d2fe9ccc3e96e5985b85 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:47:09 -0700 Subject: [PATCH 10/30] feat(docs): add reset control to the file-tree directory card A subtle reset button in the sidebar header clears all added files back to the default (instructions.md), shown only once extra files are selected. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../[lang]/(home)/components/file-tree-view.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 66c01b2ad..04de093e0 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -1,5 +1,6 @@ "use client"; +import { RotateCcw } from "lucide-react"; import { type ReactNode, useState } from "react"; import { IconPlusCircle } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; @@ -25,6 +26,11 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { setVisited((prev) => new Set(prev).add(index)); } + function reset() { + setSelectedIndex(0); + setVisited(new Set([0])); + } + return ( // Full-width container so the header divider can bleed to the page frame's // vertical borders, forming a cross with the layout grid. @@ -44,6 +50,17 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { {visited.size} {visited.size === 1 ? "file" : "files"} + {visited.size > 1 ? ( + + ) : null}
{items.map((item, i) => { From 618ae6396ce4be518d63e28b3ca5e82dff8744b4 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:48:33 -0700 Subject: [PATCH 11/30] feat(docs): use geistcn trash icon for the file-tree reset button Hard-copy IconTrash from @vercel/geistcn-assets and use it for the reset control instead of the lucide icon. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 5 ++--- .../components/geistcn-icons/icon-trash.tsx | 18 ++++++++++++++++++ apps/docs/components/geistcn-icons/index.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 apps/docs/components/geistcn-icons/icon-trash.tsx diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 04de093e0..87cee6ccf 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -1,8 +1,7 @@ "use client"; -import { RotateCcw } from "lucide-react"; import { type ReactNode, useState } from "react"; -import { IconPlusCircle } from "@/components/geistcn-icons"; +import { IconPlusCircle, IconTrash } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; export interface FileTreeItem { @@ -58,7 +57,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { title="Reset" className="cursor-pointer text-gray-600 transition-colors hover:text-gray-1000" > - + ) : null}
diff --git a/apps/docs/components/geistcn-icons/icon-trash.tsx b/apps/docs/components/geistcn-icons/icon-trash.tsx new file mode 100644 index 000000000..30a880c6a --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-trash.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconTrash from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconTrash(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/index.ts b/apps/docs/components/geistcn-icons/index.ts index 06212e229..b5d2e2552 100644 --- a/apps/docs/components/geistcn-icons/index.ts +++ b/apps/docs/components/geistcn-icons/index.ts @@ -14,6 +14,7 @@ export { IconPlusCircle } from "./icon-plus-circle"; export { IconRobot } from "./icon-robot"; export { IconSandbox } from "./icon-sandbox"; export { IconSparkles } from "./icon-sparkles"; +export { IconTrash } from "./icon-trash"; export { IconUser } from "./icon-user"; export { IconWorkflow } from "./icon-workflow"; export { IconWrench } from "./icon-wrench"; From c83664a7894f993673ed35ea891d8821db08fa18 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:50:06 -0700 Subject: [PATCH 12/30] feat(docs): always show file-tree reset, disabled at the default file Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 87cee6ccf..ea97f5c2b 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -49,17 +49,21 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { {visited.size} {visited.size === 1 ? "file" : "files"} - {visited.size > 1 ? ( - - ) : null} +
{items.map((item, i) => { From 06a916ca23b1b68a5d25bad52c701aa8af890bad Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:50:53 -0700 Subject: [PATCH 13/30] style(docs): use text-gray-900 for the enabled reset button Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index ea97f5c2b..3d7124b4e 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -58,7 +58,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { className={cn( "transition-colors", visited.size > 1 - ? "cursor-pointer text-gray-600 hover:text-gray-1000" + ? "cursor-pointer text-gray-900 hover:text-gray-1000" : "cursor-not-allowed text-gray-400", )} > From 418caf0d54dbff2ae21b9c23ae01b180025cf5eb Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:54:34 -0700 Subject: [PATCH 14/30] feat(docs): wrap Next.js code blocks in one gradient-bordered frame Extract GradientBorder into a shared component and wrap both interop code cards in a single gradient-bordered frame (matching the architecture cards), instead of a border per card. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../[lang]/(home)/components/architecture.tsx | 19 +----------- .../(home)/components/gradient-border.tsx | 23 +++++++++++++++ .../(home)/components/nextjs-interop.tsx | 29 +++++++++++-------- 3 files changed, 41 insertions(+), 30 deletions(-) create mode 100644 apps/docs/app/[lang]/(home)/components/gradient-border.tsx diff --git a/apps/docs/app/[lang]/(home)/components/architecture.tsx b/apps/docs/app/[lang]/(home)/components/architecture.tsx index a7863cfc5..04397f227 100644 --- a/apps/docs/app/[lang]/(home)/components/architecture.tsx +++ b/apps/docs/app/[lang]/(home)/components/architecture.tsx @@ -9,6 +9,7 @@ import { IconWorkflow, IconWrench, } from "@/components/geistcn-icons"; +import { GradientBorder } from "./gradient-border"; // Runtime primitives shown in a row (desktop) / stacked (mobile) below the // full-width Durable Workflow card. @@ -105,24 +106,6 @@ function PrimitiveCard({ ); } -// Subtle gradient hairline border around the outer Runtime / Channel cards. -function GradientBorder(): JSX.Element { - return ( -
- ); -} - export function ArchitectureDiagram() { return (
diff --git a/apps/docs/app/[lang]/(home)/components/gradient-border.tsx b/apps/docs/app/[lang]/(home)/components/gradient-border.tsx new file mode 100644 index 000000000..199fe2582 --- /dev/null +++ b/apps/docs/app/[lang]/(home)/components/gradient-border.tsx @@ -0,0 +1,23 @@ +import type { JSX } from "react"; + +/** + * Subtle gradient hairline border — strongest at the top, fading to transparent + * toward the bottom. Render inside a `relative rounded-*` element; it inherits + * the parent's corner radius and paints a 1px masked ring at the edge. + */ +export function GradientBorder(): JSX.Element { + return ( +
+ ); +} diff --git a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx index be4170470..bf2199770 100644 --- a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx +++ b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx @@ -5,6 +5,7 @@ import { highlight } from "fumadocs-core/highlight"; import type { ComponentProps } from "react"; import { IconAcronymTs } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; +import { GradientBorder } from "./gradient-border"; interface InteropFile { fileName: string; @@ -71,19 +72,23 @@ export async function NextjsInterop() { mounted routes on its own — no CORS to configure and no URL env vars to keep in sync.

-
- {FILES.map((file, i) => ( -
-
- - {file.fileName} + {/* A single gradient-bordered frame around both code blocks. */} +
+ +
+ {FILES.map((file, i) => ( +
+
+ + {file.fileName} +
+
{rendered[i]}
-
{rendered[i]}
-
- ))} + ))} +
From 32c973b7eb8f31d169a0dfb33bd38f4dfcc909b4 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:56:13 -0700 Subject: [PATCH 15/30] style(docs): use material-small shadow on Next.js code cards Match the architecture inner cards' subtle shadow border instead of a plain border. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx index bf2199770..8d9ec587d 100644 --- a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx +++ b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx @@ -77,10 +77,7 @@ export async function NextjsInterop() {
{FILES.map((file, i) => ( -
+
{file.fileName} From 4b29ecb5f2d3c3c11edf47e3db8fa8c324ac750f Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:57:06 -0700 Subject: [PATCH 16/30] style(docs): make the disabled reset button more visible (gray-600) Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 3d7124b4e..cf6415f68 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -59,7 +59,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { "transition-colors", visited.size > 1 ? "cursor-pointer text-gray-900 hover:text-gray-1000" - : "cursor-not-allowed text-gray-400", + : "cursor-not-allowed text-gray-600", )} > From 568f879293450b4175c660b8e980e18027f56ad5 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:02:22 -0700 Subject: [PATCH 17/30] feat(docs): show a check icon on added files in the directory card Hard-copy IconCheck from @vercel/geistcn-assets; added optional files now show a check where the plus-circle hover affordance sits. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 12 +++++++----- .../app/[lang]/(home)/components/file-tree.tsx | 6 ------ .../components/geistcn-icons/icon-check.tsx | 18 ++++++++++++++++++ apps/docs/components/geistcn-icons/index.ts | 1 + 4 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 apps/docs/components/geistcn-icons/icon-check.tsx diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index cf6415f68..823538c95 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -1,7 +1,7 @@ "use client"; import { type ReactNode, useState } from "react"; -import { IconPlusCircle, IconTrash } from "@/components/geistcn-icons"; +import { IconCheck, IconPlusCircle, IconTrash } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; export interface FileTreeItem { @@ -41,9 +41,9 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { />
-
+
{/* Sidebar */} -
+
agent/ @@ -86,7 +86,9 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { > {item.name} - {i > 0 && !added ? ( + {i > 0 && added ? ( + + ) : i > 0 ? (
- {selected.icon} + {/* {selected.icon} */} {selected.fileName} {selectedIndex > 0 ? ( diff --git a/apps/docs/app/[lang]/(home)/components/file-tree.tsx b/apps/docs/app/[lang]/(home)/components/file-tree.tsx index 1151234b5..87b146713 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree.tsx @@ -165,13 +165,7 @@ export async function FileTree() { data-line-numbers="true" className={cn( props.className, - // No pre padding: each `.line` already carries a 16px inset - // (geistdocs), which matches the header's px-4 so the gutter - // aligns with the header icon. "rounded-none border-0 bg-transparent px-0 py-4", - // Left-align the line-number gutter so the numbers sit flush - // under the header icon instead of floating right-aligned. - // `!` overrides the more specific geistdocs `.line::before` rule. "[&_.line]:before:!mr-4 [&_.line]:before:!w-5 [&_.line]:before:!text-left", )} > diff --git a/apps/docs/components/geistcn-icons/icon-check.tsx b/apps/docs/components/geistcn-icons/icon-check.tsx new file mode 100644 index 000000000..094ecef74 --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-check.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconCheck from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconCheck(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/index.ts b/apps/docs/components/geistcn-icons/index.ts index b5d2e2552..c0378fe17 100644 --- a/apps/docs/components/geistcn-icons/index.ts +++ b/apps/docs/components/geistcn-icons/index.ts @@ -4,6 +4,7 @@ export type { IconColor, IconProps, IconSize } from "./base-icon"; export { IconAcronymTs } from "./icon-acronym-ts"; export { IconAgents } from "./icon-agents"; export { IconArrowUpRight } from "./icon-arrow-up-right"; +export { IconCheck } from "./icon-check"; export { IconClock } from "./icon-clock"; export { IconFileText } from "./icon-file-text"; export { IconFolderOpen } from "./icon-folder-open"; From 3f974f2c6faa78b2b5293df07b0866c7690a5e39 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:04:20 -0700 Subject: [PATCH 18/30] chore(docs): tidy file-tree directory card markup Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 823538c95..862950287 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -43,7 +43,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) {
{/* Sidebar */} -
+
agent/ From 09ac9ed90558200db109054b7d94236c6de81c8b Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:09:05 -0700 Subject: [PATCH 19/30] style(docs): drop the TS badge from Next.js code card headers Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx index 8d9ec587d..b27608738 100644 --- a/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx +++ b/apps/docs/app/[lang]/(home)/components/nextjs-interop.tsx @@ -3,7 +3,6 @@ import { CodeBlock } from "@vercel/geistdocs/components/code-block"; import { geistShikiTheme } from "@vercel/geistdocs/shiki-theme"; import { highlight } from "fumadocs-core/highlight"; import type { ComponentProps } from "react"; -import { IconAcronymTs } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; import { GradientBorder } from "./gradient-border"; @@ -79,7 +78,6 @@ export async function NextjsInterop() { {FILES.map((file, i) => (
- {file.fileName}
{rendered[i]}
@@ -88,7 +86,7 @@ export async function NextjsInterop() {
-
+
{BENEFITS.map((benefit) => ( {benefit} From fa8d9b50ea374f230e7e8e7a182f4ee943f0e60f Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:11:27 -0700 Subject: [PATCH 20/30] style(docs): use heading-16 medium title-case feature labels Switch the production-features labels from mono uppercase to sans title-case at text-heading-16 with medium weight. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../app/[lang]/(home)/components/feature-grid.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/feature-grid.tsx b/apps/docs/app/[lang]/(home)/components/feature-grid.tsx index 54198d9d6..d42a878cf 100644 --- a/apps/docs/app/[lang]/(home)/components/feature-grid.tsx +++ b/apps/docs/app/[lang]/(home)/components/feature-grid.tsx @@ -11,24 +11,24 @@ import { const FEATURES: { icon: ReactNode; label: string; description: string }[] = [ { icon: , - label: "Durable execution", + label: "Durable Execution", description: "Workflows survive crashes and restarts. Every step is checkpointed. Agents park when waiting, resume on the next message.", }, { icon: , - label: "Sandboxed compute", + label: "Sandboxed Compute", description: "Agents run code in isolated sandboxes. File system access, bash execution, and code, all fully isolated.", }, { icon: , - label: "Multi-channel delivery", + label: "Multi-Channel Delivery", description: "One agent codebase deploys to web chat, Slack, API, cron, CLI, and custom apps.", }, { icon: , - label: "Human-in-the-loop", + label: "Human-in-the-Loop", description: "Tools that need confirmation trigger approval gates. Sessions park until resolved, then resume seamlessly.", }, @@ -62,9 +62,7 @@ export function FeatureGrid(): JSX.Element {
  • {feature.icon} - - {feature.label} - + {feature.label}

    {feature.description}

  • From b7032fb4eb9ac310990907b75898484f665af507 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:16:40 -0700 Subject: [PATCH 21/30] feat(docs): show per-file descriptions in the directory viz Add a compact description line under the code panel header for each file (content from vercel.com/eve, platform-agnostic), so the viz explains what each file does without extra cards. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 7 ++++++- .../[lang]/(home)/components/file-tree.tsx | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 862950287..a40320e05 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -7,6 +7,8 @@ import { cn } from "@/lib/utils"; export interface FileTreeItem { name: string; fileName: string; + /** Short, what-this-file-does line shown under the code panel header. */ + description: string; /** File-type icon shown beside the file name in the code panel header. */ icon: ReactNode; /** Pre-highlighted code, rendered on the server through the geistdocs CodeBlock. */ @@ -113,11 +115,14 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { ) : null}
    - {/* Re-keyed per file so the code subtly flies in on selection. */} + {/* Re-keyed per file so the description + code fly in on selection. */}
    +

    + {selected.description} +

    {selected.code}
    diff --git a/apps/docs/app/[lang]/(home)/components/file-tree.tsx b/apps/docs/app/[lang]/(home)/components/file-tree.tsx index 87b146713..3249cbf5b 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree.tsx @@ -21,6 +21,7 @@ interface Snippet { fileName: string; lang: string; Icon: (props: IconProps) => JSX.Element; + description: string; code: string; } @@ -30,6 +31,8 @@ const snippets: Snippet[] = [ fileName: "instructions.md", lang: "markdown", Icon: IconFileText, + description: + "An instructions.md file is a complete agent — describe its role in Markdown, then run eve.", code: `# Identity You are an expert weather assistant. @@ -41,6 +44,8 @@ city in the world.`, fileName: "agent.ts", lang: "typescript", Icon: IconAcronymTs, + description: + "eve uses a default model. Add agent.ts when you want to choose a model or configure the runtime.", code: `import { defineAgent } from "eve"; export default defineAgent({ @@ -52,6 +57,8 @@ export default defineAgent({ fileName: "skills/research.md", lang: "markdown", Icon: IconFileText, + description: + "Skills are Markdown playbooks loaded only when relevant, so the agent gets focused guidance without carrying it in every prompt.", code: `--- name: research description: Research unfamiliar topics @@ -65,6 +72,8 @@ gather evidence first, then answer.`, fileName: "tools/get_weather.ts", lang: "typescript", Icon: IconWrench, + description: + "Drop a TypeScript file in tools/ and the model can call it — the filename becomes the tool name, no registration required.", code: `import { defineTool } from "eve/tools"; import z from "zod"; @@ -87,6 +96,8 @@ export default defineTool({ fileName: "sandbox/sandbox.ts", lang: "typescript", Icon: IconSandbox, + description: + "Every agent includes an isolated sandbox. Add sandbox/sandbox.ts to choose a backend or customize its setup.", code: `import { defineSandbox } from "eve/sandbox"; @@ -103,6 +114,7 @@ export default defineSandbox({ fileName: "channels/slack.ts", lang: "typescript", Icon: IconMessage, + description: "Add channel files to use the same agent in Slack, Discord, Teams, or the web.", code: `import { slackChannel } from "eve/channels/slack"; @@ -115,6 +127,8 @@ export default slackChannel({ fileName: "connections/linear.ts", lang: "typescript", Icon: IconLinked, + description: + "Connections handle auth for services like GitHub, Stripe, and Linear, so tools can call them without managing tokens.", code: `import { defineMcpClientConnection } from "eve/connections"; @@ -127,6 +141,8 @@ export default defineMcpClientConnection({ fileName: "subagents/researcher/agent.ts", lang: "typescript", Icon: IconAgents, + description: + "Add subagents for specialized work — the main agent delegates tasks and combines the results.", code: `import { defineAgent } from "eve"; @@ -140,6 +156,8 @@ export default defineAgent({ fileName: "schedules/daily-report.md", lang: "markdown", Icon: IconClock, + description: + "Schedules run agents automatically for jobs like daily reports and weekly digests, continuing durably without an active session.", code: `--- cron: "0 8 * * *" --- @@ -180,6 +198,7 @@ export async function FileTree() { const items = snippets.map((snippet, i) => ({ name: snippet.name, fileName: snippet.fileName, + description: snippet.description, icon: , code: rendered[i], })); From b77326dceccaff58082350edc576e2da6680823c Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:18:55 -0700 Subject: [PATCH 22/30] style(docs): copy-14 description, fly in only the code block Move the per-file description out of the keyed fly-in wrapper so only the code animates in; the description persists and transitions its height as the text length changes. Bump it to text-copy-14. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../app/[lang]/(home)/components/file-tree-view.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index a40320e05..2470e8def 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -115,14 +115,16 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { ) : null}
    - {/* Re-keyed per file so the description + code fly in on selection. */} + {/* Persists across files: only its height transitions as the + description length changes. */} +

    + {selected.description} +

    + {/* Re-keyed per file so the code subtly flies in on selection. */}
    -

    - {selected.description} -

    {selected.code}
    From 3fe1e8facb8d4d5ede91e12fdba3b2d51771381d Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:21:22 -0700 Subject: [PATCH 23/30] feat(docs): use refresh-counter-clockwise icon for the reset button Hard-copy IconRefreshCounterClockwise from @vercel/geistcn-assets and use it for the directory reset control instead of the trash icon. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 4 ++-- .../icon-refresh-counter-clockwise.tsx | 18 ++++++++++++++++++ apps/docs/components/geistcn-icons/index.ts | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 apps/docs/components/geistcn-icons/icon-refresh-counter-clockwise.tsx diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 2470e8def..20006ba3a 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -1,7 +1,7 @@ "use client"; import { type ReactNode, useState } from "react"; -import { IconCheck, IconPlusCircle, IconTrash } from "@/components/geistcn-icons"; +import { IconCheck, IconPlusCircle, IconRefreshCounterClockwise } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; export interface FileTreeItem { @@ -64,7 +64,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { : "cursor-not-allowed text-gray-600", )} > - +
    diff --git a/apps/docs/components/geistcn-icons/icon-refresh-counter-clockwise.tsx b/apps/docs/components/geistcn-icons/icon-refresh-counter-clockwise.tsx new file mode 100644 index 000000000..53171143f --- /dev/null +++ b/apps/docs/components/geistcn-icons/icon-refresh-counter-clockwise.tsx @@ -0,0 +1,18 @@ +// Hard copy of IconRefreshCounterClockwise from @vercel/geistcn-assets/icons. +import type { JSX } from "react"; +import { BaseIcon, type IconProps } from "./base-icon"; + +const GENERATED_CONFIG = { height: 16, aspectRatio: 1, viewBox: "0 0 16 16" }; + +export function IconRefreshCounterClockwise(props: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/docs/components/geistcn-icons/index.ts b/apps/docs/components/geistcn-icons/index.ts index c0378fe17..10aa87ea6 100644 --- a/apps/docs/components/geistcn-icons/index.ts +++ b/apps/docs/components/geistcn-icons/index.ts @@ -12,6 +12,7 @@ export { IconLinked } from "./icon-linked"; export { IconLogs } from "./icon-logs"; export { IconMessage } from "./icon-message"; export { IconPlusCircle } from "./icon-plus-circle"; +export { IconRefreshCounterClockwise } from "./icon-refresh-counter-clockwise"; export { IconRobot } from "./icon-robot"; export { IconSandbox } from "./icon-sandbox"; export { IconSparkles } from "./icon-sparkles"; From 1bd109d103aab7f8556ec8834a1da0efa4e9056a Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:24:17 -0700 Subject: [PATCH 24/30] feat(docs): allow deselecting the active file in the directory viz Clicking the active, already-added file removes it again (selection falls back to the highest remaining file); instructions.md stays pinned. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../[lang]/(home)/components/file-tree-view.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 20006ba3a..79cdd0527 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -23,6 +23,16 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { const selected = items[selectedIndex]; function select(index: number) { + // Clicking the active, already-added file deselects it again. The default + // file (instructions.md) is always present and can't be removed. + if (index !== 0 && index === selectedIndex && visited.has(index)) { + const next = new Set(visited); + next.delete(index); + setVisited(next); + const remaining = [...next]; + setSelectedIndex(remaining.length > 0 ? Math.max(...remaining) : 0); + return; + } setSelectedIndex(index); setVisited((prev) => new Set(prev).add(index)); } @@ -108,15 +118,13 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) {
    {/* {selected.icon} */} - {selected.fileName} + {selected.fileName} {selectedIndex > 0 ? ( Optional ) : null}
    - {/* Persists across files: only its height transitions as the - description length changes. */}

    {selected.description}

    From e6e9a4d932e245b7ecb13b6fee4bef9e1a9a48e4 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:31:42 -0700 Subject: [PATCH 25/30] feat(docs): reveal reset button with a slide, hidden at one file The reset is hidden when only the default file is selected; selecting more reveals it with a width/opacity transition that slides the counter left. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../(home)/components/file-tree-view.tsx | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 79cdd0527..08b074f92 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -58,24 +58,28 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) {
    agent/ - - {visited.size} {visited.size === 1 ? "file" : "files"} - - + {/* Counter slides left as the reset button reveals once more + than the default file is selected. */} +
    + + {visited.size} {visited.size === 1 ? "file" : "files"} selected + + +
    {items.map((item, i) => { @@ -99,13 +103,18 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { {item.name} {i > 0 && added ? ( - + ) : i > 0 ? ( ) : null} From 97c48c2697d0dff3f3cd762961ba4dd6519aa1d0 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:32:15 -0700 Subject: [PATCH 26/30] fix(docs): correct en-dash typo in plus-circle margin class Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 08b074f92..4fe4e3024 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -112,7 +112,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { ) : i > 0 ? ( From 199ba47ca1bc8cac2e2c10453dbd072af69779a9 Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:35:21 -0700 Subject: [PATCH 27/30] style(docs): use trash icon for the directory reset button Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/file-tree-view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index 4fe4e3024..c6cdcd88c 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -1,7 +1,7 @@ "use client"; import { type ReactNode, useState } from "react"; -import { IconCheck, IconPlusCircle, IconRefreshCounterClockwise } from "@/components/geistcn-icons"; +import { IconCheck, IconPlusCircle, IconTrash } from "@/components/geistcn-icons"; import { cn } from "@/lib/utils"; export interface FileTreeItem { @@ -77,7 +77,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { : "pointer-events-none ml-0 w-0 opacity-0", )} > - +
    From 8ca709aa9e19aca96d405f7a6c7ca47ba4f2896c Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:42:08 -0700 Subject: [PATCH 28/30] feat(docs): refine architecture/file-tree copy and mobile layout - Reframe the architecture section around open-source, self-hostable SDKs (title, copy) and add the eve mark above the cards. - Chat SDK channels span two columns in the stacked (mobile) layout. - File-tree code panel wraps content on mobile (no forced min-height) with the bottom fade kept clear of the code; platform-agnostic copy tweaks. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../app/[lang]/(home)/components/architecture.tsx | 13 ++++++++----- .../app/[lang]/(home)/components/file-tree-view.tsx | 4 ++-- .../docs/app/[lang]/(home)/components/file-tree.tsx | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/architecture.tsx b/apps/docs/app/[lang]/(home)/components/architecture.tsx index 04397f227..931f81907 100644 --- a/apps/docs/app/[lang]/(home)/components/architecture.tsx +++ b/apps/docs/app/[lang]/(home)/components/architecture.tsx @@ -111,12 +111,13 @@ export function ArchitectureDiagram() {

    - Three layers, cleanly separated + Built on open-source SDKs, yours to self-host

    - The runtime owns durability and state. The channel is where your agent gets surfaced. + Each capability is its own open-source SDK — workflows, AI, sandbox, connections, and + channels. Swap any backend and self-host the whole runtime, with zero + managed-infrastructure dependencies.

    -
    {/* Runtime */}
    @@ -163,9 +164,11 @@ export function ArchitectureDiagram() { className="group relative flex items-start gap-2.5 overflow-hidden rounded-lg p-4 transition-colors material-small hover:bg-background-200 lg:h-0 lg:min-h-0 lg:grow" > -
    +
    Chat SDK -
    + {/* Two columns while the cards are stacked (one-column layout); + a single column once they sit side by side at lg. */} +
    {CHANNELS.map((channel) => ( {channel} diff --git a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx index c6cdcd88c..9ef69ddb0 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree-view.tsx @@ -53,7 +53,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { />
    -
    +
    {/* Sidebar */}
    @@ -140,7 +140,7 @@ export function FileTreeView({ items }: { items: FileTreeItem[] }) { {/* Re-keyed per file so the code subtly flies in on selection. */}
    {selected.code}
    diff --git a/apps/docs/app/[lang]/(home)/components/file-tree.tsx b/apps/docs/app/[lang]/(home)/components/file-tree.tsx index 3249cbf5b..fcfec91c5 100644 --- a/apps/docs/app/[lang]/(home)/components/file-tree.tsx +++ b/apps/docs/app/[lang]/(home)/components/file-tree.tsx @@ -97,7 +97,7 @@ export default defineTool({ lang: "typescript", Icon: IconSandbox, description: - "Every agent includes an isolated sandbox. Add sandbox/sandbox.ts to choose a backend or customize its setup.", + "Every agent includes an isolated sandbox. Add sandbox/sandbox.ts to swap in any backend or customize its setup.", code: `import { defineSandbox } from "eve/sandbox"; @@ -210,8 +210,8 @@ export async function FileTree() { An agent is a directory

    - Define instructions and skills in markdown, tools in TypeScript, and deploy. The framework - compiles the directory, wires up durable workflows, and connects channels. + Define instructions and skills in markdown, tools in TypeScript, and deploy anywhere. The + framework compiles the directory, wires up durable workflows, and connects channels.

    From 783ddbb9ffb60df31e63a3b01d29e0c9d1d7895c Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:15:50 -0700 Subject: [PATCH 29/30] feat(docs): add managed vs self-hosted setup switcher to architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a real-world setup example below the SDK cards: a toggle (ported from the flags-sdk docs Switch, built on radix-ui) flips between a managed (Vercel) and a fully self-hosted stack — Ollama, Docker, DigitalOcean, Ansible. Self-hosted is the default. The Managed/Self-hosted labels are clickable triggers, the four Vercel cards link to their docs, and the card grid is inset to align with the inner cards above. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- .../[lang]/(home)/components/architecture.tsx | 3 + .../(home)/components/setup-switcher.tsx | 173 ++++++++++++++++++ apps/docs/components/ui/switch.tsx | 36 ++++ 3 files changed, 212 insertions(+) create mode 100644 apps/docs/app/[lang]/(home)/components/setup-switcher.tsx create mode 100644 apps/docs/components/ui/switch.tsx diff --git a/apps/docs/app/[lang]/(home)/components/architecture.tsx b/apps/docs/app/[lang]/(home)/components/architecture.tsx index 931f81907..56e54e7ef 100644 --- a/apps/docs/app/[lang]/(home)/components/architecture.tsx +++ b/apps/docs/app/[lang]/(home)/components/architecture.tsx @@ -10,6 +10,7 @@ import { IconWrench, } from "@/components/geistcn-icons"; import { GradientBorder } from "./gradient-border"; +import { SetupSwitcher } from "./setup-switcher"; // Runtime primitives shown in a row (desktop) / stacked (mobile) below the // full-width Durable Workflow card. @@ -190,6 +191,8 @@ export function ArchitectureDiagram() {
    + +
    ); diff --git a/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx b/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx new file mode 100644 index 000000000..9300d847f --- /dev/null +++ b/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { + SiAnsible, + SiDigitalocean, + SiDocker, + SiOllama, + SiVercel, +} from "@icons-pack/react-simple-icons"; +import Link from "next/link"; +import { type ComponentType, useState } from "react"; +import { IconArrowUpRight } from "@/components/geistcn-icons"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; + +type Mode = "managed" | "self-hosted"; + +interface StackEntry { + category: string; + name: string; + /** Brand mark, rendered in its original color for recognizability. */ + Logo: ComponentType<{ + size?: number; + color?: string; + className?: string; + title?: string; + }>; + /** When set, the card links to the backend's docs/site. */ + href?: string; +} + +// Same agent, two real-world deployment targets: managed (Vercel-native) and a +// fully self-hosted box. The self-hosted column mirrors a real setup — Docker +// sandbox, a single DigitalOcean server, Ansible deploys, zero managed services. +const STACKS: Record = { + managed: [ + { + category: "Models", + name: "AI Gateway", + Logo: SiVercel, + href: "https://vercel.com/docs/ai-gateway", + }, + { + category: "Sandbox", + name: "Vercel Sandbox", + Logo: SiVercel, + href: "https://vercel.com/docs/vercel-sandbox", + }, + { + category: "Runtime", + name: "Vercel Workflows", + Logo: SiVercel, + href: "https://vercel.com/docs/workflows", + }, + { + category: "Deploy", + name: "Vercel", + Logo: SiVercel, + href: "https://vercel.com/docs/deployments", + }, + ], + "self-hosted": [ + { category: "Models", name: "Ollama", Logo: SiOllama }, + { category: "Sandbox", name: "Docker", Logo: SiDocker }, + { category: "Runtime", name: "DigitalOcean", Logo: SiDigitalocean }, + { category: "Deploy", name: "Ansible", Logo: SiAnsible }, + ], +}; + +const CAPTIONS: Record = { + managed: "Deploy to Vercel — sandboxes, durable workflows, and model routing handled for you.", + "self-hosted": + "Runs on a single 4 GB server with Docker and Ansible — no managed-service dependencies.", +}; + +/** + * Toggle that shows the concrete backends behind each capability for a managed + * (Vercel) vs. fully self-hosted deployment, illustrating a real-world setup. + */ +export function SetupSwitcher() { + const [mode, setMode] = useState("self-hosted"); + const selfHosted = mode === "self-hosted"; + + return ( +
    +
    + + setMode(checked ? "self-hosted" : "managed")} + aria-label="Toggle deployment target" + className="cursor-pointer" + /> + +
    + + {/* Re-keyed per mode so the stack cross-fades on toggle. px-5 insets the + grid by the gradient cards' padding so these cards line up with the + inner cards (AI SDK, Connection SDK, …) in the section above. */} +
    + {STACKS[mode].map((entry) => { + const body = ( + <> + +
    + + {entry.category} + + + {entry.name} + +
    + + ); + + if (!entry.href) { + return ( +
    + {body} +
    + ); + } + + return ( + + {body} + + + ); + })} +
    + +

    + {CAPTIONS[mode]} +

    +
    + ); +} diff --git a/apps/docs/components/ui/switch.tsx b/apps/docs/components/ui/switch.tsx new file mode 100644 index 000000000..794525ffd --- /dev/null +++ b/apps/docs/components/ui/switch.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { Switch as SwitchPrimitive } from "radix-ui"; +import type * as React from "react"; +import { cn } from "@/lib/utils"; + +/** + * Toggle switch, ported from the flags-sdk docs homepage. Built on the + * radix-ui `Switch` primitive with geist gray tokens and a `sm`/`default` size. + */ +function Switch({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "sm" | "default"; +}) { + return ( + + + + ); +} + +export { Switch }; From 1dc9a94f745a435bd13f2c962ad57f32a35c12fb Mon Sep 17 00:00:00 2001 From: christopherkindl <53372002+christopherkindl@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:21:40 -0700 Subject: [PATCH 30/30] fix(docs): point setup-switcher Vercel cards to product pages Link AI Gateway, Sandbox, and Workflow to their vercel.com product pages instead of the docs sections. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com> --- apps/docs/app/[lang]/(home)/components/setup-switcher.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx b/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx index 9300d847f..381ea56be 100644 --- a/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx +++ b/apps/docs/app/[lang]/(home)/components/setup-switcher.tsx @@ -38,19 +38,19 @@ const STACKS: Record = { category: "Models", name: "AI Gateway", Logo: SiVercel, - href: "https://vercel.com/docs/ai-gateway", + href: "https://vercel.com/ai-gateway", }, { category: "Sandbox", name: "Vercel Sandbox", Logo: SiVercel, - href: "https://vercel.com/docs/vercel-sandbox", + href: "https://vercel.com/sandbox", }, { category: "Runtime", name: "Vercel Workflows", Logo: SiVercel, - href: "https://vercel.com/docs/workflows", + href: "https://vercel.com/workflow", }, { category: "Deploy",