chore(lint): escape entities and hoist inline objects in website

This commit is contained in:
Mohamed Boudra
2026-04-23 23:15:01 +07:00
parent 9715df9385
commit 8852d62cef
15 changed files with 403 additions and 298 deletions

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useEffect, useRef, useState } from "react";
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
// --- Vector math utilities ---
@@ -107,10 +107,21 @@ function ButterflyVisual({
direction = "right",
heading = 0,
}: ButterflyVisualProps) {
const wingStyle = {
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
};
const wingStyle = useMemo(
() => ({
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
}),
[delay, duration],
);
const bodyStyle = useMemo(
() => ({
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
}),
[delay, duration],
);
const scaleX = direction === "left" ? -1 : 1;
@@ -119,24 +130,28 @@ function ButterflyVisual({
// -30° = horizontal, 0° = 30° down (base), +90° = 120° down (nearly straight down)
const clampedHeading = Math.max(-30, Math.min(90, heading));
const svgStyle = useMemo(
() => ({
filter: "url(#painterly)",
transform: `scaleX(${scaleX}) rotateY(25deg) rotate(30deg) rotateZ(${clampedHeading}deg)`,
transition: "transform 0.3s ease-out",
}),
[scaleX, clampedHeading],
);
const farWingStyle = useMemo(
() => ({ ...wingStyle, animationDelay: `${delay + 0.08}s` }),
[wingStyle, delay],
);
const lowerWingStyle = useMemo(
() => ({ ...wingStyle, animationDelay: `${delay + 0.05}s` }),
[wingStyle, delay],
);
return (
<div
className="animate-flutter-body"
style={{
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
}}
>
<svg
viewBox="0 0 50 40"
width={size}
height={size * 0.8}
style={{
filter: "url(#painterly)",
transform: `scaleX(${scaleX}) rotateY(25deg) rotate(30deg) rotateZ(${clampedHeading}deg)`,
transition: "transform 0.3s ease-out",
}}
>
<div className="animate-flutter-body" style={bodyStyle}>
<svg viewBox="0 0 50 40" width={size} height={size * 0.8} style={svgStyle}>
<defs>
<filter id="painterly" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" result="noise" />
@@ -153,7 +168,7 @@ function ButterflyVisual({
{/* Far side wings (darker, behind, slightly rotated) */}
<g
className="animate-flutter-left origin-[38px_20px]"
style={{ ...wingStyle, animationDelay: `${delay + 0.08}s` }}
style={farWingStyle}
transform="translate(6, 0) rotate(8, 38, 20)"
>
<path d="M38 20 Q20 2 8 10 Q0 18 10 28 Q25 32 38 22" fill={darken(color, 50)} />
@@ -165,10 +180,7 @@ function ButterflyVisual({
<path d="M38 20 Q20 2 8 10 Q0 18 10 28 Q25 32 38 22" fill={color} />
</g>
<g
className="animate-flutter-left origin-[38px_20px]"
style={{ ...wingStyle, animationDelay: `${delay + 0.05}s` }}
>
<g className="animate-flutter-left origin-[38px_20px]" style={lowerWingStyle}>
<path d="M38 22 Q25 30 18 38 Q28 42 36 34 Q40 28 38 24" fill={color} />
</g>
</svg>
@@ -358,17 +370,18 @@ export function FloatingButterfly({
};
}, [mounted]);
const containerStyle = useMemo(
() => ({
...style,
transform: `translate(${renderState.x}px, ${renderState.y}px)`,
}),
[style, renderState.x, renderState.y],
);
if (!mounted) return null;
return (
<div
ref={elementRef}
className="absolute pointer-events-none"
style={{
...style,
transform: `translate(${renderState.x}px, ${renderState.y}px)`,
}}
>
<div ref={elementRef} className="absolute pointer-events-none" style={containerStyle}>
<ButterflyVisual
size={size}
color={color}

View File

@@ -1,5 +1,15 @@
import * as React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { motion, AnimatePresence, type Transition } from "framer-motion";
const OVERLAY_INITIAL = { opacity: 0 };
const OVERLAY_ANIMATE = { opacity: 1 };
const OVERLAY_EXIT = { opacity: 0 };
const OVERLAY_TRANSITION: Transition = { duration: 0.2 };
const PANEL_INITIAL = { opacity: 0, scale: 0.95 };
const PANEL_ANIMATE = { opacity: 1, scale: 1 };
const PANEL_EXIT = { opacity: 0, scale: 0.95 };
const PANEL_TRANSITION: Transition = { duration: 0.2, ease: "easeOut" };
interface CommandDialogProps {
trigger: React.ReactNode;
@@ -43,18 +53,18 @@ export function CommandDialog({
{open && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
initial={OVERLAY_INITIAL}
animate={OVERLAY_ANIMATE}
exit={OVERLAY_EXIT}
transition={OVERLAY_TRANSITION}
className="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm"
onClick={() => setOpen(false)}
/>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2, ease: "easeOut" }}
initial={PANEL_INITIAL}
animate={PANEL_ANIMATE}
exit={PANEL_EXIT}
transition={PANEL_TRANSITION}
className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 w-full max-w-md rounded-xl border border-white/20 bg-background p-6 space-y-4"
>
<div className="space-y-2">

View File

@@ -27,6 +27,9 @@ import {
} from "lucide-react";
import { ClaudeIcon, CodexIcon, SourceControlIcon } from "~/components/mockup";
const USER_BUBBLE_STYLE = { borderRadius: "16px 2px 16px 16px" };
const NEW_BADGE_STYLE = { backgroundColor: "rgba(46, 160, 67, 0.2)" };
// ── Stagger timing ──────────────────────────────────────
const D = {
@@ -477,7 +480,7 @@ function ChatMessages({ items }: { items: ChatItem[] }) {
<div key={i} className="flex justify-end py-1">
<div
className="bg-mock-surface2 px-2.5 py-1.5 max-w-[85%] leading-none"
style={{ borderRadius: "16px 2px 16px 16px" }}
style={USER_BUBBLE_STYLE}
>
<span className="text-[11px] text-mock-fg leading-none">{item.text}</span>
</div>
@@ -593,7 +596,7 @@ function ExplorerSidebar() {
<span className="text-[11px] text-mock-fg-muted truncate min-w-0"> src/pages</span>
<span
className="text-[10px] text-mock-green-400 px-2 py-[2px] rounded-md flex-shrink-0"
style={{ backgroundColor: "rgba(46, 160, 67, 0.2)" }}
style={NEW_BADGE_STYLE}
>
New
</span>
@@ -658,7 +661,7 @@ function ExplorerSidebar() {
{file.isNew && (
<span
className="text-[10px] text-mock-green-400 px-2 py-[2px] rounded-md flex-shrink-0"
style={{ backgroundColor: "rgba(46, 160, 67, 0.2)" }}
style={NEW_BADGE_STYLE}
>
New
</span>
@@ -683,53 +686,66 @@ const SYNCED_LOADER_DURATION_MS = 950;
const DOT_COUNT = 6;
const STEP_MS = SYNCED_LOADER_DURATION_MS / DOT_COUNT; // 158.333…ms per step
interface SyncedLoaderDotProps {
dotIndex: number;
dotSize: number;
gap: number;
}
function SyncedLoaderDot({ dotIndex, dotSize, gap }: SyncedLoaderDotProps) {
const col = dotIndex % 2;
const row = Math.floor(dotIndex / 2);
const sequenceIndex = DOT_SEQUENCE.indexOf(dotIndex as (typeof DOT_SEQUENCE)[number]);
const delayMs = -(sequenceIndex * STEP_MS);
const style = React.useMemo(
() => ({
position: "absolute" as const,
left: col * (dotSize + gap),
top: row * (dotSize + gap),
width: dotSize,
height: dotSize,
borderRadius: "50%",
backgroundColor: "#f59e0b",
animationName: "synced-snake-dot",
animationDuration: `${SYNCED_LOADER_DURATION_MS}ms`,
animationTimingFunction: "linear",
animationIterationCount: "infinite",
animationDelay: `${delayMs}ms`,
}),
[col, row, dotSize, gap, delayMs],
);
return <div style={style} />;
}
function SyncedLoader({ size = 11 }: { size?: number }) {
const gap = Math.max(1, Math.round(size * 0.12));
const dotSize = Math.max(2, Math.floor((size - gap * 2) / 3));
const gridW = dotSize * 2 + gap;
const gridH = dotSize * 3 + gap * 2;
const outerStyle = React.useMemo(
() => ({
width: size,
height: size,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}),
[size],
);
const innerStyle = React.useMemo(
() => ({ position: "relative" as const, width: gridW, height: gridH }),
[gridW, gridH],
);
return (
<div
style={{
width: size,
height: size,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}}
>
<div style={{ position: "relative", width: gridW, height: gridH }}>
{Array.from({ length: DOT_COUNT }).map((_, dotIndex) => {
const col = dotIndex % 2;
const row = Math.floor(dotIndex / 2);
// The snake sequence index for this dot: when is it the "head"?
const sequenceIndex = DOT_SEQUENCE.indexOf(dotIndex as (typeof DOT_SEQUENCE)[number]);
// Negative delay syncs the animation so this dot is the head at t=0 when sequenceIndex=0.
// At t=0, headIndex should be 0, meaning sequenceIndex=0 dot is the head.
// Each dot is shifted by sequenceIndex steps back in time.
const delayMs = -(sequenceIndex * STEP_MS);
return (
<div
key={dotIndex}
style={{
position: "absolute",
left: col * (dotSize + gap),
top: row * (dotSize + gap),
width: dotSize,
height: dotSize,
borderRadius: "50%",
backgroundColor: "#f59e0b",
animationName: "synced-snake-dot",
animationDuration: `${SYNCED_LOADER_DURATION_MS}ms`,
animationTimingFunction: "linear",
animationIterationCount: "infinite",
animationDelay: `${delayMs}ms`,
}}
/>
);
})}
<div style={outerStyle}>
<div style={innerStyle}>
{Array.from({ length: DOT_COUNT }).map((_, dotIndex) => (
<SyncedLoaderDot key={dotIndex} dotIndex={dotIndex} dotSize={dotSize} gap={gap} />
))}
</div>
</div>
);
@@ -869,15 +885,14 @@ function DesktopMockup() {
return () => window.removeEventListener("resize", updateScale);
}, []);
const containerStyle = React.useMemo(() => ({ height: `${1200 * (9 / 16) * scale}px` }), [scale]);
const innerStyle = React.useMemo(() => ({ width: 1200, transform: `scale(${scale})` }), [scale]);
return (
<div
ref={containerRef}
className="w-full overflow-hidden"
style={{ height: `${1200 * (9 / 16) * scale}px` }}
>
<div ref={containerRef} className="w-full overflow-hidden" style={containerStyle}>
<div
className="mx-auto rounded-xl overflow-hidden border border-mock-border bg-mock-surface0 shadow-[6px_6px_0_rgba(0,0,0,0.4)] origin-top-left"
style={{ width: 1200, transform: `scale(${scale})` }}
style={innerStyle}
>
{/* Top-level: left sidebar | center column | explorer sidebar — all full height */}
<div className="flex aspect-video">

View File

@@ -1,5 +1,38 @@
import * as React from "react";
import { motion, AnimatePresence, useInView, useScroll, useTransform } from "framer-motion";
import {
motion,
AnimatePresence,
useInView,
useScroll,
useTransform,
type Transition,
} from "framer-motion";
// Shared motion presets — hoisted so every JSX site receives the same object
// reference and doesn't trigger jsx-no-new-object-as-prop.
const FADE_IN_UP = { opacity: 0, y: 20 };
const FADE_IN = { opacity: 1, y: 0 };
const FADE_IN_UP_SMALL = { opacity: 0, y: 16 };
const FADE_IN_UP_TINY = { opacity: 0, y: -10 };
const FADE_IN_UP_XL = { opacity: 0, y: 30 };
const FADE_IN_UP_40 = { opacity: 0, y: 40 };
const FADE_IN_UP_4 = { opacity: 0, y: 4 };
const FADE_OUT_UP_4 = { opacity: 0, y: 4 };
const EASE_OUT_06: Transition = { duration: 0.6, ease: "easeOut" };
const EASE_OUT_06_DELAY_015: Transition = { duration: 0.6, delay: 0.15, ease: "easeOut" };
const EASE_OUT_06_DELAY_01: Transition = { duration: 0.6, delay: 0.1, ease: "easeOut" };
const EASE_OUT_06_DELAY_04: Transition = { duration: 0.6, delay: 0.4, ease: "easeOut" };
const EASE_OUT_08_DELAY_05: Transition = { duration: 0.8, delay: 0.5, ease: "easeOut" };
const EASE_OUT_05: Transition = { duration: 0.5, ease: "easeOut" };
const EASE_OUT_015: Transition = { duration: 0.15, ease: "easeOut" };
const DURATION_05: Transition = { duration: 0.5 };
const VIEWPORT_60 = { once: true, margin: "-60px" };
const VIEWPORT_40 = { once: true, margin: "-40px" };
const SVG_OVERFLOW_VISIBLE_STYLE = { overflow: "visible" as const };
const PHONE_PERSPECTIVE_STYLE = { minHeight: 480, perspective: 1200 };
import { CursorFieldProvider } from "~/components/butterfly";
import { CommandDialog } from "~/components/command-dialog";
import {
@@ -38,9 +71,9 @@ export function LandingPage({ title, subtitle }: LandingPageProps) {
{/* Mockup - inside hero so it's above the gradient, positioned to overflow into black section */}
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.5, ease: "easeOut" }}
initial={FADE_IN_UP_40}
animate={FADE_IN}
transition={EASE_OUT_08_DELAY_05}
className="relative px-6 md:px-8 pb-8 md:pb-16"
>
<div className="max-w-7xl mx-auto">
@@ -203,17 +236,17 @@ function Hero({ title, subtitle }: { title: React.ReactNode; subtitle: string })
return (
<div className="space-y-6">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
initial={FADE_IN_UP}
animate={FADE_IN}
transition={EASE_OUT_06}
className="text-3xl md:text-5xl font-medium tracking-tight"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
initial={FADE_IN_UP}
animate={FADE_IN}
transition={EASE_OUT_06_DELAY_015}
className="text-white/70 text-lg leading-relaxed max-w-lg"
>
{subtitle}
@@ -235,10 +268,10 @@ function AgentBadge({ name, icon }: { name: string; icon: React.ReactNode }) {
<AnimatePresence>
{hovered && (
<motion.span
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 4 }}
transition={{ duration: 0.15, ease: "easeOut" }}
initial={FADE_IN_UP_4}
animate={FADE_IN}
exit={FADE_OUT_UP_4}
transition={EASE_OUT_015}
className="absolute -top-8 left-1/2 -translate-x-1/2 px-2 py-1 rounded bg-white text-black text-xs whitespace-nowrap pointer-events-none"
>
{name}
@@ -260,10 +293,10 @@ function FeatureSection({
}) {
return (
<motion.section
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
initial={FADE_IN_UP}
whileInView={FADE_IN}
viewport={VIEWPORT_60}
transition={EASE_OUT_05}
className="space-y-8"
>
<div className="space-y-2">
@@ -278,14 +311,14 @@ function FeatureSection({
function PrinciplesSection() {
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
initial={FADE_IN_UP_SMALL}
whileInView={FADE_IN}
viewport={VIEWPORT_40}
transition={EASE_OUT_05}
className="py-32 px-6 md:px-20 max-w-3xl mx-auto text-center"
>
<p className="text-2xl md:text-4xl font-medium text-white/90">
Here's what's under the hood.
Here&apos;s what&apos;s under the hood.
</p>
</motion.div>
);
@@ -510,7 +543,7 @@ function SelfHostedDiagram() {
{/* SVG curves */}
<svg
className="absolute inset-0 w-full h-full pointer-events-none"
style={{ overflow: "visible" }}
style={SVG_OVERFLOW_VISIBLE_STYLE}
>
{[...paths.left, ...paths.right].map(
(d, i) =>
@@ -696,38 +729,41 @@ function ShortcutsSection() {
);
}
interface VoiceBarProps {
index: number;
barCount: number;
}
function VoiceBar({ index, barCount }: VoiceBarProps) {
const style = React.useMemo(() => {
const center = barCount / 2;
const dist = Math.abs(index - center) / center;
const envelope = 1 - dist * dist;
const minH = 4;
const maxH = 56;
const baseH = minH + (maxH - minH) * envelope;
const jitter = Math.sin(index * 2.3) * 0.3 + Math.cos(index * 1.7) * 0.2;
const h = Math.max(minH, baseH * (0.5 + 0.5 * Math.abs(jitter + Math.sin(index * 0.8))));
return {
height: h,
animationName: "voice-bar",
animationDuration: `${800 + (index % 5) * 200}ms`,
animationTimingFunction: "ease-in-out",
animationIterationCount: "infinite",
animationDirection: "alternate" as const,
animationDelay: `${(index % 7) * 80}ms`,
};
}, [index, barCount]);
return <div className="w-[3px] rounded-full bg-white/30" style={style} />;
}
function VoiceWaveform() {
const barCount = 48;
return (
<div className="flex items-center justify-center gap-[3px] h-16">
{Array.from({ length: barCount }).map((_, i) => {
// Create a natural-looking waveform envelope — louder in center, quieter at edges
const center = barCount / 2;
const dist = Math.abs(i - center) / center;
const envelope = 1 - dist * dist; // quadratic falloff
const minH = 4;
const maxH = 56;
const baseH = minH + (maxH - minH) * envelope;
// Vary per-bar so it doesn't look uniform
const jitter = Math.sin(i * 2.3) * 0.3 + Math.cos(i * 1.7) * 0.2;
const h = Math.max(minH, baseH * (0.5 + 0.5 * Math.abs(jitter + Math.sin(i * 0.8))));
return (
<div
key={i}
className="w-[3px] rounded-full bg-white/30"
style={{
height: h,
animationName: "voice-bar",
animationDuration: `${800 + (i % 5) * 200}ms`,
animationTimingFunction: "ease-in-out",
animationIterationCount: "infinite",
animationDirection: "alternate",
animationDelay: `${(i % 7) * 80}ms`,
}}
/>
);
})}
{Array.from({ length: barCount }).map((_, i) => (
<VoiceBar key={i} index={i} barCount={barCount} />
))}
</div>
);
}
@@ -925,9 +961,9 @@ function LocalVoiceSection() {
function GetStarted() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4, ease: "easeOut" }}
initial={FADE_IN_UP}
animate={FADE_IN}
transition={EASE_OUT_06_DELAY_04}
className="pt-10"
>
<div className="flex flex-row flex-wrap gap-3">
@@ -1440,13 +1476,24 @@ function PhoneShowcase() {
const leftX = useTransform(scrollYProgress, [0.2, 0.6], [0, -slideDistance]);
const rightX = useTransform(scrollYProgress, [0.2, 0.6], [0, slideDistance]);
const leftPhoneStyle = React.useMemo(
() => ({ opacity: sideOpacity, x: leftX, rotateY: -15, scale: 0.97 }),
[sideOpacity, leftX],
);
const rightPhoneStyle = React.useMemo(
() => ({ opacity: sideOpacity, x: rightX, rotateY: 15, scale: 0.97 }),
[sideOpacity, rightX],
);
const centerPhoneAnimate = React.useMemo(() => (textInView ? FADE_IN : {}), [textInView]);
const textAnimate = React.useMemo(() => (textInView ? FADE_IN : {}), [textInView]);
return (
<div ref={containerRef} className="flex flex-col items-center pt-4 pb-16 gap-20">
{/* Arrow + text */}
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={textInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5 }}
initial={FADE_IN_UP_TINY}
animate={textAnimate}
transition={DURATION_05}
className="flex flex-col items-center gap-1.5 px-6"
>
<svg
@@ -1474,13 +1521,10 @@ function PhoneShowcase() {
{/* Phone trio — side phones are absolute, start behind center, slide outward with perspective rotation */}
<div
className="relative flex items-center justify-center overflow-x-clip w-full"
style={{ minHeight: 480, perspective: 1200 }}
style={PHONE_PERSPECTIVE_STYLE}
>
{/* Left phone — rotated to face inward */}
<motion.div
style={{ opacity: sideOpacity, x: leftX, rotateY: -15, scale: 0.97 }}
className="w-[160px] md:w-[240px] absolute"
>
<motion.div style={leftPhoneStyle} className="w-[160px] md:w-[240px] absolute">
<img
src="/phone-1.png"
alt="Paseo sessions list"
@@ -1490,9 +1534,9 @@ function PhoneShowcase() {
{/* Center phone */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={textInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.1, ease: "easeOut" }}
initial={FADE_IN_UP_XL}
animate={centerPhoneAnimate}
transition={EASE_OUT_06_DELAY_01}
className="w-[220px] md:w-[240px] relative z-10"
>
<img
@@ -1503,10 +1547,7 @@ function PhoneShowcase() {
</motion.div>
{/* Right phone — rotated to face inward */}
<motion.div
style={{ opacity: sideOpacity, x: rightX, rotateY: 15, scale: 0.97 }}
className="w-[160px] md:w-[240px] absolute"
>
<motion.div style={rightPhoneStyle} className="w-[160px] md:w-[240px] absolute">
<img
src="/phone-3.png"
alt="Paseo diff view"
@@ -1573,10 +1614,10 @@ function CLISection() {
function FAQ() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
initial={FADE_IN_UP}
whileInView={FADE_IN}
viewport={VIEWPORT_60}
transition={EASE_OUT_05}
className="space-y-6"
>
<h2 className="text-3xl font-medium">FAQ</h2>
@@ -1587,8 +1628,8 @@ function FAQ() {
speech providers if you configure them.
</FAQItem>
<FAQItem question="Does my code leave my machine?">
Paseo doesn't send your code anywhere. Agents run locally and talk to their own APIs as
they normally would. For remote access, you can use the optional{" "}
Paseo doesn&apos;t send your code anywhere. Agents run locally and talk to their own APIs
as they normally would. For remote access, you can use the optional{" "}
<a href="/docs/security" className="underline hover:text-white/80">
end-to-end encrypted relay
</a>
@@ -1596,7 +1637,7 @@ function FAQ() {
</FAQItem>
<FAQItem question="What agents does it support?">
Claude Code, Codex, and OpenCode. Each agent runs as its own process using its own CLI.
Paseo doesn't modify or wrap their behavior.
Paseo doesn&apos;t modify or wrap their behavior.
</FAQItem>
<FAQItem question="Do I need the desktop app?">
No. You can run the daemon headless with{" "}
@@ -1616,9 +1657,9 @@ function FAQ() {
.
</FAQItem>
<FAQItem question="Can I connect from outside my network?">
Yes. You can use the hosted relay (end-to-end encrypted, Paseo can't read your traffic),
set up your own tunnel (Tailscale, Cloudflare Tunnel, etc.), or expose the daemon port
directly. See{" "}
Yes. You can use the hosted relay (end-to-end encrypted, Paseo can&apos;t read your
traffic), set up your own tunnel (Tailscale, Cloudflare Tunnel, etc.), or expose the
daemon port directly. See{" "}
<a href="/docs/configuration" className="underline hover:text-white/80">
configuration
</a>
@@ -1626,17 +1667,17 @@ function FAQ() {
</FAQItem>
<FAQItem question="Do I need git or GitHub?">
No. Paseo works in any directory. Worktrees are optional and only relevant if you use git.
You can run agents anywhere you'd normally work.
You can run agents anywhere you&apos;d normally work.
</FAQItem>
<FAQItem question="Can I get banned for using Paseo?">
<p>We can't make promises on behalf of providers.</p>
<p>We can&apos;t make promises on behalf of providers.</p>
<p>
That said, Paseo launches the official first-party CLIs (Claude Code, Codex, OpenCode)
as subprocesses. It doesn't extract tokens or call inference APIs directly. From the
provider's perspective, usage through Paseo is indistinguishable from running the CLI
yourself.
as subprocesses. It doesn&apos;t extract tokens or call inference APIs directly. From
the provider&apos;s perspective, usage through Paseo is indistinguishable from running
the CLI yourself.
</p>
<p>I've been using Paseo with all providers for months without issue.</p>
<p>I&apos;ve been using Paseo with all providers for months without issue.</p>
</FAQItem>
<FAQItem question="How do worktrees work?">
When you launch an agent with the worktree option (from the app, desktop, or CLI), Paseo
@@ -1655,17 +1696,17 @@ function FAQ() {
function SponsorCTA() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
initial={FADE_IN_UP}
whileInView={FADE_IN}
viewport={VIEWPORT_60}
transition={EASE_OUT_05}
className="rounded-xl bg-white/5 border border-white/10 p-8 md:p-10 text-left space-y-4 max-w-xl mx-auto"
>
<div className="text-sm text-muted-foreground leading-relaxed space-y-3">
<p>
I built Paseo because I wanted better tools for coding agents on my own setup. It's an
independent open source project, built around freedom of choice and real workflows. If you
like what I'm building, consider becoming a supporter.
I built Paseo because I wanted better tools for coding agents on my own setup. It&apos;s
an independent open source project, built around freedom of choice and real workflows. If
you like what I&apos;m building, consider becoming a supporter.
</p>
<p>- Mo</p>
</div>

View File

@@ -15,6 +15,10 @@ interface StarsContext {
const ReleaseCtx = createContext<ReleaseContext>({ version: "" });
const StarsCtx = createContext<StarsContext>({ stars: "" });
const PLAUSIBLE_INIT_SCRIPT = {
__html: `window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};plausible.init()`,
};
export function useRelease(): ReleaseContext {
return useContext(ReleaseCtx);
}
@@ -67,11 +71,7 @@ function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
<head>
<HeadContent />
<script async src="https://plausible.io/js/pa-cKNUoWbeH_Iksb2fh82s3.js" />
<script
dangerouslySetInnerHTML={{
__html: `window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};plausible.init()`,
}}
/>
<script dangerouslySetInnerHTML={PLAUSIBLE_INIT_SCRIPT} />
</head>
<body className="antialiased bg-background text-foreground">
{children}

View File

@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { getPosts, formatDate } from "~/posts";
import { pageMeta } from "~/meta";
@@ -12,6 +13,34 @@ export const Route = createFileRoute("/blog/")({
component: BlogIndex,
});
interface PostRowProps {
slug: string;
title: string;
date: string;
draft?: boolean;
}
function PostRow({ slug, title, date, draft }: PostRowProps) {
const params = useMemo(() => ({ _splat: slug }), [slug]);
return (
<div className="flex flex-col-reverse items-start md:flex-row md:items-center gap-x-4">
<span className="text-lg text-muted-foreground tabular-nums">
{formatDate(new Date(date))}
</span>
<Link
to="/blog/$"
params={params}
className="text-lg text-foreground hover:text-primary transition-colors"
>
{title}
{draft && (
<span className="ml-2 text-xs px-2 py-1 bg-primary/20 text-primary rounded">DRAFT</span>
)}
</Link>
</div>
);
}
function BlogIndex() {
const { drafts } = Route.useSearch();
const posts = getPosts(drafts);
@@ -25,26 +54,13 @@ function BlogIndex() {
)}
<div className="space-y-2">
{posts.map(({ slug, frontmatter }) => (
<div
<PostRow
key={slug}
className="flex flex-col-reverse items-start md:flex-row md:items-center gap-x-4"
>
<span className="text-lg text-muted-foreground tabular-nums">
{formatDate(new Date(frontmatter.date))}
</span>
<Link
to="/blog/$"
params={{ _splat: slug }}
className="text-lg text-foreground hover:text-primary transition-colors"
>
{frontmatter.title}
{frontmatter.draft && (
<span className="ml-2 text-xs px-2 py-1 bg-primary/20 text-primary rounded">
DRAFT
</span>
)}
</Link>
</div>
slug={slug}
title={frontmatter.title}
date={frontmatter.date}
draft={frontmatter.draft}
/>
))}
{posts.length === 0 && <p className="text-muted-foreground">No posts yet.</p>}
</div>

View File

@@ -18,6 +18,10 @@ const navigation = [
{ name: "Best practices", href: "/docs/best-practices" },
];
const ACTIVE_OPTIONS_EXACT = { exact: true };
const MOBILE_ACTIVE_PROPS = { className: "text-foreground" };
const DESKTOP_ACTIVE_PROPS = { className: "bg-muted text-foreground" };
function DocsLayout() {
return (
<div className="min-h-screen bg-background">
@@ -32,9 +36,9 @@ function DocsLayout() {
<Link
key={item.href}
to={item.href}
activeOptions={{ exact: true }}
activeOptions={ACTIVE_OPTIONS_EXACT}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
activeProps={{ className: "text-foreground" }}
activeProps={MOBILE_ACTIVE_PROPS}
>
{item.name}
</Link>
@@ -54,9 +58,9 @@ function DocsLayout() {
<Link
key={item.href}
to={item.href}
activeOptions={{ exact: true }}
activeOptions={ACTIVE_OPTIONS_EXACT}
className="block px-3 py-2 text-sm rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
activeProps={{ className: "bg-muted text-foreground" }}
activeProps={DESKTOP_ACTIVE_PROPS}
>
{item.name}
</Link>

View File

@@ -17,22 +17,23 @@ function BestPractices() {
<div>
<h1 className="text-3xl font-medium font-title mb-4">Best Practices</h1>
<p className="text-white/60 leading-relaxed">
What I've learned from using Paseo daily. Not rules, just patterns that have worked for
me.
What I&apos;ve learned from using Paseo daily. Not rules, just patterns that have worked
for me.
</p>
</div>
<section className="space-y-4">
<h2 className="text-xl font-medium">Agents replace typing, not thinking</h2>
<p className="text-white/60 leading-relaxed">
Your role has changed. You're no longer the one writing code line by line. You're the one
making decisions: what to build, how it should work, what the architecture looks like. The
agent executes, but you direct.
Your role has changed. You&apos;re no longer the one writing code line by line.
You&apos;re the one making decisions: what to build, how it should work, what the
architecture looks like. The agent executes, but you direct.
</p>
<p className="text-white/60 leading-relaxed">
You can't just say "implement feature X" and walk away. You still have to do the hard
part: deciding what to build, how it fits into the system, what trade-offs to make.
Thinking is not optional. At least for now, agents replace the typing, not the thinking.
You can&apos;t just say &quot;implement feature X&quot; and walk away. You still have to
do the hard part: deciding what to build, how it fits into the system, what trade-offs to
make. Thinking is not optional. At least for now, agents replace the typing, not the
thinking.
</p>
</section>
@@ -41,16 +42,17 @@ function BestPractices() {
<p className="text-white/60 leading-relaxed">
The agent needs a way to verify its work. TDD is one implementation of this pattern: get
the agent to write a failing test, verify it fails for the right reasons, then tell it to
make the test pass. The agent can loop on its own because it knows what "done" means.
make the test pass. The agent can loop on its own because it knows what &quot;done&quot;
means.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Invest in tooling</h2>
<p className="text-white/60 leading-relaxed">
It's not just test runners. For web apps, something like Playwright MCP lets the agent
take screenshots and verify UI changes. For a SaaS app I built a CLI that wraps all the
business logic so the agent could launch jobs, check statuses, and scrape data without
It&apos;s not just test runners. For web apps, something like Playwright MCP lets the
agent take screenshots and verify UI changes. For a SaaS app I built a CLI that wraps all
the business logic so the agent could launch jobs, check statuses, and scrape data without
going through the UI.
</p>
<p className="text-white/60 leading-relaxed">
@@ -62,22 +64,22 @@ function BestPractices() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Agents are cheap</h2>
<p className="text-white/60 leading-relaxed">
Don't be shy about running multiple agents. Paseo lets you launch agents in isolated
Don&apos;t be shy about running multiple agents. Paseo lets you launch agents in isolated
worktrees. Kick one off with voice while walking, then kick off another. They work
independently. You get a notification when they're done.
independently. You get a notification when they&apos;re done.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Use voice extensively</h2>
<p className="text-white/60 leading-relaxed">
It's much more natural to use voice to communicate ideas and pull them out of your brain.
The agent will parse and organize your thoughts better than if you try to write the
perfect prompt. You don't need to organize anything. Just talk.
It&apos;s much more natural to use voice to communicate ideas and pull them out of your
brain. The agent will parse and organize your thoughts better than if you try to write the
perfect prompt. You don&apos;t need to organize anything. Just talk.
</p>
<p className="text-white/60 leading-relaxed">
Current speech-to-text models are really good. They catch accents, acronyms, technical
terms. And even when they don't, the LLM will infer what you meant.
terms. And even when they don&apos;t, the LLM will infer what you meant.
</p>
</section>
@@ -85,22 +87,22 @@ function BestPractices() {
<h2 className="text-xl font-medium">Understand the type of work</h2>
<p className="text-white/60 leading-relaxed">
Sometimes you need to plan: design a spec, verify it, get the agent to follow through.
Maybe it takes a couple of agents to work through it. Other times it's conversational:
kick off a single agent and start talking, asking questions. Match your approach to the
task.
Maybe it takes a couple of agents to work through it. Other times it&apos;s
conversational: kick off a single agent and start talking, asking questions. Match your
approach to the task.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Iterate and refactor often</h2>
<p className="text-white/60 leading-relaxed">
Don't expect perfect. Expect working. Make it work, make it correct, make it beautiful.
Each iteration gets you closer. With tests, refactoring is cheap.
Don&apos;t expect perfect. Expect working. Make it work, make it correct, make it
beautiful. Each iteration gets you closer. With tests, refactoring is cheap.
</p>
<p className="text-white/60 leading-relaxed">
I don't let myself add too many features before stopping to refactor. Sometimes I kick off
an agent and have it trace code paths, explain dependencies, show me how modules connect.
I make mental notes during code review and circle back.
I don&apos;t let myself add too many features before stopping to refactor. Sometimes I
kick off an agent and have it trace code paths, explain dependencies, show me how modules
connect. I make mental notes during code review and circle back.
</p>
</section>
@@ -108,25 +110,25 @@ function BestPractices() {
<h2 className="text-xl font-medium">Use agents to check agents</h2>
<p className="text-white/60 leading-relaxed">
If an agent implements something and you ask it to review its own work, it will never find
issues. Launch a separate agent with a fresh context to review the first agent's code. It
will catch things the first agent missed or glossed over. An agent might say it's done
when it's not. Another agent can detect that.
issues. Launch a separate agent with a fresh context to review the first agent&apos;s
code. It will catch things the first agent missed or glossed over. An agent might say
it&apos;s done when it&apos;s not. Another agent can detect that.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Learn your agents' quirks</h2>
<h2 className="text-xl font-medium">Learn your agents&apos; quirks</h2>
<p className="text-white/60 leading-relaxed">
People argue about which model is better. That's the wrong question. Each model has
People argue about which model is better. That&apos;s the wrong question. Each model has
strengths and weaknesses. Knowing them is more useful than chasing benchmarks. Benchmarks
don't mean anything. You need to try the models yourself to form an opinion.
don&apos;t mean anything. You need to try the models yourself to form an opinion.
</p>
<p className="text-white/60 leading-relaxed">
I use Claude Code as my main driver because it's quick and uses tools well. But sometimes
it jumps to conclusions and gives up too easily. Codex is frustratingly slow but goes
deep, doesn't stop, and is methodical. It's also stubborn and too serious. These aren't
good or bad traits, just differences you learn to work around. Use the right model for the
job.
I use Claude Code as my main driver because it&apos;s quick and uses tools well. But
sometimes it jumps to conclusions and gives up too easily. Codex is frustratingly slow but
goes deep, doesn&apos;t stop, and is methodical. It&apos;s also stubborn and too serious.
These aren&apos;t good or bad traits, just differences you learn to work around. Use the
right model for the job.
</p>
</section>
</div>

View File

@@ -25,8 +25,9 @@ function CLI() {
<div>
<h1 className="text-3xl font-medium font-title mb-4">CLI</h1>
<p className="text-white/60 leading-relaxed">
The Paseo CLI lets you manage agents from your terminal. It's the same interface exposed
by the daemon's API, so anything you can do in the app you can do from the command line.
The Paseo CLI lets you manage agents from your terminal. It&apos;s the same interface
exposed by the daemon&apos;s API, so anything you can do in the app you can do from the
command line.
</p>
</div>
@@ -96,14 +97,14 @@ paseo ls -a -g --json # Full list as JSON`}</pre>
<section className="space-y-4">
<h2 className="text-xl font-medium">Streaming output</h2>
<p className="text-white/60 leading-relaxed">
Use <code className="font-mono">paseo attach</code> to stream an agent's output in
Use <code className="font-mono">paseo attach</code> to stream an agent&apos;s output in
real-time:
</p>
<Code>
<pre className="text-white/80">{`paseo attach abc123 # Attach to agent (Ctrl+C to detach)`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Agent IDs can be shortened — <code className="font-mono">abc</code> works if it's
Agent IDs can be shortened <code className="font-mono">abc</code> works if it&apos;s
unambiguous.
</p>
</section>
@@ -164,7 +165,7 @@ paseo permit deny <id> --all # Deny all pending`}</pre>
<section className="space-y-4">
<h2 className="text-xl font-medium">Agent modes</h2>
<p className="text-white/60 leading-relaxed">
Change an agent's operational mode (provider-specific):
Change an agent&apos;s operational mode (provider-specific):
</p>
<Code>
<pre className="text-white/80">{`paseo agent mode <id> --list # Show available modes

View File

@@ -84,7 +84,7 @@ function GettingStarted() {
>
GitHub releases page
</a>
. Open it and you're done.
. Open it and you&apos;re done.
</p>
<p className="text-white/60">
The desktop app bundles and manages its own daemon automatically, so you do not need a

View File

@@ -43,12 +43,12 @@ function Providers() {
</p>
<ul className="text-white/60 space-y-3 list-disc list-inside">
<li>
<code className="font-mono">claude</code> Anthropic's Claude Code. Multi-tool
<code className="font-mono">claude</code> Anthropic&apos;s Claude Code. Multi-tool
assistant with MCP support, streaming, and deep reasoning.
</li>
<li>
<code className="font-mono">codex</code> — OpenAI's Codex workspace agent with sandbox
controls and optional network access.
<code className="font-mono">codex</code> OpenAI&apos;s Codex workspace agent with
sandbox controls and optional network access.
</li>
<li>
<code className="font-mono">opencode</code> Open-source coding assistant with
@@ -90,14 +90,15 @@ function Providers() {
Client Protocol over stdio.
</li>
<li>
<strong>Disable</strong> a provider you don't use.
<strong>Disable</strong> a provider you don&apos;t use.
</li>
</ul>
<p className="text-white/60 leading-relaxed">
Provider IDs must be lowercase alphanumeric with hyphens (
<code className="font-mono">/^[a-z][a-z0-9-]*$/</code>). Every custom entry needs{" "}
<code className="font-mono">extends</code> (a first-class provider ID or{" "}
<code className="font-mono">"acp"</code>) and a <code className="font-mono">label</code>.
<code className="font-mono">&quot;acp&quot;</code>) and a{" "}
<code className="font-mono">label</code>.
</p>
<p className="text-white/60 leading-relaxed">
The examples below are a quick tour. The full, up-to-date reference is on GitHub:{" "}
@@ -139,7 +140,7 @@ function Providers() {
Z.AI exposes GLM models through an Anthropic-compatible endpoint. Point{" "}
<code className="font-mono">ANTHROPIC_BASE_URL</code> at their API and use{" "}
<code className="font-mono">ANTHROPIC_AUTH_TOKEN</code> for the key. Third-party endpoints
don't support Anthropic's server-side tools, so disable{" "}
don&apos;t support Anthropic&apos;s server-side tools, so disable{" "}
<code className="font-mono">WebSearch</code>.
</p>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
@@ -169,8 +170,8 @@ function Providers() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Alibaba Cloud (Qwen) coding plan</h2>
<p className="text-white/60 leading-relaxed">
Alibaba's coding plan routes Claude Code to Qwen models via an Anthropic-compatible API.
Subscription keys look like <code className="font-mono">sk-sp-...</code> and must be
Alibaba&apos;s coding plan routes Claude Code to Qwen models via an Anthropic-compatible
API. Subscription keys look like <code className="font-mono">sk-sp-...</code> and must be
created in the Singapore region.
</p>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
@@ -253,8 +254,8 @@ function Providers() {
>
ACP
</a>{" "}
over stdio can be added with <code className="font-mono">extends: "acp"</code> and a{" "}
<code className="font-mono">command</code>. Paseo spawns the process, sends an{" "}
over stdio can be added with <code className="font-mono">extends: &quot;acp&quot;</code>{" "}
and a <code className="font-mono">command</code>. Paseo spawns the process, sends an{" "}
<code className="font-mono">initialize</code> JSON-RPC request, and the agent reports its
capabilities, modes, and models at runtime.
</p>

View File

@@ -83,8 +83,8 @@ function Security() {
<code className="font-mono">$PASEO_HOME/daemon-keypair.json</code>
</li>
<li>
When you scan the QR code or click the pairing link, your phone receives the daemon's
public key
When you scan the QR code or click the pairing link, your phone receives the
daemon&apos;s public key
</li>
<li>
Your phone sends a handshake message with its own public key. The daemon will not accept
@@ -100,15 +100,15 @@ function Security() {
message contents, forge messages, or derive encryption keys from observing the handshake.
</p>
<h3 className="text-lg font-medium mt-6">Why the relay can't attack you</h3>
<h3 className="text-lg font-medium mt-6">Why the relay can&apos;t attack you</h3>
<p className="text-white/60 leading-relaxed">
The daemon requires a valid cryptographic handshake before processing any commands. A
compromised relay cannot:
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<strong className="text-white/80">Send commands</strong> Without your phone's private
key, it cannot complete the handshake
<strong className="text-white/80">Send commands</strong> Without your phone&apos;s
private key, it cannot complete the handshake
</li>
<li>
<strong className="text-white/80">Read your traffic</strong> All messages are
@@ -126,9 +126,9 @@ function Security() {
<h3 className="text-lg font-medium mt-6">Trust model</h3>
<p className="text-white/60 leading-relaxed">
The QR code or pairing link is the trust anchor. It contains the daemon's public key,
which is required to establish the encrypted connection. Treat it like a password don't
share it publicly.
The QR code or pairing link is the trust anchor. It contains the daemon&apos;s public key,
which is required to establish the encrypted connection. Treat it like a password
don&apos;t share it publicly.
</p>
<p className="text-white/60 leading-relaxed">
If you believe a pairing offer has been compromised, restart the daemon to generate a new
@@ -226,7 +226,7 @@ function Security() {
<code className="font-mono">*.localhost</code>, and all IP addresses
</li>
<li>
<code className="font-mono">['.example.com']</code>: allow{" "}
<code className="font-mono">[&apos;.example.com&apos;]</code>: allow{" "}
<code className="font-mono">example.com</code> and any subdomain, plus defaults
</li>
<li>
@@ -244,8 +244,8 @@ function Security() {
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<strong className="text-white/80">Claude Code</strong> — authenticates via Anthropic's
OAuth flow, stored in <code className="font-mono">~/.claude/</code>
<strong className="text-white/80">Claude Code</strong> authenticates via
Anthropic&apos;s OAuth flow, stored in <code className="font-mono">~/.claude/</code>
</li>
<li>
<strong className="text-white/80">Codex</strong> uses your OpenAI API key or OAuth
@@ -267,8 +267,8 @@ function Security() {
<h2 className="text-xl font-medium">Recommendations</h2>
<ul className="text-white/60 space-y-3 list-disc list-inside">
<li>
<strong className="text-white/80">Use the relay</strong> for mobile access it's the
simplest option and all traffic is end-to-end encrypted
<strong className="text-white/80">Use the relay</strong> for mobile access it&apos;s
the simplest option and all traffic is end-to-end encrypted
</li>
<li>
<strong className="text-white/80">Treat the QR code like a password</strong> anyone

View File

@@ -28,8 +28,8 @@ function Skills() {
Paseo ships orchestration skills that teach coding agents (Claude Code, Codex) how to use
the Paseo CLI to spawn, coordinate, and manage other agents. Skills are slash commands
your agent can invoke they provide the prompts, context, and workflows so agents know
how to orchestrate without you writing boilerplate. Install them from the desktop app's
Integrations settings or via the CLI.
how to orchestrate without you writing boilerplate. Install them from the desktop
app&apos;s Integrations settings or via the CLI.
</p>
</div>
@@ -60,7 +60,8 @@ function Skills() {
command reference so agents know how to run commands.
</p>
<p className="text-white/60 leading-relaxed">
Not typically invoked directly by users — it's a reference that other skills depend on.
Not typically invoked directly by users it&apos;s a reference that other skills depend
on.
</p>
</section>
@@ -71,8 +72,8 @@ function Skills() {
</h2>
<p className="text-white/60 leading-relaxed">
Hands off your current task to another agent with full context. The receiving agent gets a
comprehensive prompt with: task description, relevant files, what's been tried, decisions
made, and acceptance criteria.
comprehensive prompt with: task description, relevant files, what&apos;s been tried,
decisions made, and acceptance criteria.
</p>
<p className="text-white/60 leading-relaxed">
Default provider is Codex. Can specify Claude (sonnet/opus). Supports{" "}

View File

@@ -59,7 +59,7 @@ function Worktrees() {
<p className="text-white/60 leading-relaxed">
Drop a <code className="font-mono">paseo.json</code> in your repo root. Paseo reads it
from the committed version of the base branch you picked, so uncommitted changes in other
branches don't apply.
branches don&apos;t apply.
</p>
<Code>
<pre className="text-white/80">{`{
@@ -113,7 +113,7 @@ function Worktrees() {
<code className="font-mono">scripts</code> are named commands you can run inside a
worktree on demand. Mark one as a <em>service</em> and Paseo supervises it as a
long-running process, assigns it a port, and routes HTTP traffic to it through the
daemon's reverse proxy.
daemon&apos;s reverse proxy.
</p>
<h3 className="text-lg font-medium mt-4">Plain scripts</h3>
@@ -167,9 +167,9 @@ http://<script>.<project>.localhost:<daemon-port>`}</pre>
<h3 className="text-lg font-medium mt-6">Service-to-service</h3>
<p className="text-white/60 leading-relaxed">
Services launched from the same workspace see each other's ports and proxy URLs. Given{" "}
<code className="font-mono">web</code> and <code className="font-mono">api</code> above,
each process gets:
Services launched from the same workspace see each other&apos;s ports and proxy URLs.
Given <code className="font-mono">web</code> and <code className="font-mono">api</code>{" "}
above, each process gets:
</p>
<Code>
<pre className="text-white/80">{`PASEO_PORT=3000 # this service's port
@@ -219,7 +219,7 @@ PASEO_SERVICE_WEB_URL=http://web.my-app.localhost:6767`}</pre>
<code className="font-mono">$PASEO_WORKTREE_PATH</code> the worktree directory
</li>
<li>
<code className="font-mono">$PASEO_BRANCH_NAME</code> the worktree's branch
<code className="font-mono">$PASEO_BRANCH_NAME</code> the worktree&apos;s branch
</li>
<li>
<code className="font-mono">$PASEO_WORKTREE_PORT</code> legacy per-worktree port
@@ -229,10 +229,10 @@ PASEO_SERVICE_WEB_URL=http://web.my-app.localhost:6767`}</pre>
<p className="text-white/60 leading-relaxed">Services additionally get:</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<code className="font-mono">$PASEO_PORT</code> — this service's assigned port
<code className="font-mono">$PASEO_PORT</code> this service&apos;s assigned port
</li>
<li>
<code className="font-mono">$PASEO_URL</code> this service's proxy URL
<code className="font-mono">$PASEO_URL</code> this service&apos;s proxy URL
</li>
<li>
<code className="font-mono">$PASEO_SERVICE_&lt;NAME&gt;_PORT</code> /{" "}

View File

@@ -25,7 +25,7 @@ function Privacy() {
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">What we collect</h2>
<p>Nothing. Paseo runs on your machine and doesn't send us any data.</p>
<p>Nothing. Paseo runs on your machine and doesn&apos;t send us any data.</p>
</section>
<section className="space-y-3">
@@ -48,7 +48,8 @@ function Privacy() {
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">Analytics and tracking</h2>
<p>
We don't use analytics, tracking pixels, cookies, or ads. The app doesn't phone home.
We don&apos;t use analytics, tracking pixels, cookies, or ads. The app doesn&apos;t
phone home.
</p>
</section>
@@ -57,7 +58,7 @@ function Privacy() {
<p>
Paseo wraps agent providers like Claude Code, Codex, and OpenCode. Those tools
communicate with their own APIs (Anthropic, OpenAI, etc.) using your credentials.
Paseo doesn't manage or intercept those API calls.
Paseo doesn&apos;t manage or intercept those API calls.
</p>
<p>
If you use voice features with cloud providers (OpenAI speech), your voice data is
@@ -66,8 +67,8 @@ function Privacy() {
</section>
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">We don't sell your data</h2>
<p>We don't have your data to sell. Paseo is self-hosted and local-first.</p>
<h2 className="text-xl font-medium text-white">We don&apos;t sell your data</h2>
<p>We don&apos;t have your data to sell. Paseo is self-hosted and local-first.</p>
</section>
<section className="space-y-3">