chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:30 +08:00
commit e7738de6d2
1723 changed files with 216139 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
"use client";
import { useState } from "react";
type ToolCall = { command: string; output?: string };
type Message =
| { role: "user"; text: string }
| { role: "assistant"; text: string }
| { role: "skill"; name: string }
| { role: "tools"; calls: ToolCall[] }
| { role: "output"; text: string };
export type ChatSpec = { title?: string; messages: Message[] };
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
<button
type="button"
aria-label="Copy message"
onClick={async () => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1400);
} catch {}
}}
className="shrink-0 cursor-pointer rounded-md p-1.5 text-muted transition hover:bg-surface-muted hover:text-fg"
>
{copied ? (
<svg width="13" height="13" viewBox="0 0 16 16" fill="none">
<path d="M13.5 4.5 6.5 11.5 3 8" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
</svg>
) : (
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor">
<path d="M5 1.75A1.75 1.75 0 0 1 6.75 0h5.5A1.75 1.75 0 0 1 14 1.75v5.5A1.75 1.75 0 0 1 12.25 9H11V7.5h1.25a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-5.5a.25.25 0 0 0-.25.25V3H5V1.75Z" />
<path d="M2 4.75A1.75 1.75 0 0 1 3.75 3h5.5A1.75 1.75 0 0 1 11 4.75v5.5A1.75 1.75 0 0 1 9.25 12h-5.5A1.75 1.75 0 0 1 2 10.25v-5.5Zm1.75-.25a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h5.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-5.5Z" />
</svg>
)}
</button>
);
}
function ChevronIcon({ open }: { open: boolean }) {
return (
<svg
width="12"
height="12"
viewBox="0 0 16 16"
fill="none"
className={`shrink-0 text-muted transition-transform duration-200 ${open ? "rotate-90" : ""}`}
>
<path
d="M6 4l4 4-4 4"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function ToolCalls({ calls }: { calls: ToolCall[] }) {
const [open, setOpen] = useState<number[]>([]);
const toggle = (i: number) =>
setOpen((prev) => (prev.includes(i) ? prev.filter((n) => n !== i) : [...prev, i]));
return (
<div className="flex flex-col gap-2">
{calls.map((call, i) => {
const isOpen = open.includes(i);
const hasOutput = typeof call.output === "string" && call.output.length > 0;
return (
<div
key={call.command}
className="overflow-hidden rounded-lg border border-border bg-surface"
>
<button
type="button"
onClick={() => hasOutput && toggle(i)}
aria-expanded={isOpen}
disabled={!hasOutput}
className="flex w-full items-center gap-2 px-3 py-2 font-mono text-[0.78125rem] leading-relaxed text-muted transition-colors enabled:cursor-pointer enabled:hover:bg-surface-muted"
>
{hasOutput ? <ChevronIcon open={isOpen} /> : <span className="w-3 shrink-0" />}
<span
className={`min-w-0 flex-1 text-left text-fg/80 ${
isOpen ? "whitespace-pre-wrap break-all" : "truncate"
}`}
>
{call.command}
</span>
</button>
{isOpen && hasOutput && (
<pre className="m-0 overflow-x-auto border-t border-border/50 bg-bg px-3 py-2.5 text-[0.78125rem] leading-relaxed text-fg/70">
{call.output}
</pre>
)}
</div>
);
})}
</div>
);
}
function ChatMessage({ message }: { message: Message }) {
if (message.role === "user") {
return (
<div className="flex items-center justify-end gap-1">
<CopyButton text={message.text} />
<div className="max-w-[82%] rounded-2xl rounded-br-md bg-fg px-4 py-2.5 text-[0.875rem] leading-relaxed text-bg">
{message.text}
</div>
</div>
);
}
if (message.role === "assistant") {
return (
<div className="max-w-[92%] text-[0.875rem] leading-[1.65] text-fg">{message.text}</div>
);
}
if (message.role === "skill") {
return (
<div className="flex items-center gap-2 text-[0.78125rem] text-muted">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" className="shrink-0">
<path
d="M6 2 3 8h3l-1 6 5-8H6l2-4H6Z"
fill="currentColor"
className="text-fg/50"
/>
</svg>
<span>
Loaded <span className="font-medium text-fg">{message.name}</span> skill
</span>
</div>
);
}
if (message.role === "tools") {
return <ToolCalls calls={message.calls} />;
}
return (
<pre className="m-0 overflow-x-auto rounded-lg border border-border bg-code-bg px-4 py-3 font-mono text-[0.8125rem] leading-relaxed text-code-fg">
{message.text}
</pre>
);
}
export function AgentChat({ spec }: { spec: ChatSpec }) {
return (
<div className="not-prose my-6 overflow-hidden rounded-2xl border border-border bg-bg">
<div className="flex flex-col gap-5 p-5 sm:p-6">
{spec.messages.map((message, i) => (
<ChatMessage key={i} message={message} />
))}
</div>
</div>
);
}
+46
View File
@@ -0,0 +1,46 @@
import Link from "next/link";
import type { ButtonHTMLAttributes, ComponentProps } from "react";
const SIZES = {
sm: "h-8 px-4 text-[0.8125rem]",
md: "h-10 px-6 text-sm",
lg: "h-11 gap-2 px-8 text-[0.9375rem]",
};
const VARIANTS = {
default: "border-border bg-bg text-fg hover:border-fg",
primary:
"border-accent bg-accent text-accent-fg hover:bg-transparent hover:text-accent",
};
type ButtonVariant = keyof typeof VARIANTS;
type ButtonSize = keyof typeof SIZES;
type ButtonStyleProps = {
variant?: ButtonVariant;
size?: ButtonSize;
className?: string;
};
function classes({ variant = "default", size = "md", className = "" }: ButtonStyleProps): string {
return [
"inline-flex items-center justify-center rounded-md border font-medium no-underline transition",
VARIANTS[variant],
SIZES[size],
className,
]
.filter(Boolean)
.join(" ");
}
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & ButtonStyleProps;
export function Button({ variant, size, className, ...rest }: ButtonProps) {
return <button className={classes({ variant, size, className })} {...rest} />;
}
type ButtonLinkProps = ComponentProps<typeof Link> & ButtonStyleProps;
export function ButtonLink({ variant, size, className, ...rest }: ButtonLinkProps) {
return <Link className={classes({ variant, size, className })} {...rest} />;
}
+58
View File
@@ -0,0 +1,58 @@
"use client";
import type { UIEvent } from "react";
import { useMemo, useRef } from "react";
import { highlight } from "@/lib/highlight";
type CodeEditorProps = {
value: string;
onChange: (value: string) => void;
language?: string;
ariaLabel: string;
className?: string;
};
export function CodeEditor({
value,
onChange,
language = "zero",
ariaLabel,
className = "",
}: CodeEditorProps) {
const preRef = useRef<HTMLPreElement | null>(null);
const html = useMemo(() => {
const trailingNewline = value.endsWith("\n") ? " " : "";
return highlight(value + trailingNewline, language);
}, [value, language]);
function handleScroll(event: UIEvent<HTMLTextAreaElement>) {
const pre = preRef.current;
if (!pre) return;
pre.scrollTop = event.currentTarget.scrollTop;
pre.scrollLeft = event.currentTarget.scrollLeft;
}
return (
<div className={`relative h-full w-full ${className}`}>
<pre
ref={preRef}
aria-hidden="true"
className="pointer-events-none absolute inset-0 m-0 overflow-hidden whitespace-pre p-6 font-mono text-[0.9375rem] leading-[1.7] text-code-fg"
>
<code dangerouslySetInnerHTML={{ __html: html }} />
</pre>
<textarea
value={value}
onChange={(event) => onChange(event.target.value)}
onScroll={handleScroll}
spellCheck={false}
autoCorrect="off"
autoCapitalize="off"
autoComplete="off"
aria-label={ariaLabel}
className="relative block h-full w-full resize-none whitespace-pre border-0 bg-transparent p-6 font-mono text-[0.9375rem] leading-[1.7] text-transparent caret-fg outline-none"
/>
</div>
);
}
+50
View File
@@ -0,0 +1,50 @@
"use client";
import type { MouseEvent } from "react";
import { useRef, useState } from "react";
export function CopyCodeButton() {
const [state, setState] = useState("idle");
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
async function handleClick(event: MouseEvent<HTMLButtonElement>) {
const button = event.currentTarget;
const block = button.closest("figure[data-rehype-pretty-code-figure], div[data-code-block]");
const code = block?.querySelector("code");
const text = code?.textContent ?? "";
if (!text) return;
try {
await navigator.clipboard.writeText(text);
setState("copied");
} catch {
setState("failed");
}
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setState("idle"), 1400);
}
const color =
state === "copied" ? "text-success" : state === "failed" ? "text-danger" : "text-muted";
return (
<button
type="button"
aria-label={state === "copied" ? "Code copied to clipboard" : "Copy code to clipboard"}
onClick={handleClick}
className={`absolute right-2 top-2 z-10 inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded border-0 bg-transparent transition hover:text-fg ${color}`}
>
{state === "copied" ? (
<svg aria-hidden="true" viewBox="0 0 16 16" className="h-3.5 w-3.5 fill-current">
<path d="M13.78 3.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 8.28a.75.75 0 0 1 1.06-1.06L6 9.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" />
</svg>
) : (
<svg aria-hidden="true" viewBox="0 0 16 16" className="h-3.5 w-3.5 fill-current">
<path d="M5 1.75A1.75 1.75 0 0 1 6.75 0h5.5A1.75 1.75 0 0 1 14 1.75v5.5A1.75 1.75 0 0 1 12.25 9H11V7.5h1.25a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-5.5a.25.25 0 0 0-.25.25V3H5V1.75Z" />
<path d="M2 4.75A1.75 1.75 0 0 1 3.75 3h5.5A1.75 1.75 0 0 1 11 4.75v5.5A1.75 1.75 0 0 1 9.25 12h-5.5A1.75 1.75 0 0 1 2 10.25v-5.5Zm1.75-.25a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h5.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-5.5Z" />
</svg>
)}
</button>
);
}
+386
View File
@@ -0,0 +1,386 @@
"use client";
import { isValidElement, useCallback, useEffect, useRef, useState } from "react";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { Streamdown } from "streamdown";
import type {
Components as StreamdownComponents,
ExtraProps as StreamdownExtraProps,
} from "streamdown";
import type {
DynamicToolUIPart,
UIDataTypes,
UIMessage,
UIMessagePart,
UITools,
ToolUIPart,
} from "ai";
import Link from "next/link";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import { highlight } from "@/lib/highlight";
const STORAGE_KEY = "docs-chat-messages";
const transport = new DefaultChatTransport({ api: "/api/docs-chat" });
const TOOL_LABELS = {
readFile: { label: "Reading", pastLabel: "Read", argKey: "path" },
bash: { label: "Running", pastLabel: "Ran", argKey: "command" },
};
type ToolLabel = {
label: string;
pastLabel: string;
argKey?: string;
};
type ToolDisplayPart = ToolUIPart<UITools> | DynamicToolUIPart;
function extractCodeText(children: ReactNode): string {
if (children == null || children === false) return "";
if (typeof children === "string") return children;
if (Array.isArray(children)) return children.map(extractCodeText).join("");
if (isValidElement<{ children?: ReactNode }>(children)) {
return extractCodeText(children.props.children);
}
return String(children);
}
function ChatCodeBlock({
children,
}: ComponentPropsWithoutRef<"pre"> & StreamdownExtraProps) {
const codeEl = Array.isArray(children) ? children[0] : children;
const className = isValidElement<{ className?: string }>(codeEl)
? codeEl.props.className ?? ""
: "";
const language = /language-([A-Za-z0-9_-]+)/.exec(className)?.[1] ?? "";
const code = extractCodeText(
isValidElement<{ children?: ReactNode }>(codeEl) ? codeEl.props.children : codeEl,
).replace(/\n$/, "");
const html = highlight(code, language);
return (
<pre className="not-prose my-3 overflow-x-auto rounded-md border border-border bg-code-bg p-3 text-[0.8125rem] leading-relaxed text-code-fg">
<code
className={className}
dangerouslySetInnerHTML={{ __html: html }}
/>
</pre>
);
}
const STREAMDOWN_COMPONENTS: StreamdownComponents = {
pre: ChatCodeBlock,
};
function isToolPart(
part: UIMessagePart<UIDataTypes, UITools>,
): part is ToolDisplayPart {
return part.type.startsWith("tool-") || part.type === "dynamic-tool";
}
function getToolName(part: ToolDisplayPart): string {
if (part.type === "dynamic-tool") return part.toolName ?? "tool";
return part.type.replace(/^tool-/, "");
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function ToolCallDisplay({ part }: { part: ToolDisplayPart }) {
const toolName = getToolName(part);
const config: ToolLabel = TOOL_LABELS[toolName as keyof typeof TOOL_LABELS] ?? {
label: toolName,
pastLabel: toolName,
};
const isDone = part.state === "output-available";
const isError = part.state === "output-error";
const isRunning = !isDone && !isError;
const displayLabel = isRunning ? config.label : config.pastLabel;
const args = isRecord(part.input) ? part.input : {};
const argValue = config.argKey ? args[config.argKey] : undefined;
const argPreview =
argValue != null
? String(argValue)
.replace(/^\/workspace\//, "/")
.replace(/\.md$/, "")
.replace(/\/index$/, "") || "/"
: "";
const docsLink = toolName === "readFile" && argPreview.startsWith("/") ? argPreview : null;
const argEl = argPreview ? (
docsLink ? (
<Link href={docsLink} className="truncate underline underline-offset-2">
{argPreview}
</Link>
) : (
<span className="truncate">{argPreview}</span>
)
) : null;
return (
<div className="min-w-0 py-0.5 text-xs">
<span
className={`inline-flex min-w-0 max-w-full items-center gap-1 font-mono ${
isRunning
? "animate-pulse text-neutral-500 dark:text-neutral-400"
: "text-neutral-400 dark:text-neutral-500"
}`}
>
<span className="shrink-0">{displayLabel}</span>
{argEl}
{isError && <span className="text-red-500">failed</span>}
</span>
</div>
);
}
const SUGGESTIONS = [
"What is Zero?",
"How do I install it?",
"What commands are available?",
"How does cross-compilation work?",
"Show me a hello world.",
];
export function DocsChat() {
const [open, setOpen] = useState(false);
const [input, setInput] = useState("");
const messagesScrollRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLTextAreaElement | null>(null);
const restoredRef = useRef(false);
const { messages, sendMessage, status, setMessages, error } = useChat({ transport });
const isLoading = status === "streaming" || status === "submitted";
const showMessages = messages.length > 0 || !!error || isLoading;
useEffect(() => {
if (restoredRef.current) return;
restoredRef.current = true;
try {
const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
if (Array.isArray(parsed) && parsed.length > 0) setMessages(parsed as UIMessage[]);
}
} catch {}
}, [setMessages]);
useEffect(() => {
if (!restoredRef.current || isLoading) return;
if (messages.length === 0) {
sessionStorage.removeItem(STORAGE_KEY);
return;
}
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
} catch {}
}, [messages, isLoading]);
useEffect(() => {
function onKey(e: globalThis.KeyboardEvent) {
if (e.key === "i" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((prev) => !prev);
}
}
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, []);
useEffect(() => {
if (open) {
const t = setTimeout(() => inputRef.current?.focus(), 200);
return () => clearTimeout(t);
}
}, [open]);
useEffect(() => {
if (error) setOpen(true);
}, [error]);
useEffect(() => {
const el = messagesScrollRef.current;
if (!el) return;
requestAnimationFrame(() => {
el.scrollTop = el.scrollHeight;
});
}, [messages, error]);
const handleSubmit = useCallback(
(e?: { preventDefault: () => void }) => {
e?.preventDefault();
if (!input.trim() || isLoading) return;
sendMessage({ text: input });
setInput("");
},
[input, isLoading, sendMessage],
);
const handleClear = useCallback(() => {
setMessages([]);
sessionStorage.removeItem(STORAGE_KEY);
}, [setMessages]);
return (
<>
{!open && (
<button
onClick={() => setOpen(true)}
className="fixed bottom-4 left-1/2 z-50 flex -translate-x-1/2 items-center gap-2 rounded-lg bg-neutral-900 px-4 py-2 text-sm font-medium text-white shadow-lg transition-opacity hover:opacity-90 dark:bg-neutral-100 dark:text-neutral-900 sm:left-auto sm:right-4 sm:translate-x-0"
aria-label="Ask AI"
>
Ask AI
<kbd className="hidden items-center gap-0.5 font-mono text-xs opacity-60 sm:inline-flex">
<span>&#8984;</span>I
</kbd>
</button>
)}
<Sheet open={open} onOpenChange={setOpen}>
<SheetContent
side="right"
showCloseButton={false}
className="flex w-full max-w-md flex-col gap-0 p-0 sm:max-w-md"
>
<SheetTitle className="sr-only">AI Chat</SheetTitle>
<div className="flex shrink-0 items-center justify-between border-b border-neutral-200 px-4 py-3 dark:border-neutral-800">
<span className="text-sm font-medium text-neutral-900 dark:text-neutral-100">
Zero Docs
</span>
<div className="flex items-center gap-3">
{showMessages && (
<button
onClick={handleClear}
className="text-xs text-neutral-500 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
aria-label="Clear conversation"
>
Clear
</button>
)}
<button
onClick={() => setOpen(false)}
className="text-neutral-500 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
aria-label="Close panel"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
</div>
{showMessages ? (
<div ref={messagesScrollRef} className="flex-1 space-y-4 overflow-y-auto p-4">
{messages.map((message) => {
const hasVisible = message.parts?.some(
(p) => (p.type === "text" && p.text.length > 0) || isToolPart(p),
);
if (!hasVisible) return null;
return (
<div key={message.id}>
{message.role === "user" ? (
<div className="whitespace-pre-wrap text-sm leading-relaxed text-neutral-500 dark:text-neutral-400">
{message.parts.filter((p) => p.type === "text").map((p) => p.text).join("")}
</div>
) : (
<div className="space-y-2">
{message.parts.map((part, i) => {
if (part.type === "text" && part.text) {
return (
<div
key={i}
className="docs-chat-content max-w-none text-sm leading-relaxed text-neutral-900 dark:text-neutral-100"
>
<Streamdown components={STREAMDOWN_COMPONENTS}>
{part.text}
</Streamdown>
</div>
);
}
if (isToolPart(part)) {
return <ToolCallDisplay key={part.toolCallId ?? i} part={part} />;
}
return null;
})}
</div>
)}
</div>
);
})}
{error && (
<div className="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600/80 dark:bg-red-950/30 dark:text-red-400/80">
{(() => {
try {
const parsed = JSON.parse(error.message);
return parsed.message || parsed.error || error.message;
} catch {
return error.message || "Something went wrong. Please try again.";
}
})()}
</div>
)}
</div>
) : (
<div className="flex flex-1 flex-col">
<div className="flex flex-wrap gap-2 p-4">
{SUGGESTIONS.map((s) => (
<button
key={s}
type="button"
onClick={() => sendMessage({ text: s })}
className="rounded-full border border-neutral-200 bg-neutral-100 px-3 py-1.5 text-xs font-medium text-neutral-600 transition-colors hover:text-neutral-900 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-100"
>
{s}
</button>
))}
</div>
</div>
)}
<form
onSubmit={handleSubmit}
className="flex shrink-0 items-end gap-2 border-t border-neutral-200 px-4 py-3 dark:border-neutral-800"
>
<textarea
ref={inputRef}
value={input}
onChange={(e) => {
setInput(e.target.value);
e.target.style.height = "auto";
e.target.style.height = `${e.target.scrollHeight}px`;
}}
rows={1}
enterKeyHint="send"
placeholder="Ask a question..."
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSubmit(e);
}
}}
className="max-h-32 flex-1 resize-none bg-transparent text-base leading-relaxed text-neutral-900 outline-none placeholder:text-neutral-400 disabled:opacity-50 dark:text-neutral-100 dark:placeholder:text-neutral-500 sm:text-sm"
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="shrink-0 rounded-full bg-neutral-900 p-1.5 text-white transition-colors hover:bg-neutral-700 disabled:opacity-30 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-neutral-300"
aria-label="Send message"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="19" x2="12" y2="5" />
<polyline points="5 12 12 5 19 12" />
</svg>
</button>
</form>
</SheetContent>
</Sheet>
</>
);
}
+140
View File
@@ -0,0 +1,140 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { ChevronDownIcon, HamburgerIcon } from "@/components/icons";
import { Sheet, SheetTrigger, SheetContent, SheetTitle } from "@/components/ui/sheet";
import type { DocsGroup } from "@/lib/types";
const COLLAPSE_STORAGE_KEY = "docs-sidebar-collapsed";
type SidebarNavProps = {
groups: DocsGroup[];
activeSlug: string;
onNavigate?: () => void;
};
function SidebarNav({ groups, activeSlug, onNavigate }: SidebarNavProps) {
const initialCollapsed = new Set(
groups
.filter((g) => g.section !== "Start Here" && !g.items.some((i) => i.slug === activeSlug))
.map((g) => g.section),
);
const [collapsed, setCollapsed] = useState<Set<string>>(initialCollapsed);
const restored = useRef(false);
// Restore the user's expand/collapse choices so navigating between pages
// does not reset sections the user opened. (The active section is always
// shown expanded via the `hasActive` check below, regardless of this set.)
useEffect(() => {
if (restored.current) return;
restored.current = true;
try {
const stored = sessionStorage.getItem(COLLAPSE_STORAGE_KEY);
if (stored) setCollapsed(new Set(JSON.parse(stored) as string[]));
} catch {}
}, []);
function toggle(section: string) {
setCollapsed((prev) => {
const next = new Set(prev);
if (next.has(section)) next.delete(section);
else next.add(section);
try {
sessionStorage.setItem(COLLAPSE_STORAGE_KEY, JSON.stringify([...next]));
} catch {}
return next;
});
}
return (
<nav className="px-3 py-4">
{groups.map((group) => {
const hasActive = group.items.some((item) => item.slug === activeSlug);
const isCollapsed = collapsed.has(group.section) && !hasActive;
return (
<div key={group.section} className="mb-4">
<button
type="button"
aria-expanded={!isCollapsed}
onClick={() => toggle(group.section)}
className="flex w-full cursor-pointer items-center justify-between rounded bg-transparent px-3 py-2 text-[0.6875rem] font-semibold uppercase tracking-[0.06em] text-muted transition hover:text-fg"
>
<span>{group.section}</span>
<ChevronDownIcon className={`shrink-0 transition-transform ${isCollapsed ? "-rotate-90" : ""}`} />
</button>
<div
className={`grid transition-[grid-template-rows] duration-200 ${
isCollapsed ? "grid-rows-[0fr]" : "grid-rows-[1fr]"
}`}
>
<div className="overflow-hidden">
{group.items.map((item) => {
const active = item.slug === activeSlug;
return (
<Link
key={item.slug}
href={item.path}
aria-current={active ? "page" : undefined}
onClick={onNavigate}
className={`block rounded px-3 py-[0.1875rem] text-[0.8125rem] leading-[1.8] no-underline transition hover:text-fg ${
active ? "font-medium text-fg" : "text-muted"
}`}
>
{item.title}
</Link>
);
})}
</div>
</div>
</div>
);
})}
</nav>
);
}
type DocsSidebarShellProps = {
groups: DocsGroup[];
};
function activeDocForPath(groups: DocsGroup[], pathname: string) {
const normalized = pathname.replace(/\/$/, "") || "/";
return groups.flatMap((group) => group.items).find((item) => item.path === normalized);
}
export function DocsSidebarShell({ groups }: DocsSidebarShellProps) {
const pathname = usePathname();
const activeDoc = activeDocForPath(groups, pathname);
const activeSlug = activeDoc?.slug ?? "";
const currentTitle = activeDoc?.title;
const [open, setOpen] = useState(false);
return (
<>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
aria-label="Open table of contents"
className="sticky top-14 z-40 flex w-full items-center justify-between border-b border-border bg-bg/80 px-6 py-3 backdrop-blur-sm focus:outline-none md:hidden"
>
<span className="text-sm font-medium text-fg">{currentTitle ?? "Documentation"}</span>
<span className="flex h-8 w-8 items-center justify-center text-muted">
<HamburgerIcon className="h-4 w-4" />
</span>
</SheetTrigger>
<SheetContent side="left" className="overflow-y-auto p-0" showCloseButton={false}>
<SheetTitle className="px-6 pt-6">Table of Contents</SheetTitle>
<SidebarNav groups={groups} activeSlug={activeSlug} onNavigate={() => setOpen(false)} />
</SheetContent>
</Sheet>
<aside
aria-label="Documentation"
className="hidden md:sticky md:top-14 md:block md:h-[calc(100vh-3.5rem)] md:w-60 md:overflow-y-auto"
>
<SidebarNav groups={groups} activeSlug={activeSlug} />
</aside>
</>
);
}
+83
View File
@@ -0,0 +1,83 @@
"use client";
import { useEffect, useRef, useState } from "react";
import type { Heading } from "@/lib/types";
export function DocsToc({ headings }: { headings: Heading[] }) {
const [activeId, setActiveId] = useState<string | null>(headings[0]?.id ?? null);
const observerRef = useRef<IntersectionObserver | null>(null);
useEffect(() => {
if (typeof window === "undefined" || headings.length === 0) return;
const ids = headings.map((h) => h.id);
const elements = ids
.map((id) => document.getElementById(id))
.filter((el): el is HTMLElement => el !== null);
if (elements.length === 0) return;
const visible = new Set();
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) visible.add(entry.target.id);
else visible.delete(entry.target.id);
}
let next = null;
for (const h of headings) {
if (visible.has(h.id)) {
next = h.id;
break;
}
}
if (!next && visible.size === 0) {
const scrollY = window.scrollY;
for (let i = elements.length - 1; i >= 0; i--) {
if (elements[i].offsetTop <= scrollY + 80) {
next = elements[i].id;
break;
}
}
}
if (next) setActiveId(next);
},
{ rootMargin: "-64px 0px -60% 0px", threshold: 0 },
);
elements.forEach((el) => observer.observe(el));
observerRef.current = observer;
return () => observer.disconnect();
}, [headings]);
if (headings.length === 0) return null;
return (
<aside
aria-label="On this page"
className="sticky top-14 hidden h-[calc(100vh-3.5rem)] overflow-y-auto py-8 pl-0 pr-4 lg:block"
>
<p className="mb-3 pl-4 text-xs font-semibold uppercase tracking-[0.05em] text-fg">
On this page
</p>
<nav>
{headings.map((h) => {
const indent = h.level === 3 ? "pl-7" : h.level === 4 ? "pl-10" : "pl-4";
return (
<a
key={h.id}
href={`#${h.id}`}
className={`block py-1 pr-4 ${indent} text-[0.8125rem] leading-snug no-underline transition hover:text-fg ${
activeId === h.id ? "text-fg" : "text-muted"
}`}
>
{h.text}
</a>
);
})}
</nav>
</aside>
);
}
+172
View File
@@ -0,0 +1,172 @@
type FlowTone = "graph" | "text" | "compiler" | "human";
type FlowNodeSpec = {
id: string;
label: string;
x: number;
y: number;
tone?: FlowTone;
};
type FlowEdgeSpec = {
id?: string;
source: string;
target: string;
label?: string;
};
export type FlowChartSpec = {
type: "flow";
title?: string;
height?: number;
nodes: FlowNodeSpec[];
edges: FlowEdgeSpec[];
};
// x/y in the spec are the top-left of each node, in px.
const NODE_W = 224;
const NODE_H = 72;
const PAD = 80;
const TONE_CLASS: Record<FlowTone, string> = {
// faint, "old" loop
text: "border border-border bg-bg text-muted",
// the enforcer: strong inverted
compiler: "bg-fg text-bg",
// the good path: clean and present
graph: "border border-fg/40 bg-surface text-fg",
// human review
human: "border border-border bg-surface-muted text-fg",
};
export function FlowChart({ spec }: { spec: FlowChartSpec }) {
const byId = new Map(spec.nodes.map((n) => [n.id, n]));
const maxX = Math.max(0, ...spec.nodes.map((n) => n.x));
const maxY = Math.max(0, ...spec.nodes.map((n) => n.y));
const width = PAD * 2 + maxX + NODE_W;
const height = Math.max(spec.height ?? 0, PAD * 2 + maxY + NODE_H);
const cx = (n: FlowNodeSpec) => PAD + n.x + NODE_W / 2;
const topY = (n: FlowNodeSpec) => PAD + n.y;
const bottomY = (n: FlowNodeSpec) => PAD + n.y + NODE_H;
const edges = spec.edges
.map((edge, i) => {
const s = byId.get(edge.source);
const t = byId.get(edge.target);
if (!s || !t) return null;
const loopBack = t.y <= s.y;
let path: string;
let labelX: number;
let labelY: number;
if (loopBack) {
const leftEdge = Math.min(PAD + s.x, PAD + t.x);
const sy = PAD + s.y + NODE_H / 2;
const ty = PAD + t.y + NODE_H / 2;
const bulge = leftEdge - 40;
path = `M ${leftEdge} ${sy} C ${bulge} ${sy}, ${bulge} ${ty}, ${leftEdge} ${ty}`;
labelX = bulge;
labelY = (sy + ty) / 2;
} else {
const sx = cx(s);
const sy = bottomY(s);
const tx = cx(t);
const ty = topY(t);
const mid = (sy + ty) / 2;
path = `M ${sx} ${sy} C ${sx} ${mid}, ${tx} ${mid}, ${tx} ${ty}`;
labelX = (sx + tx) / 2;
labelY = mid;
}
return {
key: edge.id ?? `${edge.source}-${edge.target}-${i}`,
path,
label: edge.label,
labelX,
labelY,
};
})
.filter((e): e is NonNullable<typeof e> => e !== null);
// Only show a connection dot where an edge actually attaches. Normal edges
// attach to the source's bottom and the target's top; loop-backs attach to
// the side, so they do not light up a top/bottom dot.
const topConn = new Set<string>();
const bottomConn = new Set<string>();
for (const edge of spec.edges) {
const s = byId.get(edge.source);
const t = byId.get(edge.target);
if (!s || !t || t.y <= s.y) continue;
bottomConn.add(s.id);
topConn.add(t.id);
}
const stroke = "color-mix(in srgb, var(--color-fg) 28%, transparent)";
const dot = "color-mix(in srgb, var(--color-fg) 40%, transparent)";
return (
<div className="not-prose my-6 overflow-hidden rounded-2xl border border-border bg-bg">
{spec.title ? (
<div className="border-b border-border px-5 py-3.5 font-mono text-xs font-medium text-muted">
{spec.title}
</div>
) : null}
<div
className="overflow-hidden p-2"
style={{
backgroundImage: "radial-gradient(var(--color-border) 1px, transparent 1px)",
backgroundSize: "22px 22px",
backgroundPosition: "-1px -1px",
}}
>
{/* Scales down to fit the column; never upscales past its natural size. */}
<svg
viewBox={`0 0 ${width} ${height}`}
className="mx-auto block h-auto w-full"
style={{ maxWidth: width }}
role="img"
aria-label={spec.title ?? "diagram"}
>
{edges.map((e) => (
<path key={e.key} d={e.path} fill="none" stroke={stroke} strokeWidth={1.5} />
))}
{spec.nodes.map((n) => (
<g key={`dots-${n.id}`} fill={dot}>
{topConn.has(n.id) && <circle cx={cx(n)} cy={topY(n)} r={3} />}
{bottomConn.has(n.id) && <circle cx={cx(n)} cy={bottomY(n)} r={3} />}
</g>
))}
{edges
.filter((e) => e.label)
.map((e) => (
<foreignObject
key={`label-${e.key}`}
x={e.labelX - 40}
y={e.labelY - 12}
width={80}
height={24}
>
<div className="flex h-full w-full items-center justify-center">
<span className="rounded border border-border bg-bg px-1.5 py-0.5 font-mono text-[0.6875rem] text-muted">
{e.label}
</span>
</div>
</foreignObject>
))}
{spec.nodes.map((n) => (
<foreignObject key={n.id} x={PAD + n.x} y={PAD + n.y} width={NODE_W} height={NODE_H}>
<div
className={`flex h-full w-full items-center justify-center rounded-xl px-4 text-center text-[0.875rem] font-medium leading-snug ${
TONE_CLASS[n.tone ?? "text"]
}`}
>
{n.label}
</div>
</foreignObject>
))}
</svg>
</div>
</div>
);
}
+42
View File
@@ -0,0 +1,42 @@
"use client";
import type { MouseEvent } from "react";
import { useRef, useState } from "react";
export function HeadingAnchor({ id }: { id?: string }) {
const [copied, setCopied] = useState(false);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
if (!id) return null;
async function handleClick(event: MouseEvent<HTMLAnchorElement>) {
event.preventDefault();
const url = `${window.location.origin}${window.location.pathname}#${id}`;
try {
await navigator.clipboard.writeText(url);
setCopied(true);
} catch {}
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setCopied(false), 1400);
}
return (
<a
href={`#${id}`}
onClick={handleClick}
aria-label={copied ? "Anchor link copied" : "Copy anchor link"}
className="heading-anchor ml-2 inline-flex h-5 w-5 items-center justify-center align-middle text-muted no-underline opacity-0 transition-opacity hover:text-fg group-hover:opacity-100 focus-visible:opacity-100"
>
{copied ? (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
) : (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
)}
</a>
);
}
+82
View File
@@ -0,0 +1,82 @@
"use client";
import type { CSSProperties } from "react";
import { useState } from "react";
import { highlight } from "@/lib/highlight";
const STEPS = [
{
command: "zero init",
output: 'Created zero.toml\nCreated zero.graph graph:e3b0c442\nInitialized package "hello"',
},
{
command: 'zero patch --op addMain --op \'addCheckWrite fn="main" text="hello from zero\\n"\'',
output: "Patched zero.graph\n graph hash graph:a7f7e689\n symbols main",
},
{
command: "zero run",
output: "hello from zero",
},
];
function TerminalIcon() {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
className="shrink-0 text-muted"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<polyline points="4 17 10 11 4 5" />
<line x1="12" y1="19" x2="20" y2="19" />
</svg>
);
}
export function ChatToolRuns({ startDelayMs = 0 }: { startDelayMs?: number }) {
const [open, setOpen] = useState<number[]>([]);
const toggle = (i: number) =>
setOpen((prev) => (prev.includes(i) ? prev.filter((n) => n !== i) : [...prev, i]));
return (
<div className="flex flex-col gap-2">
{STEPS.map((step, i) => {
const isOpen = open.includes(i);
return (
<div
key={step.command}
className="home-tool-row overflow-hidden rounded-lg border border-border bg-surface"
style={{ "--chat-delay": `${startDelayMs + i * 180}ms` } as CSSProperties}
>
<button
type="button"
onClick={() => toggle(i)}
aria-expanded={isOpen}
className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 font-mono text-[0.78125rem] leading-relaxed text-muted transition-colors hover:bg-surface-muted"
>
<TerminalIcon />
<span
className={`min-w-0 flex-1 text-left text-fg/80 ${
isOpen ? "whitespace-pre-wrap break-all" : "truncate"
}`}
dangerouslySetInnerHTML={{ __html: highlight(step.command, "sh") }}
/>
</button>
{isOpen && (
<pre className="m-0 overflow-x-auto border-t border-border/50 bg-bg px-3 py-2.5 text-[0.78125rem] leading-relaxed text-fg/70">
{step.output}
</pre>
)}
</div>
);
})}
</div>
);
}
+57
View File
@@ -0,0 +1,57 @@
"use client";
import type { ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
export function HomeChatViewport({ children }: { children: ReactNode }) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (visible) {
return;
}
const element = ref.current;
if (!element) {
return;
}
const isReady = () => {
const rect = element.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const fitsInViewport = rect.height <= viewportHeight;
if (fitsInViewport) {
return rect.top >= 0 && rect.bottom <= viewportHeight;
}
return rect.top <= viewportHeight * 0.12 && rect.bottom >= viewportHeight * 0.88;
};
const update = () => {
if (!isReady()) {
return;
}
setVisible(true);
window.removeEventListener("scroll", update);
window.removeEventListener("resize", update);
};
update();
window.addEventListener("scroll", update, { passive: true });
window.addEventListener("resize", update);
return () => {
window.removeEventListener("scroll", update);
window.removeEventListener("resize", update);
};
}, [visible]);
return (
<div ref={ref} className="home-chat-viewport" data-chat-visible={visible ? "true" : "false"}>
{children}
</div>
);
}
+64
View File
@@ -0,0 +1,64 @@
import type { SVGProps } from "react";
export function LogoIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width="18" height="16" viewBox="0 0 76 65" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="currentColor" />
</svg>
);
}
export function ArrowRightIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
);
}
export function GithubIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12Z" />
</svg>
);
}
export function ChevronDownIcon({ className = "", ...props }: SVGProps<SVGSVGElement>) {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
{...props}
>
<polyline points="6 9 12 15 18 9" />
</svg>
);
}
export function HamburgerIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" {...props}>
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
);
}
export function CloseIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" {...props}>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
);
}
+80
View File
@@ -0,0 +1,80 @@
"use client";
import { useState } from "react";
const TABS = [
{
id: "humans",
label: "For humans",
command: "curl -fsSL https://zerolang.ai/install.sh | bash",
},
{
id: "agents",
label: "For agents",
command: "npx skills add vercel-labs/zerolang",
},
] as const;
type TabId = (typeof TABS)[number]["id"];
export function InstallCopy() {
const [activeId, setActiveId] = useState<TabId>(TABS[0].id);
const [copied, setCopied] = useState(false);
const active = TABS.find((t) => t.id === activeId) ?? TABS[0];
async function handleCopy() {
try {
await navigator.clipboard.writeText(active.command);
setCopied(true);
setTimeout(() => setCopied(false), 1400);
} catch {}
}
return (
<div className="mt-6 flex w-full max-w-[32rem] flex-col items-center gap-3">
<div className="flex items-center gap-4 text-[0.9375rem]">
{TABS.map((tab, i) => (
<div key={tab.id} className="flex items-center gap-4">
{i > 0 && <span className="h-4 w-px bg-border" />}
<button
type="button"
onClick={() => {
setActiveId(tab.id);
setCopied(false);
}}
className={`cursor-pointer font-medium transition ${
activeId === tab.id ? "text-fg" : "text-muted hover:text-fg"
}`}
>
{tab.label}
</button>
</div>
))}
</div>
<div className="flex w-full items-center overflow-hidden rounded-md border border-border bg-surface">
<code className="flex-1 overflow-x-auto whitespace-nowrap px-4 py-2 text-left font-mono text-[0.8125rem] tracking-tight text-muted">
<span className="text-muted/60">$ </span>
{active.command}
</code>
<button
type="button"
aria-label="Copy install command"
onClick={handleCopy}
className="flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center self-stretch border-l border-border text-muted transition hover:bg-surface-muted hover:text-fg"
>
{copied ? (
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" className="text-success">
<path d="M13.78 3.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 8.28a.75.75 0 0 1 1.06-1.06L6 9.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" />
</svg>
) : (
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
<path d="M5 1.75A1.75 1.75 0 0 1 6.75 0h5.5A1.75 1.75 0 0 1 14 1.75v5.5A1.75 1.75 0 0 1 12.25 9H11V7.5h1.25a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-5.5a.25.25 0 0 0-.25.25V3H5V1.75Z" />
<path d="M2 4.75A1.75 1.75 0 0 1 3.75 3h5.5A1.75 1.75 0 0 1 11 4.75v5.5A1.75 1.75 0 0 1 9.25 12h-5.5A1.75 1.75 0 0 1 2 10.25v-5.5Zm1.75-.25a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h5.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-5.5Z" />
</svg>
)}
</button>
</div>
</div>
);
}
+143
View File
@@ -0,0 +1,143 @@
/* Theme-aware, responsive version of the "agent coding loop" comparison.
Scales to fit its container (never upscales past natural size). */
type Tone = "step" | "compiler" | "optional";
type Node = { label: string; tone: Tone };
type Loop = { from: number; to: number; side: "left" | "right" };
type Col = { x: number; kicker: string; title: string; loop: Loop; nodes: Node[] };
const W = 372;
const H = 68;
const NSTART = 88;
const STEP = 104;
const COLS: Col[] = [
{
x: 44,
kicker: "Traditional",
title: "Text is the source of truth",
loop: { from: 5, to: 0, side: "left" },
nodes: [
{ label: "agent writes source text", tone: "step" },
{ label: "format", tone: "step" },
{ label: "check", tone: "step" },
{ label: "build", tone: "step" },
{ label: "test", tone: "step" },
{ label: "inspect failures", tone: "step" },
],
},
{
x: 520,
kicker: "Zerolang",
title: "Graph is the program",
loop: { from: 1, to: 0, side: "right" },
nodes: [
{ label: "agent writes graph patch", tone: "step" },
{ label: "compiler checks patch", tone: "compiler" },
{ label: "projection available for review", tone: "optional" },
],
},
];
const TONE: Record<Tone, string> = {
step: "h-full w-full border border-border bg-surface text-fg",
compiler: "h-full w-full bg-fg font-semibold text-bg",
optional: "h-full w-full border border-dashed border-border bg-transparent text-muted",
};
const VB_W = 936;
const MAX = Math.max(...COLS.map((c) => c.nodes.length));
const VB_H = NSTART + (MAX - 1) * STEP + H;
const STROKE = "color-mix(in srgb, var(--color-fg) 30%, transparent)";
const ARROW = "color-mix(in srgb, var(--color-fg) 55%, transparent)";
const nodeTop = (i: number) => NSTART + i * STEP;
export function LoopDiagram() {
const verticals: { key: string; d: string }[] = [];
const loops: { key: string; d: string; lx: number; ly: number }[] = [];
for (const col of COLS) {
const cx = col.x + W / 2;
col.nodes.forEach((_, i) => {
if (i < col.nodes.length - 1) {
verticals.push({
key: `${col.x}-${i}`,
d: `M ${cx} ${nodeTop(i) + H + 2} L ${cx} ${nodeTop(i + 1) - 4}`,
});
}
});
const sy = nodeTop(col.loop.from) + H / 2;
const ty = nodeTop(col.loop.to) + H / 2;
const r = 12;
let d: string;
let lx: number;
if (col.loop.side === "right") {
const edge = col.x + W;
const bulge = edge + 28;
d = `M ${edge} ${sy} H ${bulge - r} Q ${bulge} ${sy} ${bulge} ${sy - r} V ${ty + r} Q ${bulge} ${ty} ${bulge - r} ${ty} H ${edge + 3}`;
lx = bulge;
} else {
const edge = col.x;
const bulge = edge - 36;
d = `M ${edge} ${sy} H ${bulge + r} Q ${bulge} ${sy} ${bulge} ${sy - r} V ${ty + r} Q ${bulge} ${ty} ${bulge + r} ${ty} H ${edge - 3}`;
lx = bulge;
}
loops.push({ key: `loop-${col.x}`, d, lx, ly: (sy + ty) / 2 });
}
return (
<svg
viewBox={`-40 0 ${VB_W + 80} ${VB_H}`}
className="mx-auto block h-auto w-full"
style={{ maxWidth: VB_W + 80 }}
role="img"
aria-label="Traditional source loop versus the Zerolang graph loop"
>
<defs>
<marker id="loop-ah" markerUnits="userSpaceOnUse" markerWidth="10" markerHeight="10" refX="7" refY="5" orient="auto">
<path d="M1.5 1.5 L8 5 L1.5 8.5 Z" fill={ARROW} />
</marker>
</defs>
{/* column headers */}
{COLS.map((col) => (
<foreignObject key={`h-${col.x}`} x={col.x} y={0} width={W} height={NSTART - 24}>
<div className="font-mono text-[12px] font-medium uppercase tracking-[0.2em] text-muted">
{col.kicker}
</div>
<div className="mt-1.5 text-[20px] font-medium tracking-[-0.02em] text-fg">{col.title}</div>
</foreignObject>
))}
{verticals.map((v) => (
<path key={v.key} d={v.d} fill="none" stroke={STROKE} strokeWidth={1.75} markerEnd="url(#loop-ah)" />
))}
{loops.map((l) => (
<path key={l.key} d={l.d} fill="none" stroke={STROKE} strokeWidth={1.75} markerEnd="url(#loop-ah)" />
))}
{loops.map((l) => (
<foreignObject key={`lbl-${l.key}`} x={l.lx - 40} y={l.ly - 16} width={80} height={32}>
<div className="flex h-full w-full items-center justify-center">
<span className="rounded border border-border bg-bg px-1.5 py-0.5 font-mono text-[11px] text-muted">
repeat
</span>
</div>
</foreignObject>
))}
{COLS.map((col) =>
col.nodes.map((n, i) => (
<foreignObject key={`${col.x}-${i}`} x={col.x} y={nodeTop(i)} width={W} height={H}>
<div
className={`flex items-center justify-center rounded-2xl px-4 text-center text-[16px] font-medium leading-[1.2] tracking-[-0.01em] ${TONE[n.tone]}`}
>
{n.label}
</div>
</foreignObject>
)),
)}
</svg>
);
}
+194
View File
@@ -0,0 +1,194 @@
"use client";
import type { KeyboardEvent } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import type { SearchResult } from "@/lib/types";
export function Search() {
const router = useRouter();
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const [results, setResults] = useState<SearchResult[]>([]);
const [loading, setLoading] = useState(false);
const [activeIndex, setActiveIndex] = useState(0);
const inputRef = useRef<HTMLInputElement | null>(null);
const listRef = useRef<HTMLDivElement | null>(null);
const abortRef = useRef<AbortController | null>(null);
const navigate = useCallback(
(href: string) => {
setOpen(false);
setQuery("");
setResults([]);
router.push(href);
},
[router],
);
useEffect(() => {
function onKeyDown(e: globalThis.KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
setOpen((prev) => !prev);
}
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
useEffect(() => {
if (open) {
setTimeout(() => inputRef.current?.focus(), 0);
} else {
setQuery("");
setResults([]);
}
}, [open]);
useEffect(() => {
const q = query.trim();
if (!q) {
setResults([]);
setLoading(false);
return;
}
setLoading(true);
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
const timeout = setTimeout(async () => {
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: controller.signal });
if (res.ok) {
const data = (await res.json()) as { results?: SearchResult[] };
setResults(Array.isArray(data.results) ? data.results : []);
}
} catch {
// aborted or network error
} finally {
if (!controller.signal.aborted) setLoading(false);
}
}, 150);
return () => {
clearTimeout(timeout);
controller.abort();
};
}, [query]);
useEffect(() => {
setActiveIndex(0);
}, [results]);
function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {
if (e.key === "ArrowDown") {
e.preventDefault();
setActiveIndex((i) => Math.min(i + 1, results.length - 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setActiveIndex((i) => Math.max(i - 1, 0));
} else if (e.key === "Enter" && results[activeIndex]) {
e.preventDefault();
navigate(results[activeIndex].href);
}
}
useEffect(() => {
const active = listRef.current?.querySelector("[data-active='true']");
active?.scrollIntoView({ block: "nearest" });
}, [activeIndex]);
const hasQuery = query.trim().length > 0;
return (
<>
<button
onClick={() => setOpen(true)}
className="hidden cursor-pointer items-center gap-2 rounded-md border border-border bg-surface-muted px-3 py-1.5 text-sm text-muted transition-colors hover:border-fg/25 hover:text-fg sm:flex"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
Search docs
<kbd className="pointer-events-none ml-1 inline-flex items-center gap-0.5 rounded border border-border bg-bg px-1.5 py-0.5 font-mono text-[10px] text-muted">
<span>&#8984;</span>K
</kbd>
</button>
<button
onClick={() => setOpen(true)}
className="flex cursor-pointer items-center text-muted transition-colors hover:text-fg sm:hidden"
aria-label="Search docs"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
</button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent showCloseButton={false} className="gap-0 p-0 sm:max-w-lg">
<DialogTitle className="sr-only">Search documentation</DialogTitle>
<div className="flex items-center gap-2 border-b border-border px-3">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="shrink-0 text-muted">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<input
ref={inputRef}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Search docs..."
className="flex-1 bg-transparent py-3 text-sm outline-none placeholder:text-muted"
/>
{query && (
<button onClick={() => setQuery("")} className="text-muted hover:text-fg">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
)}
</div>
<div ref={listRef} className="max-h-[min(60vh,400px)] overflow-y-auto p-2">
{loading && hasQuery ? (
<div className="flex items-center justify-center py-6">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-muted border-t-transparent" />
</div>
) : hasQuery && results.length === 0 ? (
<p className="py-6 text-center text-sm text-muted">No results found.</p>
) : !hasQuery ? (
<p className="py-6 text-center text-sm text-muted">Type to search documentation...</p>
) : (
results.map((item, i) => (
<button
key={item.href}
data-active={i === activeIndex}
onClick={() => navigate(item.href)}
onMouseEnter={() => setActiveIndex(i)}
className={cn(
"flex w-full flex-col gap-1 rounded-md px-3 py-2 text-left transition-colors",
i === activeIndex ? "bg-surface-muted text-fg" : "text-fg",
)}
>
<span className="text-sm font-medium">{item.title}</span>
{item.snippet && (
<span className="line-clamp-2 text-xs leading-relaxed text-muted">{item.snippet}</span>
)}
</button>
))
)}
</div>
</DialogContent>
</Dialog>
</>
);
}
+42
View File
@@ -0,0 +1,42 @@
import Link from "next/link";
import { GeistPixelSquare } from "geist/font/pixel";
import { Search } from "@/components/search";
import { ThemeToggle } from "@/components/theme-toggle";
import { LogoIcon } from "@/components/icons";
export function SiteHeader({ stars }: { stars: string }) {
return (
<header className="sticky top-0 z-50 bg-white/90 backdrop-blur-sm dark:bg-neutral-950/90">
<div className="flex h-14 items-center justify-between gap-6 px-4">
<div className="flex items-center gap-2">
<Link href="/" className="flex items-center gap-2">
<LogoIcon width="16" height="14" />
<span className={`${GeistPixelSquare.className} text-lg`}>zerolang</span>
</Link>
</div>
<nav className="flex items-center gap-4">
<Link
href="/getting-started"
className="text-sm text-neutral-500 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
>
Docs
</Link>
<Search />
<a
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPO || "vercel-labs/zerolang"}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-sm text-neutral-500 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
>
<svg viewBox="0 0 16 16" className="h-4 w-4" fill="currentColor" aria-hidden="true">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
{stars ? <span>{stars}</span> : null}
</a>
<ThemeToggle />
</nav>
</div>
</header>
);
}
+17
View File
@@ -0,0 +1,17 @@
"use client";
import type { ReactNode } from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
export function ThemeProvider({ children }: { children: ReactNode }) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}
+46
View File
@@ -0,0 +1,46 @@
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export function ThemeToggle() {
const { theme, resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <div className="h-8 w-8" />;
}
const isDark = (theme === "system" ? resolvedTheme : theme) === "dark";
return (
<button
type="button"
onClick={() => setTheme(isDark ? "light" : "dark")}
aria-label="Toggle theme"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-800 dark:hover:text-neutral-100"
>
{isDark ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2" />
<path d="M12 20v2" />
<path d="m4.93 4.93 1.41 1.41" />
<path d="m17.66 17.66 1.41 1.41" />
<path d="M2 12h2" />
<path d="M20 12h2" />
<path d="m6.34 17.66-1.41 1.41" />
<path d="m19.07 4.93-1.41 1.41" />
</svg>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
</svg>
)}
</button>
);
}
+75
View File
@@ -0,0 +1,75 @@
"use client";
import type { ComponentProps, ReactNode } from "react";
import { Dialog as DialogPrimitive } from "radix-ui";
import { cn } from "@/lib/utils";
function Dialog(props: ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogPortal(props: ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogOverlay({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
/>
);
}
type DialogContentProps = ComponentProps<typeof DialogPrimitive.Content> & {
children?: ReactNode;
showCloseButton?: boolean;
};
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: DialogContentProps) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border border-border bg-bg p-6 shadow-card outline-none duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogTitle({ className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("font-semibold text-fg", className)}
{...props}
/>
);
}
export { Dialog, DialogPortal, DialogOverlay, DialogContent, DialogTitle };
+87
View File
@@ -0,0 +1,87 @@
"use client";
import type { ComponentProps, ReactNode } from "react";
import { Dialog as SheetPrimitive } from "radix-ui";
import { cn } from "@/lib/utils";
function Sheet(props: ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}
function SheetPortal(props: ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}
function SheetOverlay({ className, ...props }: ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
/>
);
}
type SheetContentProps = ComponentProps<typeof SheetPrimitive.Content> & {
children?: ReactNode;
side?: "left" | "right";
showCloseButton?: boolean;
overlayClassName?: string;
};
function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
overlayClassName,
...props
}: SheetContentProps) {
return (
<SheetPortal>
<SheetOverlay className={overlayClassName} />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"fixed z-50 flex flex-col gap-4 bg-white shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-neutral-950",
side === "right" &&
"inset-y-0 right-0 h-full w-3/4 border-l border-neutral-200 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right dark:border-neutral-800 sm:max-w-sm",
side === "left" &&
"inset-y-0 left-0 h-full w-3/4 border-r border-neutral-200 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left dark:border-neutral-800 sm:max-w-sm",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Content>
</SheetPortal>
);
}
function SheetTitle({ className, ...props }: ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("font-semibold text-neutral-900 dark:text-neutral-100", className)}
{...props}
/>
);
}
function SheetTrigger({ className, ...props }: ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" className={cn(className)} {...props} />;
}
export { Sheet, SheetTrigger, SheetContent, SheetTitle };