mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-03 15:12:48 +00:00
* feat(website): serve paseo.sh in Simplified Chinese
Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.
The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.
Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.
Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.
* feat(website): replace the native language select with an EN/ZH menu
The header showed an OS-native select whose popup did not match the site.
The trigger now shows the language abbreviated (EN, ZH) and opens a menu in
the app's dropdown style that names each language in itself, with its name
in the page language underneath. Rows are real links to the same page in
that language; arrows, Escape, outside presses and taps behave as a menu
button, and the menu keeps itself on screen when a narrow header pushes the
trigger toward an edge.
Also drops the stray space after the full-width comma in the Chinese
homepage subtitle.
* docs(website): translate main's new docs and 0.12.0 release notes
Main added Keep run workspaces to the schedules docs, rewrote plugin icons
around inline SVG, and promoted 0.12.0 with new entries. Bring the Chinese
pages and changelog in line so no section falls back to English.
* fix(server): leave plugin listing translations to the registry
The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.
* docs(plugins): describe the registry's final overview translation discovery
* style(server): format manifest test
* test(app): measure sidebar rows one at a time
After a reload the developer error toast appears while expectVerticalOrder
measures every row at once. Each concurrent boundingBox runs the toast
locator handler, and only one of them resumes, so the poll timed out on
every attempt since #7424. Measuring sequentially keeps one action in flight.
* test(app): return sidebar row measuring to main
#7480 hides the developer toast instead of running a locator handler, so
measuring the rows concurrently no longer races it. Reverts 41ac23016.
* test(app): wait for the compact shell before opening compact Changes
The shell survives the breakpoint, so right after the resize the wide
Explorer could still be visible: the test skipped the Explorer toggle and
the tab then disappeared, or toggled at the wrong time. Wait for the
compact-shell marker, as the composer helpers do since #7424.
* docs(website): translate main's plugin file panel docs
* test(app): edit the code font size after the font family remount
A committed appearance change remounts the settings screen through
AppearanceStyleBoundary, and the new form starts from the stored size.
Filling the size before that remount lost the edit in CI.
* docs(website): translate main's client/ui import docs
Follows #7474: one /client/ui entry, StreamedText instead of useRevealedText,
SettingsGroup dropped from the settings table, and the new
'UI imports in v0.13' migration section.
* test(app): wait for the previous layout's file panel picker to leave
After a resize the old layout's picker animates out while the new one is
already mounted, so the Use button matched two elements and the strict
locator failed. Wait for the shell's layout and a single picker first.
266 lines
8.7 KiB
TypeScript
266 lines
8.7 KiB
TypeScript
import {
|
|
AudioLines,
|
|
BookOpen,
|
|
Brain,
|
|
ChevronDown,
|
|
CircleDot,
|
|
Copy,
|
|
FilePenLine,
|
|
LoaderCircle,
|
|
ListTodo,
|
|
Mic,
|
|
Plus,
|
|
ShieldOff,
|
|
Split,
|
|
SquareTerminal,
|
|
Zap,
|
|
} from "lucide-react";
|
|
import type * as React from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { DiffStat } from "./atoms";
|
|
import { CodexIcon } from "./icons";
|
|
|
|
// Icons passed as props are hoisted so they are not rebuilt on every render.
|
|
const MODEL_ICON = <CodexIcon size={13} />;
|
|
const EFFORT_ICON = <Brain size={13} />;
|
|
const ACCESS_ICON = <ShieldOff size={13} />;
|
|
|
|
// --- transcript -------------------------------------------------------------
|
|
|
|
function Bullet({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex gap-[10px] text-[13.5px] leading-[22px] text-mock-fg">
|
|
<span className="text-mock-fg-muted">·</span>
|
|
<span>{children}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Para({ children }: { children: React.ReactNode }) {
|
|
return <p className="text-[13.5px] leading-[20px] text-mock-fg">{children}</p>;
|
|
}
|
|
|
|
/** The user's message — a right-aligned bubble that opens the turn. */
|
|
function UserBubble({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex justify-end pb-[12px]">
|
|
<span className="max-w-[80%] rounded-[11px] bg-mock-surface2 px-[13px] py-[8px] text-[13px] leading-[19px] text-mock-fg">
|
|
{children}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** The message footer — copy, branch off, and how long the turn took. */
|
|
function TurnFooter({ duration }: { duration: string }) {
|
|
return (
|
|
<div className="flex items-center gap-[16px] pt-[14px] text-mock-fg-muted">
|
|
<Copy size={14} />
|
|
<Split size={14} />
|
|
<span className="text-[12.5px]">Worked for {duration}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function AgentTranscript() {
|
|
return (
|
|
<div className="space-y-[13px] pt-[2px]">
|
|
<Para>
|
|
The homepage is built. It leads with Paseo’s actual value, then uses the product
|
|
itself to prove the story instead of falling back to generic dashboard art.
|
|
</Para>
|
|
<div>
|
|
<Bullet>Reworked the hero around “The control plane for coding agents.”</Bullet>
|
|
<Bullet>Built desktop and mobile scenes from real Paseo workflows and assets.</Bullet>
|
|
<Bullet>Added believable agent, review, plugin, shipping, and host states.</Bullet>
|
|
<Bullet>
|
|
Matched the app’s tabs, explorer, diff tree, controls, and layout rails.
|
|
</Bullet>
|
|
</div>
|
|
<TurnFooter duration="31m 46s" />
|
|
<UserBubble>Create a PR</UserBubble>
|
|
<Para>PR opened: Rebuild the homepage around live Paseo workflows.</Para>
|
|
<span className="block text-[13.5px] text-mock-accent-bright">
|
|
https://github.com/getpaseo/paseo/pull/3981
|
|
</span>
|
|
<div>
|
|
<Bullet>Ships the new product-led hero and interactive desktop mockup.</Bullet>
|
|
<Bullet>Includes responsive mobile proof and the supporting homepage story.</Bullet>
|
|
<Bullet>Formatting, typecheck, and lint are clean.</Bullet>
|
|
</div>
|
|
<Para>Ready for review.</Para>
|
|
<TurnFooter duration="1m 18s" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface PluginToolCall {
|
|
kind: "read" | "shell" | "write";
|
|
label: string;
|
|
detail: string;
|
|
}
|
|
|
|
const PLUGIN_TOOL_CALLS: PluginToolCall[] = [
|
|
{ kind: "read", label: "Read", detail: "paseo-plugin/SKILL.md" },
|
|
{
|
|
kind: "shell",
|
|
label: "Shell",
|
|
detail: "paseo plugin init ~/dev/release-radar",
|
|
},
|
|
{
|
|
kind: "write",
|
|
label: "Write",
|
|
detail: "~/dev/release-radar/index.ts",
|
|
},
|
|
{
|
|
kind: "write",
|
|
label: "Write",
|
|
detail: "~/dev/release-radar/release-radar.client.tsx",
|
|
},
|
|
{
|
|
kind: "shell",
|
|
label: "Shell",
|
|
detail: "npm install && npm run typecheck",
|
|
},
|
|
{
|
|
kind: "shell",
|
|
label: "Shell",
|
|
detail: "paseo plugin install ~/dev/release-radar",
|
|
},
|
|
];
|
|
|
|
const PLUGIN_DISCOVERY_CALLS = PLUGIN_TOOL_CALLS.slice(0, 1);
|
|
const PLUGIN_BUILD_CALLS = PLUGIN_TOOL_CALLS.slice(1, 4);
|
|
const PLUGIN_INSTALL_CALLS = PLUGIN_TOOL_CALLS.slice(4);
|
|
|
|
function PluginToolIcon({ kind }: { kind: PluginToolCall["kind"] }) {
|
|
if (kind === "read") return <BookOpen size={14} />;
|
|
if (kind === "write") return <FilePenLine size={14} />;
|
|
return <SquareTerminal size={14} />;
|
|
}
|
|
|
|
function PluginToolRow({ call }: { call: PluginToolCall }) {
|
|
return (
|
|
<div className="flex items-center gap-[9px] py-[5px] text-[12.5px] leading-[18px]">
|
|
<span className="text-mock-fg-muted">
|
|
<PluginToolIcon kind={call.kind} />
|
|
</span>
|
|
<span className="shrink-0 text-mock-fg">{call.label}</span>
|
|
<span className="min-w-0 truncate font-mono text-[11.5px] text-mock-fg-muted">
|
|
{call.detail}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PluginToolGroup({ calls }: { calls: PluginToolCall[] }) {
|
|
return (
|
|
<div>
|
|
{calls.map((call) => (
|
|
<PluginToolRow key={`${call.label}:${call.detail}`} call={call} />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PluginBuildTranscript() {
|
|
return (
|
|
<div className="space-y-[13px] pt-[2px]">
|
|
<UserBubble>
|
|
Build me a release radar for Paseo. I want an explorer panel showing merged PRs, changes in
|
|
review, failing checks, and seven-day activity, accessible from the sidebar.
|
|
</UserBubble>
|
|
<Para>
|
|
I’ll scaffold a local plugin, add the explorer surface, and wire it into the sidebar.
|
|
</Para>
|
|
<PluginToolGroup calls={PLUGIN_DISCOVERY_CALLS} />
|
|
<Para>
|
|
The plugin API exposes Paseo’s live project and workspace state, so the radar
|
|
doesn’t need a separate service.
|
|
</Para>
|
|
<PluginToolGroup calls={PLUGIN_BUILD_CALLS} />
|
|
<Para>The surface is wired. I’m checking the project and installing it now.</Para>
|
|
<PluginToolGroup calls={PLUGIN_INSTALL_CALLS} />
|
|
<Para>Release Radar is live. It should now be visible in the right sidebar.</Para>
|
|
<TurnFooter duration="2m 14s" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// --- composer ---------------------------------------------------------------
|
|
|
|
function ControlPill({ icon, label }: { icon: React.ReactNode; label: string }) {
|
|
return (
|
|
<span className="flex items-center gap-[6px] text-[12px] text-mock-fg-muted">
|
|
{icon}
|
|
{label}
|
|
<ChevronDown size={11} className="text-mock-fg-xmuted" />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
/** A rounded pill that floats above the composer — task tracker, subagents, diff. */
|
|
function ComposerPill({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<span className="flex items-center gap-[6px] rounded-full border border-mock-border-accent px-[11px] py-[4px] text-[11.5px] text-mock-fg-muted">
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
/** The amber ring the app shows while a turn is streaming. */
|
|
function BusyRing() {
|
|
return <LoaderCircle size={14} className="text-mock-warning" strokeWidth={2.4} />;
|
|
}
|
|
|
|
export function Composer({
|
|
showProgress = true,
|
|
busy = true,
|
|
}: {
|
|
showProgress?: boolean;
|
|
busy?: boolean;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<div className="shrink-0 px-[27px] pt-[16px] pb-[17px]">
|
|
<div className={showProgress ? "pb-[13px]" : ""}>
|
|
{showProgress ? (
|
|
<div className="flex items-center gap-[8px]">
|
|
<ComposerPill>
|
|
{t("landing.mockup.app.tasksProgress", { completed: 6, total: 6 })}
|
|
</ComposerPill>
|
|
<ComposerPill>
|
|
<CircleDot size={12} className="text-mock-dot-running" />
|
|
{t("landing.mockup.app.subagents", { count: 3 })}
|
|
</ComposerPill>
|
|
<ComposerPill>
|
|
<DiffStat add="1.9k" remove="684" />
|
|
</ComposerPill>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
<div className="rounded-[11px] border border-mock-border-accent bg-mock-surface1">
|
|
<div className="px-[14px] pt-[13px] pb-[12px]">
|
|
<div className="flex items-start gap-[10px]">
|
|
<span className="min-w-0 flex-1 text-[13px] text-mock-surface4">
|
|
{t("landing.mockup.app.composerPlaceholder")}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-[17px] pt-[26px]">
|
|
<Plus size={15} className="text-mock-fg-muted" />
|
|
<ControlPill icon={MODEL_ICON} label="GPT-5.6-Sol" />
|
|
<ControlPill icon={EFFORT_ICON} label="Medium" />
|
|
<ControlPill icon={ACCESS_ICON} label="Full access" />
|
|
<Zap size={14} className="text-mock-fg-muted" />
|
|
<ListTodo size={14} className="text-mock-fg-muted" />
|
|
<span className="flex-1" />
|
|
{busy ? <BusyRing /> : null}
|
|
<Mic size={14} className="text-mock-fg-muted" />
|
|
<AudioLines size={14} className="text-mock-fg-muted" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|